mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
246
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a0a666a41 | ||
|
|
83db2efec3 | ||
|
|
13e4c315b6 | ||
|
|
4242ec5a64 | ||
|
|
c6b8210d7c | ||
|
|
6568fe9a84 | ||
|
|
57c4a6ee11 | ||
|
|
54abbf48f2 | ||
|
|
56d3ce98f0 | ||
|
|
cbe5b62947 | ||
|
|
f7ae86c982 | ||
|
|
30a4673c60 | ||
|
|
a5ceb48184 | ||
|
|
c8caa27936 | ||
|
|
e4019af522 | ||
|
|
f7226ca7dc | ||
|
|
d172ab75ca | ||
|
|
bcf13e101e | ||
|
|
2caf2f7e4e | ||
|
|
df92a08990 | ||
|
|
e291589177 | ||
|
|
2285a25182 | ||
|
|
a50e4de389 | ||
|
|
959bee4799 | ||
|
|
232e2f2593 | ||
|
|
b4406b0467 | ||
|
|
fb213e8ae5 | ||
|
|
87d938139d | ||
|
|
2a4ccaa5d5 | ||
|
|
9c04abacc5 | ||
|
|
5916a184cc | ||
|
|
fcab4e5d0d | ||
|
|
fe23334ff0 | ||
|
|
81169d77ea | ||
|
|
7d24972b8f | ||
|
|
1d3fd07fe6 | ||
|
|
28766a915b | ||
|
|
279bc95941 | ||
|
|
12c522bba5 | ||
|
|
65a6f30f51 | ||
|
|
bb2d6dbeb3 | ||
|
|
50844badbf | ||
|
|
c1f6838e40 | ||
|
|
6455546f26 | ||
|
|
dcc642551f | ||
|
|
dd429ef858 | ||
|
|
bc2664cefa | ||
|
|
8b81f665a2 | ||
|
|
1039c54f5d | ||
|
|
b24ce3440d | ||
|
|
2f6ee0e91d | ||
|
|
6b1c4a7a71 | ||
|
|
166b9a5eea | ||
|
|
5313181ecf | ||
|
|
ceccd746d8 | ||
|
|
82470c7181 | ||
|
|
4843d8d942 | ||
|
|
c914435f20 | ||
|
|
7468e4f60a | ||
|
|
b6da068313 | ||
|
|
127cf8d46f | ||
|
|
e0586cc85a | ||
|
|
302240da70 | ||
|
|
892e70c8a1 | ||
|
|
7c9da0e56f | ||
|
|
6d6da57a95 | ||
|
|
04cb4c6553 | ||
|
|
487bbf95fd | ||
|
|
3a925efddf | ||
|
|
aeecc9343a | ||
|
|
8c7fe539b8 | ||
|
|
c88f05f7b5 | ||
|
|
f3c017b168 | ||
|
|
4b549b9cb3 | ||
|
|
9cc0bba80b | ||
|
|
150bcb06a9 | ||
|
|
280b87adec | ||
|
|
26d326d1b0 | ||
|
|
4cf5a96b79 | ||
|
|
7bba785963 | ||
|
|
8d18be71db | ||
|
|
6abfe3bbe4 | ||
|
|
1905b28b3c | ||
|
|
8e4c3b5e14 | ||
|
|
be5915c248 | ||
|
|
9e41d8320a | ||
|
|
e998f21e71 | ||
|
|
3197ef093c | ||
|
|
fa0e81bfba | ||
|
|
01ce973882 | ||
|
|
f57d2c0965 | ||
|
|
735a229236 | ||
|
|
9ade7e3582 | ||
|
|
259a40abfc | ||
|
|
f1577a5a94 | ||
|
|
d08a6cef64 | ||
|
|
907c65f54c | ||
|
|
fe6499b86e | ||
|
|
6309ac838d | ||
|
|
19799b3079 | ||
|
|
4e16234d32 | ||
|
|
3c9da9c3f4 | ||
|
|
ff6dffe298 | ||
|
|
5f6dc0a8d0 | ||
|
|
34893dd8c8 | ||
|
|
50595d1c13 | ||
|
|
717c54e085 | ||
|
|
31b7be9ff2 | ||
|
|
40692dfb64 | ||
|
|
9a3aca963a | ||
|
|
1fa79d5cac | ||
|
|
c9ec82e32e | ||
|
|
2f88a0a3b2 | ||
|
|
7e884f55e0 | ||
|
|
d70cbd4184 | ||
|
|
9bcf80bf67 | ||
|
|
ed1195d1e7 | ||
|
|
25e1cc7ec9 | ||
|
|
a287a54f26 | ||
|
|
56738aa4e1 | ||
|
|
c11254a996 | ||
|
|
aec8c88755 | ||
|
|
68d7bc94e2 | ||
|
|
8ee31abd9d | ||
|
|
9a7c54da58 | ||
|
|
5dc6914688 | ||
|
|
a7a734b5d3 | ||
|
|
e2c8b61de6 | ||
|
|
145578ac03 | ||
|
|
f01a3750d4 | ||
|
|
0e9c77b0a2 | ||
|
|
d692d8ee08 | ||
|
|
460a196fca | ||
|
|
652154aead | ||
|
|
49ebbfd627 | ||
|
|
7688093ca3 | ||
|
|
df08b83287 | ||
|
|
fa68e104ea | ||
|
|
fd66f580b4 | ||
|
|
8f7d745383 | ||
|
|
f4692a450e | ||
|
|
df5b9b92be | ||
|
|
5b0ef07742 | ||
|
|
5ce9a52936 | ||
|
|
dad900c61d | ||
|
|
5b730d4df0 | ||
|
|
e699a35f20 | ||
|
|
41beda4367 | ||
|
|
048fe9f413 | ||
|
|
e51de54ee4 | ||
|
|
984cfcebd1 | ||
|
|
df0412afb5 | ||
|
|
f28c0fccb2 | ||
|
|
4c87dbebb2 | ||
|
|
16a7fe689a | ||
|
|
6f0cddd440 | ||
|
|
4084a008ea | ||
|
|
c0ebf22476 | ||
|
|
cb375ec35a | ||
|
|
6f3bebcb50 | ||
|
|
1029511bbc | ||
|
|
65144ce4ac | ||
|
|
f848d2761b | ||
|
|
730e1a3cf3 | ||
|
|
d4bb7fd786 | ||
|
|
fbea51c684 | ||
|
|
a6f4e8e7f4 | ||
|
|
448a4c8a6a | ||
|
|
ece4e28740 | ||
|
|
58ba1e4cb4 | ||
|
|
cf77fb4353 | ||
|
|
7e4e142aae | ||
|
|
b7b335ac63 | ||
|
|
9a5dd9faff | ||
|
|
947627d10d | ||
|
|
1cfb9522a3 | ||
|
|
f63d3befdc | ||
|
|
307fdcf7ad | ||
|
|
679be25c37 | ||
|
|
fa56c5eae9 | ||
|
|
3b9e176b70 | ||
|
|
053f5fd3cc | ||
|
|
f3836bb831 | ||
|
|
2f4d0f383e | ||
|
|
d247d3d60c | ||
|
|
93452b24e3 | ||
|
|
5559cfcbb2 | ||
|
|
1c301c9aab | ||
|
|
59183dbfa8 | ||
|
|
c1926f915d | ||
|
|
7da3c4ed21 | ||
|
|
3e9f88bf79 | ||
|
|
8c81386568 | ||
|
|
bb5edbd490 | ||
|
|
1818aae562 | ||
|
|
165cd884ca | ||
|
|
973809cde0 | ||
|
|
71c7261740 | ||
|
|
5b68a26cce | ||
|
|
3b1a94707a | ||
|
|
488c0acae9 | ||
|
|
4c8fc53a85 | ||
|
|
3ae5a9b70a | ||
|
|
a31e2cd361 | ||
|
|
1bd282ec0a | ||
|
|
1fc520cd62 | ||
|
|
7b9997baea | ||
|
|
393257eaae | ||
|
|
13e3ca314c | ||
|
|
b4853a0e08 | ||
|
|
ee4dd90649 | ||
|
|
3df456675d | ||
|
|
8abf99c56e | ||
|
|
7c6121cba5 | ||
|
|
46997dfc7a | ||
|
|
97fda7b10f | ||
|
|
dbe5353f3a | ||
|
|
e1ac893a0c | ||
|
|
b35cdc39b7 | ||
|
|
52dbc724f8 | ||
|
|
2f4b8a335d | ||
|
|
b38b7454a6 | ||
|
|
b65dc12208 | ||
|
|
58978d154a | ||
|
|
1f3adf5109 | ||
|
|
107d5f750a | ||
|
|
f40755173f | ||
|
|
4e14eed770 | ||
|
|
aa3b2cd57f | ||
|
|
4865dd92ca | ||
|
|
3c62359e05 | ||
|
|
62643c3424 | ||
|
|
210d4dcd99 | ||
|
|
5a8808086a | ||
|
|
861dd76d7f | ||
|
|
db41d0482f | ||
|
|
de62664908 | ||
|
|
34f6fd1f5f | ||
|
|
79a91ecfaf | ||
|
|
420bb51d6c | ||
|
|
c86c8b4d93 | ||
|
|
e43aadf75f | ||
|
|
9f25e3c23e | ||
|
|
7881c69a54 | ||
|
|
9e666e4dd2 | ||
|
|
c3a12397b0 |
No files matched your search
@@ -257,6 +257,7 @@ jobs:
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
file "$binary"
|
||||
lipo "$binary" -verify_arch x86_64
|
||||
@@ -266,6 +267,7 @@ jobs:
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/MacOS/kyty_updater" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
codesign --verify --strict "$binary"
|
||||
done
|
||||
@@ -407,6 +409,7 @@ jobs:
|
||||
run: |
|
||||
file _Build/linux/install/launcher
|
||||
file _Build/linux/install/kyty_emulator
|
||||
test -x _Build/linux/install/kyty_updater
|
||||
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
|
||||
ldd _Build/linux/install/kyty_emulator > /dev/null
|
||||
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
|
||||
@@ -495,7 +498,8 @@ jobs:
|
||||
test -d "$linux_dir"
|
||||
chmod a+x \
|
||||
"$linux_dir/launcher" \
|
||||
"$linux_dir/kyty_emulator"
|
||||
"$linux_dir/kyty_emulator" \
|
||||
"$linux_dir/kyty_updater"
|
||||
tar -C "$linux_dir" -czf \
|
||||
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
|
||||
|
||||
|
||||
+32
-15
@@ -40,6 +40,11 @@ if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prevent Windows headers from defining min/max macros in all targets.
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
@@ -244,7 +249,7 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||
)
|
||||
|
||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
||||
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
|
||||
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||
set(gpu_tiler_shader_names
|
||||
@@ -298,28 +303,27 @@ list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
set(gpu_blit_shader_sources
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
||||
"${gpu_tiler_shader_dir}/gpu_video_out_overlay.frag"
|
||||
)
|
||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
||||
function(add_gpu_blit_shader shader_name shader_file)
|
||||
set(shader_source "${gpu_tiler_shader_dir}/${shader_file}")
|
||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os ${ARGN}
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
set(gpu_blit_shader_headers ${gpu_blit_shader_headers} "${shader_header}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
add_gpu_blit_shader(gpu_blit_fs_triangle gpu_blit_fs_triangle.vert)
|
||||
add_gpu_blit_shader(gpu_blit_color_to_ms_depth gpu_blit_color_to_ms_depth.frag)
|
||||
add_gpu_blit_shader(gpu_video_out_overlay gpu_video_out_overlay.frag)
|
||||
add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1)
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
@@ -393,11 +397,13 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
@@ -426,6 +432,14 @@ add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
|
||||
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(vdecsw_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/VdecswTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/videoDec2Decoder.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(vdecsw_tests common FFmpeg::ffmpeg)
|
||||
target_include_directories(vdecsw_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||
)
|
||||
@@ -465,7 +479,7 @@ add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt)
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
@@ -477,9 +491,8 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/BindingLayout.cpp"
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
@@ -617,6 +630,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||
add_test(NAME vdecsw COMMAND $<TARGET_FILE:vdecsw_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
@@ -656,6 +670,8 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
||||
add_test(NAME image_transfer_layout
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-layout-only)
|
||||
|
||||
add_test(NAME texture_cache_layered_image
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||
@@ -691,6 +707,7 @@ if(BUILD_TESTING)
|
||||
archive_file_tests
|
||||
trophy_system_tests
|
||||
avplayer_file_tests
|
||||
vdecsw_tests
|
||||
bit_array_tests
|
||||
lru_cache_tests
|
||||
shader_vertex_metadata_tests
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
[](#current-status)
|
||||
[](LICENSE)
|
||||
|
||||
**[Weekly updates](https://github.com/KytyPS5/KytyPS5/discussions/862)** — game progress, recent fixes and ongoing development.
|
||||
**[Weekly updates](https://github.com/KytyPS5/KytyPS5/discussions/862)** — game progress, recent fixes, and ongoing development.
|
||||
|
||||
**[Development on Discord](https://discord.gg/UNrkMqGaBg)** — KytyPS5 development.
|
||||
|
||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
|
||||
with experimental macOS support. It is based on a heavily modified version of
|
||||
KytyPS5 is a free and open-source PlayStation 5 translation layer written in C++ for Windows
|
||||
and Linux, with experimental macOS support. It is based on a heavily modified version of
|
||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
|
||||
can change significantly between builds.
|
||||
|
||||
@@ -23,13 +23,13 @@ can change significantly between builds.
|
||||
|
||||
## Current Status
|
||||
|
||||
KytyPS5 can boot 2D games and a selection of 3D games, including titles built with Unreal Engine
|
||||
KytyPS5 can boot 2D and 3D games, including titles built with Unreal Engine
|
||||
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
|
||||
planned.
|
||||
|
||||
Development is currently focused on expanding game compatibility and improving boot reliability.
|
||||
|
||||
Windows and Linux are the primary platforms and receive the most testing.
|
||||
Windows and Linux are the primary target platforms and receive the most testing.
|
||||
|
||||
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
|
||||
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
|
||||
@@ -52,8 +52,8 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>Dreaming Sarah</strong><br>
|
||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
||||
<strong>Saros</strong><br>
|
||||
<img src="docs/screenshots/ps5-03.png" width="300" alt="Saros running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -85,14 +85,20 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||
issues first, then use the **Game Emulation Status Report** template and attach the complete log file.
|
||||
|
||||
Code contributions should be focused, build successfully on the platforms they touch, and include
|
||||
relevant tests where practical. Windows is the primary target, so a change that alters shared code
|
||||
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
|
||||
starting a large change.
|
||||
**Pull requests for general fixes, architectural changes, or refactoring that do not address a concrete bug or launch problem in a specific game will be automatically closed.**
|
||||
|
||||
**Large, unfocused commits or pull requests that bundle unrelated changes without a clear purpose or game-related benefit will also be automatically closed.**
|
||||
|
||||
Keep each PR focused. Name the affected game, describe the exact problem, and provide evidence showing what the change fixes.
|
||||
|
||||
Windows and Linux are the primary target platforms, so changes to shared code should not
|
||||
introduce regressions on either platform. Changes confined to a platform's own code paths
|
||||
only need to build on that platform. Because KytyPS5 is still evolving quickly, consider
|
||||
opening an issue before starting a large change.
|
||||
|
||||
### Formatting
|
||||
|
||||
Set up the clang-format hook after cloning:
|
||||
Set up the clang-format hook after cloning.
|
||||
|
||||
Install `pre-commit` using the method appropriate for your platform:
|
||||
|
||||
@@ -141,7 +147,8 @@ the Vulkan/SPIR-V validation rules.
|
||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||
**C++ Clang tools for Windows** component
|
||||
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslang` or `glslangValidator`) on `PATH`
|
||||
- Python 3 on `PATH`; the bundled SPIRV-Tools runs it at configure time
|
||||
|
||||
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
||||
|
||||
@@ -170,20 +177,20 @@ The finished application and its runtime dependencies will be placed in
|
||||
|
||||
### Building on Linux
|
||||
|
||||
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and
|
||||
udev development packages SDL3 quietly configures itself without those backends, and the resulting
|
||||
build has no working sound and no gamepad hotplug:
|
||||
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland, and
|
||||
udev development packages, SDL3 quietly configures itself without those backends, and the resulting
|
||||
build has no working audio or gamepad hotplug support:
|
||||
|
||||
```bash
|
||||
sudo apt-get install --no-install-recommends \
|
||||
clang lld ninja-build cmake git glslang-tools pkg-config \
|
||||
clang lld ninja-build cmake git glslang-tools python3 pkg-config \
|
||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
```
|
||||
|
||||
Qt 6 (Concurrent, Network, Widgets) is required for the launcher — either the distribution packages
|
||||
(`qt6-base-dev`) or an official Qt installation.
|
||||
Qt 6 (Concurrent, Network, and Widgets) is required for the launcher. Use your distribution's
|
||||
packages (`qt6-base-dev`) or an official Qt installation.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
@@ -197,7 +204,7 @@ cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The install step copies the Qt libraries and plugins next to the binaries, so
|
||||
`_Build/linux/install` runs without a matching system Qt. FFmpeg is linked statically
|
||||
`_Build/linux/install` runs without a matching system Qt installation. FFmpeg is linked statically
|
||||
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
|
||||
release, including VP9 and WebM support. System FFmpeg packages are not required.
|
||||
|
||||
@@ -241,6 +248,43 @@ sources, so it needs network access; a fully sandboxed `nix build` would require
|
||||
inputs. A Vulkan 1.3 driver must be available at runtime (on NixOS,
|
||||
`hardware.graphics.enable = true`).
|
||||
|
||||
### Building on Fedora and Fedora Atomic (Bazzite, Bluefin, Silverblue)
|
||||
|
||||
On regular Fedora, install dependencies on the host. On Fedora Atomic variants such as Bazzite,
|
||||
Bluefin and Silverblue, use [Distrobox](https://distrobox.it/) and run the install and build steps
|
||||
inside the container. Keep the checkout in your home directory so it is shared with the container.
|
||||
Add `--nvidia` to the create command for hosts using the proprietary NVIDIA driver:
|
||||
|
||||
```bash
|
||||
distrobox create --name kyty-build --image registry.fedoraproject.org/fedora:latest
|
||||
distrobox enter kyty-build
|
||||
```
|
||||
|
||||
Install the toolchain, Qt 6 and SDL3 dependencies:
|
||||
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
clang lld ninja-build cmake git glslang python3 pkgconf-pkg-config \
|
||||
mesa-libGL-devel libX11-devel libXcursor-devel libXext-devel libXfixes-devel \
|
||||
libXi-devel libXrandr-devel libXScrnSaver-devel libXtst-devel libxkbcommon-devel \
|
||||
alsa-lib-devel pulseaudio-libs-devel systemd-devel dbus-devel \
|
||||
wayland-devel wayland-protocols-devel qt6-qtbase-devel
|
||||
```
|
||||
|
||||
From the repository root, follow the launcher configure, build and install steps in
|
||||
[Building on Linux](#building-on-linux), omitting `-DCMAKE_PREFIX_PATH="$Qt6_DIR"`; CMake finds
|
||||
Fedora's packaged Qt 6 automatically.
|
||||
|
||||
Run `./_Build/linux/install/launcher` on regular Fedora or inside the container. To launch a
|
||||
container build from the host, leave the container with `exit`, then run from the repository root:
|
||||
|
||||
```bash
|
||||
distrobox enter kyty-build -- ./_Build/linux/install/launcher
|
||||
```
|
||||
|
||||
Qt libraries and plugins are bundled. You can also launch directly on a Fedora Atomic host
|
||||
if its other system libraries are compatible with the container build.
|
||||
|
||||
### Building on macOS
|
||||
|
||||
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
|
||||
@@ -251,8 +295,8 @@ Requirements:
|
||||
|
||||
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
||||
- Xcode (or the Command Line Tools)
|
||||
- Homebrew packages: `brew install cmake ninja glslang`
|
||||
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
|
||||
- Homebrew packages: `brew install cmake ninja glslang python`
|
||||
- Qt 6 (Concurrent, Network, and Widgets) with x86-64 support. The official Qt installation is
|
||||
universal and works; Homebrew's Qt is arm64-only and will not link
|
||||
|
||||
```bash
|
||||
@@ -268,14 +312,14 @@ cmake --install _Build/macos --prefix _Build/macos/install
|
||||
```
|
||||
|
||||
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
||||
guest code; no manual signing step is required. When the launcher is built, the install
|
||||
guest code; no manual signing step is required. When the launcher is built, the installation
|
||||
also produces `_Build/macos/install/KytyPS5.app` — double-click to launch the GUI.
|
||||
A flat `kyty_emulator` is kept for CLI usage.
|
||||
|
||||
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
||||
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to the flat `kyty_emulator`
|
||||
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and ad-hoc sign it:
|
||||
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and sign it with an ad hoc signature:
|
||||
|
||||
```bash
|
||||
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||
@@ -337,6 +381,14 @@ folders recursively for game directories containing `eboot.bin` and ZArchive (`.
|
||||
whose archive root contains `eboot.bin`. Select a detected game and run it from the game list.
|
||||
ZArchive dumps are mounted read-only and streamed directly; they do not need to be extracted first.
|
||||
|
||||
Official release builds include a built-in updater. Leave **Check for updates on startup** enabled,
|
||||
or click **Check for updates**, then choose **Install Update**. The launcher downloads the package
|
||||
for your platform from GitHub, checks its SHA-256 checksum, and restarts after installation. Close
|
||||
any running games first. Settings, saves, patches, and other files outside the release package are
|
||||
preserved; replacement failures restore the previous files. The installation folder must be writable.
|
||||
If a package cannot be installed automatically, the updater offers the GitHub release page.
|
||||
Source builds and builds from forks do not replace themselves with official releases.
|
||||
|
||||
The emulator can also be started directly with a legally obtained game directory, ELF file, or
|
||||
ZArchive dump:
|
||||
|
||||
@@ -392,5 +444,5 @@ licenses included with those components.
|
||||
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
|
||||
of the original Kyty project.
|
||||
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for understanding PS4
|
||||
memory behavior, GPU resource aliasing and cache coherency,
|
||||
memory behavior, GPU resource aliasing, cache coherency,
|
||||
and the AVPlayer implementation.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,112 @@
|
||||
#include "common/additionalContent.h"
|
||||
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
#include <system_error>
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetString(const nlohmann::json& object, const std::string& key) {
|
||||
const auto it = object.find(key);
|
||||
return it != object.end() && it->is_string() ? it->get<std::string>() : std::string {};
|
||||
}
|
||||
|
||||
std::string GetTitle(const nlohmann::json& param) {
|
||||
const auto localized = param.find("localizedParameters");
|
||||
if (localized != param.end() && localized->is_object()) {
|
||||
const auto preferred = localized->find(GetString(*localized, "defaultLanguage"));
|
||||
if (preferred != localized->end()) {
|
||||
if (auto title = GetString(*preferred, "titleName"); !title.empty()) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
for (const auto& language: *localized) {
|
||||
if (auto title = GetString(language, "titleName"); !title.empty()) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
return GetString(param, "titleName");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id) {
|
||||
if (dir.empty() || title_id.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const auto root = std::filesystem::absolute(dir, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Scan directories separately so an unreadable child does not discard its siblings.
|
||||
std::vector<std::filesystem::path> pending {root};
|
||||
std::vector<std::filesystem::path> params;
|
||||
while (!pending.empty()) {
|
||||
const auto current = std::move(pending.back());
|
||||
pending.pop_back();
|
||||
std::filesystem::directory_iterator it(
|
||||
current, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||
const std::filesystem::directory_iterator end;
|
||||
for (; !ec && it != end; it.increment(ec)) {
|
||||
std::error_code entry_ec;
|
||||
if (it->is_directory(entry_ec) && !it->is_symlink(entry_ec) && !entry_ec) {
|
||||
pending.push_back(it->path());
|
||||
} else if (it->is_regular_file(entry_ec) && !entry_ec &&
|
||||
EqualNoCase(PathToString(it->path().filename()), "param.json") &&
|
||||
EqualNoCase(PathToString(it->path().parent_path().filename()), "sce_sys")) {
|
||||
params.push_back(it->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(params.begin(), params.end());
|
||||
|
||||
static const std::regex content_id_pattern(
|
||||
"^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_[0-9]{2}-[A-Z0-9]{16}$");
|
||||
std::map<std::string, AddContentEntry> by_label;
|
||||
for (const auto& file: params) {
|
||||
const auto size = std::filesystem::file_size(file, ec);
|
||||
if (ec || size > 1024 * 1024) {
|
||||
continue;
|
||||
}
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
if (!stream) {
|
||||
continue;
|
||||
}
|
||||
const auto param = nlohmann::json::parse(stream, nullptr, false);
|
||||
// applicationCategoryType is required for applications, not additional content.
|
||||
if (!param.is_object() || param.contains("applicationCategoryType")) {
|
||||
continue;
|
||||
}
|
||||
const auto content_id = GetString(param, "contentId");
|
||||
if (!std::regex_match(content_id, content_id_pattern) ||
|
||||
content_id.compare(7, 9, title_id) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto mount_dir = file.parent_path().parent_path();
|
||||
const auto label = content_id.substr(20);
|
||||
by_label.try_emplace(label,
|
||||
AddContentEntry {content_id, label, mount_dir, GetTitle(param)});
|
||||
}
|
||||
|
||||
std::vector<AddContentEntry> entries;
|
||||
entries.reserve(by_label.size());
|
||||
for (auto& [label, entry]: by_label) {
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct AddContentEntry {
|
||||
std::string content_id;
|
||||
std::string entitlement_label;
|
||||
std::filesystem::path mount_dir;
|
||||
std::string title;
|
||||
};
|
||||
|
||||
// Finds packages for this title, deduplicated by entitlement label.
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
|
||||
@@ -72,6 +72,10 @@ bool FullscreenEnabled() {
|
||||
return g_config->fullscreen_enabled;
|
||||
}
|
||||
|
||||
bool HdrEnabled() {
|
||||
return g_config->hdr_enabled;
|
||||
}
|
||||
|
||||
bool HideCursorEnabled() {
|
||||
return g_config->hide_cursor_enabled;
|
||||
}
|
||||
@@ -96,6 +100,10 @@ bool VulkanValidationEnabled() {
|
||||
return g_config->vulkan_validation_enabled;
|
||||
}
|
||||
|
||||
bool IsNetworkConnected() {
|
||||
return g_config->network_connected;
|
||||
}
|
||||
|
||||
bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
@@ -164,6 +172,10 @@ bool TrophyEnabled() {
|
||||
return g_config->trophy_enabled;
|
||||
}
|
||||
|
||||
uint32_t GetTrophyDurationSeconds() {
|
||||
return std::clamp(g_config->trophy_duration_seconds, 5u, 10u);
|
||||
}
|
||||
|
||||
bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
@@ -182,4 +194,8 @@ const Keymap& GetKeymap() {
|
||||
return g_config->keymap;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetGuestArguments() {
|
||||
return g_config->guest_arguments;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
@@ -53,9 +53,12 @@ struct ConfigOptions {
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool splash_enabled = true;
|
||||
bool vr_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
bool network_connected = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -76,12 +79,15 @@ struct ConfigOptions {
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
uint32_t trophy_duration_seconds = 6;
|
||||
bool playgo_hack_enabled = false;
|
||||
bool skip_notice_screen = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
#endif
|
||||
Keymap keymap;
|
||||
// Ordinary boots need a non-null argv[0]; --guest-exec supplies the caller's arguments.
|
||||
std::vector<std::string> guest_arguments {""};
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
@@ -97,9 +103,11 @@ uint32_t GetControllerVibrationIntensity();
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
bool HdrEnabled();
|
||||
bool HideCursorEnabled();
|
||||
bool VrEnabled();
|
||||
bool AmdCpuEnabled();
|
||||
bool IsNetworkConnected();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
@@ -128,6 +136,7 @@ bool ReadbackLinearImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool TrophyEnabled();
|
||||
uint32_t GetTrophyDurationSeconds();
|
||||
bool PlayGoHackEnabled();
|
||||
bool SkipNoticeScreen();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -135,6 +144,7 @@ bool RedZoneProtectionEnabled();
|
||||
#endif
|
||||
|
||||
const Keymap& GetKeymap();
|
||||
const std::vector<std::string>& GetGuestArguments();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
@@ -45,8 +46,8 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
std::filesystem::create_directories(parent);
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
||||
const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr;
|
||||
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), truncate);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ struct Lifecycle {
|
||||
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
// Set by LoadExec for the program it starts, which continues the log file of the one it replaces
|
||||
// instead of starting it over.
|
||||
inline constexpr const char* RelaunchedVariable = "KYTY_RELAUNCHED";
|
||||
|
||||
Direction GetDirection();
|
||||
bool IsSilent();
|
||||
void Write(std::string_view text);
|
||||
|
||||
@@ -152,42 +152,29 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
||||
}
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
static sys_file_t* OpenHostFile(const std::filesystem::path& file_name, const char* mode,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
ret->f = fopen(real_name_str.c_str(), "w+");
|
||||
|
||||
if (ret->f == nullptr) {
|
||||
printf("can't create file: %s\n", real_name_str.c_str());
|
||||
ret->f = fopen(get_internal_name(file_name).c_str(), mode);
|
||||
ret->type = ret->f == nullptr ? SYS_FILE_ERROR : SYS_FILE_FILE;
|
||||
if (ret->f != nullptr) {
|
||||
// Independent handles must observe writes and truncation immediately.
|
||||
setvbuf(ret->f, nullptr, _IONBF, 0);
|
||||
apply_cache_hint(ret->f, cache_type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = OpenHostFile(file_name, "w+", SYS_FILE_CACHE_AUTO);
|
||||
if (ret->f == nullptr) {
|
||||
printf("can't create file: %s\n", file_name.c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
auto internal_name = get_internal_name(file_name);
|
||||
auto internal_name_str = internal_name.string();
|
||||
|
||||
FILE* f = fopen(internal_name_str.c_str(), "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
return OpenHostFile(file_name, "r", cache_type);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
@@ -215,46 +202,12 @@ sys_file_t* SysFileCreate() {
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
return OpenHostFile(file_name, "r+", cache_type);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
return OpenHostFile(file_name, "r+", cache_type);
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
|
||||
+15
-19
@@ -8,10 +8,6 @@
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_WIN_CS
|
||||
#endif
|
||||
|
||||
// macOS has no clock_nanosleep.
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__)
|
||||
#define KYTY_POSIX_HIGH_RES_SLEEP
|
||||
@@ -22,7 +18,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
|
||||
@@ -133,7 +129,7 @@ namespace Common {
|
||||
using thread_id_t = std::thread::id;
|
||||
|
||||
struct MutexPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
MutexPrivate() { InitializeCriticalSectionAndSpinCount(&m_cs, KYTY_CS_SPIN_COUNT); }
|
||||
~MutexPrivate() { DeleteCriticalSection(&m_cs); }
|
||||
KYTY_CLASS_NO_COPY(MutexPrivate);
|
||||
@@ -144,7 +140,7 @@ struct MutexPrivate {
|
||||
};
|
||||
|
||||
struct CondVarPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
CondVarPrivate() {
|
||||
static auto func = ResolveInitializeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
@@ -216,7 +212,7 @@ void Thread::Detach() {
|
||||
}
|
||||
|
||||
void Thread::SleepMicro(uint32_t micros) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
|
||||
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
|
||||
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
|
||||
@@ -226,7 +222,7 @@ void Thread::SleepMicro(uint32_t micros) {
|
||||
}
|
||||
|
||||
void Thread::SleepNano(uint64_t nanos) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SleepHighResolution100ns((nanos + 99) / 100);
|
||||
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
|
||||
SleepHighResolutionNanos(nanos);
|
||||
@@ -262,7 +258,7 @@ Mutex::~Mutex() {
|
||||
}
|
||||
|
||||
void Mutex::Lock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
EnterCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.lock();
|
||||
@@ -270,7 +266,7 @@ void Mutex::Lock() {
|
||||
}
|
||||
|
||||
void Mutex::Unlock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
LeaveCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.unlock();
|
||||
@@ -278,7 +274,7 @@ void Mutex::Unlock() {
|
||||
}
|
||||
|
||||
bool Mutex::TryLock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return (TryEnterCriticalSection(&m_mutex->m_cs) != 0);
|
||||
#else
|
||||
return m_mutex->m_mutex.try_lock();
|
||||
@@ -292,7 +288,7 @@ CondVar::~CondVar() {
|
||||
}
|
||||
|
||||
void CondVar::Wait(Mutex* mutex) {
|
||||
#ifndef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
auto poll_callback = [&] {
|
||||
@@ -300,7 +296,7 @@ void CondVar::Wait(Mutex* mutex) {
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
#if defined(KYTY_WIN_CS)
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
LeaveCriticalSection(&mutex->m_mutex->m_cs);
|
||||
callback();
|
||||
EnterCriticalSection(&mutex->m_mutex->m_cs);
|
||||
@@ -310,7 +306,7 @@ void CondVar::Wait(Mutex* mutex) {
|
||||
cpp_lock.lock();
|
||||
#endif
|
||||
};
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
if (g_cond_wait_poll_callback == nullptr) {
|
||||
@@ -340,10 +336,10 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
|
||||
|
||||
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
bool ok = false;
|
||||
#ifndef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
ok = !(func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, (micros < 1000 ? 1 : micros / 1000)) ==
|
||||
@@ -358,7 +354,7 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
}
|
||||
|
||||
void CondVar::Signal() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static auto func = ResolveWakeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cond_var->m_cv);
|
||||
@@ -368,7 +364,7 @@ void CondVar::Signal() {
|
||||
}
|
||||
|
||||
void CondVar::SignalAll() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static auto func = ResolveWakeAllConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cond_var->m_cv);
|
||||
|
||||
+15
-1
@@ -11,6 +11,7 @@
|
||||
#include "common/subsystems.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/memory.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include "libs/controller.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/additionalContent.h"
|
||||
#include "loader/runtimeLinker.h"
|
||||
#include "loader/systemContent.h"
|
||||
#include "loader/timer.h"
|
||||
@@ -138,6 +140,9 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
if (cfg.splash_enabled) {
|
||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||
}
|
||||
|
||||
// Initialization order is explicit; destruction is automatic and reversed.
|
||||
subsystems.Initialize<Loader::Timer::Lifecycle>();
|
||||
@@ -197,7 +202,15 @@ void Run(const RunOptions& options) {
|
||||
|
||||
PrintSystemInfo();
|
||||
std::string title_id;
|
||||
if (Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) && !title_id.empty()) {
|
||||
Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id);
|
||||
if (title_id.empty()) {
|
||||
std::string content_id;
|
||||
if (Loader::SystemContentParamSfoGetString("CONTENT_ID", &content_id) &&
|
||||
content_id.size() == 36 && content_id[6] == '-' && content_id[16] == '_') {
|
||||
title_id = content_id.substr(7, 9);
|
||||
}
|
||||
}
|
||||
if (!title_id.empty()) {
|
||||
Log::WriteToConsoleAndLog(fmt::format("Title ID: {}\n", title_id));
|
||||
}
|
||||
|
||||
@@ -213,6 +226,7 @@ void Run(const RunOptions& options) {
|
||||
|
||||
MountSandboxDirs();
|
||||
|
||||
Loader::AddContentLoad(options.addcont_dir, title_id);
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
Libs::InitAll(rt->Symbols());
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ struct RunOptions {
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
std::filesystem::path game_patch;
|
||||
std::filesystem::path addcont_dir;
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
int LoadExec(const char* path, const char* const argv[]);
|
||||
|
||||
} // namespace Emulator
|
||||
|
||||
|
||||
@@ -231,7 +231,6 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
|
||||
g_cp_op_custom_func[Pm4::R_WAIT_FLIP_DONE] = CpOpWaitFlipDone;
|
||||
g_cp_op_custom_func[Pm4::R_PUSH_MARKER] = CpOpPushMarker;
|
||||
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
|
||||
g_cp_op_custom_func[Pm4::R_ACQUIRE_MEM] = CpOpAcquireMem;
|
||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
||||
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
|
||||
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
|
||||
|
||||
@@ -295,10 +295,6 @@ static void HwCtxIgnoreDisabledUserClipPlane(CommandProcessor& cp, uint32_t valu
|
||||
EXIT_NOT_IMPLEMENTED(value != 0 || cp.GetCtx().GetClipControl().user_clip_planes != 0);
|
||||
}
|
||||
|
||||
static void HwCtxIgnoreDrawPayloadControl([[maybe_unused]] uint32_t value) {}
|
||||
|
||||
static void HwCtxIgnoreObjprimIdControl([[maybe_unused]] uint32_t value) {}
|
||||
|
||||
static void HwCtxIgnorePrimitiveIdReset([[maybe_unused]] uint32_t value) {}
|
||||
|
||||
static void HwCtxIgnoreVtxControl([[maybe_unused]] uint32_t value) {}
|
||||
@@ -454,23 +450,13 @@ KYTY_HW_CTX_PARSER(HwCtxSetAlphaToMask) {
|
||||
}
|
||||
|
||||
KYTY_HW_CTX_PARSER(HwCtxSetDrawPayloadControl) {
|
||||
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
|
||||
for (uint32_t i = 0; i < num_values; i++) {
|
||||
HwCtxIgnoreDrawPayloadControl(buffer[i]);
|
||||
}
|
||||
|
||||
return num_values;
|
||||
cp.GetCtx().SetDrawPayloadControl(buffer[0]);
|
||||
return KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
}
|
||||
|
||||
KYTY_HW_CTX_PARSER(HwCtxSetObjprimIdControl) {
|
||||
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
|
||||
for (uint32_t i = 0; i < num_values; i++) {
|
||||
HwCtxIgnoreObjprimIdControl(buffer[i]);
|
||||
}
|
||||
|
||||
return num_values;
|
||||
cp.GetCtx().SetObjprimIdControl(buffer[0]);
|
||||
return KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
}
|
||||
|
||||
KYTY_HW_CTX_PARSER(HwCtxSetPrimitiveIdReset) {
|
||||
@@ -1304,8 +1290,8 @@ KYTY_HW_UC_PARSER(HwUcSetGdsOaRegisters) {
|
||||
KYTY_CP_OP_PARSER(CpOpAcquireMem) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0055800 && cmd_id != 0xc0061050);
|
||||
return (cmd_id == 0xc0061050 ? 7 : 6);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0055800 && cmd_id != 0xc0065800);
|
||||
return KYTY_PM4_LEN(cmd_id) - 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpDispatchDirect) {
|
||||
@@ -1934,13 +1920,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
|
||||
|
||||
auto* indirect_buffer =
|
||||
reinterpret_cast<const uint32_t*>(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
|
||||
uint32_t indirect_num_dw = control & 0xfffffu;
|
||||
static std::atomic<uint32_t> indirect_log_count {0};
|
||||
if (indirect_log_count.fetch_add(1) < 128) {
|
||||
LOGF("\t indirect buffer: addr=0x%016" PRIx64 ", num_dw=%" PRIu32 ", control=0x%08" PRIx32
|
||||
"\n",
|
||||
reinterpret_cast<uint64_t>(indirect_buffer), indirect_num_dw, control);
|
||||
}
|
||||
const uint32_t indirect_num_dw = control & 0xfffffu;
|
||||
|
||||
if (indirect_num_dw == 0) {
|
||||
return 3;
|
||||
@@ -2148,27 +2128,23 @@ KYTY_CP_OP_PARSER(CpOpIndirectUcRegs) {
|
||||
KYTY_CP_OP_PARSER(CpOpMarker) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
// EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0001000);
|
||||
|
||||
uint32_t id = buffer[0] & 0xfffu;
|
||||
uint32_t len_dw = ((cmd_id >> 16u) & 0x3fffu);
|
||||
|
||||
switch (id) {
|
||||
case 0x0: break;
|
||||
case 0x4: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
|
||||
case 0xd: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
|
||||
case 0x777: {
|
||||
switch (buffer[0]) {
|
||||
case 0x68750004: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
|
||||
case 0x6875000d: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
|
||||
case 0x68750777: {
|
||||
cp.Flip();
|
||||
break;
|
||||
}
|
||||
case 0x778: {
|
||||
case 0x68750778: {
|
||||
auto* addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t value = buffer[3];
|
||||
cp.Flip(addr, value);
|
||||
break;
|
||||
}
|
||||
case 0x781: {
|
||||
case 0x68750781: {
|
||||
auto* addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t value = buffer[3];
|
||||
@@ -2177,7 +2153,8 @@ KYTY_CP_OP_PARSER(CpOpMarker) {
|
||||
cp.FlipWithInterrupt(eop_event_type, cache_action, addr, value);
|
||||
break;
|
||||
}
|
||||
default: EXIT("unknown marker at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, id);
|
||||
// NOP payloads are caller-owned data; only complete known marker tags execute.
|
||||
default: break;
|
||||
}
|
||||
|
||||
return len_dw + 1;
|
||||
@@ -3115,7 +3092,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
cp.GetCtx().SetTfParam(value);
|
||||
};
|
||||
g_hw_ctx_indirect_func[Pm4::VGT_DRAW_PAYLOAD_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnoreDrawPayloadControl(value);
|
||||
cp.GetCtx().SetDrawPayloadControl(value);
|
||||
};
|
||||
g_hw_ctx_indirect_func[Pm4::VGT_PRIMITIVEID_RESET] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnorePrimitiveIdReset(value);
|
||||
@@ -3124,7 +3101,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
cp.GetCtx().SetShaderZFormat(value);
|
||||
};
|
||||
g_hw_ctx_indirect_func[Pm4::PA_CL_OBJPRIM_ID_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnoreObjprimIdControl(value);
|
||||
cp.GetCtx().SetObjprimIdControl(value);
|
||||
};
|
||||
|
||||
for (auto cmd_offset = Pm4::PA_SC_CLIPRECT_RULE; cmd_offset < Pm4::PA_SC_CLIPRECT_0_TL + 8u;
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace {
|
||||
|
||||
struct FormatInfo {
|
||||
BufferFormat format;
|
||||
uint32_t bytes_per_element;
|
||||
uint32_t block_compressed_bytes_per_block;
|
||||
uint32_t render_target_bytes_per_element;
|
||||
uint8_t bytes_per_element;
|
||||
uint8_t block_compressed_bytes_per_block;
|
||||
uint8_t render_target_bytes_per_element;
|
||||
bool sampled_texture;
|
||||
bool uint_texture;
|
||||
bool sint_texture = false;
|
||||
@@ -21,8 +21,9 @@ struct FormatInfo {
|
||||
constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k8UNorm, 1, 0, 1, true, false},
|
||||
{BufferFormat::k8UScaled, 1, 0, 0, true, false},
|
||||
{BufferFormat::k8SNorm, 1, 0, 1, false, false},
|
||||
{BufferFormat::k8SNorm, 1, 0, 1, true, false},
|
||||
{BufferFormat::k8UInt, 1, 0, 1, true, true},
|
||||
{BufferFormat::k8SInt, 1, 0, 1, true, false, true},
|
||||
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k16SNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k16UInt, 2, 0, 2, true, true},
|
||||
@@ -41,6 +42,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k16_16SInt, 4, 0, 4, true, false, true},
|
||||
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
|
||||
{BufferFormat::k11_11_10UNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
|
||||
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
|
||||
@@ -237,6 +239,7 @@ BufferFormat RemapTextureFormat(BufferFormat format) {
|
||||
switch (format) {
|
||||
case BufferFormat::k8UScaled: return BufferFormat::k8UNorm;
|
||||
case BufferFormat::k8_8UScaled: return BufferFormat::k8_8UNorm;
|
||||
case BufferFormat::k11_11_10UNorm:
|
||||
case BufferFormat::k11_11_10UInt: return BufferFormat::k32UInt;
|
||||
default: return format;
|
||||
}
|
||||
|
||||
@@ -576,12 +576,7 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
}
|
||||
}
|
||||
if (progressed) {
|
||||
if (complete) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
}
|
||||
cp.BufferFlush();
|
||||
} else if (complete) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -602,20 +597,16 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
complete = cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
if (submission.command_execution.MadeProgress()) {
|
||||
if (complete) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
}
|
||||
cp.BufferFlush();
|
||||
} else if (complete) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::FlipPreparation:
|
||||
m_renderer.RunGarbageCollector();
|
||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||
break;
|
||||
case SubmissionType::SuspendPoint:
|
||||
// Age caches at the frame boundary, not once per command list.
|
||||
m_renderer.RunGarbageCollector();
|
||||
cp.EmitGlobalBarrier();
|
||||
m_renderer.GetCommandScheduler().DeferPriorityOperation(
|
||||
[ready = m_suspend_point_ready] { ready->release(); });
|
||||
@@ -689,7 +680,7 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
|
||||
const auto opcode = (packet_header >> 8u) & 0xffu;
|
||||
EXIT_NOT_IMPLEMENTED(remaining_dw > total_dw);
|
||||
|
||||
if (packet_header == 0x80000000u) {
|
||||
if (packet_header == 0x80000000u || Pm4::PacketSizeDw(packet_header) == 1u) {
|
||||
cursor.offset_dw++;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
@@ -1003,7 +994,7 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
thread_group_y, thread_group_z, mode, IsAsyncComputeQueue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,7 +1006,8 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
return;
|
||||
}
|
||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||
m_renderer.GetRenderExecutor().DispatchIndirect(m_submit_id, CurrentBuffer(), args_addr, mode);
|
||||
m_renderer.GetRenderExecutor().DispatchIndirect(m_submit_id, CurrentBuffer(), args_addr, mode,
|
||||
IsAsyncComputeQueue());
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
@@ -1071,8 +1063,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x03: with_interrupt = false; break;
|
||||
case 0x01:
|
||||
if (!IsAsyncComputeQueue()) {
|
||||
Sync::TriggerEopEventAtEndOfPipe(command, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
return;
|
||||
}
|
||||
with_interrupt = true;
|
||||
@@ -1081,24 +1072,15 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
default: EXIT("unknown interrupt_selector\n");
|
||||
}
|
||||
|
||||
auto write32 = [&](bool with_writeback) {
|
||||
auto write32 = [&] {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
auto data = static_cast<uint32_t>(value);
|
||||
EXIT_IF(dst == nullptr);
|
||||
(void)command.Handle();
|
||||
std::memcpy(dst, &data, sizeof(data));
|
||||
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, command, dst,
|
||||
data, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, command, dst, data,
|
||||
m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, command, dst, data);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe32(m_submit_id, command, dst, data);
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1110,15 +1092,13 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, command, dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
if (with_interrupt) {
|
||||
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) {
|
||||
write32(false);
|
||||
write32();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1127,8 +1107,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if constexpr (sizeof(T) == sizeof(uint32_t)) {
|
||||
if (event_write_source == 0x02 && eop_event_type == 0x2f && event_index == 0x06) {
|
||||
switch (cache_action) {
|
||||
case 0x00: write32(false); return;
|
||||
case 0x38: write32(true); return;
|
||||
case 0x00:
|
||||
case 0x38: write32(); return;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -1136,25 +1116,14 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (event_write_source == 0x04) {
|
||||
value = Sync::ReadReferenceClock();
|
||||
}
|
||||
auto write64 = [&](bool with_writeback) {
|
||||
auto write64 = [&] {
|
||||
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
|
||||
EXIT_IF(dst == nullptr);
|
||||
(void)command.Handle();
|
||||
std::memcpy(dst, &value, sizeof(value));
|
||||
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, command, dst, value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, command, dst,
|
||||
value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, command, dst,
|
||||
value);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe64(m_submit_id, command, dst, value);
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1163,7 +1132,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
switch (eop_event_type) {
|
||||
case 0x04:
|
||||
if (event_index == 0x05) {
|
||||
write64(false);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1171,7 +1140,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x28:
|
||||
case 0x2f:
|
||||
if (event_index == 0x00) {
|
||||
write64(false);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1179,7 +1148,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x2d:
|
||||
case 0x30:
|
||||
if (event_index == 0x00 && !with_interrupt) {
|
||||
write64(false);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1194,20 +1163,20 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (((eop_event_type == 0x04 || eop_event_type == 0x28) &&
|
||||
event_index == 0x05) ||
|
||||
(event_index == 0x00)) {
|
||||
write64(true);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x2b:
|
||||
case 0x2d:
|
||||
if (event_index == 0x00 && !with_interrupt) {
|
||||
write64(true);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x2f:
|
||||
if (event_index == 0x06 && !with_interrupt) {
|
||||
write64(true);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1216,7 +1185,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
break;
|
||||
case 0x3b:
|
||||
if (eop_event_type == 0x04 && event_index == 0x05 && with_interrupt) {
|
||||
write64(true);
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1269,7 +1238,11 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
|
||||
(void)CurrentBuffer().Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||
renderer.TriggerInterrupt(event_id, interrupt_context_id);
|
||||
});
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
@@ -1359,8 +1332,9 @@ void CommandProcessor::Flip() {
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeOnlyFlip(m_submit_id, command, m_flip.handle, m_flip.index,
|
||||
m_flip.flip_mode, m_flip.flip_arg, request);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
@@ -1374,12 +1348,13 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr),
|
||||
value, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg, request);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
@@ -1399,12 +1374,16 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
if (eop_event_type != 0x00000004 || cache_action != 0x00000038) {
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
|
||||
m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr), value, m_flip.handle,
|
||||
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request, m_interrupt_event_id);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, request] {
|
||||
renderer.GetVideoOut().CompleteFlip(request);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -556,6 +556,8 @@ struct ShaderRegisters {
|
||||
uint32_t m_vgtGsMaxVertOut = 0;
|
||||
uint32_t m_vgtGsOutPrimType = 0;
|
||||
uint32_t m_vgtPrimitiveIdEn = 0;
|
||||
uint32_t m_vgtDrawPayloadCntl = 0;
|
||||
uint32_t m_paClObjprimIdCntl = 0;
|
||||
uint32_t m_vgtReuseOff = 0;
|
||||
uint32_t m_vgtTessDistribution = 0;
|
||||
uint32_t m_vgtLsHsConfig = 0;
|
||||
@@ -679,6 +681,10 @@ public:
|
||||
|
||||
void SetRenderTargetMask(uint32_t mask) { m_render_target_mask = mask; }
|
||||
[[nodiscard]] uint32_t GetRenderTargetMask() const { return m_render_target_mask; }
|
||||
[[nodiscard]] bool HasObjectPrimitiveIdOutput() const {
|
||||
return (m_sh_regs.m_vgtDrawPayloadCntl & 1u) != 0 &&
|
||||
(m_render_target_mask & 0xf0000000u) != 0;
|
||||
}
|
||||
|
||||
void SetShaderStages(uint32_t flags) { m_shader_stages = flags; }
|
||||
[[nodiscard]] uint32_t GetShaderStages() const { return m_shader_stages; }
|
||||
@@ -877,6 +883,8 @@ public:
|
||||
void SetGsMaxVertOut(uint32_t value) { m_sh_regs.m_vgtGsMaxVertOut = value; }
|
||||
void SetGsOutPrimType(uint32_t value) { m_sh_regs.m_vgtGsOutPrimType = value; }
|
||||
void SetPrimitiveIdEn(uint32_t value) { m_sh_regs.m_vgtPrimitiveIdEn = value; }
|
||||
void SetDrawPayloadControl(uint32_t value) { m_sh_regs.m_vgtDrawPayloadCntl = value; }
|
||||
void SetObjprimIdControl(uint32_t value) { m_sh_regs.m_paClObjprimIdCntl = value; }
|
||||
void SetReuseOff(uint32_t value) { m_sh_regs.m_vgtReuseOff = value; }
|
||||
void SetTessDistribution(uint32_t value) { m_sh_regs.m_vgtTessDistribution = value; }
|
||||
void SetLsHsConfig(uint32_t value) { m_sh_regs.m_vgtLsHsConfig = value; }
|
||||
|
||||
@@ -23,7 +23,6 @@ constexpr auto MakeRegisterNames() {
|
||||
names[R_DISPATCH_RESET] = "R_DISPATCH_RESET";
|
||||
names[R_PUSH_MARKER] = "R_PUSH_MARKER";
|
||||
names[R_POP_MARKER] = "R_POP_MARKER";
|
||||
names[R_ACQUIRE_MEM] = "R_ACQUIRE_MEM";
|
||||
names[R_WRITE_DATA] = "R_WRITE_DATA";
|
||||
names[R_FLIP] = "R_FLIP";
|
||||
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
|
||||
@@ -120,15 +119,15 @@ void DumpPm4PacketStream(Common::File* file, const uint32_t* cmd_buffer, uint32_
|
||||
uint32_t len = 0;
|
||||
|
||||
const auto packet_type = static_cast<PacketType>(cmd_id >> 30u);
|
||||
// Type-2 packets are header-only padding; every other packet type requires a body.
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2 && packet_type != PacketType::Type2);
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2 && packet_type != PacketType::Type2 &&
|
||||
PacketSizeDw(cmd_id) != 1u);
|
||||
|
||||
switch (packet_type) {
|
||||
case PacketType::Type3: {
|
||||
const bool sh_gx = (cmd_id & 0x2u) == 0;
|
||||
const uint8_t op = ((cmd_id >> 8u) & 0xffu);
|
||||
const auto r = KYTY_PM4_R(cmd_id);
|
||||
len = ((cmd_id >> 16u) & 0x3fffu) + 1;
|
||||
len = PacketSizeDw(cmd_id) - 1u;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(len >= dw);
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ class File;
|
||||
|
||||
namespace Libs::Graphics::Pm4 {
|
||||
|
||||
constexpr uint32_t PacketSizeDw(uint32_t header) {
|
||||
return (header & 0x3fffff00u) == 0x3fff1000u ? 1u : KYTY_PM4_LEN(header);
|
||||
}
|
||||
|
||||
constexpr uint32_t IT_NOP = 0x10;
|
||||
constexpr uint32_t IT_SET_BASE = 0x11;
|
||||
constexpr uint32_t IT_CLEAR_STATE = 0x12;
|
||||
@@ -82,7 +86,6 @@ constexpr uint32_t R_WAIT_FLIP_DONE = 0x06;
|
||||
constexpr uint32_t R_DISPATCH_RESET = 0x09;
|
||||
constexpr uint32_t R_PUSH_MARKER = 0x0B;
|
||||
constexpr uint32_t R_POP_MARKER = 0x0C;
|
||||
constexpr uint32_t R_ACQUIRE_MEM = 0x14;
|
||||
constexpr uint32_t R_WRITE_DATA = 0x15;
|
||||
constexpr uint32_t R_FLIP = 0x17;
|
||||
constexpr uint32_t R_RELEASE_MEM = 0x18;
|
||||
|
||||
@@ -299,8 +299,8 @@ bool TileGetTextureBlockLayout(Prospero::BufferFormat format, Prospero::TileMode
|
||||
if (!TileGetTextureElementLayout(format, element)) {
|
||||
return false;
|
||||
}
|
||||
if ((family == TileBlockFamily::Depth64KB || family == TileBlockFamily::RenderTarget64KB) &&
|
||||
Prospero::RenderTargetBytesPerElement(format) != element.bytes) {
|
||||
if (family == TileBlockFamily::Depth64KB &&
|
||||
(element.texel_width != 1 || element.texel_height != 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ struct GraphicContext {
|
||||
// bool fp64_denorm_preserve = false; // Temporarily disabled.
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool maintenance8_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
|
||||
@@ -60,7 +60,9 @@ private:
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
static thread_local const uint32_t tid =
|
||||
static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
return tid;
|
||||
#elif defined(__linux__)
|
||||
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
|
||||
return tid;
|
||||
|
||||
+37
-25
@@ -27,6 +27,26 @@ namespace {
|
||||
constexpr uint64_t MiB = 1024 * 1024;
|
||||
constexpr uint64_t GdsBufferSize = 64 * 1024;
|
||||
|
||||
std::pair<Buffer*, uint64_t> UploadMemory(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
StreamBuffer& staging, uint64_t size, uint64_t alignment,
|
||||
vk::BufferUsageFlags usage, const auto& write) {
|
||||
const auto [mapped, offset] = staging.Map(size, alignment);
|
||||
if (mapped != nullptr) {
|
||||
write(mapped, offset);
|
||||
staging.Commit();
|
||||
return {&staging, offset};
|
||||
}
|
||||
|
||||
// Large transfers use the same one-time host allocation as ordinary buffer uploads.
|
||||
auto temporary = std::make_unique<Buffer>(graphics, scheduler, MemoryUsage::Upload, 0,
|
||||
usage, size);
|
||||
write(temporary->Mapped().data(), 0);
|
||||
temporary->Flush(0, size);
|
||||
auto* buffer = temporary.get();
|
||||
scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
|
||||
return {buffer, 0};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source,
|
||||
@@ -447,28 +467,17 @@ vk::Buffer BufferCache::UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> c
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto [mapped, base_offset] = m_staging_buffer.Map(total_size, 4);
|
||||
if (mapped != nullptr) {
|
||||
const auto source = UploadMemory(m_graphics, m_scheduler, m_staging_buffer, total_size, 4,
|
||||
vk::BufferUsageFlagBits::eTransferSrc, [&](uint8_t* mapped, uint64_t base_offset) {
|
||||
for (auto& copy: copies) {
|
||||
const auto address = buffer.CpuAddress() + copy.dstOffset;
|
||||
std::memcpy(mapped + copy.srcOffset, reinterpret_cast<const void*>(address), copy.size);
|
||||
if (!LibKernel::Memory::CopySparseMemory(address, mapped + copy.srcOffset, copy.size)) {
|
||||
EXIT("BufferCache: failed to read sparse guest buffer memory\n");
|
||||
}
|
||||
copy.srcOffset += base_offset;
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
return m_staging_buffer.Handle();
|
||||
}
|
||||
|
||||
auto temporary = std::make_unique<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
|
||||
vk::BufferUsageFlagBits::eTransferSrc, total_size);
|
||||
for (const auto& copy: copies) {
|
||||
const auto address = buffer.CpuAddress() + copy.dstOffset;
|
||||
std::memcpy(temporary->Mapped().data() + copy.srcOffset,
|
||||
reinterpret_cast<const void*>(address), copy.size);
|
||||
}
|
||||
temporary->Flush(0, total_size);
|
||||
const auto handle = temporary->Handle();
|
||||
m_scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
|
||||
return handle;
|
||||
});
|
||||
return source.first->Handle();
|
||||
}
|
||||
|
||||
std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t size,
|
||||
@@ -486,7 +495,9 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
|
||||
auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
|
||||
if (mapped != nullptr) {
|
||||
std::memcpy(mapped, reinterpret_cast<const void*>(vaddr), size);
|
||||
if (!LibKernel::Memory::CopySparseMemory(vaddr, mapped, size)) {
|
||||
EXIT("BufferCache: failed to read sparse guest buffer memory\n");
|
||||
}
|
||||
m_stream_buffer.Commit();
|
||||
return {&m_stream_buffer, offset};
|
||||
}
|
||||
@@ -521,12 +532,13 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
|
||||
return ObtainBuffer(vaddr, size, false, false);
|
||||
}
|
||||
|
||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
||||
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, staging, size)) {
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
return {&m_staging_buffer, stage_offset};
|
||||
return UploadMemory(m_graphics, m_scheduler, m_staging_buffer, size, 16,
|
||||
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eStorageBuffer,
|
||||
[&](uint8_t* mapped, uint64_t) {
|
||||
if (!Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, mapped, size)) {
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
||||
|
||||
+266
-353
@@ -25,6 +25,7 @@
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vulkan/vulkan_format_traits.hpp>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -36,7 +37,9 @@ constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
[[nodiscard]] bool DecodeColorClear(const TextureCache::ImageDesc& desc, uint8_t code,
|
||||
vk::ClearColorValue& clear) {
|
||||
const auto& metadata = desc.info.metadata;
|
||||
const auto format = desc.view_info.format;
|
||||
const auto format = desc.info.IsDepth()
|
||||
? TextureGetSurfaceFormatInfo(desc.info.guest_format).vk_format
|
||||
: desc.view_info.format;
|
||||
const bool cmask = metadata.kind == ImageMetadataKind::Cmask;
|
||||
if (cmask ? code == 0 : code == 0x20) {
|
||||
// Register clears belong to the color buffer; the texture pipe cannot decode them.
|
||||
@@ -110,37 +113,6 @@ constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
|
||||
uint64_t slice_stride,
|
||||
vk::ImageAspectFlags aspect) {
|
||||
std::vector<vk::BufferImageCopy> copies(info.resources.layers);
|
||||
for (uint32_t layer = 0; layer < info.resources.layers; ++layer) {
|
||||
auto& copy = copies[layer];
|
||||
copy.bufferOffset = slice_stride * layer;
|
||||
copy.bufferRowLength = info.pitch;
|
||||
copy.bufferImageHeight = info.extent.height;
|
||||
copy.imageSubresource = {aspect, 0, layer, 1};
|
||||
copy.imageExtent = {info.extent.width, info.extent.height, 1};
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<GpuTileInfo> BuildDepthTiles(const ImageInfo& info) {
|
||||
TileBlockLayout block {};
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(TileBlockFamily::Depth64KB, info.bytes_per_block, block));
|
||||
const auto full_slice_size = info.data.size / info.resources.layers;
|
||||
std::vector<GpuTileInfo> tiles;
|
||||
tiles.reserve(info.resources.layers);
|
||||
for (uint32_t layer = 0; layer < info.resources.layers; ++layer) {
|
||||
const auto offset = full_slice_size * layer;
|
||||
tiles.push_back({block.family, block.bytes_per_element, offset, full_slice_size, offset,
|
||||
full_slice_size, 0, info.extent.width, info.extent.height, 1, info.pitch});
|
||||
tiles.back().surface_z = layer;
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
@@ -227,9 +199,7 @@ TextureCache::BindingType TextureCache::UploadBinding(const Image& image) {
|
||||
|
||||
ImageId TextureCache::InsertImage(const ImageInfo& info) {
|
||||
const auto id = m_slot_images.insert(m_graphics, m_scheduler, info);
|
||||
if (!info.data.Empty()) {
|
||||
RegisterImage(id);
|
||||
}
|
||||
RegisterImage(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -280,14 +250,11 @@ void TextureCache::DeleteImage(ImageId id) {
|
||||
if (image == nullptr || !image->registered) {
|
||||
return;
|
||||
}
|
||||
if (!image->depth_id) {
|
||||
std::vector<ImageId> associations;
|
||||
m_slot_images.ForEach([&](ImageId candidate, const Image& associated) {
|
||||
if (associated.depth_id == id) {
|
||||
associations.push_back(candidate);
|
||||
}
|
||||
});
|
||||
for (const auto association: associations) {
|
||||
if (image->depth_id) {
|
||||
std::erase(m_slot_images[image->depth_id].stencil_ids, id);
|
||||
} else {
|
||||
// Detach first: deleting each association also unlinks it from its depth owner.
|
||||
for (const auto association: std::exchange(image->stencil_ids, {})) {
|
||||
FreeImage(association);
|
||||
}
|
||||
}
|
||||
@@ -328,9 +295,6 @@ void TextureCache::TouchImage(Image& image) {
|
||||
|
||||
void TextureCache::MarkAsMaybeDirty(ImageId id, Image& image) {
|
||||
image.MarkMaybeCpuDirty();
|
||||
if (image.NeedsMaybeCpuHash()) {
|
||||
image.SetMaybeCpuHash(image.HashGuestEdges());
|
||||
}
|
||||
UntrackImage(id);
|
||||
}
|
||||
|
||||
@@ -442,7 +406,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
|
||||
}
|
||||
|
||||
void TextureCache::TrackImageDownload(ImageId id, Image& image) {
|
||||
if (m_readback_linear_images && !image.info.IsTiled() && !image.info.data.Empty()) {
|
||||
if (m_readback_linear_images && !image.info.IsTiled()) {
|
||||
if (!image.IsGpuModified()) {
|
||||
EXIT("TextureCache: cannot enroll a non-GPU-owned image for download\n");
|
||||
}
|
||||
@@ -486,27 +450,6 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
|
||||
return result;
|
||||
}
|
||||
|
||||
ImageId TextureCache::GetNullImage(const ImageDesc& desc) {
|
||||
const auto format = desc.info.pixel_format;
|
||||
if (const auto found = m_null_images.find(format); found != m_null_images.end()) {
|
||||
return found->second;
|
||||
}
|
||||
ImageInfo info {};
|
||||
info.pixel_format = desc.info.pixel_format;
|
||||
info.guest_format = desc.info.guest_format;
|
||||
info.type = Prospero::ImageType::kColor2D;
|
||||
info.extent = {1, 1, 1};
|
||||
info.resources = {1, 1};
|
||||
info.pitch = 1;
|
||||
info.bytes_per_block = std::max(desc.info.bytes_per_block, 1u);
|
||||
info.samples = 1;
|
||||
info.tile_mode = Prospero::TileMode::kLinear;
|
||||
info.mip_layout[0] = {0, info.bytes_per_block, 1, 1};
|
||||
const auto id = InsertImage(info);
|
||||
m_null_images.emplace(format, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void TextureCache::ValidateImageDesc(const ImageDesc& desc) const {
|
||||
ImageOps::Validate(desc.info);
|
||||
if (desc.view_info.format == vk::Format::eUndefined || desc.view_info.level_count == 0 ||
|
||||
@@ -570,20 +513,20 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
||||
const auto depth_size = depth_slice * layers;
|
||||
const auto color_size = color_slice * layers;
|
||||
std::vector<vk::BufferImageCopy> depth_copies(layers);
|
||||
std::vector<vk::BufferImageCopy> color_copies(layers);
|
||||
for (uint32_t layer = 0; layer < layers; layer++) {
|
||||
depth_copies[layer].bufferOffset = depth_slice * layer;
|
||||
depth_copies[layer].bufferRowLength = depth.info.pitch;
|
||||
depth_copies[layer].bufferImageHeight = depth.info.extent.height;
|
||||
depth_copies[layer].imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1};
|
||||
depth_copies[layer].imageExtent = depth.info.extent;
|
||||
color_copies[layer].bufferOffset = color_slice * layer;
|
||||
color_copies[layer].bufferRowLength = color.info.pitch;
|
||||
color_copies[layer].bufferImageHeight = color.info.extent.height;
|
||||
color_copies[layer].imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, layer, 1};
|
||||
color_copies[layer].imageExtent = color.info.extent;
|
||||
}
|
||||
const std::array depth_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
depth.info.pitch,
|
||||
depth.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
depth.info.extent}};
|
||||
const std::array color_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
color.info.pitch,
|
||||
color.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
||||
{},
|
||||
color.info.extent}};
|
||||
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
||||
@@ -662,6 +605,9 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) {
|
||||
void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint32_t mip,
|
||||
uint32_t layer) {
|
||||
RefreshCopySource(source_id);
|
||||
if (m_slot_images[destination_id].IsBufferModified()) {
|
||||
RefreshImage(destination_id);
|
||||
}
|
||||
auto& destination = m_slot_images[destination_id];
|
||||
auto& source = m_slot_images[source_id];
|
||||
TrackImage(destination_id);
|
||||
@@ -740,9 +686,13 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
if (retain_cached_layout) {
|
||||
info.data = cached.info.data;
|
||||
info.resources = cached.info.resources;
|
||||
info.mip_layout = cached.info.mip_layout;
|
||||
info.mip_layout = cached.info.mip_layout;
|
||||
info.tiling = cached.info.tiling;
|
||||
info.tiled_slice_stride = cached.info.tiled_slice_stride;
|
||||
info.linear_slice_stride = cached.info.linear_slice_stride;
|
||||
info.first_tail_level = cached.info.first_tail_level;
|
||||
}
|
||||
info.htile_clear_mask = 0;
|
||||
info.htile_initial_clear = false;
|
||||
const auto replacement_id = InsertImage(info);
|
||||
auto& replacement = m_slot_images[replacement_id];
|
||||
replacement.usage = cached.usage;
|
||||
@@ -778,11 +728,63 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
return replacement_id;
|
||||
}
|
||||
|
||||
namespace BcAlias {
|
||||
|
||||
bool Matches(const ImageInfo& requested, const ImageInfo& cached) {
|
||||
return requested.guest_format != cached.guest_format &&
|
||||
requested.IsBlock() != cached.IsBlock() &&
|
||||
requested.tile_mode == cached.tile_mode &&
|
||||
requested.bytes_per_block == cached.bytes_per_block &&
|
||||
requested.samples == 1 && cached.samples == 1 &&
|
||||
(requested.data != cached.data || requested.resources != cached.resources ||
|
||||
requested.BlockExtent() != cached.BlockExtent()) &&
|
||||
!TextureCache::SameBacking(cached, requested, false);
|
||||
}
|
||||
|
||||
bool Publish(TextureCache& cache, Image& image) {
|
||||
if (!cache.CanDownload(image)) {
|
||||
return false;
|
||||
}
|
||||
// ObtainBuffer preserves padding and records ownership before the GPU-only transfer.
|
||||
auto [buffer, offset] =
|
||||
cache.m_buffer_cache.ObtainBuffer(image.info.data.address, image.info.data.size, true);
|
||||
cache.DownloadImage(image, *buffer, offset, image.info.data.size, image.info.resources.levels);
|
||||
image.ClearGpuModified();
|
||||
image.MarkBufferModified();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Resolve(TextureCache& cache, ImageId cached_id, ImageId merged_id) {
|
||||
auto& cached = cache.m_slot_images[cached_id];
|
||||
auto* merged = merged_id ? &cache.m_slot_images[merged_id] : nullptr;
|
||||
// PS5 raw block images can span several packed BC mips. Transfer their complete
|
||||
// guest bytes, including tail padding, before retiring the incompatible backing.
|
||||
if (cached.SafeToDownload()) {
|
||||
if (merged && merged->SafeToDownload() && !Publish(cache, *merged)) {
|
||||
return;
|
||||
}
|
||||
if (!Publish(cache, cached)) {
|
||||
return;
|
||||
}
|
||||
if (merged) {
|
||||
merged->MarkBufferModified();
|
||||
}
|
||||
}
|
||||
cached.binding.needs_rebind |= cached.binding.is_bound || cached.binding.is_target;
|
||||
cache.FreeImage(cached_id);
|
||||
}
|
||||
|
||||
} // namespace BcAlias
|
||||
|
||||
TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& requested,
|
||||
BindingType binding, ImageId cached_id,
|
||||
ImageId merged_id) {
|
||||
auto owner = m_slot_images.try_get(cached_id);
|
||||
if (owner == nullptr) {
|
||||
if (owner == nullptr || !owner->registered) {
|
||||
return {merged_id};
|
||||
}
|
||||
if (BcAlias::Matches(requested, owner->info) && !owner->IsCpuDirty()) {
|
||||
BcAlias::Resolve(*this, cached_id, merged_id);
|
||||
return {merged_id};
|
||||
}
|
||||
auto& cached = *owner;
|
||||
@@ -801,7 +803,14 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
// a level into or out of the mip tail. These are separate guest layouts.
|
||||
if (requested.tile_mode != cached.info.tile_mode ||
|
||||
(requested.resources == cached.info.resources &&
|
||||
requested.mip_layout != cached.info.mip_layout)) {
|
||||
!std::equal(requested.mip_layout.begin(), requested.mip_layout.end(),
|
||||
cached.info.mip_layout.begin(),
|
||||
[](const ImageMipInfo& left, const ImageMipInfo& right) {
|
||||
return left.offset == right.offset && left.size == right.size &&
|
||||
left.pitch == right.pitch && left.height == right.height &&
|
||||
left.tail_x == right.tail_x && left.tail_y == right.tail_y &&
|
||||
left.surface_z == right.surface_z;
|
||||
}))) {
|
||||
if (safe_to_delete) {
|
||||
FreeImage(cached_id);
|
||||
}
|
||||
@@ -913,168 +922,55 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
|
||||
return expanded_id;
|
||||
}
|
||||
|
||||
struct TextureCache::TextureTransfer {
|
||||
TextureUploadLayout layout;
|
||||
std::vector<vk::BufferImageCopy> regions;
|
||||
std::vector<GpuTileInfo> tiles;
|
||||
ColorTransform color_transform = ColorTransform::None;
|
||||
bool valid = false;
|
||||
|
||||
[[nodiscard]] uint64_t LinearSize() const {
|
||||
uint64_t size = 0;
|
||||
for (const auto& tile: tiles) {
|
||||
size = std::max(size, tile.linear_offset + tile.linear_size);
|
||||
}
|
||||
return size;
|
||||
bool TextureCache::CanDownload(const Image& image) const {
|
||||
const auto& info = image.info;
|
||||
if (info.samples != 1 || image.backing.samples != 1 || info.resources.layers == 0 ||
|
||||
info.data.size % info.resources.layers != 0) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureCache::ImageDownload {
|
||||
TextureTransfer texture;
|
||||
bool depth_target = false;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
TextureCache::TextureTransfer
|
||||
TextureCache::BuildTextureTransfer(const Image& image, BindingType binding,
|
||||
TransferDirection direction) const {
|
||||
const auto& info = image.info;
|
||||
const bool upload = direction == TransferDirection::Upload;
|
||||
const bool render_target = binding == BindingType::RenderTarget;
|
||||
const bool video_out = binding == BindingType::VideoOut;
|
||||
uint32_t layers = info.TransferLayers();
|
||||
bool volume = info.IsVolume();
|
||||
const char* owner = "TextureCache readback";
|
||||
|
||||
TextureTransfer transfer;
|
||||
transfer.color_transform = info.GetColorTransform();
|
||||
if (upload) {
|
||||
if ((render_target || video_out) &&
|
||||
(info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
info.samples != 1 || image.backing.samples != 1)) {
|
||||
EXIT("TextureCache: invalid color-attachment upload\n");
|
||||
}
|
||||
owner = "TextureCache";
|
||||
if (render_target) {
|
||||
owner = "RenderTarget";
|
||||
} else if (binding == BindingType::Storage) {
|
||||
owner = "StorageTextureCache";
|
||||
} else if (video_out) {
|
||||
if (info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("TextureCache: invalid color-attachment upload\n");
|
||||
}
|
||||
layers = info.resources.layers;
|
||||
volume = false;
|
||||
owner = "VideoOut";
|
||||
}
|
||||
if (UploadBinding(image) == BindingType::DepthTarget) {
|
||||
return IsSupportedDepthPlaneReadback(info) &&
|
||||
Prospero::NumBytesPerElement(info.guest_format) == info.bytes_per_block;
|
||||
}
|
||||
|
||||
transfer.layout = TextureCalcUploadLayout(info.guest_format, info.extent.width,
|
||||
info.extent.height, info.resources.levels, layers,
|
||||
info.tile_mode, info.data.size, volume, owner);
|
||||
transfer.regions = TextureBuildImageCopies(transfer.layout);
|
||||
if (info.IsDepth()) {
|
||||
for (auto& region: transfer.regions) {
|
||||
region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
}
|
||||
if (transfer.layout.surface.description.tile_mode != Prospero::TileMode::kLinear) {
|
||||
if (!TextureBuildGpuTileInfos(info.data.size, transfer.regions, transfer.layout,
|
||||
info.resources.levels, transfer.tiles)) {
|
||||
return transfer;
|
||||
}
|
||||
}
|
||||
transfer.valid = true;
|
||||
return transfer;
|
||||
}
|
||||
|
||||
TextureCache::ImageDownload TextureCache::BuildDownload(const Image& image) const {
|
||||
const auto& info = image.info;
|
||||
const auto binding = UploadBinding(image);
|
||||
ImageDownload transfer {.depth_target = binding == BindingType::DepthTarget};
|
||||
if (info.samples != 1 || image.backing.samples != 1) {
|
||||
return transfer;
|
||||
}
|
||||
if (transfer.depth_target) {
|
||||
transfer.valid = IsSupportedDepthPlaneReadback(info) && info.resources.layers != 0 &&
|
||||
info.data.size % info.resources.layers == 0 &&
|
||||
Prospero::NumBytesPerElement(info.guest_format) == info.bytes_per_block;
|
||||
return transfer;
|
||||
}
|
||||
if (info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
return transfer;
|
||||
}
|
||||
transfer.texture = BuildTextureTransfer(image, binding, TransferDirection::Download);
|
||||
transfer.valid = transfer.texture.valid;
|
||||
return transfer;
|
||||
return info.metadata.compression == VideoOutCompression::Uncompressed &&
|
||||
(!info.IsTiled() || !Prospero::IsFmaskTextureFormat(info.guest_format)) &&
|
||||
info.LinearSize() != 0;
|
||||
}
|
||||
|
||||
void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_offset) {
|
||||
auto& destination = image.depth_id ? m_slot_images[image.depth_id] : image;
|
||||
const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image);
|
||||
const auto upload = [&](std::vector<vk::BufferImageCopy>& copies, TileManager::Result linear) {
|
||||
for (auto& copy: copies) {
|
||||
copy.bufferOffset += linear.offset;
|
||||
}
|
||||
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
};
|
||||
|
||||
const auto& info = image.info;
|
||||
const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image);
|
||||
if (binding != BindingType::DepthTarget) {
|
||||
const auto& info = image.info;
|
||||
auto transfer = BuildTextureTransfer(image, binding, TransferDirection::Upload);
|
||||
if (!transfer.valid) {
|
||||
EXIT("TextureCache: invalid texture upload: binding=%u addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " format=%u tile=%u family=%u extent=%ux%ux%u "
|
||||
"pitch=%u levels=%u layers=%u samples=%u\n",
|
||||
static_cast<uint32_t>(binding), info.data.address, info.data.size,
|
||||
static_cast<uint32_t>(info.guest_format), static_cast<uint32_t>(info.tile_mode),
|
||||
static_cast<uint32_t>(transfer.layout.surface.texture.block.family), info.extent.width,
|
||||
info.extent.height, info.extent.depth, info.pitch, info.resources.levels,
|
||||
info.resources.layers, info.samples);
|
||||
if ((binding == BindingType::RenderTarget || binding == BindingType::VideoOut) &&
|
||||
(info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
info.samples != 1 || image.backing.samples != 1)) {
|
||||
EXIT("TextureCache: invalid color-attachment upload\n");
|
||||
}
|
||||
TileManager::Result linear {source.Handle(), source_offset, info.data.size};
|
||||
if (!transfer.tiles.empty()) {
|
||||
linear = m_tiler.Detile(source.Handle(), source_offset, info.data.size,
|
||||
transfer.LinearSize(), transfer.tiles, transfer.color_transform);
|
||||
} else if (transfer.color_transform != ColorTransform::None) {
|
||||
linear = m_tiler.TransformColor(linear, transfer.color_transform, true);
|
||||
if (binding == BindingType::VideoOut &&
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("TextureCache: invalid video-out upload\n");
|
||||
}
|
||||
upload(transfer.regions, linear);
|
||||
const auto linear = m_tiler.Detile(source.Handle(), source_offset, info);
|
||||
const auto copies = info.BufferCopies(linear.offset);
|
||||
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
return;
|
||||
}
|
||||
|
||||
// The stencil plane has its own row pitch and shares the native image
|
||||
// with the depth plane.
|
||||
auto info = destination.info;
|
||||
if (image.depth_id) {
|
||||
info.data = image.info.data;
|
||||
info.resources = {image.stencil_subresources.level_count,
|
||||
image.stencil_subresources.layer_count};
|
||||
info.guest_format = Prospero::BufferFormat::k8UInt;
|
||||
info.bytes_per_block = 1;
|
||||
if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0);
|
||||
}
|
||||
if (info.samples != 1 || destination.backing.samples != 1 ||
|
||||
info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) {
|
||||
EXIT("TextureCache: invalid depth upload\n");
|
||||
}
|
||||
const auto layers = info.resources.layers;
|
||||
const auto full_slice_size = info.data.size / layers;
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id
|
||||
? vk::ImageAspectFlagBits::eStencil
|
||||
: vk::ImageAspectFlagBits::eDepth);
|
||||
const auto layers = info.resources.layers;
|
||||
const auto full_slice_size = info.linear_slice_stride;
|
||||
auto linear = m_tiler.Detile(source.Handle(), source_offset, info);
|
||||
auto copies = info.BufferCopies(linear.offset);
|
||||
if (image.depth_id) {
|
||||
for (auto& copy: copies) {
|
||||
copy.imageSubresource.baseArrayLayer += image.stencil_subresources.base_layer;
|
||||
}
|
||||
}
|
||||
TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset};
|
||||
if (info.IsTiled()) {
|
||||
const auto tiles = BuildDepthTiles(info);
|
||||
linear =
|
||||
m_tiler.Detile(source.Handle(), source_offset, info.data.size, info.data.size, tiles);
|
||||
}
|
||||
const auto transfer_bytes = image.depth_id ? 1u : DepthAspectTransferBytes(info.pixel_format);
|
||||
if (transfer_bytes != info.bytes_per_block) {
|
||||
const uint64_t texels_per_slice = static_cast<uint64_t>(info.pitch) * info.extent.height;
|
||||
@@ -1095,18 +991,18 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
|
||||
.source_slice_stride = full_slice_size,
|
||||
.target_slice_stride = transfer_slice});
|
||||
linear = promoted;
|
||||
for (uint32_t layer = 0; layer < layers; layer++) {
|
||||
copies[layer].bufferOffset = transfer_slice * layer;
|
||||
}
|
||||
copies.resize(1);
|
||||
copies[0].bufferOffset = linear.offset;
|
||||
copies[0].bufferRowLength = info.pitch;
|
||||
copies[0].bufferImageHeight = info.extent.height;
|
||||
copies[0].imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, 0, layers};
|
||||
copies[0].imageExtent = info.extent;
|
||||
}
|
||||
upload(copies, linear);
|
||||
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
}
|
||||
|
||||
void TextureCache::InitializeImage(ImageId id) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.info.data.Empty()) {
|
||||
return;
|
||||
}
|
||||
TrackImage(id);
|
||||
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
if (image.IsCpuDirty()) {
|
||||
@@ -1189,8 +1085,15 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
ClearImage(m_scheduler.Current(), id, view.format,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
|
||||
const auto& image = m_slot_images[id];
|
||||
const bool depth = image.info.IsDepth();
|
||||
if (depth) {
|
||||
const auto value = clear.color.float32[0];
|
||||
clear.depthStencil = {value, 0u};
|
||||
}
|
||||
ClearImage(m_scheduler.Current(), id, depth ? image.backing.format : view.format,
|
||||
{depth ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor,
|
||||
view.base_level, view.level_count,
|
||||
image_first + slice, 1}, clear);
|
||||
}
|
||||
// Publish the conversion's expanded keys without treating them as guest writes
|
||||
@@ -1212,12 +1115,7 @@ void TextureCache::RefreshImage(ImageId id) {
|
||||
}
|
||||
TrackImage(id);
|
||||
if (image.IsMaybeCpuDirty()) {
|
||||
const auto hash = image.HashGuestEdges();
|
||||
if (image.NeedsMaybeCpuHash()) {
|
||||
image.SetMaybeCpuHash(hash);
|
||||
return;
|
||||
}
|
||||
(void)image.ResolveMaybeCpuHash(hash);
|
||||
(void)image.ResolveMaybeCpuHash(image.HashGuestEdges());
|
||||
}
|
||||
bool cpu_dirty = image.IsBufferModified() || image.IsDefinitelyCpuDirty();
|
||||
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
@@ -1257,8 +1155,31 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
}
|
||||
auto& record = m_slot_images[association];
|
||||
TouchImage(record);
|
||||
record.depth_id = depth_id;
|
||||
record.stencil_subresources = depth.stencil_subresources;
|
||||
if (record.depth_id != depth_id || record.stencil_subresources != depth.stencil_subresources) {
|
||||
ImageInfo plane {};
|
||||
plane.data = stencil;
|
||||
plane.pixel_format = vk::Format::eS8Uint;
|
||||
plane.guest_format = Prospero::BufferFormat::k8UInt;
|
||||
plane.type = depth.info.type;
|
||||
plane.extent = depth.info.extent;
|
||||
plane.resources = {depth.stencil_subresources.level_count,
|
||||
depth.stencil_subresources.layer_count};
|
||||
plane.tile_mode = depth.info.tile_mode;
|
||||
plane.pitch =
|
||||
plane.IsTiled() ? TileGetDepthPitch(plane.extent.width, 1, 0) : depth.info.pitch;
|
||||
plane.bytes_per_block = 1;
|
||||
plane.UpdateSize();
|
||||
if (record.depth_id != depth_id) {
|
||||
if (record.depth_id) {
|
||||
std::erase(m_slot_images[record.depth_id].stencil_ids, association);
|
||||
}
|
||||
depth.stencil_ids.push_back(association);
|
||||
}
|
||||
record.info = plane;
|
||||
record.depth_id = depth_id;
|
||||
record.stencil_subresources = depth.stencil_subresources;
|
||||
}
|
||||
|
||||
return association;
|
||||
}
|
||||
|
||||
@@ -1268,10 +1189,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
EXIT("TextureCache: image lookup requires a valid command buffer\n");
|
||||
}
|
||||
ValidateImageDesc(desc);
|
||||
if (desc.info.data.Empty()) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
return GetNullImage(desc);
|
||||
}
|
||||
EXIT_IF(desc.info.data.Empty());
|
||||
const auto metadata_base_layer = desc.view_info.base_layer;
|
||||
|
||||
ImageId result {};
|
||||
@@ -1279,11 +1197,28 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto candidates =
|
||||
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
|
||||
bool block_alias = false;
|
||||
|
||||
for (const auto id: candidates) {
|
||||
const auto& image = m_slot_images[id];
|
||||
// Stencil descriptors name the associated plane; its depth owner supplies
|
||||
// the native image and the canonical guest tiling.
|
||||
if ((desc.type == BindingType::Texture || desc.type == BindingType::Storage) &&
|
||||
image.depth_id && image.info.data == desc.info.data &&
|
||||
m_slot_images[image.depth_id].info.samples == desc.info.samples &&
|
||||
image.info.extent == desc.info.extent && image.info.type == desc.info.type &&
|
||||
image.info.resources.levels >= desc.info.resources.levels &&
|
||||
image.info.resources.layers >= desc.info.resources.layers &&
|
||||
desc.info.bytes_per_block == 1 &&
|
||||
desc.info.guest_format == Prospero::BufferFormat::k8UInt) {
|
||||
result = id;
|
||||
break;
|
||||
}
|
||||
if (SameBacking(image.info, desc.info, exact_format)) {
|
||||
result = id;
|
||||
} else if (image.SafeToDownload() &&
|
||||
BcAlias::Matches(desc.info, image.info)) {
|
||||
block_alias = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,9 +1236,18 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
view_layer = overlap.layer;
|
||||
}
|
||||
}
|
||||
} else if (block_alias) {
|
||||
for (const auto candidate: candidates) {
|
||||
const auto* owner = m_slot_images.try_get(candidate);
|
||||
if (candidate != result && owner != nullptr && owner->registered &&
|
||||
!owner->IsCpuDirty() &&
|
||||
BcAlias::Matches(m_slot_images[result].info, owner->info)) {
|
||||
BcAlias::Resolve(*this, candidate, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
if (result && !m_slot_images[result].depth_id) {
|
||||
auto& resolved = m_slot_images[result];
|
||||
if (exact_format && resolved.info.pixel_format != desc.info.pixel_format) {
|
||||
result = {};
|
||||
@@ -1397,36 +1341,30 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
TouchImage(image);
|
||||
if (!image.info.data.Empty()) {
|
||||
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
|
||||
EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
|
||||
}
|
||||
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
|
||||
EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
|
||||
}
|
||||
if (desc.type == BindingType::Storage) {
|
||||
image.MarkGpuModified();
|
||||
}
|
||||
if (!image.info.data.Empty()) {
|
||||
RefreshImage(id);
|
||||
if (image.info.HasStencil() &&
|
||||
desc.info.data.address >= image.info.stencil.address &&
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
RefreshImage(id);
|
||||
if (image.info.HasStencil() &&
|
||||
desc.info.data.address >= image.info.stencil.address &&
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (desc.type) {
|
||||
case BindingType::Texture: break;
|
||||
case BindingType::Storage:
|
||||
if (!image.info.data.Empty()) {
|
||||
CommitGpuWrite(image);
|
||||
}
|
||||
CommitGpuWrite(image);
|
||||
TrackImageDownload(id, image);
|
||||
break;
|
||||
default: EXIT("TextureCache: invalid texture binding\n");
|
||||
@@ -1463,7 +1401,6 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
}
|
||||
TouchImage(image);
|
||||
image.MarkGpuModified();
|
||||
image.usage.depth_target = true;
|
||||
if (desc.info.HasStencil()) {
|
||||
const auto layers = image.info.resources.layers;
|
||||
EXIT_IF(layers == 0 || image.info.data.size % layers != 0 ||
|
||||
@@ -1479,9 +1416,13 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
image.info.stencil = desc.info.stencil;
|
||||
image.info.metadata = desc.info.metadata;
|
||||
if (desc.info.HasMetadata()) {
|
||||
m_surface_metas.emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask});
|
||||
const auto [metadata, inserted] = m_surface_metas.try_emplace(desc.info.metadata.range.address);
|
||||
if (inserted) {
|
||||
metadata->second.type = MetaDataInfo::Type::HTile;
|
||||
if (image.info.htile_initial_clear) {
|
||||
metadata->second.clear_mask.set();
|
||||
}
|
||||
}
|
||||
}
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
@@ -1685,7 +1626,7 @@ void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset) {
|
||||
const auto& info = image.info;
|
||||
const auto layers = info.resources.layers;
|
||||
const auto full_slice_size = info.data.size / layers;
|
||||
const auto full_slice_size = info.linear_slice_stride;
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
const uint64_t texels_per_slice = static_cast<uint64_t>(info.pitch) * info.extent.height;
|
||||
EXIT_NOT_IMPLEMENTED(transfer_bytes == 0 || texels_per_slice > UINT32_MAX ||
|
||||
@@ -1696,31 +1637,31 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
|
||||
EXIT_NOT_IMPLEMENTED(transfer_slice > UINT64_MAX / layers);
|
||||
const uint64_t transfer_size = transfer_slice * layers;
|
||||
EXIT_NOT_IMPLEMENTED(guest_slice > full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, vk::ImageAspectFlagBits::eDepth);
|
||||
if (transfer_bytes == info.bytes_per_block) {
|
||||
if (!info.IsTiled()) {
|
||||
for (auto& copy: copies) {
|
||||
copy.bufferOffset += destination_offset;
|
||||
}
|
||||
const auto copies = info.BufferCopies(destination_offset);
|
||||
image.Download(copies, destination.Handle(), destination_offset, info.data.size);
|
||||
return;
|
||||
} else {
|
||||
const auto copies = info.BufferCopies();
|
||||
m_tiler.TileImage(image, copies, destination.Handle(), destination_offset,
|
||||
info.data.size);
|
||||
}
|
||||
const auto tiles = BuildDepthTiles(info);
|
||||
m_tiler.TileImage(image, copies, destination.Handle(), destination_offset, info.data.size,
|
||||
info.data.size, tiles);
|
||||
return;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(info.bytes_per_block != sizeof(uint16_t) ||
|
||||
transfer_bytes != sizeof(uint32_t));
|
||||
for (uint32_t layer = 0; layer < layers; layer++) {
|
||||
copies[layer].bufferOffset = transfer_slice * layer;
|
||||
}
|
||||
const std::array copies {vk::BufferImageCopy {0,
|
||||
info.pitch,
|
||||
info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
info.extent}};
|
||||
auto host_linear = m_tiler.GetScratchBuffer(transfer_size);
|
||||
image.Download(copies, host_linear.buffer, 0, host_linear.size);
|
||||
const bool tiled = info.IsTiled();
|
||||
auto guest_linear = tiled ? m_tiler.GetScratchBuffer(info.data.size, host_linear.buffer)
|
||||
: TileManager::Result {destination.Handle(), destination_offset,
|
||||
destination.Size() - destination_offset};
|
||||
auto guest_linear = tiled ? m_tiler.GetScratchBuffer(info.LinearSize(), host_linear.buffer)
|
||||
: TileManager::Result {destination.Handle(), destination_offset,
|
||||
destination.Size() - destination_offset};
|
||||
m_tiler.ConvertD16(host_linear, guest_linear, TileManager::D16Direction::Demote,
|
||||
DepthAspectTransferFormat(info.pixel_format) == vk::Format::eD32Sfloat,
|
||||
{.width = info.extent.width,
|
||||
@@ -1730,47 +1671,39 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
|
||||
.target_row_stride = static_cast<uint64_t>(info.pitch) * sizeof(uint16_t),
|
||||
.source_slice_stride = transfer_slice,
|
||||
.target_slice_stride = full_slice_size});
|
||||
if (!tiled) {
|
||||
return;
|
||||
if (tiled) {
|
||||
m_tiler.Tile(guest_linear.buffer, guest_linear.offset, guest_linear.size,
|
||||
destination.Handle(), destination_offset, info.data.size, info);
|
||||
}
|
||||
const auto tiles = BuildDepthTiles(info);
|
||||
m_tiler.Tile(guest_linear.buffer, guest_linear.offset, info.data.size, destination.Handle(),
|
||||
destination_offset, info.data.size, tiles);
|
||||
}
|
||||
|
||||
void TextureCache::DownloadImage(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
uint64_t destination_size, ImageDownload transfer) {
|
||||
if (!transfer.valid) {
|
||||
EXIT("TextureCache: invalid image download transfer\n");
|
||||
uint64_t destination_size, uint32_t levels) {
|
||||
if (!CanDownload(image)) {
|
||||
EXIT("TextureCache: invalid image download\n");
|
||||
}
|
||||
if (transfer.depth_target) {
|
||||
if (destination_size != image.info.data.size) {
|
||||
EXIT("TextureCache: partial depth image download is unsupported\n");
|
||||
}
|
||||
if (UploadBinding(image) == BindingType::DepthTarget) {
|
||||
EXIT_IF(destination_size != image.info.data.size || levels != image.info.resources.levels);
|
||||
DownloadDepth(image, destination, destination_offset);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& texture = transfer.texture;
|
||||
const auto transform = texture.color_transform;
|
||||
if (texture.tiles.empty()) {
|
||||
if (transform != ColorTransform::None) {
|
||||
auto linear = m_tiler.GetScratchBuffer(destination_size);
|
||||
image.Download(texture.regions, linear.buffer, 0, linear.size);
|
||||
m_tiler.TransformColor(linear,
|
||||
{destination.Handle(), destination_offset, destination_size},
|
||||
transform, false);
|
||||
return;
|
||||
}
|
||||
for (auto& copy: texture.regions) {
|
||||
copy.bufferOffset += destination_offset;
|
||||
}
|
||||
image.Download(texture.regions, destination.Handle(), destination_offset, destination_size);
|
||||
return;
|
||||
const auto& info = image.info;
|
||||
const auto transform = info.GetColorTransform();
|
||||
if (info.IsTiled()) {
|
||||
const auto copies = info.BufferCopies(0, levels);
|
||||
m_tiler.TileImage(image, copies, destination.Handle(), destination_offset, destination_size,
|
||||
levels);
|
||||
} else if (transform != ColorTransform::None) {
|
||||
auto linear = m_tiler.GetScratchBuffer(destination_size);
|
||||
const auto copies = info.BufferCopies(0, levels);
|
||||
image.Download(copies, linear.buffer, 0, linear.size);
|
||||
m_tiler.TransformColor(linear, {destination.Handle(), destination_offset, destination_size},
|
||||
transform, false);
|
||||
} else {
|
||||
const auto copies = info.BufferCopies(destination_offset, levels);
|
||||
image.Download(copies, destination.Handle(), destination_offset, destination_size);
|
||||
}
|
||||
|
||||
m_tiler.TileImage(image, texture.regions, destination.Handle(), destination_offset,
|
||||
destination_size, texture.LinearSize(), texture.tiles, transform);
|
||||
}
|
||||
|
||||
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size) {
|
||||
@@ -1810,30 +1743,12 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
if (copy_size == 0) {
|
||||
return false;
|
||||
}
|
||||
auto transfer = m_texture_cache.BuildDownload(image);
|
||||
if (!transfer.valid) {
|
||||
if (!m_texture_cache.CanDownload(image) ||
|
||||
(TextureCache::UploadBinding(image) == TextureCache::BindingType::DepthTarget &&
|
||||
copy_size != image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
if (transfer.depth_target && copy_size != image.info.data.size) {
|
||||
return false;
|
||||
}
|
||||
if (!transfer.depth_target && levels < image.info.resources.levels) {
|
||||
auto& texture = transfer.texture;
|
||||
std::erase_if(texture.regions, [levels](const vk::BufferImageCopy& region) {
|
||||
return region.imageSubresource.mipLevel >= levels;
|
||||
});
|
||||
if (texture.regions.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!texture.tiles.empty()) {
|
||||
texture.tiles.clear();
|
||||
if (!TextureBuildGpuTileInfos(copy_size, texture.regions, texture.layout, levels,
|
||||
texture.tiles)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, std::move(transfer));
|
||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1842,8 +1757,7 @@ bool TextureCache::DownloadImageMemory(ImageId id) {
|
||||
if (image.depth_id) {
|
||||
return false;
|
||||
}
|
||||
auto transfer = BuildDownload(image);
|
||||
if (!transfer.valid || !image.SafeToDownload()) {
|
||||
if (!image.SafeToDownload() || !CanDownload(image)) {
|
||||
return false;
|
||||
}
|
||||
const auto range = image.info.data;
|
||||
@@ -1859,7 +1773,7 @@ bool TextureCache::DownloadImageMemory(ImageId id) {
|
||||
}
|
||||
download.Flush(offset, range.size);
|
||||
|
||||
DownloadImage(image, download, offset, range.size, std::move(transfer));
|
||||
DownloadImage(image, download, offset, range.size, image.info.resources.levels);
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
@@ -1906,7 +1820,7 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
|
||||
continue;
|
||||
}
|
||||
if (owner->Overlaps(address, size)) {
|
||||
owner->InvalidateCpuWrite(address, size);
|
||||
owner->MarkCpuDirty();
|
||||
UntrackImage(id);
|
||||
continue;
|
||||
}
|
||||
@@ -1931,10 +1845,10 @@ bool TextureCache::IsMeta(uint64_t address) {
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) {
|
||||
return false;
|
||||
}
|
||||
return (found->second.clear_mask & (1u << slice)) != 0;
|
||||
return found->second.clear_mask.test(slice);
|
||||
}
|
||||
|
||||
bool TextureCache::ClearMeta(uint64_t address) {
|
||||
@@ -1943,21 +1857,17 @@ bool TextureCache::ClearMeta(uint64_t address) {
|
||||
if (found == m_surface_metas.end()) {
|
||||
return false;
|
||||
}
|
||||
found->second.clear_mask = UINT32_MAX;
|
||||
found->second.clear_mask.set();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) {
|
||||
return false;
|
||||
}
|
||||
if (is_clear) {
|
||||
found->second.clear_mask |= 1u << slice;
|
||||
} else {
|
||||
found->second.clear_mask &= ~(1u << slice);
|
||||
}
|
||||
found->second.clear_mask.set(slice, is_clear);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2050,6 +1960,9 @@ void TextureCache::RunGarbageCollector() {
|
||||
}
|
||||
|
||||
void TextureCache::ProcessDownloadImages() {
|
||||
if (!m_readback_linear_images) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: m_download_images) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
|
||||
+16
-13
@@ -12,9 +12,9 @@
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,8 +26,15 @@ class BufferCache;
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
class RenderExecutor;
|
||||
class TextureCache;
|
||||
struct TextureCacheTestAccess;
|
||||
|
||||
namespace BcAlias {
|
||||
[[nodiscard]] bool Matches(const ImageInfo& requested, const ImageInfo& cached);
|
||||
[[nodiscard]] bool Publish(TextureCache& cache, Image& image);
|
||||
void Resolve(TextureCache& cache, ImageId cached, ImageId merged);
|
||||
} // namespace BcAlias
|
||||
|
||||
class TextureCache {
|
||||
public:
|
||||
enum class BindingType : uint8_t { Texture, Storage, RenderTarget, DepthTarget, VideoOut };
|
||||
@@ -73,15 +80,12 @@ public:
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
enum class TransferDirection { Upload, Download };
|
||||
struct TextureTransfer;
|
||||
struct ImageDownload;
|
||||
|
||||
struct MetaDataInfo {
|
||||
enum class Type : uint8_t { CMask, FMask, HTile };
|
||||
|
||||
Type type;
|
||||
uint32_t clear_mask = UINT32_MAX;
|
||||
Type type;
|
||||
// PS5 DB_DEPTH_VIEW encodes 13-bit base and last array-slice indices.
|
||||
std::bitset<8192> clear_mask;
|
||||
};
|
||||
|
||||
struct OverlapResult {
|
||||
@@ -111,7 +115,6 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
|
||||
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
|
||||
void RegisterImage(ImageId id);
|
||||
void UnregisterImage(ImageId id);
|
||||
void DeleteImage(ImageId id);
|
||||
@@ -141,12 +144,10 @@ private:
|
||||
void MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer);
|
||||
void InitializeImage(ImageId id);
|
||||
[[nodiscard]] TextureTransfer
|
||||
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
|
||||
[[nodiscard]] ImageDownload BuildDownload(const Image& image) const;
|
||||
[[nodiscard]] bool CanDownload(const Image& image) const;
|
||||
void UploadImage(Image& image, Buffer& source, uint64_t source_offset);
|
||||
void DownloadImage(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
uint64_t destination_size, ImageDownload transfer);
|
||||
uint64_t destination_size, uint32_t levels);
|
||||
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
|
||||
void CommitGpuWrite(Image& image);
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
@@ -172,7 +173,6 @@ private:
|
||||
BufferCache& m_buffer_cache;
|
||||
Common::SlotVector<Image> m_slot_images;
|
||||
ImagePageTable m_image_page_table;
|
||||
std::unordered_map<vk::Format, ImageId> m_null_images;
|
||||
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
|
||||
std::unordered_set<ImageId> m_download_images;
|
||||
std::map<uint64_t, MetaDataInfo> m_surface_metas;
|
||||
@@ -187,6 +187,9 @@ private:
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
friend class RenderExecutor;
|
||||
friend bool BcAlias::Matches(const ImageInfo&, const ImageInfo&);
|
||||
friend bool BcAlias::Publish(TextureCache&, Image&);
|
||||
friend void BcAlias::Resolve(TextureCache&, ImageId, ImageId);
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -139,9 +139,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
// Nonlinear clear values are still stored as normalized components.
|
||||
// Metadata clears are materialized during image discovery; render-pass loads preserve contents.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
uint32_t height = 0;
|
||||
bool tile = false;
|
||||
const bool standard4 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard4KB;
|
||||
const bool standard64 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard64KB;
|
||||
@@ -164,6 +162,9 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
if (texture_tile && samples != 1) {
|
||||
EXIT("texture-tiled color render targets do not support multisampling\n");
|
||||
}
|
||||
if (volume && !tile) {
|
||||
EXIT("linear 3D render targets are unsupported\n");
|
||||
}
|
||||
|
||||
width = rt.attrib2.width + 1;
|
||||
height = rt.attrib2.height + 1;
|
||||
@@ -198,80 +199,25 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
" layer=%u/%u\n",
|
||||
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
|
||||
}
|
||||
if (samples == 1) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
}
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
|
||||
TileSizeOffset mip_sizes[16] {};
|
||||
TilePaddedSize mip_padded[16] {};
|
||||
TileSurfaceLayout volume_layout {};
|
||||
uint64_t backing_size = 0;
|
||||
if (volume) {
|
||||
const TileSurfaceDescription description {transfer_format,
|
||||
rt.attrib3.tile_mode,
|
||||
TileSurfaceDimension::Dim3D,
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
levels,
|
||||
1};
|
||||
if (!tile || !TileGetTiledTextureLayout(description, volume_layout)) {
|
||||
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
|
||||
depth, levels, static_cast<uint32_t>(rt.attrib3.tile_mode));
|
||||
}
|
||||
size = volume_layout.block_slice_size;
|
||||
backing_size = volume_layout.total_size;
|
||||
} else {
|
||||
TileSizeAlign layout {};
|
||||
bool valid_layout = false;
|
||||
if (samples == 1) {
|
||||
TileGetTextureSize(transfer_format, width, height, levels, rt.attrib3.tile_mode,
|
||||
&layout, mip_sizes, mip_padded);
|
||||
valid_layout = layout.size != 0 && layout.align != 0 &&
|
||||
(rt.attrib3.tile_mode != Prospero::TileMode::kRenderTarget ||
|
||||
levels <= std::bit_width(std::max(width, height)));
|
||||
} else {
|
||||
valid_layout = TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
|
||||
layout, rt.attrib.num_fragments);
|
||||
mip_sizes[0] = {layout.size, 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (!valid_layout) {
|
||||
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
|
||||
height, pitch, bytes_per_element, levels);
|
||||
}
|
||||
size = layout.size;
|
||||
}
|
||||
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
}
|
||||
if (!volume) {
|
||||
backing_size = size * view.image_layers;
|
||||
}
|
||||
if (backing_size == 0) {
|
||||
EXIT("render-target backing is empty\n");
|
||||
}
|
||||
if (!GuestRange {rt.base.addr, backing_size}.Valid()) {
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
|
||||
auto& desc = r.desc;
|
||||
desc.type = TextureCache::BindingType::RenderTarget;
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.data = {rt.base.addr, 0};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
desc.info.guest_format = target_format.guest_format;
|
||||
desc.info.type = image_type;
|
||||
desc.info.extent = {width, height, depth};
|
||||
desc.info.resources = {levels, volume ? 1u : view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.resources = {levels, volume ? 1u : view.image_layers};
|
||||
desc.info.bytes_per_block = bytes_per_element;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
if (!volume && rt.attrib3.tile_mode == Prospero::TileMode::kRenderTarget &&
|
||||
levels > std::bit_width(std::max(width, height))) {
|
||||
EXIT("unsupported render-target mip count\n");
|
||||
}
|
||||
desc.info.UpdateSize();
|
||||
if (!desc.info.data.Valid()) {
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||
const bool has_cmask = !rt.info.dcc_compression_enable && rt.info.cmask_fast_clear_enable &&
|
||||
rt.cmask.addr != 0 && samples == 1 &&
|
||||
@@ -290,33 +236,10 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
|
||||
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
|
||||
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
|
||||
desc.info.metadata.clear_word = rt.clear_word0.word0;
|
||||
desc.info.metadata.clear_word =
|
||||
rt.clear_word0.word0 | (uint64_t {rt.clear_word1.word1} << 32u);
|
||||
desc.info.metadata.clear_register_valid = true;
|
||||
}
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
const auto& mip = volume_layout.mips[level];
|
||||
desc.info.mip_layout[level] = {
|
||||
mip.offset,
|
||||
mip.size,
|
||||
mip.padded_width,
|
||||
mip.padded_height,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const auto level_offset =
|
||||
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
|
||||
const auto level_size =
|
||||
static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
|
||||
: mip_sizes[level].size) *
|
||||
view.image_layers;
|
||||
desc.info.mip_layout[level] = {
|
||||
level_offset,
|
||||
level_size,
|
||||
mip_padded[level].width,
|
||||
mip_padded[level].height,
|
||||
};
|
||||
}
|
||||
desc.view_info.format = target_format.format;
|
||||
if (is_1d) {
|
||||
desc.view_info.type =
|
||||
|
||||
@@ -334,6 +334,7 @@ CommandBuffer& CommandScheduler::BeginCommand() {
|
||||
EXIT_IF(!m_command.IsInvalid());
|
||||
m_command.m_buffer = m_command_pool.Commit();
|
||||
m_command.Begin();
|
||||
m_dynamic_state.Invalidate();
|
||||
return m_command;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
#include "graphics/host_gpu/renderer/dynamicState.h"
|
||||
#include "graphics/host_gpu/renderer/masterSemaphore.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -50,6 +49,7 @@ public:
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() noexcept { return m_master; }
|
||||
[[nodiscard]] DynamicState& GetDynamicState() noexcept { return m_dynamic_state; }
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||
|
||||
@@ -92,6 +92,7 @@ private:
|
||||
GraphicContext& m_graphics;
|
||||
CommandPool m_command_pool;
|
||||
CommandBuffer m_command;
|
||||
DynamicState m_dynamic_state;
|
||||
std::queue<PendingOperation> m_pending_operations;
|
||||
std::queue<PendingOperation> m_priority_operations;
|
||||
std::mutex m_operation_mutex;
|
||||
|
||||
@@ -394,26 +394,20 @@ static void RcPrint(const char* func, const HW::RenderControl& c) {
|
||||
|
||||
LOGF("\t depth_clear_enable = %s\n"
|
||||
"\t stencil_clear_enable = %s\n"
|
||||
"\t copy_depth_to_color = %s\n"
|
||||
"\t copy_stencil_to_color = %s\n"
|
||||
"\t resummarize_enable = %s\n"
|
||||
"\t stencil_compress_disable = %s\n"
|
||||
"\t depth_compress_disable = %s\n"
|
||||
"\t copy_centroid = %s\n"
|
||||
"\t copy_sample = %" PRIu8 "\n",
|
||||
c.depth_clear_enable ? "true" : "false", c.stencil_clear_enable ? "true" : "false",
|
||||
c.copy_depth_to_color ? "true" : "false", c.copy_stencil_to_color ? "true" : "false",
|
||||
c.resummarize_enable ? "true" : "false", c.stencil_compress_disable ? "true" : "false",
|
||||
c.depth_compress_disable ? "true" : "false", c.copy_centroid ? "true" : "false",
|
||||
c.copy_sample);
|
||||
}
|
||||
|
||||
static void RcCheck(const HW::RenderControl& c) {
|
||||
// EXIT_NOT_IMPLEMENTED(c.depth_clear_enable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencil_clear_enable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencil_compress_disable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.depth_compress_disable != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.copy_centroid != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.copy_sample != 0);
|
||||
}
|
||||
|
||||
static void McPrint(const char* func, const HW::ModeControl& c) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
@@ -869,9 +863,6 @@ void hw_check(const CommandBuffer& buffer) {
|
||||
VpCheck(vp, smc);
|
||||
log_phase("clip");
|
||||
ClipCheck(c);
|
||||
log_phase("rc");
|
||||
RcCheck(rc);
|
||||
log_phase("depth");
|
||||
log_phase("mode");
|
||||
McCheck(mc);
|
||||
log_phase("blend");
|
||||
@@ -883,14 +874,33 @@ void hw_check(const CommandBuffer& buffer) {
|
||||
log_phase("done");
|
||||
|
||||
if (graphics_debug_dump_enabled() && RenderTargetMaskHasBoundMrt(buffer)) {
|
||||
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
|
||||
const auto& sh = hw.GetShaderRegisters();
|
||||
LOGF("MRT render target mask: 0x%08" PRIx32
|
||||
" export_modes={%u,%u,%u,%u,%u,%u,%u,%u}\n",
|
||||
hw.GetRenderTargetMask(), sh.target_output_mode[0], sh.target_output_mode[1],
|
||||
sh.target_output_mode[2], sh.target_output_mode[3], sh.target_output_mode[4],
|
||||
sh.target_output_mode[5], sh.target_output_mode[6], sh.target_output_mode[7]);
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
const auto& mrt = hw.GetRenderTarget(i);
|
||||
const auto& bc = hw.GetBlendControl(i);
|
||||
LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32
|
||||
" width=%u height=%u tile=%u\n",
|
||||
" type=%u order=%u shader_mask=0x%x"
|
||||
" width=%u height=%u tile=%u blend=%u bypass=%u clamp=%u round_mode=%u"
|
||||
" samples=%u fragments=%u dcc=%u dcc_addr=0x%010" PRIx64
|
||||
" cmask_clear=%u cmask_addr=0x%010" PRIx64
|
||||
" dcc_clear_key=%u clear={%08" PRIx32 ",%08" PRIx32 "}\n",
|
||||
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu,
|
||||
static_cast<uint32_t>(mrt.info.format), mrt.attrib2.width + 1,
|
||||
mrt.attrib2.height + 1, static_cast<uint32_t>(mrt.attrib3.tile_mode));
|
||||
static_cast<uint32_t>(mrt.info.format), static_cast<uint32_t>(mrt.info.channel_type),
|
||||
static_cast<uint32_t>(mrt.info.channel_order), (sh.m_cbShaderMask >> (i * 4u)) & 0x0fu,
|
||||
mrt.attrib2.width + 1, mrt.attrib2.height + 1,
|
||||
static_cast<uint32_t>(mrt.attrib3.tile_mode), static_cast<uint32_t>(bc.enable),
|
||||
static_cast<uint32_t>(mrt.info.blend_bypass), static_cast<uint32_t>(mrt.info.blend_clamp),
|
||||
static_cast<uint32_t>(mrt.info.round_mode), mrt.attrib.num_samples,
|
||||
mrt.attrib.num_fragments,
|
||||
static_cast<uint32_t>(mrt.info.dcc_compression_enable), mrt.dcc_addr.addr,
|
||||
static_cast<uint32_t>(mrt.info.cmask_fast_clear_enable), mrt.cmask.addr,
|
||||
static_cast<uint32_t>(mrt.dcc.dcc_clear_key_enable), mrt.clear_word0.word0,
|
||||
mrt.clear_word1.word1);
|
||||
}
|
||||
}
|
||||
if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f &&
|
||||
|
||||
@@ -177,9 +177,6 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
|
||||
if (z.htile_data_base_addr == 0 || (z.htile_data_base_addr & 0x7fffu) != 0) {
|
||||
DepthFatal("invalid HTile metadata address");
|
||||
}
|
||||
if (z.depth_view.slice_max >= 32) {
|
||||
DepthFatal("HTile clear tracking supports at most 32 slices");
|
||||
}
|
||||
}
|
||||
if (!z.size.valid) {
|
||||
DepthFatal("missing depth extent");
|
||||
@@ -239,7 +236,7 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
|
||||
desc.info.bytes_per_block = bytes;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = Prospero::TileMode::kDepth;
|
||||
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
|
||||
desc.info.UpdateSize();
|
||||
desc.info.metadata.range =
|
||||
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
|
||||
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
|
||||
@@ -298,13 +295,20 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
if (rc.copy_stencil_to_color ||
|
||||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
|
||||
(has_stencil && !z.depth_view.stencil_write_disable &&
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr)) {
|
||||
DepthFatal("unsupported depth register state");
|
||||
}
|
||||
if (rc.copy_depth_to_color &&
|
||||
(z.z_info.num_samples != 0 || rc.copy_sample != 0 ||
|
||||
z.z_info.format != Prospero::DepthFormat::kZ32F ||
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || dc.stencil_enable ||
|
||||
rc.depth_clear_enable || rc.stencil_clear_enable)) {
|
||||
DepthFatal("unsupported depth-to-color copy state");
|
||||
}
|
||||
r.desc = MakeDepthTargetDesc(buffer, z);
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable;
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "graphics/host_gpu/renderer/dynamicState.h"
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void DynamicState::Commit(const GraphicContext& graphics, vk::CommandBuffer cmdbuf) {
|
||||
if (dirty_state.viewports) {
|
||||
dirty_state.viewports = false;
|
||||
cmdbuf.setViewportWithCount(num_viewports, viewports.data());
|
||||
}
|
||||
if (dirty_state.scissors) {
|
||||
dirty_state.scissors = false;
|
||||
cmdbuf.setScissorWithCount(num_scissors, scissors.data());
|
||||
}
|
||||
if (dirty_state.depth_test_enabled) {
|
||||
dirty_state.depth_test_enabled = false;
|
||||
cmdbuf.setDepthTestEnable(depth_test_enabled);
|
||||
}
|
||||
if (dirty_state.depth_write_enabled) {
|
||||
dirty_state.depth_write_enabled = false;
|
||||
// Note that this must be set in a command buffer even if depth test is disabled.
|
||||
cmdbuf.setDepthWriteEnable(depth_write_enabled);
|
||||
}
|
||||
if (depth_test_enabled && dirty_state.depth_compare_op) {
|
||||
dirty_state.depth_compare_op = false;
|
||||
cmdbuf.setDepthCompareOp(depth_compare_op);
|
||||
}
|
||||
if (dirty_state.depth_bounds_test_enabled) {
|
||||
dirty_state.depth_bounds_test_enabled = false;
|
||||
#if !defined(__APPLE__)
|
||||
cmdbuf.setDepthBoundsTestEnable(depth_bounds_test_enabled);
|
||||
#endif
|
||||
}
|
||||
if (depth_bounds_test_enabled && dirty_state.depth_bounds) {
|
||||
dirty_state.depth_bounds = false;
|
||||
#if !defined(__APPLE__)
|
||||
cmdbuf.setDepthBounds(depth_bounds_min, depth_bounds_max);
|
||||
#endif
|
||||
}
|
||||
if (dirty_state.depth_bias_enabled) {
|
||||
dirty_state.depth_bias_enabled = false;
|
||||
cmdbuf.setDepthBiasEnable(depth_bias_enabled);
|
||||
}
|
||||
if (depth_bias_enabled && dirty_state.depth_bias) {
|
||||
dirty_state.depth_bias = false;
|
||||
cmdbuf.setDepthBias(depth_bias_constant, depth_bias_clamp, depth_bias_slope);
|
||||
}
|
||||
if (dirty_state.stencil_test_enabled) {
|
||||
dirty_state.stencil_test_enabled = false;
|
||||
cmdbuf.setStencilTestEnable(stencil_test_enabled);
|
||||
}
|
||||
if (stencil_test_enabled) {
|
||||
if (dirty_state.stencil_front_ops && dirty_state.stencil_back_ops &&
|
||||
stencil_front_ops == stencil_back_ops) {
|
||||
dirty_state.stencil_front_ops = false;
|
||||
dirty_state.stencil_back_ops = false;
|
||||
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFrontAndBack, stencil_front_ops.fail_op,
|
||||
stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op,
|
||||
stencil_front_ops.compare_op);
|
||||
} else {
|
||||
if (dirty_state.stencil_front_ops) {
|
||||
dirty_state.stencil_front_ops = false;
|
||||
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op,
|
||||
stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op,
|
||||
stencil_front_ops.compare_op);
|
||||
}
|
||||
if (dirty_state.stencil_back_ops) {
|
||||
dirty_state.stencil_back_ops = false;
|
||||
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op,
|
||||
stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op,
|
||||
stencil_back_ops.compare_op);
|
||||
}
|
||||
}
|
||||
if (dirty_state.stencil_front_reference && dirty_state.stencil_back_reference &&
|
||||
stencil_front_reference == stencil_back_reference) {
|
||||
dirty_state.stencil_front_reference = false;
|
||||
dirty_state.stencil_back_reference = false;
|
||||
cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack,
|
||||
stencil_front_reference);
|
||||
} else {
|
||||
if (dirty_state.stencil_front_reference) {
|
||||
dirty_state.stencil_front_reference = false;
|
||||
cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront,
|
||||
stencil_front_reference);
|
||||
}
|
||||
if (dirty_state.stencil_back_reference) {
|
||||
dirty_state.stencil_back_reference = false;
|
||||
cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, stencil_back_reference);
|
||||
}
|
||||
}
|
||||
if (dirty_state.stencil_front_write_mask && dirty_state.stencil_back_write_mask &&
|
||||
stencil_front_write_mask == stencil_back_write_mask) {
|
||||
dirty_state.stencil_front_write_mask = false;
|
||||
dirty_state.stencil_back_write_mask = false;
|
||||
cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack,
|
||||
stencil_front_write_mask);
|
||||
} else {
|
||||
if (dirty_state.stencil_front_write_mask) {
|
||||
dirty_state.stencil_front_write_mask = false;
|
||||
cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
|
||||
stencil_front_write_mask);
|
||||
}
|
||||
if (dirty_state.stencil_back_write_mask) {
|
||||
dirty_state.stencil_back_write_mask = false;
|
||||
cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, stencil_back_write_mask);
|
||||
}
|
||||
}
|
||||
if (dirty_state.stencil_front_compare_mask && dirty_state.stencil_back_compare_mask &&
|
||||
stencil_front_compare_mask == stencil_back_compare_mask) {
|
||||
dirty_state.stencil_front_compare_mask = false;
|
||||
dirty_state.stencil_back_compare_mask = false;
|
||||
cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack,
|
||||
stencil_front_compare_mask);
|
||||
} else {
|
||||
if (dirty_state.stencil_front_compare_mask) {
|
||||
dirty_state.stencil_front_compare_mask = false;
|
||||
cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
|
||||
stencil_front_compare_mask);
|
||||
}
|
||||
if (dirty_state.stencil_back_compare_mask) {
|
||||
dirty_state.stencil_back_compare_mask = false;
|
||||
cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
|
||||
stencil_back_compare_mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dirty_state.blend_constants) {
|
||||
dirty_state.blend_constants = false;
|
||||
cmdbuf.setBlendConstants(blend_constants.data());
|
||||
}
|
||||
if (dirty_state.color_write_enables) {
|
||||
dirty_state.color_write_enables = false;
|
||||
#if !defined(__APPLE__)
|
||||
if (num_color_write_enables != 0) {
|
||||
cmdbuf.setColorWriteEnableEXT(num_color_write_enables, color_write_enables.data());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (dirty_state.line_width) {
|
||||
dirty_state.line_width = false;
|
||||
cmdbuf.setLineWidth(line_width);
|
||||
}
|
||||
if (dirty_state.feedback_loop_aspects && graphics.attachment_feedback_loop_enabled) {
|
||||
dirty_state.feedback_loop_aspects = false;
|
||||
cmdbuf.setAttachmentFeedbackLoopEnableEXT(feedback_loop_aspects);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,258 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DYNAMICSTATE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DYNAMICSTATE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
struct StencilOps {
|
||||
vk::StencilOp fail_op {};
|
||||
vk::StencilOp pass_op {};
|
||||
vk::StencilOp depth_fail_op {};
|
||||
vk::CompareOp compare_op {};
|
||||
|
||||
bool operator==(const StencilOps& other) const {
|
||||
return fail_op == other.fail_op && pass_op == other.pass_op &&
|
||||
depth_fail_op == other.depth_fail_op && compare_op == other.compare_op;
|
||||
}
|
||||
};
|
||||
|
||||
struct DynamicState {
|
||||
struct {
|
||||
bool viewports : 1;
|
||||
bool scissors : 1;
|
||||
|
||||
bool depth_test_enabled : 1;
|
||||
bool depth_write_enabled : 1;
|
||||
bool depth_compare_op : 1;
|
||||
|
||||
bool depth_bounds_test_enabled : 1;
|
||||
bool depth_bounds : 1;
|
||||
|
||||
bool depth_bias_enabled : 1;
|
||||
bool depth_bias : 1;
|
||||
|
||||
bool stencil_test_enabled : 1;
|
||||
bool stencil_front_ops : 1;
|
||||
bool stencil_front_reference : 1;
|
||||
bool stencil_front_write_mask : 1;
|
||||
bool stencil_front_compare_mask : 1;
|
||||
bool stencil_back_ops : 1;
|
||||
bool stencil_back_reference : 1;
|
||||
bool stencil_back_write_mask : 1;
|
||||
bool stencil_back_compare_mask : 1;
|
||||
|
||||
bool blend_constants : 1;
|
||||
bool color_write_enables : 1;
|
||||
bool line_width : 1;
|
||||
bool feedback_loop_aspects : 1;
|
||||
} dirty_state {};
|
||||
|
||||
std::array<vk::Viewport, 16> viewports {};
|
||||
std::array<vk::Rect2D, 16> scissors {};
|
||||
uint32_t num_viewports {};
|
||||
uint32_t num_scissors {};
|
||||
|
||||
bool depth_test_enabled {};
|
||||
bool depth_write_enabled {};
|
||||
vk::CompareOp depth_compare_op {};
|
||||
|
||||
bool depth_bounds_test_enabled {};
|
||||
float depth_bounds_min {};
|
||||
float depth_bounds_max {};
|
||||
|
||||
bool depth_bias_enabled {};
|
||||
float depth_bias_constant {};
|
||||
float depth_bias_clamp {};
|
||||
float depth_bias_slope {};
|
||||
|
||||
bool stencil_test_enabled {};
|
||||
StencilOps stencil_front_ops {};
|
||||
uint32_t stencil_front_reference {};
|
||||
uint32_t stencil_front_write_mask {};
|
||||
uint32_t stencil_front_compare_mask {};
|
||||
StencilOps stencil_back_ops {};
|
||||
uint32_t stencil_back_reference {};
|
||||
uint32_t stencil_back_write_mask {};
|
||||
uint32_t stencil_back_compare_mask {};
|
||||
|
||||
std::array<float, 4> blend_constants {};
|
||||
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
||||
uint32_t num_color_write_enables {};
|
||||
float line_width {};
|
||||
vk::ImageAspectFlags feedback_loop_aspects {};
|
||||
|
||||
/// Commits the dynamic state to the provided command buffer.
|
||||
void Commit(const GraphicContext& graphics, vk::CommandBuffer cmdbuf);
|
||||
|
||||
/// Invalidates all dynamic state to be flushed into the next command buffer.
|
||||
void Invalidate() { std::memset(&dirty_state, 0xFF, sizeof(dirty_state)); }
|
||||
|
||||
void SetViewports(std::span<const vk::Viewport> viewports_) {
|
||||
EXIT_IF(viewports_.size() > viewports.size());
|
||||
if (!std::ranges::equal(std::span(viewports).first(num_viewports), viewports_)) {
|
||||
std::ranges::copy(viewports_, viewports.begin());
|
||||
num_viewports = static_cast<uint32_t>(viewports_.size());
|
||||
dirty_state.viewports = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetScissors(std::span<const vk::Rect2D> scissors_) {
|
||||
EXIT_IF(scissors_.size() > scissors.size());
|
||||
if (!std::ranges::equal(std::span(scissors).first(num_scissors), scissors_)) {
|
||||
std::ranges::copy(scissors_, scissors.begin());
|
||||
num_scissors = static_cast<uint32_t>(scissors_.size());
|
||||
dirty_state.scissors = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthTestEnabled(const bool enabled) {
|
||||
if (depth_test_enabled != enabled) {
|
||||
depth_test_enabled = enabled;
|
||||
dirty_state.depth_test_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthWriteEnabled(const bool enabled) {
|
||||
if (depth_write_enabled != enabled) {
|
||||
depth_write_enabled = enabled;
|
||||
dirty_state.depth_write_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthCompareOp(const vk::CompareOp compare_op) {
|
||||
if (depth_compare_op != compare_op) {
|
||||
depth_compare_op = compare_op;
|
||||
dirty_state.depth_compare_op = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthBoundsTestEnabled(const bool enabled) {
|
||||
if (depth_bounds_test_enabled != enabled) {
|
||||
depth_bounds_test_enabled = enabled;
|
||||
dirty_state.depth_bounds_test_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthBounds(const float min, const float max) {
|
||||
if (depth_bounds_min != min || depth_bounds_max != max) {
|
||||
depth_bounds_min = min;
|
||||
depth_bounds_max = max;
|
||||
dirty_state.depth_bounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthBiasEnabled(const bool enabled) {
|
||||
if (depth_bias_enabled != enabled) {
|
||||
depth_bias_enabled = enabled;
|
||||
dirty_state.depth_bias_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDepthBias(const float constant, const float clamp, const float slope) {
|
||||
if (depth_bias_constant != constant || depth_bias_clamp != clamp ||
|
||||
depth_bias_slope != slope) {
|
||||
depth_bias_constant = constant;
|
||||
depth_bias_clamp = clamp;
|
||||
depth_bias_slope = slope;
|
||||
dirty_state.depth_bias = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStencilTestEnabled(const bool enabled) {
|
||||
if (stencil_test_enabled != enabled) {
|
||||
stencil_test_enabled = enabled;
|
||||
dirty_state.stencil_test_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStencilOps(const StencilOps& front_ops, const StencilOps& back_ops) {
|
||||
if (stencil_front_ops != front_ops) {
|
||||
stencil_front_ops = front_ops;
|
||||
dirty_state.stencil_front_ops = true;
|
||||
}
|
||||
if (stencil_back_ops != back_ops) {
|
||||
stencil_back_ops = back_ops;
|
||||
dirty_state.stencil_back_ops = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStencilReferences(const uint32_t front_reference, const uint32_t back_reference) {
|
||||
if (stencil_front_reference != front_reference) {
|
||||
stencil_front_reference = front_reference;
|
||||
dirty_state.stencil_front_reference = true;
|
||||
}
|
||||
if (stencil_back_reference != back_reference) {
|
||||
stencil_back_reference = back_reference;
|
||||
dirty_state.stencil_back_reference = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStencilWriteMasks(const uint32_t front_write_mask, const uint32_t back_write_mask) {
|
||||
if (stencil_front_write_mask != front_write_mask) {
|
||||
stencil_front_write_mask = front_write_mask;
|
||||
dirty_state.stencil_front_write_mask = true;
|
||||
}
|
||||
if (stencil_back_write_mask != back_write_mask) {
|
||||
stencil_back_write_mask = back_write_mask;
|
||||
dirty_state.stencil_back_write_mask = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStencilCompareMasks(const uint32_t front_compare_mask,
|
||||
const uint32_t back_compare_mask) {
|
||||
if (stencil_front_compare_mask != front_compare_mask) {
|
||||
stencil_front_compare_mask = front_compare_mask;
|
||||
dirty_state.stencil_front_compare_mask = true;
|
||||
}
|
||||
if (stencil_back_compare_mask != back_compare_mask) {
|
||||
stencil_back_compare_mask = back_compare_mask;
|
||||
dirty_state.stencil_back_compare_mask = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
||||
if (blend_constants != blend_constants_) {
|
||||
blend_constants = blend_constants_;
|
||||
dirty_state.blend_constants = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetColorWriteEnables(std::span<const vk::Bool32> enables) {
|
||||
EXIT_IF(enables.size() > color_write_enables.size());
|
||||
if (!std::ranges::equal(std::span(color_write_enables).first(num_color_write_enables),
|
||||
enables)) {
|
||||
std::ranges::copy(enables, color_write_enables.begin());
|
||||
num_color_write_enables = static_cast<uint32_t>(enables.size());
|
||||
dirty_state.color_write_enables = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetLineWidth(const float width) {
|
||||
if (line_width != width) {
|
||||
line_width = width;
|
||||
dirty_state.line_width = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetAttachmentFeedbackLoopAspects(const vk::ImageAspectFlags aspects) {
|
||||
if (feedback_loop_aspects != aspects) {
|
||||
feedback_loop_aspects = aspects;
|
||||
dirty_state.feedback_loop_aspects = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DYNAMICSTATE_H_
|
||||
@@ -193,6 +193,8 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
// The helper pipeline has static state that invalidates the guest draw's dynamic state.
|
||||
m_scheduler.GetDynamicState().Invalidate();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -29,10 +29,8 @@ struct CachedImageView {
|
||||
};
|
||||
|
||||
struct ImageUsage {
|
||||
bool texture = false;
|
||||
bool storage = false;
|
||||
bool render_target = false;
|
||||
bool depth_target = false;
|
||||
bool video_out = false;
|
||||
};
|
||||
|
||||
@@ -70,40 +68,25 @@ public:
|
||||
void CopyImageWithBuffer(Image& source, Buffer& buffer, TileManager& tiler);
|
||||
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
|
||||
|
||||
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_maybe_cpu_dirty = true;
|
||||
}
|
||||
void MarkCpuDirty() {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
|
||||
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
|
||||
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
|
||||
void MarkMaybeCpuDirty() {
|
||||
if (!m_cpu_dirty) {
|
||||
if (!IsCpuDirty()) {
|
||||
m_maybe_cpu_hash = HashGuestEdges();
|
||||
m_maybe_cpu_dirty = true;
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool NeedsMaybeCpuHash() const {
|
||||
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
|
||||
}
|
||||
void SetMaybeCpuHash(uint64_t hash) {
|
||||
if (!NeedsMaybeCpuHash()) {
|
||||
EXIT("image cannot initialize maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_hash = hash;
|
||||
m_maybe_hash_valid = true;
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
|
||||
if (!m_maybe_cpu_dirty || m_cpu_dirty) {
|
||||
EXIT("image cannot resolve maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_cpu_dirty |= hash != m_maybe_cpu_hash;
|
||||
return m_cpu_dirty;
|
||||
}
|
||||
@@ -112,9 +95,8 @@ public:
|
||||
if (!IsCpuDirty()) {
|
||||
EXIT("clean image cannot complete a refresh\n");
|
||||
}
|
||||
m_cpu_dirty = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
m_cpu_dirty = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
|
||||
@@ -149,6 +131,8 @@ public:
|
||||
uint64_t track_addr = 0;
|
||||
uint64_t track_addr_end = 0;
|
||||
ImageId depth_id {};
|
||||
// Stencil associations that refer to this depth image.
|
||||
std::vector<ImageId> stencil_ids;
|
||||
// The current stencil plane's mapping into the depth image, also retained by its association.
|
||||
ImageSubresourceRange stencil_subresources;
|
||||
uint64_t tick_accessed_last = 0;
|
||||
@@ -165,12 +149,11 @@ private:
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
bool m_maybe_hash_valid = false;
|
||||
bool m_gpu_modified = false;
|
||||
bool m_buffer_modified = false;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
bool m_gpu_modified = false;
|
||||
bool m_buffer_modified = false;
|
||||
};
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
uint64_t Multiply(uint64_t left, uint64_t right) {
|
||||
EXIT_IF(right != 0 && left > UINT64_MAX / right);
|
||||
return left * right;
|
||||
}
|
||||
|
||||
uint64_t Add(uint64_t left, uint64_t right) {
|
||||
EXIT_IF(right > UINT64_MAX - left);
|
||||
return left + right;
|
||||
}
|
||||
|
||||
bool IsDepthPlane(const ImageInfo& info) {
|
||||
return (info.IsDepth() || info.pixel_format == vk::Format::eS8Uint) &&
|
||||
(info.tile_mode == Prospero::TileMode::kDepth ||
|
||||
info.tile_mode == Prospero::TileMode::kLinear);
|
||||
}
|
||||
|
||||
GuestRange TiledMipRange(const ImageInfo& info, uint32_t level) {
|
||||
const auto& block = info.tiling.block;
|
||||
if (info.tile_mode != Prospero::TileMode::kStandard4KB ||
|
||||
block.family != TileBlockFamily::Standard4KB || info.IsVolume() || info.samples != 1 ||
|
||||
info.HasMetadata() || level >= info.resources.levels || level >= info.mip_layout.size() ||
|
||||
info.tiling.texel_width == 0 || info.tiling.texel_height == 0) {
|
||||
return {};
|
||||
}
|
||||
const auto extent = info.MipExtent(level);
|
||||
const auto width = (extent.width + info.tiling.texel_width - 1u) / info.tiling.texel_width;
|
||||
const auto height = (extent.height + info.tiling.texel_height - 1u) / info.tiling.texel_height;
|
||||
const auto& mip = info.mip_layout[level];
|
||||
uint32_t last = 0, origin = 0;
|
||||
if (!std::has_single_bit(width) || !std::has_single_bit(height) ||
|
||||
width > block.block_width || height > block.block_height ||
|
||||
mip.tail_x > block.block_width - width || mip.tail_y > block.block_height - height ||
|
||||
mip.tail_x % width != 0 || mip.tail_y % height != 0 ||
|
||||
!TileGetBlockOffset(block, width - 1u, height - 1u, 0, last) ||
|
||||
!TileGetBlockOffset(block, mip.tail_x, mip.tail_y, 0, origin)) {
|
||||
return {};
|
||||
}
|
||||
// Standard4KB interleaves monotonically ordered X/Y bits, so the last
|
||||
// corner bounds every active texel. Tiling preserves any holes in this span.
|
||||
const uint64_t size = uint64_t {last} + block.bytes_per_element;
|
||||
if ((origin & (std::bit_ceil(size) - 1u)) != 0 || size > block.block_size - origin ||
|
||||
mip.offset > UINT64_MAX - origin) {
|
||||
return {};
|
||||
}
|
||||
return {mip.offset + origin, size};
|
||||
}
|
||||
|
||||
uint32_t CompactMipSize(const ImageInfo& info) {
|
||||
if (info.resources.levels != 1 || info.resources.layers != 1) {
|
||||
return 0;
|
||||
}
|
||||
const auto range = TiledMipRange(info, 0);
|
||||
if (range.size == 0 || range.size % 4 != 0 || range.address != 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto alignment = std::max<uint64_t>(256, std::bit_ceil(range.size));
|
||||
const auto in_block = info.data.address & (info.tiling.block.block_size - 1u);
|
||||
return (info.data.address & (alignment - 1u)) == 0 &&
|
||||
range.size <= info.tiling.block.block_size - in_block
|
||||
? static_cast<uint32_t>(range.size)
|
||||
: 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::Extent3D ImageInfo::MipExtent(uint32_t level) const {
|
||||
EXIT_IF(level >= resources.levels || level >= mip_layout.size());
|
||||
return {std::max(extent.width >> level, 1u), std::max(extent.height >> level, 1u),
|
||||
IsVolume() ? std::max(extent.depth >> level, 1u) : resources.layers};
|
||||
}
|
||||
|
||||
void ImageInfo::UpdateSize() {
|
||||
EXIT_IF(guest_format == Prospero::BufferFormat::kInvalid || extent.width == 0 ||
|
||||
extent.height == 0 || extent.depth == 0 || resources.levels == 0 ||
|
||||
resources.levels > mip_layout.size() || resources.layers == 0 || samples == 0 ||
|
||||
!std::has_single_bit(samples));
|
||||
const bool auto_size = data.size == 0;
|
||||
TileTextureElementLayout element {};
|
||||
EXIT_NOT_IMPLEMENTED(!TileGetTextureElementLayout(guest_format, element));
|
||||
if (bytes_per_block == 0) {
|
||||
bytes_per_block = element.bytes;
|
||||
}
|
||||
EXIT_IF(bytes_per_block != element.bytes);
|
||||
mip_layout = {};
|
||||
tiling = {};
|
||||
tiling.texel_width = element.texel_width;
|
||||
tiling.texel_height = element.texel_height;
|
||||
tiling.block.bytes_per_element = element.bytes;
|
||||
tiled_slice_stride = 0;
|
||||
linear_slice_stride = 0;
|
||||
first_tail_level = 16;
|
||||
|
||||
if (samples > 1) {
|
||||
EXIT_NOT_IMPLEMENTED(IsVolume() || resources.levels != 1 ||
|
||||
(tile_mode != Prospero::TileMode::kDepth &&
|
||||
tile_mode != Prospero::TileMode::kRenderTarget));
|
||||
const auto fragments = std::countr_zero(samples);
|
||||
if (pitch == 0) {
|
||||
pitch = tile_mode == Prospero::TileMode::kDepth
|
||||
? TileGetDepthPitch(extent.width, bytes_per_block, fragments)
|
||||
: TileGetRenderTargetPitch(extent.width, bytes_per_block, fragments);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(!TileGetTextureBlockLayout(guest_format, tile_mode, false, tiling));
|
||||
if (data.size == 0) {
|
||||
TileSizeAlign slice {};
|
||||
EXIT_NOT_IMPLEMENTED(!TileGetRenderTargetSize(extent.width, extent.height, pitch,
|
||||
bytes_per_block, slice, fragments));
|
||||
data.size = Multiply(slice.size, resources.layers);
|
||||
}
|
||||
EXIT_IF(data.size % resources.layers != 0);
|
||||
tiled_slice_stride = data.size / resources.layers;
|
||||
mip_layout[0] = {0, data.size, pitch, extent.height};
|
||||
return;
|
||||
}
|
||||
|
||||
if (pitch == 0) {
|
||||
pitch = TileGetTexturePitch(guest_format, extent.width, tile_mode);
|
||||
}
|
||||
EXIT_IF(pitch == 0);
|
||||
if (IsDepthPlane(*this) && resources.levels == 1) {
|
||||
EXIT_NOT_IMPLEMENTED(IsVolume());
|
||||
if (IsTiled()) {
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetTextureBlockLayout(guest_format, tile_mode, false, tiling));
|
||||
}
|
||||
if (data.size == 0) {
|
||||
TileSizeAlign slice {};
|
||||
TileGetTextureSize(guest_format, extent.width, extent.height, 1, tile_mode, &slice,
|
||||
nullptr, nullptr);
|
||||
data.size = Multiply(slice.size, resources.layers);
|
||||
}
|
||||
EXIT_IF(data.size % resources.layers != 0);
|
||||
const auto slice = data.size / resources.layers;
|
||||
EXIT_IF(Multiply(Multiply(pitch, extent.height), bytes_per_block) > slice);
|
||||
linear_slice_stride = slice;
|
||||
tiled_slice_stride = IsTiled() ? slice : 0;
|
||||
mip_layout[0] = {0, data.size, pitch, extent.height, 0, slice, pitch};
|
||||
return;
|
||||
}
|
||||
if (!IsTiled()) {
|
||||
TileSizeAlign total {};
|
||||
TileSizeOffset offsets[16] {};
|
||||
TilePaddedSize padded[16] {};
|
||||
TileGetTextureSize(guest_format, extent.width, extent.height, resources.levels, tile_mode,
|
||||
&total, offsets, padded);
|
||||
if (data.size == 0) {
|
||||
data.size = Multiply(total.size, TransferLayers());
|
||||
}
|
||||
for (uint32_t level = 0; level < resources.levels; ++level) {
|
||||
auto& mip = mip_layout[level];
|
||||
mip.offset = offsets[level].offset;
|
||||
mip.size = Multiply(offsets[level].size, MipExtent(level).depth);
|
||||
mip.pitch = padded[level].width / element.texel_width;
|
||||
mip.height = padded[level].height / element.texel_height;
|
||||
mip.linear_offset = offsets[level].offset;
|
||||
mip.linear_size = offsets[level].size;
|
||||
mip.linear_pitch = mip.pitch;
|
||||
linear_slice_stride =
|
||||
std::max(linear_slice_stride, Add(mip.linear_offset, mip.linear_size));
|
||||
}
|
||||
if (TransferLayers() > 1 && data.size % TransferLayers() == 0) {
|
||||
linear_slice_stride = std::max(linear_slice_stride, data.size / TransferLayers());
|
||||
}
|
||||
} else {
|
||||
TileSurfaceLayout surface {};
|
||||
const TileSurfaceDescription description {guest_format,
|
||||
tile_mode,
|
||||
IsVolume() ? TileSurfaceDimension::Dim3D
|
||||
: TileSurfaceDimension::Dim2D,
|
||||
extent.width,
|
||||
extent.height,
|
||||
IsVolume() ? extent.depth : 1u,
|
||||
resources.levels,
|
||||
resources.layers};
|
||||
EXIT_NOT_IMPLEMENTED(!TileGetTiledTextureLayout(description, surface));
|
||||
tiling = surface.texture;
|
||||
first_tail_level = surface.first_tail_level;
|
||||
tiled_slice_stride = surface.block_slice_size;
|
||||
if (data.size == 0) {
|
||||
data.size = surface.total_size;
|
||||
}
|
||||
if (!IsVolume() && resources.layers > 1 && data.size % resources.layers == 0) {
|
||||
tiled_slice_stride = std::max(tiled_slice_stride, data.size / resources.layers);
|
||||
}
|
||||
uint64_t linear_offset = 0;
|
||||
uint32_t mip_pitch = IsVolume() ? extent.width : pitch;
|
||||
for (uint32_t level = 0; level < resources.levels; ++level) {
|
||||
const auto& source = surface.mips[level];
|
||||
auto& mip = mip_layout[level];
|
||||
const auto active = MipExtent(level);
|
||||
mip.offset = source.offset;
|
||||
mip.size = Multiply(source.size, IsVolume() ? 1u : resources.layers);
|
||||
mip.pitch = source.padded_width;
|
||||
mip.height = source.padded_height;
|
||||
mip.tail_x = source.tail_x;
|
||||
mip.tail_y = source.tail_y;
|
||||
mip.linear_pitch =
|
||||
Common::AlignUp(mip_pitch, element.texel_width) / element.texel_width;
|
||||
const auto rows =
|
||||
Common::AlignUp(active.height, element.texel_height) / element.texel_height;
|
||||
mip.linear_size = Multiply(Multiply(mip.linear_pitch, rows), element.bytes);
|
||||
const auto alignment = std::max(element.bytes, 4u);
|
||||
EXIT_IF(linear_offset > UINT64_MAX - (alignment - 1u));
|
||||
mip.linear_offset = Common::AlignUp<uint64_t>(linear_offset, alignment);
|
||||
linear_offset = Add(mip.linear_offset, Multiply(mip.linear_size, active.depth));
|
||||
mip_pitch = std::max(mip_pitch >> 1u, 1u);
|
||||
}
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && (auto_size || data.size == compact_size)) {
|
||||
data.size = mip_layout[0].size = compact_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageInfo::DataAlignment() const {
|
||||
if (!IsTiled()) {
|
||||
return 256;
|
||||
}
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && data.size == compact_size && mip_layout[0].size == compact_size) {
|
||||
return std::max(256u, std::bit_ceil(compact_size));
|
||||
}
|
||||
return tiling.block.block_size;
|
||||
}
|
||||
|
||||
int32_t ImageInfo::SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
|
||||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
|
||||
container.resources.layers == 0 || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
|
||||
data.End() > container.data.End()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
(HasStencil() && (stencil.address < container.stencil.address ||
|
||||
stencil.End() > container.stencil.End()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u) ||
|
||||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
|
||||
mip_layout[0].height != layout.height || layout.size == 0 ||
|
||||
layout.size % container.resources.layers != 0 ||
|
||||
container.data.size % container.resources.layers != 0 ||
|
||||
data.size % resources.layers != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// PS5 block slices contain the entire reversed mip chain. A rebased tail
|
||||
// descriptor covers its addressed span within the shared tail block.
|
||||
const auto slice_stride = container.data.size / container.resources.layers;
|
||||
const auto child_stride = data.size / resources.layers;
|
||||
auto mip_offset = layout.offset;
|
||||
auto mip_size = layout.size / container.resources.layers;
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && child_stride == compact_size && mip_layout[0].size == compact_size) {
|
||||
const auto range = TiledMipRange(container, level);
|
||||
if (range.size != compact_size) {
|
||||
return -1;
|
||||
}
|
||||
mip_offset = range.address;
|
||||
mip_size = range.size;
|
||||
} else if (mip_layout[0].tail_x != layout.tail_x || mip_layout[0].tail_y != layout.tail_y) {
|
||||
return -1;
|
||||
}
|
||||
if (child_stride != mip_size || mip_offset >= slice_stride ||
|
||||
child_stride > slice_stride - mip_offset ||
|
||||
(resources.layers > 1 && child_stride != slice_stride)) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - container.data.address;
|
||||
if (address_delta < mip_offset || (address_delta - mip_offset) % slice_stride != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto layer = (address_delta - mip_offset) / slice_stride;
|
||||
if (layer > INT32_MAX || layer >= container.resources.layers ||
|
||||
resources.layers > container.resources.layers - layer) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(layer);
|
||||
}
|
||||
|
||||
uint64_t ImageInfo::LinearSize(uint32_t levels) const {
|
||||
levels = levels == 0 ? resources.levels : levels;
|
||||
EXIT_IF(levels == 0 || levels > resources.levels || levels > mip_layout.size());
|
||||
if (linear_slice_stride == 0) {
|
||||
const auto& mip = mip_layout[levels - 1u];
|
||||
return Add(mip.linear_offset, Multiply(mip.linear_size, MipExtent(levels - 1u).depth));
|
||||
}
|
||||
uint64_t size = 0;
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
const auto& mip = mip_layout[level];
|
||||
const auto slices = MipExtent(level).depth;
|
||||
const auto stride = linear_slice_stride != 0 ? linear_slice_stride : mip.linear_size;
|
||||
size = std::max(
|
||||
size, Add(Add(mip.linear_offset, Multiply(slices - 1u, stride)), mip.linear_size));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
std::vector<vk::BufferImageCopy> ImageInfo::BufferCopies(uint64_t buffer_offset,
|
||||
uint32_t levels) const {
|
||||
levels = levels == 0 ? resources.levels : levels;
|
||||
EXIT_IF(levels > resources.levels || levels > mip_layout.size() || tiling.texel_width == 0 ||
|
||||
tiling.texel_height == 0);
|
||||
const auto aspect = IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: pixel_format == vk::Format::eS8Uint ? vk::ImageAspectFlagBits::eStencil
|
||||
: vk::ImageAspectFlagBits::eColor;
|
||||
std::vector<vk::BufferImageCopy> copies;
|
||||
std::array<uint32_t, 16> image_heights {};
|
||||
uint64_t count = 0;
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
const auto& mip = mip_layout[level];
|
||||
const auto active = MipExtent(level);
|
||||
if (linear_slice_stride == 0) {
|
||||
image_heights[level] = Common::AlignUp(active.height, tiling.texel_height);
|
||||
} else {
|
||||
const auto row_bytes = Multiply(mip.linear_pitch, tiling.block.bytes_per_element);
|
||||
EXIT_IF(row_bytes == 0);
|
||||
const auto rows = linear_slice_stride / row_bytes;
|
||||
if (linear_slice_stride % row_bytes == 0 &&
|
||||
rows >=
|
||||
(uint64_t {active.height} + tiling.texel_height - 1u) / tiling.texel_height &&
|
||||
rows <= UINT32_MAX / tiling.texel_height) {
|
||||
image_heights[level] = static_cast<uint32_t>(rows) * tiling.texel_height;
|
||||
}
|
||||
}
|
||||
count += image_heights[level] != 0 ? 1u : active.depth;
|
||||
}
|
||||
copies.reserve(static_cast<size_t>(count));
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
const auto& mip = mip_layout[level];
|
||||
const auto active = MipExtent(level);
|
||||
vk::BufferImageCopy copy {};
|
||||
copy.bufferOffset = Add(buffer_offset, mip.linear_offset);
|
||||
copy.bufferRowLength = mip.linear_pitch * tiling.texel_width;
|
||||
copy.bufferImageHeight = image_heights[level] != 0 ? image_heights[level]
|
||||
: !IsTiled() ? mip.height * tiling.texel_height
|
||||
: Common::AlignUp(active.height, tiling.texel_height);
|
||||
copy.imageSubresource = {aspect, level, 0, 1};
|
||||
copy.imageExtent = {active.width, active.height, 1};
|
||||
if (image_heights[level] == 0) {
|
||||
for (uint32_t slice = 0; slice < active.depth; ++slice) {
|
||||
copy.bufferOffset = Add(Add(buffer_offset, mip.linear_offset),
|
||||
Multiply(slice, linear_slice_stride));
|
||||
copy.imageSubresource.baseArrayLayer = IsVolume() ? 0u : slice;
|
||||
copy.imageOffset.z = IsVolume() ? static_cast<int32_t>(slice) : 0;
|
||||
copies.push_back(copy);
|
||||
}
|
||||
} else {
|
||||
copy.imageSubresource.layerCount = IsVolume() ? 1u : active.depth;
|
||||
copy.imageExtent.depth = IsVolume() ? active.depth : 1u;
|
||||
copies.push_back(copy);
|
||||
}
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
@@ -12,6 +13,7 @@
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -25,7 +27,7 @@ struct ImageMetadataInfo {
|
||||
GuestRange range;
|
||||
ImageMetadataKind kind = ImageMetadataKind::None;
|
||||
uint32_t control = 0;
|
||||
uint32_t clear_word = 0;
|
||||
uint64_t clear_word = 0;
|
||||
VideoOutCompression compression = VideoOutCompression::Uncompressed;
|
||||
bool stencil_compressed = false;
|
||||
bool clear_register_valid = false;
|
||||
@@ -47,11 +49,19 @@ struct ImageSubresourceRange {
|
||||
};
|
||||
|
||||
struct ImageMipInfo {
|
||||
// Guest mip chains run backwards within each layer or volume block-slice.
|
||||
// Tail levels share an offset and use tail_x/tail_y within that block.
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
// Padded dimensions in storage elements (compressed blocks for BC formats).
|
||||
uint32_t pitch = 0;
|
||||
uint32_t height = 0;
|
||||
uint64_t linear_offset = 0;
|
||||
uint64_t linear_size = 0; // Per transfer slice.
|
||||
uint32_t linear_pitch = 0; // Storage elements.
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
uint32_t surface_z = 0;
|
||||
auto operator<=>(const ImageMipInfo&) const = default;
|
||||
};
|
||||
|
||||
@@ -59,7 +69,7 @@ struct ImageInfo {
|
||||
GuestRange data;
|
||||
GuestRange stencil;
|
||||
ImageMetadataInfo metadata;
|
||||
uint32_t htile_clear_mask = UINT32_MAX;
|
||||
bool htile_initial_clear = true;
|
||||
vk::Format pixel_format = vk::Format::eUndefined;
|
||||
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
|
||||
Prospero::ImageType type = Prospero::ImageType::kColor2D;
|
||||
@@ -71,6 +81,18 @@ struct ImageInfo {
|
||||
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
|
||||
bool bgra16 = false;
|
||||
std::array<ImageMipInfo, 16> mip_layout {};
|
||||
TileTextureBlockLayout tiling;
|
||||
uint64_t tiled_slice_stride = 0;
|
||||
// Nonzero for guest-linear and depth-plane transfers; tiled colors are mip-major.
|
||||
uint64_t linear_slice_stride = 0;
|
||||
uint32_t first_tail_level = 16;
|
||||
|
||||
void UpdateSize();
|
||||
[[nodiscard]] uint32_t DataAlignment() const;
|
||||
[[nodiscard]] uint64_t LinearSize(uint32_t levels = 0) const;
|
||||
[[nodiscard]] vk::Extent3D MipExtent(uint32_t level) const;
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BufferCopies(uint64_t buffer_offset = 0,
|
||||
uint32_t levels = 0) const;
|
||||
|
||||
[[nodiscard]] ColorTransform GetColorTransform() const noexcept {
|
||||
if (bgra16) return ColorTransform::SwapBgra16;
|
||||
@@ -115,53 +137,7 @@ struct ImageInfo {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
|
||||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
|
||||
container.resources.layers == 0 || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
|
||||
data.End() > container.data.End()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
(HasStencil() && (stencil.address < container.stencil.address ||
|
||||
stencil.End() > container.stencil.End()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u) ||
|
||||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
|
||||
mip_layout[0].height != layout.height || layout.size == 0 ||
|
||||
layout.size % container.resources.layers != 0 ||
|
||||
container.data.size % container.resources.layers != 0 ||
|
||||
data.size % resources.layers != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// PS5 block slices contain the entire reversed mip chain.
|
||||
const auto slice_stride = container.data.size / container.resources.layers;
|
||||
const auto child_stride = data.size / resources.layers;
|
||||
if (child_stride != layout.size / container.resources.layers ||
|
||||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
|
||||
(resources.layers > 1 && child_stride != slice_stride)) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - container.data.address;
|
||||
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto layer = (address_delta - layout.offset) / slice_stride;
|
||||
if (layer > INT32_MAX || layer >= container.resources.layers ||
|
||||
resources.layers > container.resources.layers - layer) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(layer);
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept;
|
||||
};
|
||||
|
||||
struct ImageViewInfo {
|
||||
@@ -341,8 +317,17 @@ struct VideoOutFormatPolicy {
|
||||
};
|
||||
|
||||
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ = 0x8100070422000000ull;
|
||||
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ = 0x8100070400000000ull;
|
||||
|
||||
inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {{
|
||||
[[nodiscard]] constexpr bool IsHdrVideoOutPixelFormat(uint64_t format) {
|
||||
switch (format) {
|
||||
case VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ:
|
||||
case VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {{
|
||||
{0x8000000022000000ull,
|
||||
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
|
||||
{0x8000000000000000ull,
|
||||
@@ -353,6 +338,8 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
||||
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||
{VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ,
|
||||
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||
{VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ,
|
||||
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||
{0xc001000622000000ull,
|
||||
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
|
||||
{0xc001000600000000ull,
|
||||
@@ -382,9 +369,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
|
||||
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint64_t clear_words,
|
||||
vk::ClearColorValue& clear) {
|
||||
vk::ClearColorValue next {};
|
||||
const auto packed = static_cast<uint32_t>(clear_words);
|
||||
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
|
||||
const auto srgb8 = [](uint32_t value) {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
@@ -402,6 +390,15 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
||||
(mantissa << 13u));
|
||||
};
|
||||
switch (format) {
|
||||
case vk::Format::eB10G11R11UfloatPack32:
|
||||
next.float32[0] = float16((packed & 0x7ffu) << 4u);
|
||||
next.float32[1] = float16(((packed >> 11u) & 0x7ffu) << 4u);
|
||||
next.float32[2] = float16((packed >> 22u) << 5u);
|
||||
break;
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
next.float32[2] = float16(static_cast<uint32_t>(clear_words >> 32u));
|
||||
next.float32[3] = float16(static_cast<uint32_t>(clear_words >> 48u));
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16G16Sfloat:
|
||||
next.float32[1] = float16(packed >> 16u);
|
||||
[[fallthrough]];
|
||||
@@ -412,9 +409,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
||||
case vk::Format::eR16Unorm:
|
||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
||||
break;
|
||||
// A single-plane float target carries its clear as raw float bits, the same encoding the
|
||||
// depth decoder below uses. Without this the clear is discarded and the target keeps stale
|
||||
// contents.
|
||||
// Each clear register stores one raw 32-bit float channel.
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
@@ -475,7 +473,7 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
||||
clear = vk::ClearColorValue {};
|
||||
return true;
|
||||
}
|
||||
return DecodePackedColorClear(format, packed, clear);
|
||||
return DecodePackedColorClear(format, uint64_t {packed} | (uint64_t {packed} << 32u), clear);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t& clear) {
|
||||
|
||||
@@ -31,18 +31,6 @@ namespace ImageViewOps {
|
||||
ImageViewOps::IsFormatDepthCompatible(view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle,
|
||||
uint32_t component_count = 4) noexcept {
|
||||
if ((swizzle & ~0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t channel = 0; channel < 4; channel++) {
|
||||
const auto selector = GetDstSel(swizzle, channel);
|
||||
if (selector > 1 && (selector < 4 || selector >= 4 + component_count)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
@@ -60,68 +57,6 @@ HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
|
||||
return volume_texture ? std::max(depth >> level, 1u) : depth;
|
||||
}
|
||||
|
||||
size_t GetTextureRegionCount(uint32_t depth, uint32_t levels, bool volume_texture) {
|
||||
size_t count = 0;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
count += GetTextureLevelDepth(depth, level, volume_texture);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint32_t levels,
|
||||
uint64_t total_size, uint32_t depth) {
|
||||
uint64_t stride = 0;
|
||||
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
|
||||
stride = std::max(stride, mips[mip_level].offset + mips[mip_level].size);
|
||||
}
|
||||
|
||||
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
|
||||
stride = std::max(stride, total_size / depth);
|
||||
}
|
||||
|
||||
return stride;
|
||||
}
|
||||
|
||||
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
|
||||
uint32_t height) {
|
||||
const uint32_t element_columns =
|
||||
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
|
||||
const uint32_t element_rows =
|
||||
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
|
||||
return static_cast<uint64_t>(element_columns) * element_rows * element.bytes;
|
||||
}
|
||||
|
||||
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
|
||||
const TileTextureElementLayout& element, uint32_t height,
|
||||
uint32_t levels, uint32_t base_pitch) {
|
||||
uint64_t offset = 0;
|
||||
auto mip_pitch = base_pitch;
|
||||
auto mip_height = height;
|
||||
|
||||
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
|
||||
const auto size = CalcLinearUploadLevelSize(element, mip_pitch, mip_height);
|
||||
mips[mip_level] = {offset, size};
|
||||
|
||||
offset += size;
|
||||
if (mip_pitch > 1) {
|
||||
mip_pitch /= 2;
|
||||
}
|
||||
if (mip_height > 1) {
|
||||
mip_height /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool FitsBufferRange(uint64_t offset, uint64_t size, uint64_t capacity) {
|
||||
return offset <= capacity && size <= capacity - offset;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
|
||||
@@ -129,7 +64,10 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
|
||||
Prospero::ChannelOrder order) {
|
||||
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
|
||||
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
|
||||
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
|
||||
const auto backing_format = encoding.buffer_format == Prospero::BufferFormat::k11_11_10UNorm
|
||||
? Prospero::RemapTextureFormat(encoding.buffer_format)
|
||||
: encoding.buffer_format;
|
||||
const auto host_format = ResolveHostFormat(backing_format, order);
|
||||
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
|
||||
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
|
||||
const auto order_mapping =
|
||||
@@ -176,203 +114,4 @@ SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
|
||||
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
|
||||
}
|
||||
|
||||
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
|
||||
uint32_t height, uint32_t levels, uint32_t depth,
|
||||
Prospero::TileMode tile_mode, uint64_t upload_size,
|
||||
bool volume_texture, const char* owner) {
|
||||
TextureUploadLayout layout {};
|
||||
layout.surface.description = {
|
||||
format,
|
||||
tile_mode,
|
||||
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
|
||||
width,
|
||||
height,
|
||||
volume_texture ? depth : 1u,
|
||||
levels,
|
||||
volume_texture ? 1u : depth,
|
||||
};
|
||||
const auto& description = layout.surface.description;
|
||||
TileTextureElementLayout element {};
|
||||
|
||||
if (format == Prospero::BufferFormat::kInvalid) {
|
||||
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
|
||||
" extent=%ux%u levels=%u\n",
|
||||
owner, static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
|
||||
levels);
|
||||
}
|
||||
|
||||
if (tile_mode == Prospero::TileMode::kLinear) {
|
||||
if (!TileGetTextureElementLayout(format, element)) {
|
||||
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
|
||||
static_cast<uint32_t>(format));
|
||||
}
|
||||
} else {
|
||||
if (!TileGetTiledTextureLayout(description, layout.surface)) {
|
||||
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
|
||||
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
|
||||
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
|
||||
upload_size, width, height, levels);
|
||||
}
|
||||
element = {layout.surface.texture.block.bytes_per_element,
|
||||
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
|
||||
}
|
||||
layout.pitch = TileGetTexturePitch(format, width, description.tile_mode);
|
||||
if (tile_mode == Prospero::TileMode::kLinear) {
|
||||
TileSizeOffset level_sizes[16] {};
|
||||
TilePaddedSize padded_sizes[16] {};
|
||||
TileGetTextureSize(format, width, height, levels, description.tile_mode, nullptr,
|
||||
level_sizes, padded_sizes);
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
layout.mips[level] = {level_sizes[level].offset, level_sizes[level].size,
|
||||
padded_sizes[level].width, padded_sizes[level].height};
|
||||
}
|
||||
} else if (volume_texture) {
|
||||
layout.slice_stride = SetLinearUploadLevels(layout.mips, element, height, levels, width);
|
||||
} else {
|
||||
layout.source_slice_stride = layout.surface.block_slice_size;
|
||||
if (depth > 1 && upload_size != 0 && upload_size % depth == 0) {
|
||||
layout.source_slice_stride = std::max(layout.source_slice_stride, upload_size / depth);
|
||||
}
|
||||
SetLinearUploadLevels(layout.mips, element, height, levels, layout.pitch);
|
||||
}
|
||||
|
||||
if (!volume_texture || tile_mode == Prospero::TileMode::kLinear) {
|
||||
layout.slice_stride = CalcTextureSliceStride(layout.mips, levels, upload_size, depth);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
|
||||
const auto& description = layout.surface.description;
|
||||
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
|
||||
const bool linear_texture = description.tile_mode == Prospero::TileMode::kLinear;
|
||||
const auto depth = volume_texture ? description.depth : description.layers;
|
||||
uint32_t mip_width = description.width;
|
||||
uint32_t mip_height = description.height;
|
||||
uint32_t mip_pitch = volume_texture && !linear_texture ? description.width : layout.pitch;
|
||||
|
||||
std::vector<vk::BufferImageCopy> regions;
|
||||
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
|
||||
for (uint32_t mip_level = 0; mip_level < description.levels; ++mip_level) {
|
||||
EXIT_NOT_IMPLEMENTED(layout.mips[mip_level].size == 0);
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
|
||||
vk::BufferImageCopy region {};
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
if (linear_texture) {
|
||||
region.bufferRowLength = layout.mips[mip_level].row_length;
|
||||
region.bufferImageHeight = layout.mips[mip_level].image_height;
|
||||
} else {
|
||||
const auto texel_width = layout.surface.texture.texel_width;
|
||||
const auto aligned_pitch = Common::AlignUp(mip_pitch, texel_width);
|
||||
region.bufferRowLength =
|
||||
aligned_pitch > Common::AlignUp(mip_width, texel_width) ? aligned_pitch : 0;
|
||||
}
|
||||
for (uint32_t slice_index = 0; slice_index < mip_depth; ++slice_index) {
|
||||
region.bufferOffset = layout.mips[mip_level].offset + slice_index * layout.slice_stride;
|
||||
region.imageSubresource.baseArrayLayer = volume_texture ? 0u : slice_index;
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(slice_index) : 0;
|
||||
regions.push_back(region);
|
||||
}
|
||||
|
||||
if (mip_width > 1) {
|
||||
mip_width /= 2;
|
||||
}
|
||||
if (mip_height > 1) {
|
||||
mip_height /= 2;
|
||||
}
|
||||
if (mip_pitch > 1) {
|
||||
mip_pitch /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t levels,
|
||||
std::vector<GpuTileInfo>& out_tile_infos) {
|
||||
const auto& description = layout.surface.description;
|
||||
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
|
||||
const auto depth = volume_texture ? description.depth : description.layers;
|
||||
if (tiled_size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
regions.size() != GetTextureRegionCount(depth, levels, volume_texture) ||
|
||||
Prospero::IsFmaskTextureFormat(description.format)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& surface = layout.surface;
|
||||
const auto& texture = surface.texture;
|
||||
const auto& block = texture.block;
|
||||
if (surface.description.levels < levels || block.bytes_per_element == 0 ||
|
||||
block.family == TileBlockFamily::Count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<GpuTileInfo> tile_infos;
|
||||
tile_infos.reserve(regions.size());
|
||||
const auto slices_per_copy = volume_texture ? block.block_depth : 1u;
|
||||
size_t region_base = 0;
|
||||
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
|
||||
const auto& mip = surface.mips[mip_level];
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
|
||||
for (uint32_t slice_index = 0; slice_index < mip_depth; slice_index += slices_per_copy) {
|
||||
const auto& region = regions[region_base + slice_index];
|
||||
GpuTileInfo tile_info {};
|
||||
tile_info.family = block.family;
|
||||
tile_info.bytes_per_element = block.bytes_per_element;
|
||||
tile_info.linear_offset = region.bufferOffset;
|
||||
tile_info.linear_size = layout.mips[mip_level].size;
|
||||
tile_info.tiled_size = mip.size;
|
||||
if (volume_texture) {
|
||||
tile_info.depth = std::min(slices_per_copy, mip_depth - slice_index);
|
||||
tile_info.linear_slice_stride = layout.slice_stride;
|
||||
tile_info.linear_size += (tile_info.depth - 1u) * layout.slice_stride;
|
||||
tile_info.tiled_offset =
|
||||
mip.offset +
|
||||
static_cast<uint64_t>(slice_index / slices_per_copy) * surface.block_slice_size;
|
||||
tile_info.surface_z =
|
||||
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
|
||||
} else {
|
||||
const auto source_stride = layout.source_slice_stride != 0
|
||||
? layout.source_slice_stride
|
||||
: surface.block_slice_size;
|
||||
if (slice_index > (UINT64_MAX - mip.offset) / source_stride) {
|
||||
return false;
|
||||
}
|
||||
tile_info.tiled_offset =
|
||||
mip.offset + static_cast<uint64_t>(slice_index) * source_stride;
|
||||
tile_info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
|
||||
block.family == TileBlockFamily::Depth64KB
|
||||
? region.imageSubresource.baseArrayLayer
|
||||
: 0;
|
||||
}
|
||||
if (!FitsBufferRange(tile_info.linear_offset, tile_info.linear_size, UINT64_MAX) ||
|
||||
!FitsBufferRange(tile_info.tiled_offset, tile_info.tiled_size, tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
const auto row_length =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
|
||||
const auto image_height = region.bufferImageHeight != 0 ? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
tile_info.width = std::max(
|
||||
(region.imageExtent.width + texture.texel_width - 1u) / texture.texel_width, 1u);
|
||||
tile_info.height =
|
||||
std::max((image_height + texture.texel_height - 1u) / texture.texel_height, 1u);
|
||||
tile_info.pitch =
|
||||
std::max((row_length + texture.texel_width - 1u) / texture.texel_width, 1u);
|
||||
tile_info.tail = mip_level >= surface.first_tail_level;
|
||||
tile_info.tail_x = tile_info.tail ? mip.tail_x : 0;
|
||||
tile_info.tail_y = tile_info.tail ? mip.tail_y : 0;
|
||||
tile_info.tiled_width = mip.padded_width;
|
||||
tile_info.tiled_height = mip.padded_height;
|
||||
tile_infos.push_back(tile_info);
|
||||
}
|
||||
region_base += mip_depth;
|
||||
}
|
||||
|
||||
out_tile_infos = std::move(tile_infos);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -2,15 +2,10 @@
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GpuTileInfo;
|
||||
|
||||
struct RenderTargetFormatInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t bytes_per_element = 0;
|
||||
@@ -24,35 +19,12 @@ struct SurfaceFormatInfo {
|
||||
Prospero::ColorComponentMapping host_to_storage;
|
||||
};
|
||||
|
||||
struct TextureUploadMipLayout {
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
uint32_t row_length = 0;
|
||||
uint32_t image_height = 0;
|
||||
};
|
||||
|
||||
struct TextureUploadLayout {
|
||||
uint32_t pitch = 0;
|
||||
uint64_t slice_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
TileSurfaceLayout surface;
|
||||
TextureUploadMipLayout mips[16] = {};
|
||||
};
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
|
||||
Prospero::ColorComponentMapping host_to_storage);
|
||||
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
|
||||
Prospero::ChannelType type,
|
||||
Prospero::ChannelOrder order);
|
||||
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
|
||||
uint32_t height, uint32_t levels, uint32_t depth,
|
||||
Prospero::TileMode tile_mode, uint64_t upload_size,
|
||||
bool volume_texture, const char* owner);
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
|
||||
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t levels,
|
||||
std::vector<GpuTileInfo>& out_tile_infos);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -31,7 +31,12 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
StreamBuffer& stream_buffer)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_stream_buffer(stream_buffer) {
|
||||
static_assert(FamilyCount == 9);
|
||||
static_assert(sizeof(Push) == 60);
|
||||
static_assert(sizeof(Mip) == 48);
|
||||
static_assert(offsetof(TilingParams, mips) == 16);
|
||||
static_assert(sizeof(TilingParams) == 784);
|
||||
static_assert(sizeof(TilingParams) <= UINT16_MAX && PipelineCount <= UINT16_MAX);
|
||||
static_assert(sizeof(Tiling) == 16);
|
||||
static_assert(sizeof(Conversion) == 32);
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
bindings[index] = {index, vk::DescriptorType::eStorageBuffer, 1,
|
||||
@@ -48,7 +53,8 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
&m_descriptor_layout),
|
||||
"create TileManager descriptor layout");
|
||||
|
||||
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
|
||||
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(Conversion)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
@@ -88,14 +94,25 @@ TileManager::~TileManager() {
|
||||
}
|
||||
}
|
||||
|
||||
void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base,
|
||||
uint64_t target_base, std::vector<Dispatch>& dispatches,
|
||||
ColorTransform transform) {
|
||||
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, const ImageInfo& info,
|
||||
uint32_t levels, uint64_t source_base,
|
||||
uint64_t target_base) {
|
||||
levels = levels != 0 ? levels : info.resources.levels;
|
||||
EXIT_IF(levels == 0 || levels > MaxMipLevels || levels > info.resources.levels ||
|
||||
tiled_capacity == 0 || linear_capacity == 0);
|
||||
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX);
|
||||
|
||||
const auto& block = info.tiling.block;
|
||||
const auto transform = info.GetColorTransform();
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!info.IsTiled() || info.samples != 1 || Prospero::IsFmaskTextureFormat(info.guest_format) ||
|
||||
block.family >= TileBlockFamily::Count || !std::has_single_bit(block.bytes_per_element) ||
|
||||
block.bytes_per_element > 16 || block.block_width == 0 || block.block_height == 0 ||
|
||||
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
|
||||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
|
||||
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
|
||||
(transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u));
|
||||
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
|
||||
};
|
||||
@@ -106,112 +123,96 @@ void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_ca
|
||||
return size != 0 && offset <= capacity && size <= capacity - offset;
|
||||
};
|
||||
|
||||
dispatches.clear();
|
||||
dispatches.reserve(infos.size());
|
||||
for (const auto& info: infos) {
|
||||
EXIT_IF((transform == ColorTransform::SwapBgra16 && info.bytes_per_element != 8u) ||
|
||||
(transform == ColorTransform::Reverse10_11_11 && info.bytes_per_element != 4u));
|
||||
TileBlockLayout block {};
|
||||
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
|
||||
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
|
||||
const uint64_t groups_x = (static_cast<uint64_t>(info.width) + 7u) / 8u;
|
||||
const uint64_t groups_y = (static_cast<uint64_t>(info.height) + 7u) / 8u;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
|
||||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
|
||||
groups_x > limits.maxComputeWorkGroupCount[0] ||
|
||||
groups_y > limits.maxComputeWorkGroupCount[1] ||
|
||||
info.depth > limits.maxComputeWorkGroupCount[2] ||
|
||||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
|
||||
!valid_range(info.linear_offset, info.linear_size, linear_capacity) ||
|
||||
!valid_range(info.tiled_offset, info.tiled_size, tiled_capacity) ||
|
||||
(block.block_depth == 1 && info.depth != 1));
|
||||
// ImageInfo owns the layout. Upload only the header and active mip records.
|
||||
TilingParams params;
|
||||
params.color_transform = static_cast<uint32_t>(transform);
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
const auto& mip = info.mip_layout[level];
|
||||
const auto extent = info.MipExtent(level);
|
||||
const auto width = static_cast<uint32_t>(
|
||||
(uint64_t {extent.width} + info.tiling.texel_width - 1u) / info.tiling.texel_width);
|
||||
const auto height = static_cast<uint32_t>(
|
||||
(uint64_t {extent.height} + info.tiling.texel_height - 1u) / info.tiling.texel_height);
|
||||
const auto depth = extent.depth;
|
||||
const bool tail = level >= info.first_tail_level;
|
||||
EXIT_NOT_IMPLEMENTED(width == 0 || height == 0 || depth == 0 || mip.linear_pitch < width ||
|
||||
mip.linear_size == 0 || mip.size == 0 ||
|
||||
(!tail && (mip.pitch < width || mip.height < height)) ||
|
||||
(!info.IsVolume() && mip.size % depth != 0));
|
||||
|
||||
uint64_t pitch_bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
|
||||
pitch_bytes > UINT32_MAX);
|
||||
uint64_t slice_bytes = info.linear_slice_stride;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!checked_multiply(mip.linear_pitch, block.bytes_per_element, pitch_bytes) ||
|
||||
pitch_bytes > UINT32_MAX);
|
||||
const uint64_t slice_bytes =
|
||||
info.linear_slice_stride != 0 ? info.linear_slice_stride : mip.linear_size;
|
||||
uint64_t minimum_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(pitch_bytes, info.height, minimum_slice));
|
||||
if (slice_bytes == 0) {
|
||||
slice_bytes = minimum_slice;
|
||||
}
|
||||
uint64_t linear_used = 0;
|
||||
uint64_t bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED((info.depth > 1 && slice_bytes < minimum_slice) ||
|
||||
!checked_multiply(info.depth - 1u, slice_bytes, bytes) ||
|
||||
uint64_t bytes = 0;
|
||||
uint64_t linear_span = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(pitch_bytes, height, minimum_slice) ||
|
||||
(depth > 1 && slice_bytes < minimum_slice) ||
|
||||
!checked_multiply(height - 1u, pitch_bytes, linear_used) ||
|
||||
!checked_multiply(width, block.bytes_per_element, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.height - 1u, pitch_bytes, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.width, info.bytes_per_element, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
|
||||
linear_used > mip.linear_size || slice_bytes > UINT32_MAX ||
|
||||
!checked_multiply(depth - 1u, slice_bytes, linear_span) ||
|
||||
!checked_add(linear_span, mip.linear_size, linear_span) ||
|
||||
!valid_range(mip.linear_offset, linear_span, linear_capacity));
|
||||
|
||||
const uint64_t columns =
|
||||
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
|
||||
const uint64_t rows =
|
||||
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
|
||||
uint64_t blocks_per_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(columns, rows, blocks_per_slice) ||
|
||||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX);
|
||||
if (info.tail) {
|
||||
(uint64_t {mip.pitch} + block.block_width - 1u) / block.block_width;
|
||||
if (tail) {
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
info.family == TileBlockFamily::Standard256B || info.depth > block.block_depth ||
|
||||
info.tail_x >= block.block_width || info.width > block.block_width - info.tail_x ||
|
||||
info.tail_y >= block.block_height ||
|
||||
info.height > block.block_height - info.tail_y ||
|
||||
info.tiled_size < block.block_size);
|
||||
} else {
|
||||
const uint64_t slices =
|
||||
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
|
||||
uint64_t tiled_used = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(blocks_per_slice, slices, tiled_used) ||
|
||||
!checked_multiply(tiled_used, block.block_size, tiled_used) ||
|
||||
tiled_used > info.tiled_size);
|
||||
block.family == TileBlockFamily::Standard256B || mip.tail_x >= block.block_width ||
|
||||
width > block.block_width - mip.tail_x || mip.tail_y >= block.block_height ||
|
||||
height > block.block_height - mip.tail_y);
|
||||
}
|
||||
const uint64_t tiled_plane = info.IsVolume() ? mip.size : mip.size / depth;
|
||||
const uint64_t tiled_slice = info.tiled_slice_stride;
|
||||
const uint64_t slices = (uint64_t {depth} + block.block_depth - 1u) / block.block_depth;
|
||||
uint64_t tiled_span = 0;
|
||||
EXIT_NOT_IMPLEMENTED(columns > UINT32_MAX || tiled_slice > UINT32_MAX ||
|
||||
(slices > 1 && tiled_slice < tiled_plane) ||
|
||||
!checked_multiply(slices - 1u, tiled_slice, tiled_span) ||
|
||||
!checked_add(tiled_span, tiled_plane, tiled_span) ||
|
||||
!valid_range(mip.offset, tiled_span, tiled_capacity));
|
||||
|
||||
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
|
||||
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
|
||||
(alignment - 1u)) != 0);
|
||||
const uint64_t src = source_base + (tile ? info.linear_offset : info.tiled_offset);
|
||||
const uint64_t dst = target_base + (tile ? info.tiled_offset : info.linear_offset);
|
||||
EXIT_NOT_IMPLEMENTED(src > UINT32_MAX || dst > UINT32_MAX);
|
||||
|
||||
const uint32_t family_index = static_cast<uint32_t>(info.family);
|
||||
const uint32_t element_index = std::countr_zero(info.bytes_per_element);
|
||||
EXIT_NOT_IMPLEMENTED(family_index >= FamilyCount || element_index >= BytesPerElementCount);
|
||||
Dispatch dispatch {};
|
||||
dispatch.pipeline_slot =
|
||||
((tile ? FamilyCount : 0u) + family_index) * BytesPerElementCount + element_index;
|
||||
dispatch.push.src_base = static_cast<uint32_t>(src);
|
||||
dispatch.push.dst_base = static_cast<uint32_t>(dst);
|
||||
dispatch.push.width = info.width;
|
||||
dispatch.push.height = info.height;
|
||||
dispatch.push.depth = info.depth;
|
||||
dispatch.push.surface_z = info.surface_z;
|
||||
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
|
||||
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
|
||||
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
|
||||
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
|
||||
dispatch.push.tail_x = info.tail_x;
|
||||
dispatch.push.tail_y = info.tail_y;
|
||||
dispatch.push.tail = info.tail;
|
||||
dispatch.push.color_transform = static_cast<uint32_t>(transform);
|
||||
dispatches.push_back(dispatch);
|
||||
const uint32_t alignment = std::min(block.bytes_per_element, 4u);
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
((mip.linear_offset | mip.offset | pitch_bytes | slice_bytes | tiled_slice) &
|
||||
(alignment - 1u)) != 0);
|
||||
uint64_t src = 0, dst = 0, texels = 0;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!checked_add(source_base, tile ? mip.linear_offset : mip.offset, src) ||
|
||||
!checked_add(target_base, tile ? mip.offset : mip.linear_offset, dst) ||
|
||||
src > UINT32_MAX || dst > UINT32_MAX || !checked_multiply(width, height, texels) ||
|
||||
!checked_multiply(texels, depth, texels) || texels > UINT32_MAX - params.num_texels);
|
||||
params.mips[level] = {static_cast<uint32_t>(src),
|
||||
static_cast<uint32_t>(dst),
|
||||
width,
|
||||
height,
|
||||
static_cast<uint32_t>(pitch_bytes),
|
||||
static_cast<uint32_t>(slice_bytes),
|
||||
static_cast<uint32_t>(tiled_slice),
|
||||
static_cast<uint32_t>(columns),
|
||||
mip.surface_z,
|
||||
tail ? mip.tail_x : 0u,
|
||||
tail ? mip.tail_y : 0u,
|
||||
static_cast<uint32_t>(texels)};
|
||||
params.num_texels += static_cast<uint32_t>(texels);
|
||||
}
|
||||
|
||||
const uint64_t uniform_alignment =
|
||||
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
|
||||
const uint64_t stride = Common::AlignUp<uint64_t>(sizeof(Push), uniform_alignment);
|
||||
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
|
||||
const uint64_t bytes = dispatches.size() * stride;
|
||||
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
|
||||
EXIT_IF(mapped == nullptr);
|
||||
for (size_t index = 0; index < dispatches.size(); index++) {
|
||||
std::memcpy(mapped + index * stride, &dispatches[index].push, sizeof(Push));
|
||||
dispatches[index].params_offset = offset + index * stride;
|
||||
}
|
||||
m_stream_buffer.Commit();
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const auto params_size =
|
||||
static_cast<uint16_t>(offsetof(TilingParams, mips) + levels * sizeof(Mip));
|
||||
const auto offset = m_stream_buffer.Copy(
|
||||
¶ms, params_size,
|
||||
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, alignof(TilingParams)));
|
||||
const auto slot =
|
||||
((tile ? FamilyCount : 0u) + static_cast<uint32_t>(block.family)) * BytesPerElementCount +
|
||||
std::countr_zero(block.bytes_per_element);
|
||||
return {offset, params.num_texels, params_size, static_cast<uint16_t>(slot)};
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
@@ -257,10 +258,9 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset,
|
||||
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
|
||||
uint64_t target_capacity, std::span<Dispatch> dispatches,
|
||||
bool clear_target) {
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
const Tiling& tiling, bool clear_target) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
@@ -295,9 +295,8 @@ void TileManager::Record(vk::Buffer source, uint64_t source_offset,
|
||||
barriers[2].srcAccessMask = vk::AccessFlagBits::eHostWrite;
|
||||
barriers[2].dstAccessMask = vk::AccessFlagBits::eUniformRead;
|
||||
barriers[2].buffer = m_stream_buffer.Handle();
|
||||
barriers[2].offset = dispatches.front().params_offset;
|
||||
barriers[2].size =
|
||||
dispatches.back().params_offset - dispatches.front().params_offset + sizeof(Push);
|
||||
barriers[2].offset = tiling.params_offset;
|
||||
barriers[2].size = tiling.params_size;
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer, {}, 0,
|
||||
@@ -314,24 +313,26 @@ void TileManager::Record(vk::Buffer source, uint64_t source_offset,
|
||||
|
||||
const vk::DescriptorBufferInfo source_info {source, source_descriptor_offset, source_range};
|
||||
const vk::DescriptorBufferInfo target_info {target, target_descriptor_offset, target_range};
|
||||
for (auto& dispatch: dispatches) {
|
||||
const vk::DescriptorBufferInfo params_info {m_stream_buffer.Handle(),
|
||||
dispatch.params_offset, sizeof(Push)};
|
||||
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
|
||||
std::array<vk::WriteDescriptorSet, 3> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
|
||||
: vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, GetPipeline(dispatch.pipeline_slot));
|
||||
command.dispatch((dispatch.push.width + 7u) / 8u, (dispatch.push.height + 7u) / 8u,
|
||||
dispatch.push.depth);
|
||||
const vk::DescriptorBufferInfo params_info {m_stream_buffer.Handle(), tiling.params_offset,
|
||||
tiling.params_size};
|
||||
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
|
||||
std::array<vk::WriteDescriptorSet, 3> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType =
|
||||
index == 2 ? vk::DescriptorType::eUniformBuffer : vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, GetPipeline(tiling.pipeline_slot));
|
||||
const uint64_t groups = (uint64_t {tiling.num_texels} + 63u) / 64u;
|
||||
const uint32_t groups_y = static_cast<uint32_t>(
|
||||
(groups + limits.maxComputeWorkGroupCount[0] - 1u) / limits.maxComputeWorkGroupCount[0]);
|
||||
const uint32_t groups_x = static_cast<uint32_t>((groups + groups_y - 1u) / groups_y);
|
||||
EXIT_NOT_IMPLEMENTED(groups_y > limits.maxComputeWorkGroupCount[1]);
|
||||
command.dispatch(groups_x, groups_y, 1);
|
||||
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
|
||||
@@ -341,51 +342,53 @@ void TileManager::Record(vk::Buffer source, uint64_t source_offset,
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
|
||||
uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform) {
|
||||
const ImageInfo& info) {
|
||||
if (!info.IsTiled()) {
|
||||
const Result source {tiled, tiled_offset, info.data.size};
|
||||
return info.GetColorTransform() == ColorTransform::None
|
||||
? source
|
||||
: TransformColor(source, info.GetColorTransform(), true);
|
||||
}
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches, transform);
|
||||
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
|
||||
const auto linear_capacity = info.LinearSize();
|
||||
const auto tiling = Prepare(false, info.data.size, linear_capacity, info, 0, source_base, 0);
|
||||
auto scratch = GetScratchBuffer(linear_capacity, tiled);
|
||||
Record(tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
|
||||
true);
|
||||
Record(tiled, tiled_offset, info.data.size, scratch.buffer, 0, scratch.size, tiling, true);
|
||||
return {scratch.buffer, 0, linear_capacity};
|
||||
}
|
||||
|
||||
void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
const ImageInfo& info, uint32_t levels) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = linear_offset & (descriptor_alignment - 1);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
|
||||
Record(linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
const uint64_t source_base = linear_offset & (descriptor_alignment - 1);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
const auto tiling =
|
||||
Prepare(true, tiled_capacity, linear_capacity, info, levels, source_base, target_base);
|
||||
Record(linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity, tiling,
|
||||
false);
|
||||
}
|
||||
|
||||
void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> regions,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform) {
|
||||
uint32_t levels) {
|
||||
EXIT_IF(regions.empty());
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
const auto linear_capacity = image.info.LinearSize(levels);
|
||||
// Reserve stream parameters before recording the image download.
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches, transform);
|
||||
const auto tiling =
|
||||
Prepare(true, tiled_capacity, linear_capacity, image.info, levels, 0, target_base);
|
||||
auto linear = GetScratchBuffer(linear_capacity, tiled);
|
||||
image.Download(regions, linear.buffer, 0, linear.size);
|
||||
Record(linear.buffer, 0, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
Record(linear.buffer, 0, linear_capacity, tiled, tiled_offset, tiled_capacity, tiling, false);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::GetScratchBuffer(uint64_t size, vk::Buffer input) {
|
||||
@@ -563,13 +566,13 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
Conversion push {};
|
||||
push.src_base = source_binding.base;
|
||||
push.dst_base = target_binding.base;
|
||||
push.width = layout.width;
|
||||
push.height = rows;
|
||||
push.pitch_bytes = static_cast<uint32_t>(layout.source_row_stride);
|
||||
push.slice_bytes = static_cast<uint32_t>(layout.target_row_stride);
|
||||
push.source_pitch = static_cast<uint32_t>(layout.source_row_stride);
|
||||
push.target_pitch = static_cast<uint32_t>(layout.target_row_stride);
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(push), &push);
|
||||
command.dispatch(static_cast<uint32_t>(groups_x), rows, 1);
|
||||
@@ -644,7 +647,7 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_transform);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
Conversion push {};
|
||||
push.src_base = input_binding.base;
|
||||
push.dst_base = output_binding.base;
|
||||
push.width = pixels;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -20,26 +19,6 @@ class StreamBuffer;
|
||||
struct GraphicContext;
|
||||
struct TileManagerTestAccess;
|
||||
|
||||
struct GpuTileInfo {
|
||||
TileBlockFamily family = TileBlockFamily::Count;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint64_t linear_offset = 0;
|
||||
uint64_t linear_size = 0;
|
||||
uint64_t tiled_offset = 0;
|
||||
uint64_t tiled_size = 0;
|
||||
uint64_t linear_slice_stride = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 1;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
bool tail = false;
|
||||
uint32_t tiled_width = 0;
|
||||
uint32_t tiled_height = 0;
|
||||
uint32_t surface_z = 0;
|
||||
};
|
||||
|
||||
class TileManager final {
|
||||
public:
|
||||
enum class D16Direction { Promote, Demote };
|
||||
@@ -64,15 +43,12 @@ public:
|
||||
KYTY_CLASS_NO_COPY(TileManager);
|
||||
|
||||
// Consume scratch results before the next acquisition, or pass their buffer as input.
|
||||
[[nodiscard]] Result Detile(vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform = ColorTransform::None);
|
||||
[[nodiscard]] Result Detile(vk::Buffer tiled, uint64_t tiled_offset, const ImageInfo& info);
|
||||
void Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, std::span<const GpuTileInfo> infos);
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, const ImageInfo& info,
|
||||
uint32_t levels = 0);
|
||||
void TileImage(Image& image, std::span<const vk::BufferImageCopy> regions, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform = ColorTransform::None);
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, uint32_t levels = 0);
|
||||
[[nodiscard]] Result GetScratchBuffer(uint64_t size, vk::Buffer input = nullptr);
|
||||
void ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout);
|
||||
@@ -86,29 +62,44 @@ private:
|
||||
static constexpr uint32_t BytesPerElementCount = 5;
|
||||
static constexpr uint32_t DirectionCount = 2;
|
||||
static constexpr uint32_t PipelineCount = FamilyCount * BytesPerElementCount * DirectionCount;
|
||||
static constexpr uint32_t MaxMipLevels = 16;
|
||||
|
||||
struct Push {
|
||||
struct alignas(16) Mip {
|
||||
uint32_t src_base;
|
||||
uint32_t dst_base;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
uint32_t surface_z;
|
||||
uint32_t pitch_bytes;
|
||||
uint32_t slice_bytes;
|
||||
uint32_t tiled_slice_bytes;
|
||||
uint32_t blocks_per_row;
|
||||
uint32_t blocks_per_slice;
|
||||
uint32_t surface_z;
|
||||
uint32_t tail_x;
|
||||
uint32_t tail_y;
|
||||
uint32_t tail;
|
||||
uint32_t num_texels;
|
||||
};
|
||||
struct TilingParams {
|
||||
uint32_t num_texels = 0;
|
||||
uint32_t color_transform = 0;
|
||||
std::array<uint32_t, 2> padding {};
|
||||
std::array<Mip, MaxMipLevels> mips;
|
||||
};
|
||||
struct Tiling {
|
||||
uint64_t params_offset;
|
||||
uint32_t num_texels;
|
||||
uint16_t params_size;
|
||||
uint16_t pipeline_slot;
|
||||
};
|
||||
struct Conversion {
|
||||
uint32_t src_base;
|
||||
uint32_t dst_base;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t source_pitch;
|
||||
uint32_t target_pitch;
|
||||
uint32_t color_transform;
|
||||
uint32_t to_host;
|
||||
};
|
||||
struct Dispatch {
|
||||
Push push {};
|
||||
uint32_t pipeline_slot = 0;
|
||||
uint64_t params_offset = 0;
|
||||
};
|
||||
struct StorageBinding {
|
||||
vk::DescriptorBufferInfo info;
|
||||
uint32_t base = 0;
|
||||
@@ -119,12 +110,12 @@ private:
|
||||
uint64_t active, uint32_t remaining,
|
||||
uint64_t alignment, uint64_t max_range,
|
||||
uint32_t max_groups) noexcept;
|
||||
void Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base, uint64_t target_base,
|
||||
std::vector<Dispatch>& dispatches, ColorTransform transform = ColorTransform::None);
|
||||
void Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
std::span<Dispatch> dispatches, bool clear_target);
|
||||
[[nodiscard]] Tiling Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
const ImageInfo& info, uint32_t levels, uint64_t source_base,
|
||||
uint64_t target_base);
|
||||
void Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
const Tiling& tiling, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "kernel/memory.h"
|
||||
@@ -76,11 +75,11 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) {
|
||||
EXIT("invalid native descriptor binding kind");
|
||||
}
|
||||
|
||||
uint32_t NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
|
||||
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) {
|
||||
if (!texture.image_id) {
|
||||
EXIT_IF(texture.image_view != nullptr || !texture.mip_views.empty());
|
||||
return {nullptr, nullptr, vk::ImageLayout::eGeneral};
|
||||
}
|
||||
vk::ImageView view = nullptr;
|
||||
if (texture.mip_views.empty()) {
|
||||
if (element == 0u) {
|
||||
@@ -89,7 +88,7 @@ vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t el
|
||||
} else if (element < texture.mip_views.size()) {
|
||||
view = texture.mip_views[element];
|
||||
}
|
||||
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
|
||||
EXIT_IF(view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
|
||||
return {nullptr, view, texture.layout};
|
||||
}
|
||||
|
||||
@@ -184,7 +183,9 @@ static void ValidateSampledDepthBinding(const ShaderRecompiler::IR::ImageResourc
|
||||
const ShaderTextureResource& descriptor, const Image& image,
|
||||
vk::Format view_format, uint64_t size) {
|
||||
const bool resource_ok = IsSupportedSampledDepthResource(resource);
|
||||
const bool encoding_ok = IsSupportedDepthTextureEncoding(descriptor, resource.r128);
|
||||
// Comparison sampling changes the host format, not the guest metadata encoding.
|
||||
const bool encoding_ok = descriptor.TileMode() != Prospero::TileMode::kDepth ||
|
||||
IsSupportedDepthTextureEncoding(descriptor, resource.r128);
|
||||
const bool view_ok =
|
||||
IsSupportedSampledDepthView(image.info.pixel_format, view_format, descriptor.DstSelXYZW());
|
||||
if (resource_ok && encoding_ok && view_ok) {
|
||||
@@ -311,9 +312,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
|
||||
const bool encoding_ok = IsSupportedStorageTextureEncoding(resource, descriptor);
|
||||
const bool uint_resource = resource.numeric_class == Prospero::TextureNumericClass::Uint;
|
||||
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
const bool raw_sint_storage = uint_resource &&
|
||||
ShaderRecompiler::IR::RawSignedStorageFormat(resource, format) != Prospero::BufferFormat::kInvalid;
|
||||
const auto numeric_class =
|
||||
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
||||
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
||||
resource.atomic && !resource.atomic64;
|
||||
const bool format_ok =
|
||||
@@ -350,84 +352,6 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
|
||||
}
|
||||
|
||||
static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
TextureCache::BindingType binding) {
|
||||
TextureCache::ImageDesc desc {};
|
||||
switch (resource.numeric_class) {
|
||||
case Prospero::TextureNumericClass::Float:
|
||||
desc.info.guest_format = Prospero::BufferFormat::k32Float;
|
||||
break;
|
||||
case Prospero::TextureNumericClass::Uint:
|
||||
desc.info.guest_format = resource.atomic64 ? Prospero::BufferFormat::k32_32UInt
|
||||
: Prospero::BufferFormat::k32UInt;
|
||||
break;
|
||||
case Prospero::TextureNumericClass::Sint:
|
||||
desc.info.guest_format = Prospero::BufferFormat::k32SInt;
|
||||
break;
|
||||
default: EXIT("null image has unsupported numeric class\n");
|
||||
}
|
||||
desc.info.pixel_format = VulkanFormat(desc.info.guest_format);
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {1, 1, 1};
|
||||
desc.info.resources = {1, 1};
|
||||
desc.info.bytes_per_block = Prospero::NumBytesPerElement(desc.info.guest_format);
|
||||
desc.info.samples = 1;
|
||||
desc.info.mip_layout[0] = {0, 0, 1, 1};
|
||||
desc.view_info.format = resource.atomic64 ? vk::Format::eR64Uint : desc.info.pixel_format;
|
||||
desc.view_info.type = vk::ImageViewType::e2D;
|
||||
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
desc.view_info.usage = binding == TextureCache::BindingType::Storage
|
||||
? vk::ImageUsageFlagBits::eStorage
|
||||
: vk::ImageUsageFlagBits::eSampled;
|
||||
desc.type = binding;
|
||||
return desc;
|
||||
}
|
||||
|
||||
static void PopulateTextureMipLayout(ImageInfo& info) {
|
||||
if (info.IsVolume() && info.tile_mode != Prospero::TileMode::kLinear) {
|
||||
TileSurfaceLayout surface {};
|
||||
const TileSurfaceDescription description {
|
||||
info.guest_format, info.tile_mode, TileSurfaceDimension::Dim3D, info.extent.width,
|
||||
info.extent.height, info.extent.depth, info.resources.levels, 1};
|
||||
if (!TileGetTiledTextureLayout(description, surface)) {
|
||||
EXIT("unsupported normalized volume texture layout\n");
|
||||
}
|
||||
for (uint32_t level = 0; level < info.resources.levels; level++) {
|
||||
const auto& mip = surface.mips[level];
|
||||
info.mip_layout[level] = {
|
||||
mip.offset,
|
||||
mip.size,
|
||||
mip.padded_width,
|
||||
mip.padded_height,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TileSizeOffset levels[16] {};
|
||||
TilePaddedSize padded[16] {};
|
||||
TileGetTextureSize(info.guest_format, info.extent.width, info.extent.height,
|
||||
info.resources.levels, info.tile_mode, nullptr, levels, padded);
|
||||
const auto texel_shift = info.IsBlock() ? 2u : 0u;
|
||||
for (uint32_t level = 0; level < info.resources.levels; level++) {
|
||||
const auto offset =
|
||||
levels[level].src_size != 0 ? levels[level].src_offset : levels[level].offset;
|
||||
auto size = static_cast<uint64_t>(levels[level].src_size != 0 ? levels[level].src_size
|
||||
: levels[level].size);
|
||||
if (info.IsVolume()) {
|
||||
size *= std::max(info.extent.depth >> level, 1u);
|
||||
} else {
|
||||
size *= info.resources.layers;
|
||||
}
|
||||
info.mip_layout[level] = {
|
||||
offset,
|
||||
size,
|
||||
padded[level].width >> texel_shift,
|
||||
padded[level].height >> texel_shift,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, vk::Format format,
|
||||
const SurfaceFormatInfo& surface_format, bool storage,
|
||||
@@ -495,45 +419,54 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
return view;
|
||||
}
|
||||
|
||||
static bool ResolveTextureMipView(const TileSurfaceDescription& description, bool metadata,
|
||||
uint32_t view_levels, uint32_t& levels, uint32_t& base_level) {
|
||||
TileSurfaceLayout physical {};
|
||||
TileSurfaceLayout view {};
|
||||
auto view_description = description;
|
||||
view_description.levels = levels;
|
||||
if (!TileGetTiledTextureLayout(description, physical) ||
|
||||
!TileGetTiledTextureLayout(view_description, view)) {
|
||||
static bool ResolveTextureMipView(ImageInfo& info, uint32_t physical_levels, bool metadata,
|
||||
uint32_t view_levels, uint32_t& base_level) {
|
||||
if (!info.IsTiled()) {
|
||||
return false;
|
||||
}
|
||||
if (physical.first_tail_level == view.first_tail_level &&
|
||||
physical.block_slice_size == view.block_slice_size &&
|
||||
physical.total_size == view.total_size &&
|
||||
std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels,
|
||||
std::begin(view.mips))) {
|
||||
return true;
|
||||
ImageInfo physical = info;
|
||||
physical.resources.levels = physical_levels;
|
||||
physical.UpdateSize();
|
||||
info.UpdateSize();
|
||||
const auto same_mip = [&](uint32_t target, uint32_t source) {
|
||||
const auto& left = physical.mip_layout[target];
|
||||
const auto& right = info.mip_layout[source];
|
||||
return left.offset == right.offset && left.size == right.size &&
|
||||
left.pitch == right.pitch && left.height == right.height &&
|
||||
left.tail_x == right.tail_x && left.tail_y == right.tail_y &&
|
||||
left.surface_z == right.surface_z &&
|
||||
physical.MipExtent(target) == info.MipExtent(source);
|
||||
};
|
||||
if (physical.first_tail_level == info.first_tail_level &&
|
||||
physical.tiled_slice_stride == info.tiled_slice_stride &&
|
||||
physical.data.size == info.data.size) {
|
||||
bool matches = true;
|
||||
for (uint32_t level = 0; level < physical_levels; ++level) {
|
||||
matches &= same_mip(level, level);
|
||||
}
|
||||
if (matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (metadata || ((description.layers > 1 || description.depth > 1) &&
|
||||
physical.block_slice_size != view.block_slice_size)) {
|
||||
if (metadata ||
|
||||
(info.TransferLayers() > 1 && physical.tiled_slice_stride != info.tiled_slice_stride)) {
|
||||
return false;
|
||||
}
|
||||
// T# addresses the last mip. A view can select the same stored subresources
|
||||
// with different mip indices; inaccessible mips need no host representation.
|
||||
for (uint32_t base = 0; base + view_levels <= description.levels; ++base) {
|
||||
for (uint32_t base = 0; base + view_levels <= physical_levels; ++base) {
|
||||
bool matches = true;
|
||||
for (uint32_t i = 0; i < view_levels; ++i) {
|
||||
const auto source = base_level + i;
|
||||
const auto target = base + i;
|
||||
if (physical.mips[target] != view.mips[source] ||
|
||||
(target >= physical.first_tail_level) != (source >= view.first_tail_level) ||
|
||||
std::max(description.width >> target, 1u) != std::max(description.width >> source, 1u) ||
|
||||
std::max(description.height >> target, 1u) != std::max(description.height >> source, 1u) ||
|
||||
std::max(description.depth >> target, 1u) != std::max(description.depth >> source, 1u)) {
|
||||
if (!same_mip(target, source) ||
|
||||
(target >= physical.first_tail_level) != (source >= info.first_tail_level)) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
levels = description.levels;
|
||||
info = std::move(physical);
|
||||
base_level = base;
|
||||
return true;
|
||||
}
|
||||
@@ -554,10 +487,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
if (descriptor.IsNull()) {
|
||||
auto desc = NullTextureDesc(resource, storage ? TextureCache::BindingType::Storage
|
||||
: TextureCache::BindingType::Texture);
|
||||
const auto id = texture_cache.FindImage(desc);
|
||||
return {id, nullptr, std::move(desc)};
|
||||
return {.layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
|
||||
const auto address = descriptor.Base40();
|
||||
@@ -605,7 +535,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
const bool sampled_numeric_class =
|
||||
storage || resource.numeric_class == Prospero::SampledTextureNumericClass(format);
|
||||
storage || resource.numeric_class ==
|
||||
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
||||
if (!storage && resource.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled &&
|
||||
!sampled_numeric_class) {
|
||||
EXIT("sampled image numeric class mismatch: numeric=%u format=%u addr=0x%016" PRIx64 "\n",
|
||||
@@ -618,82 +549,62 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
const auto image_layers = layered ? depth : 1u;
|
||||
auto view_base = static_cast<uint32_t>(base_level);
|
||||
if (levels > physical_levels) {
|
||||
const TileSurfaceDescription physical {
|
||||
format, tile, volume ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
|
||||
width, height, volume ? depth : 1u, physical_levels, image_layers};
|
||||
if (!ResolveTextureMipView(physical, !resource.r128 && descriptor.MetaCompress(),
|
||||
view_levels, levels, view_base)) {
|
||||
EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u "
|
||||
"extent=%ux%ux%u tile=%u\n",
|
||||
base_level, last_level, max_mip, width, height, depth,
|
||||
static_cast<uint32_t>(tile));
|
||||
}
|
||||
}
|
||||
uint32_t pitch = 0;
|
||||
TileSizeAlign size {};
|
||||
if (multisampled) {
|
||||
const auto bytes = Prospero::NumBytesPerElement(format);
|
||||
pitch = depth_tile ? TileGetDepthPitch(width, bytes, last_level)
|
||||
: TileGetRenderTargetPitch(width, bytes, last_level);
|
||||
if (pitch == 0 || !TileGetRenderTargetSize(width, height, pitch, bytes, size, last_level) ||
|
||||
size.size > UINT32_MAX / image_layers) {
|
||||
EXIT("unsupported multisample texture layout\n");
|
||||
}
|
||||
size.size *= image_layers;
|
||||
} else {
|
||||
pitch = TileGetTexturePitch(format, width, tile);
|
||||
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers,
|
||||
physical_levels, tile, volume, size);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
|
||||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, size.size);
|
||||
}
|
||||
|
||||
auto pixel_format = surface_format.vk_format;
|
||||
if (resource.depth_compare) {
|
||||
if (const auto* depth_format = FindGuestDepthFormatPolicy(format)) {
|
||||
pixel_format = depth_format->depth_attachment_format;
|
||||
}
|
||||
}
|
||||
const auto raw_sint_format = ShaderRecompiler::IR::RawSignedStorageFormat(resource, format);
|
||||
const auto storage_view_format = resource.atomic64 ? vk::Format::eR64Uint
|
||||
: storage && (resource.atomic ||
|
||||
format == Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: storage && resource.atomic ? vk::Format::eR32Uint
|
||||
: raw_sint_format != Prospero::BufferFormat::kInvalid
|
||||
? VulkanFormat(raw_sint_format)
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||
? storage_view_format
|
||||
: pixel_format;
|
||||
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
|
||||
TextureCache::ImageDesc desc {};
|
||||
desc.info.data = {address, size.size};
|
||||
desc.info.data = {address, 0};
|
||||
desc.info.pixel_format = pixel_format;
|
||||
desc.info.guest_format = format;
|
||||
desc.info.type = TextureBaseType(type);
|
||||
desc.info.extent = {width, height, volume ? depth : 1u};
|
||||
desc.info.resources = {levels, image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.bytes_per_block =
|
||||
block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format);
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = tile;
|
||||
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth &&
|
||||
!desc.info.IsDepth()) {
|
||||
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth) {
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
}
|
||||
if (levels > physical_levels) {
|
||||
if (!ResolveTextureMipView(desc.info, physical_levels,
|
||||
!resource.r128 && descriptor.MetaCompress(), view_levels,
|
||||
view_base)) {
|
||||
EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u "
|
||||
"extent=%ux%ux%u tile=%u\n",
|
||||
base_level, last_level, max_mip, width, height, depth,
|
||||
static_cast<uint32_t>(tile));
|
||||
}
|
||||
} else {
|
||||
desc.info.UpdateSize();
|
||||
}
|
||||
const auto alignment = desc.info.DataAlignment();
|
||||
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
|
||||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, desc.info.data.size);
|
||||
}
|
||||
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth) {
|
||||
TileSizeAlign metadata_size {};
|
||||
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
|
||||
desc.info.bytes_per_block, physical_levels, tile, metadata_size,
|
||||
std::countr_zero(samples));
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
|
||||
desc.info.metadata.dcc_alpha_msb = descriptor.DccAlphaPos();
|
||||
}
|
||||
if (samples > 1) {
|
||||
desc.info.mip_layout[0] = {0, size.size, pitch, height};
|
||||
} else {
|
||||
PopulateTextureMipLayout(desc.info);
|
||||
}
|
||||
desc.view_info = TextureViewInfo(resource, descriptor, view_format, surface_format, storage,
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.view_info.base_level = view_base;
|
||||
@@ -709,7 +620,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
if (storage) {
|
||||
EXIT("depth target cannot be bound as a storage image\n");
|
||||
}
|
||||
ValidateSampledDepthBinding(resource, descriptor, *image, pixel_format, size.size);
|
||||
ValidateSampledDepthBinding(resource, descriptor, *image, pixel_format,
|
||||
desc.info.data.size);
|
||||
} else if (storage) {
|
||||
ValidateStorageColorView(image->info.pixel_format, view_format, descriptor.DstSelXYZW());
|
||||
} else {
|
||||
@@ -745,10 +657,10 @@ static vk::DescriptorBufferInfo NativeUpload(RenderContext& context,
|
||||
}
|
||||
|
||||
void RenderExecutor::BindImage(ImageId id, bool storage) {
|
||||
auto& image = m_context.GetTextureCache().GetImage(id);
|
||||
if (image.info.data.Empty()) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
auto& image = m_context.GetTextureCache().GetImage(id);
|
||||
if (image.binding.is_bound) {
|
||||
image.binding.force_general |= image.binding.shader_write != storage;
|
||||
}
|
||||
@@ -799,12 +711,11 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
m_context, program, i, snapshot.samplers[program.info.samplers[i].snapshot_index]));
|
||||
}
|
||||
prepared.shader_data.reserve(program.bindings.ShaderDataDwords());
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
for (const auto reg: program.info.user_data_registers) {
|
||||
prepared.shader_data.push_back(snapshot.user_data[reg - program.user_data_base]);
|
||||
}
|
||||
prepared.shader_data.resize(program.bindings.ShaderDataDwords());
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
if (program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::Gds)] != 0) {
|
||||
prepared.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
|
||||
}
|
||||
}
|
||||
@@ -817,19 +728,20 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
prepared.buffer_sources.clear();
|
||||
const auto& layout = program.bindings;
|
||||
if (layout.memory_offset_count == 0) {
|
||||
prepared.buffer_sources.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)] == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto& resources = layout.descriptors.front().resources;
|
||||
prepared.buffer_sources.reserve(resources.size());
|
||||
for (const auto resource: resources) {
|
||||
for (uint32_t resource = 0; resource < program.info.buffers.size(); ++resource) {
|
||||
const auto& metadata = program.info.buffers[resource];
|
||||
if (metadata.descriptor_index == UINT32_MAX) continue;
|
||||
auto& source = prepared.buffer_sources[metadata.descriptor_index];
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
|
||||
const auto address = descriptor.Base48();
|
||||
auto size = descriptor.GetSize();
|
||||
if (address == 0 || size == 0) {
|
||||
prepared.buffer_sources.push_back({});
|
||||
source = {};
|
||||
continue;
|
||||
}
|
||||
if (descriptor.NumRecords() == UINT32_MAX) {
|
||||
@@ -853,7 +765,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
}
|
||||
}
|
||||
size = Libs::LibKernel::Memory::ClampRangeSize(address, size);
|
||||
prepared.buffer_sources.push_back({address, size, cache.FindBuffer(address, size)});
|
||||
source = {address, size, cache.FindBuffer(address, size)};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -864,10 +776,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.memory_offset_count);
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
|
||||
prepared.buffers.clear();
|
||||
prepared.buffers.reserve(layout.memory_offset_count);
|
||||
prepared.buffers.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
EXIT_IF(prepared.shader_data.size() != layout.ShaderDataDwords());
|
||||
std::fill(prepared.shader_data.begin() + layout.memory_offset_dword,
|
||||
prepared.shader_data.end(), 0);
|
||||
@@ -876,20 +787,18 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto shift = (index % 4u) * 8u;
|
||||
prepared.shader_data[dword] |= offset << shift;
|
||||
};
|
||||
for (uint32_t i = 0; i < layout.memory_offset_count; i++) {
|
||||
const auto resource = layout.descriptors.front().resources[i];
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
const auto index = resource.descriptor_index;
|
||||
if (index == UINT32_MAX) continue;
|
||||
uint32_t buffer_offset = 0;
|
||||
prepared.buffers.push_back(NativeStorageBuffer(m_context, prepared.buffer_sources[i],
|
||||
program.info.buffers[resource],
|
||||
buffer_offset));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||
resource, buffer_offset);
|
||||
pack_memory_offset(index, buffer_offset);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
layout, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::ShaderData) != nullptr) {
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] != 0) {
|
||||
prepared.shader_data_buffer = NativeUpload(m_context, prepared.shader_data);
|
||||
}
|
||||
}
|
||||
@@ -904,8 +813,9 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
// Acquire each view before a later overlapping descriptor can replace its image.
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (!images[i].image_id) continue;
|
||||
const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
|
||||
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
@@ -936,7 +846,6 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto& image = texture_cache.GetImage(binding.image_id);
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
image.usage.storage |= storage;
|
||||
image.usage.texture |= !storage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,7 +866,7 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
|
||||
for (auto& target: colors) {
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.m_slot_images.try_get(target.image_id);
|
||||
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
@@ -981,7 +890,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
std::span<PreparedBindings* const> prepared_bindings) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto vk_buffer = buffer.Handle();
|
||||
size_t descriptor_count = 0;
|
||||
size_t image_descriptor_count = 0;
|
||||
size_t write_count = 0;
|
||||
ShaderRecompiler::IR::PushData push_data;
|
||||
bool has_push_data = false;
|
||||
@@ -995,9 +904,14 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
EXIT_IF(prepared == nullptr || prepared->runtime == nullptr || !*prepared->runtime);
|
||||
const auto& program = *prepared->runtime->program;
|
||||
write_count += program.bindings.descriptors.size();
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
descriptor_count += NativeDescriptorCount(binding);
|
||||
write_count += std::popcount(program.bindings.descriptor_mask);
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto count = program.bindings.descriptor_counts[index];
|
||||
if (index >= ShaderRecompiler::IR::FirstImageBinding &&
|
||||
index <= static_cast<size_t>(BindingKind::Samplers)) {
|
||||
image_descriptor_count += count;
|
||||
}
|
||||
}
|
||||
const auto shader_stage = NativeShaderStage(program.stage);
|
||||
push_stages |= shader_stage;
|
||||
@@ -1029,10 +943,9 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
const auto& program = *writer->runtime->program;
|
||||
for (uint32_t i = 0; i < writer->buffer_sources.size(); ++i) {
|
||||
const auto resource = program.bindings.descriptors.front().resources[i];
|
||||
if (!program.info.buffers[resource].written) continue;
|
||||
const auto& written = writer->buffer_sources[i];
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
|
||||
const auto& written = writer->buffer_sources[resource.descriptor_index];
|
||||
if (written.size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written.size)) {
|
||||
EXIT("scalar resource reads overlap a shader buffer write\n");
|
||||
@@ -1041,12 +954,11 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
m_descriptor_buffers.clear();
|
||||
m_descriptor_images.clear();
|
||||
m_descriptor_images.resize(image_descriptor_count);
|
||||
m_descriptor_writes.clear();
|
||||
m_descriptor_buffers.reserve(descriptor_count);
|
||||
m_descriptor_images.reserve(descriptor_count);
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
size_t image_cursor = 0;
|
||||
std::array<vk::DescriptorBufferInfo, 2> address_buffers;
|
||||
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
const auto& program = *prepared->runtime->program;
|
||||
@@ -1064,19 +976,14 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
|
||||
auto& binding = descriptors.images[i];
|
||||
if (!binding.image_id) continue;
|
||||
auto& image = m_context.GetTextureCache().GetImage(binding.image_id);
|
||||
const auto& view = binding.desc.view_info;
|
||||
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count};
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
if (image.info.data.Empty()) {
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
storage ? vk::AccessFlagBits2::eShaderRead |
|
||||
vk::AccessFlagBits2::eShaderWrite
|
||||
: vk::AccessFlagBits2::eShaderRead,
|
||||
range, vk_buffer);
|
||||
} else if (image.binding.is_target) {
|
||||
if (image.binding.is_target) {
|
||||
const auto layout = image.binding.attachment_layout;
|
||||
EXIT_IF(layout == vk::ImageLayout::eUndefined);
|
||||
if (image.info.IsDepth()) {
|
||||
@@ -1111,37 +1018,37 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
|
||||
m_image_occurrences.assign(descriptors.images.size(), 0);
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
std::array<size_t, ShaderRecompiler::IR::ImageBindingCount> image_starts {};
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto kind = static_cast<BindingKind>(index);
|
||||
vk::WriteDescriptorSet write {};
|
||||
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind);
|
||||
write.descriptorType = NativeDescriptorType(binding.kind);
|
||||
write.descriptorCount = NativeDescriptorCount(binding);
|
||||
const auto buffer_start = m_descriptor_buffers.size();
|
||||
const auto image_start = m_descriptor_images.size();
|
||||
if (ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind) !=
|
||||
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, kind);
|
||||
write.descriptorType = NativeDescriptorType(kind);
|
||||
write.descriptorCount = program.bindings.descriptor_counts[index];
|
||||
const auto image_start = image_cursor;
|
||||
if (ShaderRecompiler::IR::ImageBindingResourceClass(kind) !=
|
||||
ShaderRecompiler::IR::ImageResourceClass::None) {
|
||||
for (const auto resource: binding.resources) {
|
||||
m_descriptor_images.push_back(MakeImageInfo(
|
||||
descriptors.images.at(resource), m_image_occurrences.at(resource)++));
|
||||
}
|
||||
image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] = image_start;
|
||||
image_cursor += write.descriptorCount;
|
||||
} else {
|
||||
switch (binding.kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Buffers:
|
||||
EXIT_IF(descriptors.buffers.size() != binding.resources.size());
|
||||
EXIT_IF(descriptors.buffers.size() != write.descriptorCount);
|
||||
for (const auto& view: descriptors.buffers) {
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.push_back(view);
|
||||
}
|
||||
write.pBufferInfo = descriptors.buffers.data();
|
||||
break;
|
||||
case BindingKind::BdaPagetable:
|
||||
case BindingKind::FaultBuffer: {
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = kind == BindingKind::BdaPagetable
|
||||
? cache.GetBdaPageTableBuffer()
|
||||
: cache.GetFaultBuffer();
|
||||
m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0,
|
||||
bda_buffer->Size());
|
||||
auto& view = address_buffers[kind == BindingKind::FaultBuffer];
|
||||
view = {bda_buffer->Handle(), 0, bda_buffer->Size()};
|
||||
write.pBufferInfo = &view;
|
||||
break;
|
||||
}
|
||||
case BindingKind::FlattenedSrt:
|
||||
@@ -1149,42 +1056,42 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
case BindingKind::SharedMemory:
|
||||
case BindingKind::Gds: {
|
||||
const vk::DescriptorBufferInfo* view = &descriptors.gds;
|
||||
if (binding.kind == BindingKind::FlattenedSrt) {
|
||||
if (kind == BindingKind::FlattenedSrt) {
|
||||
view = &descriptors.flattened_srt;
|
||||
} else if (binding.kind == BindingKind::ShaderData) {
|
||||
} else if (kind == BindingKind::ShaderData) {
|
||||
view = &descriptors.shader_data_buffer;
|
||||
} else if (binding.kind == BindingKind::SharedMemory) {
|
||||
} else if (kind == BindingKind::SharedMemory) {
|
||||
view = &descriptors.shared_memory;
|
||||
}
|
||||
EXIT_IF(view->buffer == nullptr);
|
||||
m_descriptor_buffers.push_back(*view);
|
||||
write.pBufferInfo = view;
|
||||
break;
|
||||
}
|
||||
case BindingKind::Samplers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto sampler = descriptors.samplers.at(resource);
|
||||
for (const auto sampler: descriptors.samplers) {
|
||||
EXIT_IF(sampler == nullptr);
|
||||
m_descriptor_images.emplace_back(sampler, nullptr,
|
||||
vk::ImageLayout::eUndefined);
|
||||
m_descriptor_images[image_cursor++] = {sampler, nullptr, vk::ImageLayout::eUndefined};
|
||||
}
|
||||
break;
|
||||
case BindingKind::Count: EXIT("invalid descriptor binding kind");
|
||||
}
|
||||
}
|
||||
if (m_descriptor_buffers.size() != buffer_start) {
|
||||
write.pBufferInfo = m_descriptor_buffers.data() + buffer_start;
|
||||
}
|
||||
if (m_descriptor_images.size() != image_start) {
|
||||
if (image_cursor != image_start) {
|
||||
write.pImageInfo = m_descriptor_images.data() + image_start;
|
||||
}
|
||||
m_descriptor_writes.push_back(write);
|
||||
}
|
||||
for (uint32_t i = 0; i < descriptors.images.size(); i++) {
|
||||
const auto expected =
|
||||
descriptors.images[i].mip_views.empty()
|
||||
? 1u
|
||||
: static_cast<uint32_t>(descriptors.images[i].mip_views.size());
|
||||
EXIT_IF(m_image_occurrences[i] != expected);
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& resource = program.info.images[i];
|
||||
const auto& texture = descriptors.images[i];
|
||||
EXIT_IF(texture.image_id &&
|
||||
resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
|
||||
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
|
||||
const auto start = image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] +
|
||||
resource.descriptor_index;
|
||||
for (uint32_t mip = 0; mip < resource.mip_count; ++mip) {
|
||||
m_descriptor_images[start + mip] = MakeImageInfo(texture, mip);
|
||||
}
|
||||
}
|
||||
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
|
||||
@@ -50,8 +50,6 @@ struct PreparedBindings {
|
||||
|
||||
[[nodiscard]] vk::DescriptorType
|
||||
NativeDescriptorType(ShaderRecompiler::IR::DescriptorBindingKind kind);
|
||||
[[nodiscard]] uint32_t
|
||||
NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding);
|
||||
[[nodiscard]] vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
|
||||
uint32_t element = 0);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
@@ -39,6 +40,8 @@ namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto DriverCacheSaveInterval = std::chrono::minutes {5};
|
||||
|
||||
uint8_t RemapSourceAlphaFactor(uint8_t factor) {
|
||||
switch (static_cast<Prospero::BlendFactor>(factor)) {
|
||||
case Prospero::BlendFactor::kSrcAlpha:
|
||||
@@ -148,7 +151,7 @@ void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
|
||||
}
|
||||
|
||||
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
const std::vector<uint32_t>& spirv, bool maintenance8_enabled) {
|
||||
if (!Config::ShaderValidationEnabled()) {
|
||||
return true;
|
||||
}
|
||||
@@ -160,7 +163,9 @@ bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
static_cast<int>(position.column), static_cast<int>(position.index),
|
||||
message);
|
||||
});
|
||||
if (tools.Validate(spirv)) {
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
validator_options.SetAllowOffsetTextureOperand(maintenance8_enabled);
|
||||
if (tools.Validate(spirv.data(), spirv.size(), validator_options)) {
|
||||
return true;
|
||||
}
|
||||
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
|
||||
@@ -217,17 +222,53 @@ struct PipelineCache::ProgramCache {
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
std::vector<uint32_t> function_code;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
ShaderRecompiler::IR::CompiledShaderInfo program;
|
||||
ShaderProgram handle;
|
||||
};
|
||||
|
||||
struct SourceEntry {
|
||||
explicit SourceEntry(ShaderRecompiler::IR::ResourcePlan plan)
|
||||
: resource_plan(std::move(plan)) {
|
||||
SourceEntry(ShaderRecompiler::IR::ResourcePlan plan,
|
||||
std::unique_ptr<ShaderRecompiler::ShaderSource> call_source)
|
||||
: call_source(std::move(call_source)), resource_plan(std::move(plan)) {
|
||||
permutations.reserve(8);
|
||||
}
|
||||
|
||||
std::span<const uint32_t> FunctionCode() const {
|
||||
return call_source
|
||||
? std::span<const uint32_t>(call_source->linked->code).subspan(call_source->code.size())
|
||||
: std::span<const uint32_t>{};
|
||||
}
|
||||
|
||||
bool RefreshCallSource(const ShaderRecompiler::IR::SrtRuntime& runtime) {
|
||||
const auto revision = call_source->revision;
|
||||
ShaderRecompiler::RefreshShaderSource(*call_source, runtime);
|
||||
resource_plan.source_reads = call_source->reads;
|
||||
return revision != call_source->revision;
|
||||
}
|
||||
|
||||
template <bool HasCalls>
|
||||
Permutation* FindPermutation(const ShaderRecompiler::IR::SrtRuntime& runtime,
|
||||
uint32_t push_data_cursor) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
resource_plan, runtime, resources, specialization));
|
||||
for (auto& candidate: permutations) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
if (layout.push_data_start_dword != ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) ||
|
||||
candidate.specialization != specialization) {
|
||||
continue;
|
||||
}
|
||||
if constexpr (HasCalls) {
|
||||
if (!std::ranges::equal(candidate.function_code, FunctionCode())) continue;
|
||||
}
|
||||
return &candidate;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<ShaderRecompiler::ShaderSource> call_source;
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
@@ -256,23 +297,26 @@ struct PipelineCache::ProgramCache {
|
||||
const ShaderRecompiler::CompileOptions& options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
uint32_t push_data_start_dword,
|
||||
std::span<const uint32_t> function_code) {
|
||||
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
|
||||
specialization, push_data_start_dword);
|
||||
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
|
||||
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv,
|
||||
options.maintenance8_enabled)) {
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
|
||||
options.shader_hash);
|
||||
}
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
|
||||
const auto module = CompileSPV(result.spirv, device);
|
||||
const auto module = CompileSPV(result.spirv, graphics.device);
|
||||
EXIT_IF(module == nullptr);
|
||||
if (options.dump_ir) {
|
||||
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
|
||||
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
|
||||
}
|
||||
return {
|
||||
.function_code = {function_code.begin(), function_code.end()},
|
||||
.specialization = std::move(specialization),
|
||||
.program = std::move(result.program).TakeCompiledInfo(),
|
||||
.handle = {.id = ++next_shader_id, .module = module},
|
||||
@@ -306,22 +350,20 @@ struct PipelineCache::ProgramCache {
|
||||
};
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
|
||||
runtime.workgroup_counts = input_info.workgroup_counts;
|
||||
runtime.indirect_args_address = input_info.indirect_args_address;
|
||||
}
|
||||
bool source_changed = false;
|
||||
if (entry != programs.end()) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
if (const auto permutation = std::ranges::find_if(
|
||||
entry->second.permutations, [&](const Permutation& candidate) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
return layout.push_data_start_dword ==
|
||||
ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) &&
|
||||
candidate.specialization == entry->second.specialization;
|
||||
});
|
||||
permutation != entry->second.permutations.end()) {
|
||||
input_info.stage = {.program = &permutation->program,
|
||||
.resources = &entry->second.resources};
|
||||
auto& cached = entry->second;
|
||||
Permutation* permutation = nullptr;
|
||||
if (cached.call_source) {
|
||||
source_changed = cached.RefreshCallSource(runtime);
|
||||
if (!source_changed) permutation = cached.FindPermutation<true>(runtime, push_data_cursor);
|
||||
} else {
|
||||
permutation = cached.FindPermutation<false>(runtime, push_data_cursor);
|
||||
}
|
||||
if (permutation != nullptr) {
|
||||
input_info.stage = {.program = &permutation->program, .resources = &entry->second.resources};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
@@ -349,11 +391,13 @@ struct PipelineCache::ProgramCache {
|
||||
}
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.maintenance8_enabled = graphics.maintenance8_enabled;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = user_data;
|
||||
options.back_code = params.back_code;
|
||||
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
|
||||
options.early_dump = options.dump_ir;
|
||||
options.emit_debug_info = Config::GraphicsDebugDumpEnabled();
|
||||
options.dump_label = label;
|
||||
options.input_info = stage_input;
|
||||
|
||||
@@ -368,16 +412,39 @@ struct PipelineCache::ProgramCache {
|
||||
options.wave_size = input_info.wave_size;
|
||||
}
|
||||
DumpShaderOriginal(stage_name, options.shader_hash, params.code);
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
ShaderRecompiler::ShaderSource source;
|
||||
if (entry == programs.end()) source = ShaderRecompiler::PrepareShaderSource(params.code, options);
|
||||
auto* current = entry == programs.end() ? &source : entry->second.call_source.get();
|
||||
ShaderRecompiler::TranslateResult translated;
|
||||
if (current != nullptr) {
|
||||
if (current->call) {
|
||||
if (entry == programs.end()) ShaderRecompiler::RefreshShaderSource(*current, runtime);
|
||||
translated = ShaderRecompiler::TranslateProgram(current->linked->decoded, options);
|
||||
translated.program.source_reads = current->reads;
|
||||
} else {
|
||||
translated = ShaderRecompiler::TranslateProgram(current->decoded, options);
|
||||
}
|
||||
} else {
|
||||
translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
}
|
||||
if (entry == programs.end()) {
|
||||
auto retained = source.call ? std::make_unique<ShaderRecompiler::ShaderSource>(std::move(source)) : nullptr;
|
||||
entry = programs.try_emplace(lookup_key,
|
||||
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
|
||||
ShaderRecompiler::IR::ExtractResourcePlan(translated.program), std::move(retained)).first;
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
} else if (source_changed) {
|
||||
entry->second.resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
if (const auto* permutation = entry->second.FindPermutation<true>(runtime, push_data_cursor)) {
|
||||
input_info.stage = {.program = &permutation->program, .resources = &entry->second.resources};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation(
|
||||
stage_name, options, std::move(translated), entry->second.specialization, push_data_cursor));
|
||||
stage_name, options, std::move(translated), entry->second.specialization, push_data_cursor,
|
||||
entry->second.FunctionCode()));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = &entry->second.resources};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
@@ -398,26 +465,27 @@ struct PipelineCache::ProgramCache {
|
||||
return permutation.handle;
|
||||
}
|
||||
|
||||
explicit ProgramCache(vk::Device device): device(device) {
|
||||
explicit ProgramCache(GraphicContext& graphics): graphics(graphics) {
|
||||
lookup_key.static_state.reserve(MaxStaticKeyWords);
|
||||
}
|
||||
~ProgramCache() {
|
||||
for (const auto& [key, entry]: programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: entry.permutations) {
|
||||
device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
|
||||
ProgramKey lookup_key;
|
||||
vk::Device device;
|
||||
GraphicContext& graphics;
|
||||
uint64_t next_shader_id = 0;
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
|
||||
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics)),
|
||||
m_next_driver_cache_save(std::chrono::steady_clock::now() + DriverCacheSaveInterval) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
InitializeDriverCache();
|
||||
}
|
||||
@@ -525,10 +593,21 @@ void PipelineCache::InitializeDriverCache() {
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::Save() {
|
||||
void PipelineCache::CheckpointDriverCache() {
|
||||
if (m_driver_cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
m_driver_cache_dirty = true;
|
||||
if (std::chrono::steady_clock::now() >= m_next_driver_cache_save) {
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::Save() {
|
||||
if (m_driver_cache == nullptr || !m_driver_cache_dirty) {
|
||||
return;
|
||||
}
|
||||
m_next_driver_cache_save = std::chrono::steady_clock::now() + DriverCacheSaveInterval;
|
||||
|
||||
size_t size = 0;
|
||||
vk::Result result;
|
||||
@@ -571,16 +650,19 @@ void PipelineCache::Save() {
|
||||
}
|
||||
const bool flushed = !file.IsInvalid() && file.Flush();
|
||||
file.Close();
|
||||
if (prefix_written != prefix.size() || payload_written != payload.size() || !flushed ||
|
||||
!Common::File::RenameFile(temp_path, m_driver_cache_path)) {
|
||||
const bool written = prefix_written == prefix.size() && payload_written == payload.size() && flushed;
|
||||
std::error_code error;
|
||||
if (written) {
|
||||
std::filesystem::rename(temp_path, m_driver_cache_path, error);
|
||||
}
|
||||
if (!written || error) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: failed to write {}",
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
return;
|
||||
}
|
||||
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
|
||||
m_driver_cache = nullptr;
|
||||
m_driver_cache_dirty = false;
|
||||
}
|
||||
|
||||
PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
@@ -596,6 +678,17 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||
}
|
||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
const bool object_primitive_id = context.HasObjectPrimitiveIdOutput();
|
||||
if (object_primitive_id) {
|
||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
||||
!pixel_active || (sh.m_vgtDrawPayloadCntl & 8u) == 0 ||
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList);
|
||||
const auto& target = context.GetRenderTarget(7);
|
||||
EXIT_NOT_IMPLEMENTED(Prospero::ResolveRenderTargetFormat(target.info.format,
|
||||
target.info.channel_type)
|
||||
.buffer_format != Prospero::BufferFormat::k32UInt);
|
||||
}
|
||||
if (mesh_active) {
|
||||
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
|
||||
auto& mesh = vertex_info[0].mesh;
|
||||
@@ -617,6 +710,36 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
pixel_info.object_primitive_id = object_primitive_id;
|
||||
for (uint32_t export_index = 0; export_index < 8u; export_index++) {
|
||||
const auto slot = ShaderPixelExportTarget(pixel_info.target_shader_mask, export_index);
|
||||
if (slot >= 8u || pixel_info.target_output_mode[export_index] == 0u) continue;
|
||||
const auto& rt = context.GetRenderTarget(slot);
|
||||
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
||||
if (rt.base.addr == 0 || target_mask == 0u) continue;
|
||||
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
|
||||
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
||||
const auto mapping = pixel_info.target_export_mapping[export_index];
|
||||
const auto shader_mask = render_target_mask_slot(pixel_info.target_shader_mask, slot);
|
||||
const bool blend = context.GetBlendControl(slot).enable && !rt.info.blend_bypass;
|
||||
if (blend || (mapping.ApplyMask(target_mask) & 7u) != 7u ||
|
||||
(mapping.ApplyMask(shader_mask) & 7u) != 7u ||
|
||||
pixel_info.target_output_mode[export_index] != 9u) {
|
||||
EXIT("unsupported packed UNorm render-target export: slot=%u export=%u mode=%u "
|
||||
"target_mask=0x%x shader_mask=0x%x mapping=0x%02x blend=%u\n",
|
||||
slot, export_index, pixel_info.target_output_mode[export_index], target_mask,
|
||||
shader_mask, mapping.packed, static_cast<uint32_t>(blend));
|
||||
}
|
||||
pixel_info.target_packed_unorm_mask |= 1u << export_index;
|
||||
pixel_info.target_round_mode |= static_cast<uint8_t>(rt.info.round_mode) << export_index;
|
||||
}
|
||||
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
|
||||
} else if (context.GetModeControl().provoking_vtx_last) {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::LastVertex;
|
||||
} else {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::FirstVertex;
|
||||
}
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
pixel_info.dual_source_blending =
|
||||
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
@@ -666,8 +789,8 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u)) * 0.5f;
|
||||
clip.enabled = true;
|
||||
}
|
||||
uint32_t push_data_cursor =
|
||||
mesh_active ? ShaderRecompiler::IR::PushData::MeshDrawDwordCount : 0;
|
||||
uint32_t push_data_cursor = ShaderRecompiler::IR::PushData::GraphicsDrawDwords(
|
||||
mesh_active, object_primitive_id);
|
||||
GraphicsPrograms result;
|
||||
if (pixel_active) {
|
||||
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
|
||||
@@ -732,6 +855,9 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
|
||||
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
|
||||
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
|
||||
if (colors[i].desc.info.guest_format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||
static_params.color_mask[slot] = 1u;
|
||||
}
|
||||
rendering.color_formats[slot] = colors[i].desc.view_info.format;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = colors[i].desc.info.samples;
|
||||
@@ -887,6 +1013,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
|
||||
auto [iter, inserted] = m_graphics_pipelines.emplace(std::move(key), std::move(cached));
|
||||
EXIT_IF(!inserted);
|
||||
CheckpointDriverCache();
|
||||
|
||||
return *iter->second;
|
||||
}
|
||||
@@ -915,6 +1042,7 @@ PipelineCache::GetComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
|
||||
auto [iter, inserted] = m_compute_pipelines.emplace(compute_program.id, std::move(cached));
|
||||
EXIT_IF(!inserted);
|
||||
CheckpointDriverCache();
|
||||
|
||||
return *iter->second;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
@@ -173,11 +174,14 @@ private:
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
std::chrono::steady_clock::time_point m_next_driver_cache_save;
|
||||
bool m_driver_cache_dirty = false;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
|
||||
|
||||
void InitializeDriverCache();
|
||||
void CheckpointDriverCache();
|
||||
};
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
@@ -180,18 +182,41 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
vk::ShaderStageFlagBits stage) {
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto kind = static_cast<ShaderRecompiler::IR::DescriptorBindingKind>(index);
|
||||
descriptor_bindings.push_back(
|
||||
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
|
||||
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding), stage, nullptr});
|
||||
{ShaderRecompiler::IR::NativeBinding(program.stage, kind), NativeDescriptorType(kind),
|
||||
program.bindings.descriptor_counts[index], stage, nullptr});
|
||||
}
|
||||
}
|
||||
|
||||
static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
|
||||
uint32_t descriptor_count = 0;
|
||||
const auto& limits = graphics.GetPhysicalDeviceProperties().limits;
|
||||
uint64_t descriptor_count = 0;
|
||||
uint64_t storage_buffers = 0;
|
||||
std::array<uint64_t, 32> stage_buffers {}, stage_resources {};
|
||||
for (const auto& binding: bindings) {
|
||||
descriptor_count += binding.descriptorCount;
|
||||
const bool storage = binding.descriptorType == vk::DescriptorType::eStorageBuffer;
|
||||
if (storage) storage_buffers += binding.descriptorCount;
|
||||
for (auto stages = static_cast<uint32_t>(binding.stageFlags); stages != 0; stages &= stages - 1u) {
|
||||
const auto stage = std::countr_zero(stages);
|
||||
if (storage) stage_buffers[stage] += binding.descriptorCount;
|
||||
if (binding.descriptorType != vk::DescriptorType::eSampler)
|
||||
stage_resources[stage] += binding.descriptorCount;
|
||||
if (stage_buffers[stage] > limits.maxPerStageDescriptorStorageBuffers ||
|
||||
stage_resources[stage] > limits.maxPerStageResources) {
|
||||
EXIT("native descriptor stage %u exceeds device limits: buffers=%" PRIu64
|
||||
"/%u resources=%" PRIu64 "/%u\n", stage, stage_buffers[stage],
|
||||
limits.maxPerStageDescriptorStorageBuffers, stage_resources[stage], limits.maxPerStageResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (storage_buffers > limits.maxDescriptorSetStorageBuffers) {
|
||||
EXIT("native storage-buffer descriptors exceed device limit: %" PRIu64 "/%u\n",
|
||||
storage_buffers, limits.maxDescriptorSetStorageBuffers);
|
||||
}
|
||||
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
|
||||
|
||||
|
||||
@@ -39,13 +39,7 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
DispatchDirect,
|
||||
DrawIndex,
|
||||
DrawIndexAuto,
|
||||
EopWrite,
|
||||
EopInterrupt,
|
||||
EopWriteBack,
|
||||
EopFlip,
|
||||
EopWriteBackFlip,
|
||||
EopOnlyFlip,
|
||||
DispatchIndirect,
|
||||
DispatchIndirect = 9,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
@@ -156,9 +150,9 @@ public:
|
||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode, bool async_compute);
|
||||
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||
uint32_t mode);
|
||||
uint32_t mode, bool async_compute);
|
||||
|
||||
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
||||
void FindBuffers(std::span<PreparedBindings* const> stages);
|
||||
@@ -186,6 +180,8 @@ private:
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
void ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& target);
|
||||
[[nodiscard]] bool DepthStencilCopy(CommandBuffer& buffer);
|
||||
[[nodiscard]] bool CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit, DrawRenderState& state);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
@@ -200,11 +196,13 @@ private:
|
||||
std::span<PreparedBindings* const> stages = {});
|
||||
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
[[nodiscard]] bool ConsumeMetadataColorOperation(CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
void BindImage(ImageId id, bool storage);
|
||||
void BindRenderTarget(ImageId id);
|
||||
void ResetBindings();
|
||||
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer);
|
||||
void UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer);
|
||||
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode);
|
||||
@@ -213,10 +211,8 @@ private:
|
||||
GraphicsBindings m_graphics_bindings;
|
||||
PreparedBindings m_compute_bindings;
|
||||
std::vector<ImageId> m_bound_images;
|
||||
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
|
||||
friend class CommandProcessor;
|
||||
friend struct RenderExecutorTestAccess;
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
@@ -50,8 +50,8 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
void RenderExecutor::UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
@@ -61,10 +61,9 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& resource = program.info.buffers[i];
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
// A metadata resource that is also read is not proven to be a full overwrite. Execute it
|
||||
// conservatively instead of replacing the dispatch with a coarse full-surface clear.
|
||||
// Reads and transforms of metadata do not establish a clear state.
|
||||
if ((!resource.written || resource.read) && cache.IsMeta(descriptor.Base48())) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,13 +73,10 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
|
||||
if (resource.written) {
|
||||
const auto descriptor =
|
||||
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
if (cache.ClearMeta(descriptor.Base48())) {
|
||||
return true;
|
||||
}
|
||||
(void)cache.ClearMeta(descriptor.Base48());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
@@ -198,8 +194,8 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
|
||||
static void BindSharedMemory(RenderContext& context, ShaderComputeInputInfo& input,
|
||||
PreparedBindings& bindings, uint64_t indirect_args = 0) {
|
||||
if (ShaderRecompiler::IR::FindBinding(input.stage.program->bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory) == nullptr) {
|
||||
if (input.stage.program->bindings.descriptor_counts[
|
||||
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory)] == 0) {
|
||||
return;
|
||||
}
|
||||
auto& cache = context.GetBufferCache();
|
||||
@@ -227,7 +223,7 @@ static void BindSharedMemory(RenderContext& context, ShaderComputeInputInfo& inp
|
||||
|
||||
void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t thread_group_x, uint32_t thread_group_y,
|
||||
uint32_t thread_group_z, uint32_t mode) {
|
||||
uint32_t thread_group_z, uint32_t mode, bool async_compute) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
@@ -282,6 +278,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0;
|
||||
input_info.dispatch_thread_dimensions = use_thread_dimensions;
|
||||
input_info.workgroup_counts[0] = thread_group_x;
|
||||
@@ -306,12 +303,14 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (resources.specialization_reads.empty() &&
|
||||
(TryConsumeComputeMetaClear(input_info, buffer) ||
|
||||
TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode))) {
|
||||
ResetBindings();
|
||||
return;
|
||||
if (resources.specialization_reads.empty()) {
|
||||
// Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes.
|
||||
UpdateComputeMetaClears(input_info, buffer);
|
||||
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_thread_dimensions) {
|
||||
@@ -376,11 +375,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
});
|
||||
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
|
||||
LOGF("GraphicsRenderDispatchDirect: frame=%u shader=0x%016" PRIx64
|
||||
" hash=0x%016" PRIx64 " tick=%" PRIu64
|
||||
" hash=0x%016" PRIx64 " tick=%" PRIu64 " completed_tick=%" PRIu64
|
||||
" groups=%ux%ux%u mode=0x%08" PRIx32 " local=%ux%ux%u "
|
||||
"buffers=%zu textures=%zu sampled=%zu storage=%zu samplers=%zu push=%u\n",
|
||||
frame_num, sh_ctx.GetCs().cs_regs.data_addr, program.shader_hash,
|
||||
m_context.GetCommandScheduler().CurrentTick(),
|
||||
m_context.GetCommandScheduler().GetMasterSemaphore().KnownGpuTick(),
|
||||
thread_group_x, thread_group_y, thread_group_z, mode,
|
||||
input_info.threads_num[0], input_info.threads_num[1],
|
||||
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
|
||||
@@ -438,7 +438,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t args_addr, uint32_t mode) {
|
||||
uint64_t args_addr, uint32_t mode, bool async_compute) {
|
||||
EXIT_IF(buffer.IsInvalid() || args_addr == 0 || (args_addr & 3u) != 0 ||
|
||||
(mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0);
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
@@ -446,11 +446,19 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
static_cast<uint32_t>(args_addr), static_cast<uint32_t>(args_addr >> 32u),
|
||||
0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr);
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
vk::DispatchIndirectCommand args {};
|
||||
if (!m_context.GetBufferCache().HasGpuDirtyBytes(args_addr, sizeof(args)) &&
|
||||
LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args)) &&
|
||||
(args.x == 0 || args.y == 0 || args.z == 0)) {
|
||||
return;
|
||||
}
|
||||
const auto& cs_regs = buffer.GetShaders().GetCs();
|
||||
if (cs_regs.cs_regs.data_addr == 0) {
|
||||
return;
|
||||
}
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
input_info.indirect_args_address = args_addr;
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
buffer.EndRendering();
|
||||
|
||||
@@ -312,9 +312,9 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
|
||||
}
|
||||
}
|
||||
|
||||
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
const RenderDepthInfo& depth, const RenderState& rendering) {
|
||||
static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState& dynamic_state,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
const RenderDepthInfo& depth, const RenderState& rendering) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
@@ -358,8 +358,8 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
scissor.extent = {0, 0};
|
||||
}
|
||||
}
|
||||
vk_buffer.setViewportWithCount(viewport_count, viewports.data());
|
||||
vk_buffer.setScissorWithCount(viewport_count, scissors.data());
|
||||
dynamic_state.SetViewports(std::span {viewports}.first(viewport_count));
|
||||
dynamic_state.SetScissors(std::span {scissors}.first(viewport_count));
|
||||
|
||||
float line_width = ctx.GetLineWidth();
|
||||
if (line_width != 1.0f) {
|
||||
@@ -372,17 +372,17 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
}
|
||||
line_width = 1.0f;
|
||||
}
|
||||
vk_buffer.setLineWidth(line_width);
|
||||
dynamic_state.SetLineWidth(line_width);
|
||||
const auto& blend = ctx.GetBlendColor();
|
||||
const std::array blend_constants {blend.red, blend.green, blend.blue, blend.alpha};
|
||||
vk_buffer.setBlendConstants(blend_constants.data());
|
||||
vk_buffer.setDepthTestEnable(depth.depth_test_enable ? VK_TRUE : VK_FALSE);
|
||||
vk_buffer.setDepthWriteEnable(depth.depth_write_enable ? VK_TRUE : VK_FALSE);
|
||||
vk_buffer.setDepthCompareOp(depth.depth_compare_op);
|
||||
dynamic_state.SetBlendConstants(blend_constants);
|
||||
dynamic_state.SetDepthTestEnabled(depth.depth_test_enable);
|
||||
dynamic_state.SetDepthWriteEnabled(depth.depth_write_enable);
|
||||
dynamic_state.SetDepthCompareOp(depth.depth_compare_op);
|
||||
#if !defined(__APPLE__)
|
||||
vk_buffer.setDepthBoundsTestEnable(depth.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
|
||||
dynamic_state.SetDepthBoundsTestEnabled(depth.depth_bounds_test_enable);
|
||||
if (depth.depth_bounds_test_enable) {
|
||||
vk_buffer.setDepthBounds(ctx.GetDepthBoundsMin(), ctx.GetDepthBoundsMax());
|
||||
dynamic_state.SetDepthBounds(ctx.GetDepthBoundsMin(), ctx.GetDepthBoundsMax());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -391,7 +391,7 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
|
||||
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
|
||||
const bool depth_bias_enable = use_front || use_back;
|
||||
vk_buffer.setDepthBiasEnable(depth_bias_enable ? VK_TRUE : VK_FALSE);
|
||||
dynamic_state.SetDepthBiasEnabled(depth_bias_enable);
|
||||
if (depth_bias_enable) {
|
||||
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
|
||||
const float guest_constant_factor =
|
||||
@@ -400,19 +400,18 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
guest_constant_factor, poly_offset, depth.desc.view_info.format);
|
||||
const float slope_factor =
|
||||
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
||||
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||
}
|
||||
|
||||
vk_buffer.setStencilTestEnable(depth.stencil_test_enable ? VK_TRUE : VK_FALSE);
|
||||
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
||||
if (depth.stencil_test_enable) {
|
||||
const auto set_stencil = [&](vk::StencilFaceFlagBits face, const vk::StencilOpState& state) {
|
||||
vk_buffer.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp, state.compareOp);
|
||||
vk_buffer.setStencilCompareMask(face, state.compareMask);
|
||||
vk_buffer.setStencilWriteMask(face, state.writeMask);
|
||||
vk_buffer.setStencilReference(face, state.reference);
|
||||
};
|
||||
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
|
||||
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
|
||||
const auto& front = depth.stencil_front;
|
||||
const auto& back = depth.stencil_back;
|
||||
dynamic_state.SetStencilOps({front.failOp, front.passOp, front.depthFailOp, front.compareOp},
|
||||
{back.failOp, back.passOp, back.depthFailOp, back.compareOp});
|
||||
dynamic_state.SetStencilCompareMasks(front.compareMask, back.compareMask);
|
||||
dynamic_state.SetStencilWriteMasks(front.writeMask, back.writeMask);
|
||||
dynamic_state.SetStencilReferences(front.reference, back.reference);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
@@ -423,9 +422,7 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
for (uint32_t slot = 0; slot < rendering.num_color_attachments; slot++) {
|
||||
enable[slot] = rendering.color_attachments[slot].image_view != nullptr;
|
||||
}
|
||||
if (rendering.num_color_attachments != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(rendering.num_color_attachments, enable);
|
||||
}
|
||||
dynamic_state.SetColorWriteEnables(std::span {enable}.first(rendering.num_color_attachments));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -588,7 +585,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = 0;
|
||||
uint32_t output_mask = ctx.HasObjectPrimitiveIdOutput() ? 1u << 7u : 0;
|
||||
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
|
||||
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
|
||||
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
|
||||
@@ -611,16 +608,19 @@ enum class CbColorMode : uint8_t {
|
||||
DccDecompress = 6,
|
||||
};
|
||||
|
||||
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
|
||||
bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto mode = ctx.GetColorControl().mode;
|
||||
// These special modes run color-buffer metadata or decompression operations. The shader is a
|
||||
// vehicle for that operation, and its exported color must not be applied as a normal draw.
|
||||
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
|
||||
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
|
||||
// future CMask/FMask support can consume its state through the same TextureCache path.
|
||||
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
|
||||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
|
||||
if (mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear)) {
|
||||
// FCE can precede any ordinary draw. Discover the target to materialize its native
|
||||
// clear metadata before the expanded image is sampled.
|
||||
RenderColorInfo target {};
|
||||
ResolveRenderColorTarget(buffer, target, render_target_slice_offset, 0, true);
|
||||
return true;
|
||||
}
|
||||
// Metadata/decompression shaders must not export colors into the expanded image.
|
||||
return mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
|
||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||
}
|
||||
|
||||
@@ -905,7 +905,8 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
||||
uint32_t mrt_mask = 0;
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt ||
|
||||
output.kind == ShaderRecompiler::IR::StageOutputKind::ObjectPrimitiveId) {
|
||||
mrt_mask |= 1u << output.location;
|
||||
}
|
||||
}
|
||||
@@ -1032,12 +1033,71 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit, DrawRenderState& state) {
|
||||
if (!buffer.GetRegisters().GetRenderControl().copy_depth_to_color) {
|
||||
return false;
|
||||
}
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
auto& depth = state.depth_info;
|
||||
auto& color = state.color_info[0];
|
||||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
|
||||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
|
||||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
|
||||
emit.first_vertex != 0 || emit.first_instance != 0 ||
|
||||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
|
||||
color.desc.info.samples != 1 ||
|
||||
depth.desc.info.extent.width != color.Extent().width ||
|
||||
depth.desc.info.extent.height != color.Extent().height ||
|
||||
depth.desc.view_info.layer_count != 1 ||
|
||||
color.desc.view_info.layer_count != 1 ||
|
||||
hw.GetBlendControl(0).enable || state.ps_input_info.ps_pixel_kill_enable ||
|
||||
state.ps_input_info.ps_depth_export_enable ||
|
||||
state.ps_input_info.ps_sample_mask_export_enable);
|
||||
vk::ImageAspectFlags feedback;
|
||||
const auto rendering = AcquireRenderTargets(buffer, &color, 1, depth, feedback);
|
||||
if (depth.depth_load_clear_enable) {
|
||||
m_context.GetCommandScheduler().BeginRendering(rendering);
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
auto& source = cache.GetImage(depth.image_id);
|
||||
auto& destination = cache.GetImage(color.image_id);
|
||||
EXIT_NOT_IMPLEMENTED((source.backing.format != vk::Format::eD32Sfloat &&
|
||||
source.backing.format != vk::Format::eD32SfloatS8Uint) ||
|
||||
destination.backing.format != vk::Format::eR32Sfloat);
|
||||
const auto rect = calc_final_scissor(hw.GetScreenViewport(), hw.GetScanModeControl(),
|
||||
{rendering.width, rendering.height}, 0);
|
||||
if (rect.right > rect.left && rect.bottom > rect.top) {
|
||||
const vk::ImageCopy region {
|
||||
{vk::ImageAspectFlagBits::eDepth, depth.desc.view_info.base_level,
|
||||
depth.desc.view_info.base_layer, 1}, {rect.left, rect.top, 0},
|
||||
{vk::ImageAspectFlagBits::eColor, color.desc.view_info.base_level,
|
||||
color.desc.view_info.base_layer, 1}, {rect.left, rect.top, 0},
|
||||
{static_cast<uint32_t>(rect.right - rect.left),
|
||||
static_cast<uint32_t>(rect.bottom - rect.top), 1}};
|
||||
m_context.GetCommandScheduler().EndRendering();
|
||||
const auto command = buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
|
||||
{}, command);
|
||||
destination.Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
destination.backing.image, vk::ImageLayout::eTransferDstOptimal,
|
||||
1, ®ion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
if (CopyDepthToColor(buffer, draw, emit, state)) {
|
||||
return;
|
||||
}
|
||||
const auto vertex_stages =
|
||||
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
|
||||
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
|
||||
@@ -1123,27 +1183,36 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
|
||||
if (mesh_active) {
|
||||
const uint32_t draw_data[] {
|
||||
draw.index_count,
|
||||
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
static_assert(std::size(draw_data) == ShaderRecompiler::IR::PushData::MeshDrawDwordCount);
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout,
|
||||
vk::ShaderStageFlagBits::eMeshEXT |
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0, sizeof(draw_data), draw_data);
|
||||
} else {
|
||||
if (!mesh_active) {
|
||||
CommitIndexBuffer(vk_buffer, index_binding);
|
||||
}
|
||||
|
||||
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
|
||||
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
|
||||
vk_buffer.setAttachmentFeedbackLoopEnableEXT(feedback_aspects);
|
||||
const auto draw_dwords = ShaderRecompiler::IR::PushData::GraphicsDrawDwords(
|
||||
mesh_active, state.ps_input_info.object_primitive_id);
|
||||
if (draw_dwords != 0) {
|
||||
std::array<uint32_t, ShaderRecompiler::IR::PushData::MeshDrawDwordCount> draw_data {};
|
||||
const auto draw_stages = vk::ShaderStageFlagBits::eFragment |
|
||||
(mesh_active ? vk::ShaderStageFlagBits::eMeshEXT
|
||||
: vk::ShaderStageFlagBits::eVertex);
|
||||
if (mesh_active) {
|
||||
draw_data = {
|
||||
draw.index_count,
|
||||
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
} else {
|
||||
draw_data[0] = buffer.GetUserConfig().GetObjectId();
|
||||
}
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout, draw_stages, 0,
|
||||
draw_dwords * sizeof(uint32_t), draw_data.data());
|
||||
}
|
||||
|
||||
auto& dynamic_state = m_context.GetCommandScheduler().GetDynamicState();
|
||||
UpdateGraphicsDynamicState(buffer, dynamic_state, vertex_stages.back(), state.depth_info,
|
||||
rendering);
|
||||
dynamic_state.SetAttachmentFeedbackLoopAspects(feedback_aspects);
|
||||
dynamic_state.Commit(m_context.GetGraphics(), vk_buffer);
|
||||
|
||||
LogDrawPhase(draw.Name(), "BeginRendering");
|
||||
if (!draw.IsIndexed()) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
|
||||
@@ -1200,7 +1269,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -1284,10 +1354,10 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
// Native mesh prologs consume the offsets written to the packet's user SGPRs.
|
||||
const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
emit.vertex_offset = mesh_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = mesh_indirect ? 0 : instance_offset;
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
@@ -1313,7 +1383,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -1375,10 +1446,10 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
emit.first_vertex = mesh_indirect ? 0 :
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.first_vertex = native_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = mesh_indirect ? 0 : instance_offset;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#include "graphics/host_gpu/renderer/sync.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -53,109 +49,6 @@ uint64_t ReadReferenceClock() {
|
||||
return value;
|
||||
}
|
||||
|
||||
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
|
||||
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
|
||||
|
||||
static CommandBufferDebugOp DebugOperation(EndOfPipeWriteAction action) {
|
||||
switch (action) {
|
||||
case EndOfPipeWriteAction::Write: return CommandBufferDebugOp::EopWrite;
|
||||
case EndOfPipeWriteAction::WriteBack:
|
||||
case EndOfPipeWriteAction::InterruptWriteBack: return CommandBufferDebugOp::EopWriteBack;
|
||||
case EndOfPipeWriteAction::Interrupt: return CommandBufferDebugOp::EopInterrupt;
|
||||
}
|
||||
EXIT("unsupported end-of-pipe write action\n");
|
||||
return CommandBufferDebugOp::Unknown;
|
||||
}
|
||||
|
||||
static bool TriggersInterrupt(EndOfPipeWriteAction action) {
|
||||
return action == EndOfPipeWriteAction::Interrupt ||
|
||||
action == EndOfPipeWriteAction::InterruptWriteBack;
|
||||
}
|
||||
|
||||
static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint64_t destination,
|
||||
uint64_t value, EndOfPipeWriteSize size,
|
||||
EndOfPipeWriteAction action, int interrupt_event_id = 0,
|
||||
uint32_t context_id = 0) {
|
||||
EXIT_IF(destination == 0);
|
||||
(void)buffer.Handle();
|
||||
|
||||
const auto width = static_cast<uint32_t>(size);
|
||||
const auto value_low = static_cast<uint32_t>(value);
|
||||
const auto value_high = static_cast<uint32_t>(value >> 32u);
|
||||
const auto operation = static_cast<uint32_t>(DebugOperation(action));
|
||||
if (TriggersInterrupt(action)) {
|
||||
buffer.SetDebugInfo(operation, submit_id, width, context_id, value_low, value_high,
|
||||
destination);
|
||||
TriggerEopEventAtEndOfPipe(buffer, interrupt_event_id, context_id);
|
||||
} else {
|
||||
buffer.SetDebugInfo(operation, submit_id, width, value_low, value_high, 0, destination);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Write);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t dw_offset, uint32_t dw_num) {
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id,
|
||||
dw_offset, dw_num, 0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
|
||||
uint64_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Write);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::WriteBack);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::WriteBack);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::InterruptWriteBack,
|
||||
event_id, context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::InterruptWriteBack,
|
||||
event_id, context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Interrupt, event_id,
|
||||
context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, event_id,
|
||||
context_id);
|
||||
}
|
||||
|
||||
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg) {
|
||||
for (;;) {
|
||||
@@ -176,65 +69,6 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
|
||||
}
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id,
|
||||
int event_id) {
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBackFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
|
||||
renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id) {
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg, uint64_t request_id) {
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopOnlyFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), 0, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
}
|
||||
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id) {
|
||||
(void)buffer.Handle();
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, event_id, context_id] { renderer.TriggerInterrupt(event_id, context_id); });
|
||||
}
|
||||
|
||||
static void InterruptEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
|
||||
EXIT_IF(event == nullptr);
|
||||
event->triggered = false;
|
||||
|
||||
@@ -17,44 +17,8 @@ namespace Sync {
|
||||
uint64_t& value);
|
||||
[[nodiscard]] uint64_t ReadReferenceClock();
|
||||
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id);
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
|
||||
uint64_t value);
|
||||
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t dw_offset, uint32_t dw_num);
|
||||
void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value);
|
||||
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value);
|
||||
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
|
||||
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg);
|
||||
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg, uint64_t request_id);
|
||||
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id);
|
||||
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id,
|
||||
int event_id);
|
||||
|
||||
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
|
||||
void* udata);
|
||||
int DeleteEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
|
||||
@@ -6,20 +6,13 @@ layout(local_size_x = 64) in;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
||||
|
||||
layout(push_constant, std430) uniform Push {
|
||||
layout(push_constant, std430) uniform Conversion {
|
||||
uint src_base;
|
||||
uint dst_base;
|
||||
uint width;
|
||||
uint height;
|
||||
uint depth;
|
||||
uint surface_z;
|
||||
uint pitch_bytes;
|
||||
uint slice_bytes;
|
||||
uint blocks_per_row;
|
||||
uint blocks_per_slice;
|
||||
uint tail_x;
|
||||
uint tail_y;
|
||||
uint tail;
|
||||
uint source_pitch;
|
||||
uint target_pitch;
|
||||
uint color_transform;
|
||||
uint to_host;
|
||||
} params;
|
||||
|
||||
@@ -1,66 +1,84 @@
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input8 { uint8_t data[]; } input_buffer8;
|
||||
layout(set = 0, binding = 1, std430) buffer Output8 { uint8_t data[]; } output_buffer8;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input16 { uint16_t data[]; } input_buffer16;
|
||||
layout(set = 0, binding = 1, std430) buffer Output16 { uint16_t data[]; } output_buffer16;
|
||||
|
||||
layout(set = 0, binding = 2, std140) uniform Push {
|
||||
struct Mip {
|
||||
uint src_base;
|
||||
uint dst_base;
|
||||
uint width;
|
||||
uint height;
|
||||
uint depth;
|
||||
uint surface_z;
|
||||
uint pitch_bytes;
|
||||
uint slice_bytes;
|
||||
uint tiled_slice_bytes;
|
||||
uint blocks_per_row;
|
||||
uint blocks_per_slice;
|
||||
uint surface_z;
|
||||
uint tail_x;
|
||||
uint tail_y;
|
||||
uint tail;
|
||||
uint num_texels;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 2, std140) uniform TilingInfo {
|
||||
uint num_texels;
|
||||
uint color_transform;
|
||||
} params;
|
||||
Mip mips[16];
|
||||
} info;
|
||||
|
||||
#include "gpu_tiler_color.inc"
|
||||
|
||||
void copy_element(uint src, uint dst) {
|
||||
if ((ELEMENT_BYTES == 4u || ELEMENT_BYTES == 8u) && params.color_transform != 0u) {
|
||||
if ((ELEMENT_BYTES == 4u || ELEMENT_BYTES == 8u) && info.color_transform != 0u) {
|
||||
uvec2 value = uvec2(input_buffer.data[src >> 2], 0u);
|
||||
if (ELEMENT_BYTES == 8u) value.y = input_buffer.data[(src >> 2) + 1u];
|
||||
value = transform_color(value, params.color_transform, TILE == 0u);
|
||||
value = transform_color(value, info.color_transform, TILE == 0u);
|
||||
output_buffer.data[dst >> 2] = value.x;
|
||||
if (ELEMENT_BYTES == 8u) output_buffer.data[(dst >> 2) + 1u] = value.y;
|
||||
} else if (ELEMENT_BYTES >= 4) {
|
||||
for (uint i = 0; i < ELEMENT_BYTES; i += 4) {
|
||||
output_buffer.data[(dst + i) >> 2] = input_buffer.data[(src + i) >> 2];
|
||||
}
|
||||
} else if (ELEMENT_BYTES == 2) {
|
||||
output_buffer16.data[dst >> 1] = input_buffer16.data[src >> 1];
|
||||
} else {
|
||||
uint mask = ELEMENT_BYTES == 1 ? 0xffu : 0xffffu;
|
||||
uint value = (input_buffer.data[src >> 2] >> ((src & 3u) * 8u)) & mask;
|
||||
uint shift = (dst & 3u) * 8u;
|
||||
atomicAnd(output_buffer.data[dst >> 2], ~(mask << shift));
|
||||
atomicOr(output_buffer.data[dst >> 2], value << shift);
|
||||
output_buffer8.data[dst] = input_buffer8.data[src];
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec3 p = gl_GlobalInvocationID;
|
||||
if (p.x >= params.width || p.y >= params.height || p.z >= params.depth) {
|
||||
uint group = gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x;
|
||||
// Reject padding groups before multiplying: the active range may approach 4 GiB.
|
||||
if (group > (info.num_texels - 1u) / 64u) {
|
||||
return;
|
||||
}
|
||||
uint texel = group * 64u + gl_LocalInvocationID.x;
|
||||
if (texel >= info.num_texels) {
|
||||
return;
|
||||
}
|
||||
uint mip = 0u;
|
||||
uint mip_texels = info.mips[mip].num_texels;
|
||||
while (texel >= mip_texels) {
|
||||
texel -= mip_texels;
|
||||
mip_texels = info.mips[++mip].num_texels;
|
||||
}
|
||||
Mip params = info.mips[mip];
|
||||
uint row = texel / params.width;
|
||||
uint z = row / params.height;
|
||||
uvec3 p = uvec3(texel - row * params.width, row - z * params.height, z);
|
||||
|
||||
uvec3 extent = block_extent();
|
||||
uvec3 swizzle = p;
|
||||
uvec3 block = uvec3(0);
|
||||
if (params.tail != 0) {
|
||||
swizzle.xy += uvec2(params.tail_x, params.tail_y);
|
||||
} else {
|
||||
block = p / extent;
|
||||
}
|
||||
uvec3 block = p / extent;
|
||||
uvec3 swizzle = p + uvec3(params.tail_x, params.tail_y, params.surface_z);
|
||||
|
||||
uint block_index = block.z * params.blocks_per_slice +
|
||||
block.y * params.blocks_per_row + block.x;
|
||||
uint tiled = block_index * BLOCK_BYTES +
|
||||
block_offset(uvec3(swizzle.xy, swizzle.z + params.surface_z));
|
||||
uint block_index = block.y * params.blocks_per_row + block.x;
|
||||
uint tiled = block.z * params.tiled_slice_bytes + block_index * BLOCK_BYTES +
|
||||
block_offset(swizzle);
|
||||
uint linear = p.z * params.slice_bytes + p.y * params.pitch_bytes +
|
||||
p.x * ELEMENT_BYTES;
|
||||
if (TILE == 0) {
|
||||
|
||||
@@ -6,20 +6,15 @@ layout(local_size_x = 64) in;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
||||
|
||||
layout(push_constant, std430) uniform Push {
|
||||
layout(push_constant, std430) uniform Conversion {
|
||||
uint src_base;
|
||||
uint dst_base;
|
||||
uint width;
|
||||
uint height;
|
||||
uint depth;
|
||||
uint surface_z;
|
||||
uint pitch_bytes;
|
||||
uint slice_bytes;
|
||||
uint blocks_per_row;
|
||||
uint blocks_per_slice;
|
||||
uint tail_x;
|
||||
uint tail_y;
|
||||
uint tail;
|
||||
uint source_pitch;
|
||||
uint target_pitch;
|
||||
uint color_transform;
|
||||
uint to_host;
|
||||
} params;
|
||||
|
||||
uint decode(uint value) {
|
||||
@@ -35,8 +30,8 @@ void main() {
|
||||
if (x >= params.width || y >= params.height) {
|
||||
return;
|
||||
}
|
||||
uint src = params.src_base + y * params.pitch_bytes + x * 4u;
|
||||
uint dst = params.dst_base + y * params.slice_bytes + x * 2u;
|
||||
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
||||
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
||||
uint shift = (dst & 2u) * 8u;
|
||||
uint mask = 0xffffu << shift;
|
||||
uint word = dst >> 2u;
|
||||
|
||||
@@ -6,20 +6,15 @@ layout(local_size_x = 64) in;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
||||
|
||||
layout(push_constant, std430) uniform Push {
|
||||
layout(push_constant, std430) uniform Conversion {
|
||||
uint src_base;
|
||||
uint dst_base;
|
||||
uint width;
|
||||
uint height;
|
||||
uint depth;
|
||||
uint surface_z;
|
||||
uint pitch_bytes;
|
||||
uint slice_bytes;
|
||||
uint blocks_per_row;
|
||||
uint blocks_per_slice;
|
||||
uint tail_x;
|
||||
uint tail_y;
|
||||
uint tail;
|
||||
uint source_pitch;
|
||||
uint target_pitch;
|
||||
uint color_transform;
|
||||
uint to_host;
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
@@ -28,12 +23,12 @@ void main() {
|
||||
if (x >= params.width || y >= params.height) {
|
||||
return;
|
||||
}
|
||||
uint src = params.src_base + y * params.pitch_bytes + x * 2u;
|
||||
uint src = params.src_base + y * params.source_pitch + x * 2u;
|
||||
uint packed = input_buffer.data[src >> 2u];
|
||||
uint value = (packed >> ((src & 2u) * 8u)) & 0xffffu;
|
||||
uint encoded = D32 != 0u
|
||||
? floatBitsToUint(float(value) / 65535.0)
|
||||
: value * 256u + (value + 128u) / 257u;
|
||||
uint dst = params.dst_base + y * params.slice_bytes + x * 4u;
|
||||
uint dst = params.dst_base + y * params.target_pitch + x * 4u;
|
||||
output_buffer.data[dst >> 2u] = encoded;
|
||||
}
|
||||
@@ -1,15 +1,53 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
vec3 SdrToHdr10(vec3 rgb, bool source_linear) {
|
||||
rgb = clamp(rgb, 0.0, 1.0);
|
||||
vec3 linear = source_linear ? rgb :
|
||||
mix(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3(2.4)),
|
||||
greaterThan(rgb, vec3(0.04045)));
|
||||
// Convert primaries and scale 250-nit reference white relative to 10,000 nits.
|
||||
const mat3 to_rec2020 = (250.0 / 10000.0) *
|
||||
mat3(0.627404, 0.069097, 0.016391,
|
||||
0.329283, 0.919540, 0.088013,
|
||||
0.043313, 0.011362, 0.895595);
|
||||
vec3 powered = pow(to_rec2020 * linear, vec3(2610.0 / 16384.0));
|
||||
return pow((3424.0 / 4096.0 + (2413.0 / 128.0) * powered) /
|
||||
(1.0 + (2392.0 / 128.0) * powered), vec3(2523.0 / 32.0));
|
||||
}
|
||||
|
||||
#ifdef IMGUI_HDR
|
||||
layout(set = 0, binding = 0) uniform texture2D image;
|
||||
layout(set = 1, binding = 0) uniform sampler image_sampler;
|
||||
layout(location = 0) in struct { vec4 Color; vec2 UV; } In;
|
||||
|
||||
void main() {
|
||||
vec4 sampled = In.Color * texture(sampler2D(image, image_sampler), In.UV);
|
||||
color = vec4(SdrToHdr10(sampled.rgb, false), sampled.a);
|
||||
}
|
||||
#else
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(binding = 0) uniform sampler2D source;
|
||||
layout(push_constant) uniform AlphaControl {
|
||||
uint premultiplied;
|
||||
uint flags; // bit 0: premultiplied, bit 1: HDR target, bit 2: PQ source, bit 3: linear source
|
||||
};
|
||||
|
||||
void main() {
|
||||
color = texture(source, uv);
|
||||
if (premultiplied == 0u) {
|
||||
color.rgb *= color.a;
|
||||
const uint HDR_TARGET = 2u;
|
||||
const uint PQ_SOURCE = 4u;
|
||||
vec4 sampled = texture(source, uv);
|
||||
bool premultiplied = (flags & 1u) != 0u;
|
||||
bool convert_to_hdr = (flags & (HDR_TARGET | PQ_SOURCE)) == HDR_TARGET;
|
||||
if (convert_to_hdr && premultiplied) {
|
||||
sampled.rgb = sampled.a > 0.0 ? sampled.rgb / sampled.a : vec3(0.0);
|
||||
}
|
||||
if (convert_to_hdr) {
|
||||
sampled.rgb = SdrToHdr10(sampled.rgb, (flags & 8u) != 0u);
|
||||
}
|
||||
if (!premultiplied || convert_to_hdr) {
|
||||
sampled.rgb *= sampled.a;
|
||||
}
|
||||
color = sampled;
|
||||
}
|
||||
#endif
|
||||
@@ -28,6 +28,14 @@ vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
|
||||
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
||||
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
|
||||
|
||||
[[nodiscard]] constexpr bool IsHdrSwapchainFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
case vk::Format::eA2R10G10B10UnormPack32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Handle, typename... Args>
|
||||
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
|
||||
Args&&... args) {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "graphics/presentation/ddsImage.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
DdsImage::~DdsImage() {
|
||||
if (m_descriptor != VK_NULL_HANDLE) {
|
||||
ImGui_ImplVulkan_RemoveTexture(m_descriptor);
|
||||
}
|
||||
m_graphics.device.destroyImageView(m_view, nullptr);
|
||||
if (m_image.image != nullptr) {
|
||||
m_graphics.DeleteImage(m_image);
|
||||
}
|
||||
if (m_staging != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(m_graphics.allocator, m_staging, m_staging_allocation);
|
||||
}
|
||||
}
|
||||
|
||||
bool DdsImage::Load(const std::filesystem::path& path) {
|
||||
EXIT_IF(m_image.image != nullptr || m_staging != VK_NULL_HANDLE);
|
||||
Common::File file(path, Common::File::Mode::Read);
|
||||
if (file.IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint32_t, 37> header {};
|
||||
uint32_t read = 0;
|
||||
file.Read(header.data(), sizeof(header), &read);
|
||||
const auto width = header[4];
|
||||
const auto height = header[3];
|
||||
if (read != sizeof(header) || header[0] != 0x20534444 || header[1] != 124 || header[19] != 32 ||
|
||||
header[20] != 4 || header[21] != 0x30315844 || header[32] != 98 || header[33] != 3 ||
|
||||
header[34] != 0 || header[35] != 1 || header[6] != 0 || header[7] != 1 || header[28] != 0 ||
|
||||
width == 0 || height == 0 || width > 3840 || height > 2160) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t size = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (file.Size() != sizeof(header) + size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr auto format = vk::Format::eBc7UnormBlock;
|
||||
constexpr auto features = vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferDst;
|
||||
if ((m_graphics.GetFormatProperties(format).optimalTilingFeatures & features) != features) {
|
||||
LOGF("[Splash] host does not support BC7 textures\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::BufferCreateInfo buffer {};
|
||||
buffer.size = size;
|
||||
buffer.usage = vk::BufferUsageFlagBits::eTransferSrc;
|
||||
VmaAllocationCreateInfo allocation {};
|
||||
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
allocation.flags =
|
||||
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
VmaAllocationInfo mapped {};
|
||||
if (vmaCreateBuffer(m_graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer),
|
||||
&allocation, &m_staging, &m_staging_allocation, &mapped) != VK_SUCCESS) {
|
||||
LOGF("[Splash] could not allocate DDS upload buffer\n");
|
||||
return false;
|
||||
}
|
||||
file.Read(mapped.pMappedData, size, &read);
|
||||
if (read != size) {
|
||||
return false;
|
||||
}
|
||||
RequireVulkanSuccess(static_cast<vk::Result>(vmaFlushAllocation(m_graphics.allocator,
|
||||
m_staging_allocation, 0, size)),
|
||||
"flush splash image upload");
|
||||
|
||||
vk::ImageCreateInfo image {};
|
||||
image.imageType = vk::ImageType::e2D;
|
||||
image.extent = {width, height, 1};
|
||||
image.mipLevels = 1;
|
||||
image.arrayLayers = 1;
|
||||
image.format = format;
|
||||
image.tiling = vk::ImageTiling::eOptimal;
|
||||
image.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled;
|
||||
image.samples = vk::SampleCountFlagBits::e1;
|
||||
if (!m_graphics.CreateImage(image, m_image)) {
|
||||
LOGF("[Splash] could not allocate DDS image\n");
|
||||
return false;
|
||||
}
|
||||
vk::ImageViewCreateInfo view {};
|
||||
view.image = m_image.image;
|
||||
view.viewType = vk::ImageViewType::e2D;
|
||||
view.format = format;
|
||||
view.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
RequireVulkanSuccess(m_graphics.device.createImageView(&view, nullptr, &m_view),
|
||||
"create splash image view");
|
||||
m_descriptor = ImGui_ImplVulkan_AddTexture(static_cast<VkImageView>(m_view),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DdsImage::Upload(vk::CommandBuffer command) {
|
||||
if (m_image.state.layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||
return;
|
||||
}
|
||||
vk::ImageMemoryBarrier barrier {};
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.oldLayout = vk::ImageLayout::eUndefined;
|
||||
barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = m_image.image;
|
||||
barrier.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 0, nullptr, 1,
|
||||
&barrier);
|
||||
vk::BufferImageCopy copy {};
|
||||
copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
|
||||
copy.imageExtent = m_image.extent;
|
||||
command.copyBufferToImage(m_staging, m_image.image, vk::ImageLayout::eTransferDstOptimal, 1,
|
||||
©);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eFragmentShader, {}, 0, nullptr, 0, nullptr,
|
||||
1, &barrier);
|
||||
m_image.state.layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
|
||||
ImTextureRef DdsImage::Texture() const {
|
||||
return ImTextureRef {static_cast<ImTextureID>(reinterpret_cast<uintptr_t>(m_descriptor))};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class DdsImage final {
|
||||
public:
|
||||
explicit DdsImage(GraphicContext& graphics): m_graphics(graphics) {}
|
||||
~DdsImage();
|
||||
KYTY_CLASS_NO_COPY(DdsImage);
|
||||
|
||||
bool Load(const std::filesystem::path& path);
|
||||
void Upload(vk::CommandBuffer command);
|
||||
[[nodiscard]] ImTextureRef Texture() const;
|
||||
|
||||
private:
|
||||
GraphicContext& m_graphics;
|
||||
VulkanImage m_image;
|
||||
vk::ImageView m_view = nullptr;
|
||||
VkDescriptorSet m_descriptor = VK_NULL_HANDLE;
|
||||
VkBuffer m_staging = VK_NULL_HANDLE;
|
||||
VmaAllocation m_staging_allocation = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
@@ -26,11 +26,13 @@ public:
|
||||
~Presenter();
|
||||
KYTY_CLASS_NO_COPY(Presenter);
|
||||
|
||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
|
||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info,
|
||||
bool hdr = false);
|
||||
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] bool PresentLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] bool IsHdrSupported() const noexcept;
|
||||
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
|
||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||
void Present(Frame& frame);
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "gpu_blit_shaders/gpu_imgui_hdr_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/presentation/ddsImage.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
@@ -13,6 +17,7 @@
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/ime.h"
|
||||
#include "libs/imeDialog.h"
|
||||
#include "loader/systemContent.h"
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -23,6 +28,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -37,6 +43,8 @@ namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
namespace CoreIme = Libs::Ime;
|
||||
namespace DialogIme = Libs::Dialog::ImeDialog;
|
||||
namespace SystemDialog = Libs::Dialog::SystemDialog;
|
||||
@@ -194,6 +202,10 @@ struct TrophyNotification {
|
||||
std::mutex g_trophy_notification_mutex;
|
||||
std::deque<TrophyNotification> g_trophy_notifications;
|
||||
std::atomic<bool> g_trophy_notification_active {false};
|
||||
std::array<std::filesystem::path, 2> g_splash_paths;
|
||||
std::atomic<bool> g_splash_active {false};
|
||||
std::atomic<Clock::duration::rep> g_splash_hidden_at {0};
|
||||
bool g_splash_fast = false;
|
||||
|
||||
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
|
||||
std::vector<std::string_view> lines;
|
||||
@@ -464,6 +476,29 @@ void CheckVulkanResult(VkResult result) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeSplashScreen(const std::filesystem::path& sce_sys) {
|
||||
const auto language = Config::GetConsoleLanguage();
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
auto& path = g_splash_paths[i];
|
||||
path = sce_sys / fmt::format("pic{}_{:02}.dds", i + 1, language);
|
||||
if (!Common::File::IsFileExisting(path)) {
|
||||
path = sce_sys / fmt::format("pic{}.dds", i + 1);
|
||||
}
|
||||
}
|
||||
int32_t attribute = 0;
|
||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute);
|
||||
g_splash_fast = (attribute & 0x1000) != 0;
|
||||
g_splash_hidden_at.store(0, std::memory_order_release);
|
||||
g_splash_active.store(Common::File::IsFileExisting(g_splash_paths[0]) &&
|
||||
Common::File::IsFileExisting(g_splash_paths[1]),
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
void HideSplashScreen() {
|
||||
Clock::duration::rep expected = 0;
|
||||
g_splash_hidden_at.compare_exchange_strong(expected, Clock::now().time_since_epoch().count());
|
||||
}
|
||||
|
||||
void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
EXIT_IF(window == nullptr);
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
@@ -481,6 +516,7 @@ void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
}
|
||||
|
||||
void ShutdownSystemOverlayInput() {
|
||||
g_splash_active.store(false, std::memory_order_release);
|
||||
CoreIme::SetVisibilityCallback(nullptr);
|
||||
DialogIme::SetVisibilityCallback(nullptr);
|
||||
SystemDialog::SetVisibilityCallback(nullptr);
|
||||
@@ -515,8 +551,10 @@ SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
|
||||
const auto dialog = DialogIme::GetVisualState();
|
||||
const auto system = SystemDialog::GetVisualState();
|
||||
const bool trophy_active = g_trophy_notification_active.load(std::memory_order_acquire);
|
||||
return {core.active || dialog.active || system.active || trophy_active,
|
||||
core.revision + dialog.revision + system.revision + (trophy_active ? 1u : 0u)};
|
||||
const bool splash = g_splash_active.load(std::memory_order_acquire);
|
||||
return {core.active || dialog.active || system.active || trophy_active || splash,
|
||||
core.revision + dialog.revision + system.revision + (trophy_active ? 1u : 0u) +
|
||||
(splash ? 2u : 0u)};
|
||||
}
|
||||
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
@@ -656,8 +694,6 @@ void NotifyTrophyUnlocked(std::string_view name, int32_t grade,
|
||||
}
|
||||
|
||||
struct SystemOverlay::Impl {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
explicit Impl(GraphicContext& context): graphics(context) {}
|
||||
|
||||
~Impl() {
|
||||
@@ -770,6 +806,65 @@ struct SystemOverlay::Impl {
|
||||
return texture;
|
||||
}
|
||||
|
||||
void FinishSplash() {
|
||||
g_splash_active.store(false, std::memory_order_release);
|
||||
if (splash_dds[0] || splash_dds[1]) {
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
RequireVulkanSuccess(graphics.queue.waitIdle(), "retire splash images");
|
||||
splash_dds = {};
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSplash(vk::Extent2D frame_extent) {
|
||||
if (!g_splash_active.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
if (splash_dds[i]) {
|
||||
continue;
|
||||
}
|
||||
const auto& path = g_splash_paths[i];
|
||||
auto image = std::make_unique<DdsImage>(graphics);
|
||||
if (!image->Load(path)) {
|
||||
LOGF("[Splash] could not load %s\n", path.string().c_str());
|
||||
FinishSplash();
|
||||
return;
|
||||
}
|
||||
splash_dds[i] = std::move(image);
|
||||
}
|
||||
const auto now = Clock::now();
|
||||
if (splash_born == Clock::time_point {}) {
|
||||
splash_born = now;
|
||||
}
|
||||
const auto hidden = g_splash_hidden_at.load(std::memory_order_acquire);
|
||||
const float fade =
|
||||
hidden == 0
|
||||
? 0.0f
|
||||
: std::chrono::duration<float>(now.time_since_epoch() - Clock::duration {hidden})
|
||||
.count() /
|
||||
0.3f;
|
||||
const float alpha = 1.0f - std::clamp(fade, 0.0f, 1.0f);
|
||||
if (alpha == 0.0f) {
|
||||
FinishSplash();
|
||||
return;
|
||||
}
|
||||
const float age = std::chrono::duration<float>(now - splash_born).count();
|
||||
const float logo = g_splash_fast ? 1.0f : std::clamp((age - 1.4f) / 2.2f, 0.0f, 1.0f);
|
||||
const ImVec2 screen {static_cast<float>(frame_extent.width),
|
||||
static_cast<float>(frame_extent.height)};
|
||||
const float fit = std::min(screen.x / 3840.0f, screen.y / 2160.0f);
|
||||
const ImVec2 size {3840.0f * fit, 2160.0f * fit};
|
||||
const ImVec2 begin {(screen.x - size.x) * 0.5f, (screen.y - size.y) * 0.5f};
|
||||
const ImVec2 end {begin.x + size.x, begin.y + size.y};
|
||||
auto* draw = ImGui::GetBackgroundDrawList();
|
||||
draw->AddRectFilled({0, 0}, screen, IM_COL32(0, 0, 0, static_cast<int>(255.0f * alpha)));
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
draw->AddImage(
|
||||
splash_dds[i]->Texture(), begin, end, {0, 0}, {1, 1},
|
||||
IM_COL32(255, 255, 255, static_cast<int>(255.0f * alpha * (i == 0 ? 1.0f : logo))));
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureVulkan(vk::Format format, uint32_t image_count) {
|
||||
EnsureContext();
|
||||
if (vulkan_initialized) {
|
||||
@@ -796,6 +891,11 @@ struct SystemOverlay::Impl {
|
||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &color_format;
|
||||
info.CheckVkResultFn = CheckVulkanResult;
|
||||
if (IsHdrSwapchainFormat(format)) {
|
||||
info.CustomShaderFragCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
info.CustomShaderFragCreateInfo.codeSize = sizeof(GPU_IMGUI_HDR_SPV);
|
||||
info.CustomShaderFragCreateInfo.pCode = GPU_IMGUI_HDR_SPV;
|
||||
}
|
||||
EXIT_IF(!ImGui_ImplVulkan_Init(&info));
|
||||
vulkan_initialized = true;
|
||||
}
|
||||
@@ -1099,7 +1199,7 @@ struct SystemOverlay::Impl {
|
||||
ImGui::UnregisterUserTexture(texture.get());
|
||||
return true;
|
||||
});
|
||||
constexpr float TOAST_LIFETIME = 5.0f;
|
||||
const float TOAST_LIFETIME = static_cast<float>(Config::GetTrophyDurationSeconds());
|
||||
if (trophy_notification &&
|
||||
std::chrono::duration<float>(now - trophy_notification_born).count() >=
|
||||
TOAST_LIFETIME) {
|
||||
@@ -1215,7 +1315,8 @@ struct SystemOverlay::Impl {
|
||||
OverlaySnapshot snapshot;
|
||||
const bool has_overlay = GetOverlaySnapshot(&snapshot);
|
||||
const auto now = Clock::now();
|
||||
if (!has_overlay && !g_trophy_notification_active.load(std::memory_order_acquire)) {
|
||||
if (!has_overlay && !g_trophy_notification_active.load(std::memory_order_acquire) &&
|
||||
!g_splash_active.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
const auto prepared_session = snapshot.session;
|
||||
@@ -1245,12 +1346,9 @@ struct SystemOverlay::Impl {
|
||||
last_frame = now;
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
DrawSplash(frame_extent);
|
||||
GetOverlaySnapshot(&snapshot);
|
||||
if (snapshot.session != prepared_session) {
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
if (has_overlay) {
|
||||
if (has_overlay && snapshot.session == prepared_session) {
|
||||
if (snapshot.session.kind == OverlayKind::Dialog) {
|
||||
DrawDialog(snapshot.dialog, frame_extent);
|
||||
} else {
|
||||
@@ -1264,6 +1362,11 @@ struct SystemOverlay::Impl {
|
||||
}
|
||||
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
for (const auto& image: splash_dds) {
|
||||
if (image) {
|
||||
image->Upload(command);
|
||||
}
|
||||
}
|
||||
vk::RenderingAttachmentInfo color {};
|
||||
color.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
color.imageView = target;
|
||||
@@ -1290,6 +1393,7 @@ struct SystemOverlay::Impl {
|
||||
return;
|
||||
}
|
||||
ImGui::SetCurrentContext(imgui_context);
|
||||
splash_dds = {};
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkan_initialized = false;
|
||||
}
|
||||
@@ -1309,6 +1413,8 @@ struct SystemOverlay::Impl {
|
||||
std::optional<TrophyNotification> trophy_notification;
|
||||
Clock::time_point trophy_notification_born;
|
||||
std::vector<std::unique_ptr<ImTextureData>> retired_trophy_images;
|
||||
std::array<std::unique_ptr<DdsImage>, 2> splash_dds;
|
||||
Clock::time_point splash_born;
|
||||
SDL_AudioStream* trophy_sound_stream = nullptr;
|
||||
uint8_t* trophy_sound_data = nullptr;
|
||||
uint32_t trophy_sound_size = 0;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
@@ -21,6 +22,8 @@ struct SystemOverlayVisualState {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeSplashScreen(const std::filesystem::path& sce_sys);
|
||||
void HideSplashScreen();
|
||||
void InitializeSystemOverlayInput(SDL_Window* window);
|
||||
void ShutdownSystemOverlayInput();
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event);
|
||||
|
||||
@@ -145,6 +145,7 @@ struct VideoOutOutputStatus {
|
||||
uint64_t flags = 0;
|
||||
uint64_t reserved[3] = {};
|
||||
};
|
||||
static_assert(sizeof(VideoOutOutputStatus) == 0x30); // Also used by VideoOutVrrStatus.
|
||||
|
||||
struct VideoOutOutputOptions {
|
||||
uint32_t internalData[16] = {};
|
||||
@@ -272,6 +273,7 @@ public:
|
||||
void Init(uint32_t width, uint32_t height);
|
||||
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
||||
Graphics::RenderContext& Renderer() const noexcept { return m_renderer; }
|
||||
bool IsHdrSupported() const noexcept { return m_presenter.IsHdrSupported(); }
|
||||
|
||||
void VblankBegin();
|
||||
void VblankEnd();
|
||||
@@ -558,37 +560,23 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
|
||||
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
|
||||
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
|
||||
}
|
||||
if (attribute.pixel_format == Graphics::VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ) {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Warning: HDR-to-SDR conversion is not implemented; displaying PQ video-out "
|
||||
"pixels unchanged (format=0x{:016x}).\n", attribute.pixel_format));
|
||||
}
|
||||
}
|
||||
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
|
||||
const auto pitch =
|
||||
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
|
||||
Graphics::TileSizeAlign total {};
|
||||
Graphics::TileGetTextureTotalSize(pixel_format.guest_format, attribute.width, attribute.height,
|
||||
1, 1, tile_mode, false, total);
|
||||
if (total.size == 0 || total.align != 65536 ||
|
||||
(buffer.data_address & (total.align - 1u)) != 0) {
|
||||
EXIT("invalid video-out surface footprint or alignment\n");
|
||||
}
|
||||
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
|
||||
Graphics::ImageInfo info {};
|
||||
info.data = {buffer.data_address, total.size};
|
||||
info.data = {buffer.data_address, 0};
|
||||
info.pixel_format = pixel_format.format;
|
||||
info.guest_format = pixel_format.guest_format;
|
||||
info.type = Graphics::Prospero::ImageType::kColor2D;
|
||||
info.extent = {attribute.width, attribute.height, 1};
|
||||
info.resources = {1, 1};
|
||||
info.pitch = pitch;
|
||||
info.bytes_per_block = pixel_format.bytes_per_element;
|
||||
info.samples = 1;
|
||||
info.tile_mode = tile_mode;
|
||||
info.bgra16 = pixel_format.bgra16;
|
||||
info.mip_layout[0] = {0, total.size, pitch, attribute.height};
|
||||
info.UpdateSize();
|
||||
if (info.data.size == 0 || info.tiling.block.block_size != 65536 ||
|
||||
(buffer.data_address & 65535u) != 0) {
|
||||
EXIT("invalid video-out surface footprint or alignment\n");
|
||||
}
|
||||
if (compression != Graphics::VideoOutCompression::Uncompressed) {
|
||||
Graphics::TileSizeAlign dcc_size {};
|
||||
if (!Graphics::TileGetDccSize(attribute.width, attribute.height, 1,
|
||||
@@ -1077,31 +1065,31 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
uint32_t height = 0;
|
||||
bool current = false;
|
||||
bool premultiplied_alpha = false;
|
||||
bool hdr = false;
|
||||
{
|
||||
Common::LockGuard lock(cfg->mutex);
|
||||
current = cfg->opened && !cfg->closing && cfg->generation == generation;
|
||||
if (current) {
|
||||
if (special) {
|
||||
width = cfg->width;
|
||||
height = cfg->height;
|
||||
} else {
|
||||
const auto& surface = cfg->buffers[index];
|
||||
if (!surface.Occupied()) {
|
||||
EXIT("cannot prepare flip from an unregistered surface, id=%" PRIu64
|
||||
" index=%d\n",
|
||||
request_id, index);
|
||||
}
|
||||
if (surface.group_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX ||
|
||||
!cfg->groups[surface.group_index].occupied) {
|
||||
EXIT("video-out surface references an unavailable attribute group, id=%" PRIu64
|
||||
" index=%d group=%d\n",
|
||||
request_id, index, surface.group_index);
|
||||
}
|
||||
source_info = cfg->groups[surface.group_index].ImageInfo(surface);
|
||||
premultiplied_alpha =
|
||||
(cfg->groups[surface.group_index].attribute.option &
|
||||
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED) != 0;
|
||||
if (current && special) {
|
||||
width = cfg->width;
|
||||
height = cfg->height;
|
||||
} else if (current) {
|
||||
const auto& surface = cfg->buffers[index];
|
||||
if (!surface.Occupied()) {
|
||||
EXIT("cannot prepare flip from an unregistered surface, id=%" PRIu64
|
||||
" index=%d\n",
|
||||
request_id, index);
|
||||
}
|
||||
if (surface.group_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX ||
|
||||
!cfg->groups[surface.group_index].occupied) {
|
||||
EXIT("video-out surface references an unavailable attribute group, id=%" PRIu64
|
||||
" index=%d group=%d\n",
|
||||
request_id, index, surface.group_index);
|
||||
}
|
||||
const auto& group = cfg->groups[surface.group_index];
|
||||
source_info = group.ImageInfo(surface);
|
||||
hdr = Graphics::IsHdrVideoOutPixelFormat(group.attribute.pixel_format);
|
||||
premultiplied_alpha = (group.attribute.option &
|
||||
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED) != 0;
|
||||
}
|
||||
}
|
||||
if (!current) {
|
||||
@@ -1114,12 +1102,18 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (hdr && !m_presenter.IsHdrSupported() && !warned.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(
|
||||
"Warning: HDR output is unavailable; HDR-to-SDR conversion is not implemented, "
|
||||
"displaying PQ video-out pixels unchanged.\n");
|
||||
}
|
||||
Graphics::Presenter::Frame* frame = nullptr;
|
||||
if (special) {
|
||||
frame = &m_presenter.PrepareBlankFrame(width, height, index == VIDEO_OUT_BUFFER_INDEX_BLACK,
|
||||
&buffer);
|
||||
} else {
|
||||
frame = &m_presenter.PrepareFrame(buffer, source_info);
|
||||
frame = &m_presenter.PrepareFrame(buffer, source_info, hdr);
|
||||
}
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
@@ -1796,11 +1790,14 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
|
||||
int32_t attribute3 = 0;
|
||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
|
||||
int32_t attribute = 0;
|
||||
const bool hdr_allowed = !Loader::SystemContentParamSfoGetInt("ATTRIBUTE", &attribute) ||
|
||||
(attribute & 0x20000000) != 0;
|
||||
ctx->mutex.Lock();
|
||||
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
|
||||
status->resolution =
|
||||
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
|
||||
status->dynamicRange = 1;
|
||||
status->dynamicRange = hdr_allowed && DriverState().IsHdrSupported() ? 2u : 1u;
|
||||
status->refreshRate =
|
||||
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
|
||||
? VIDEO_OUT_REFRESH_RATE_119_88HZ
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -31,6 +32,8 @@ struct Presenter::Frame {
|
||||
vk::ImageView view = nullptr;
|
||||
uint64_t present_tick = 0;
|
||||
bool busy = false;
|
||||
bool hdr = false;
|
||||
bool blank = false;
|
||||
|
||||
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
||||
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
|
||||
@@ -263,7 +266,7 @@ public:
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] bool NeedsResize() const;
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] Status AcquireNextImage(CommandScheduler& scheduler);
|
||||
[[nodiscard]] bool PrepareSystemOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, Presenter::Frame* source,
|
||||
const Presenter::Layer& overlay, bool draw_system_overlay);
|
||||
@@ -274,20 +277,31 @@ public:
|
||||
return static_cast<uint32_t>(m_images.size());
|
||||
}
|
||||
[[nodiscard]] vk::Format Format() const noexcept { return m_format; }
|
||||
[[nodiscard]] bool IsHdrSupported() const noexcept { return m_hdr_supported.load(); }
|
||||
bool SetHdr(bool hdr) {
|
||||
const bool changed = m_hdr_active != (hdr && IsHdrSupported());
|
||||
m_hdr_requested = hdr;
|
||||
return changed;
|
||||
}
|
||||
|
||||
private:
|
||||
vk::SurfaceFormatKHR SelectFormat();
|
||||
void Destroy();
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
std::atomic_bool m_hdr_supported {false};
|
||||
bool m_hdr_requested = false;
|
||||
bool m_hdr_active = false;
|
||||
vk::Extent2D m_extent {};
|
||||
// Drawable pixel size observed when this swapchain was created.
|
||||
vk::Extent2D m_window_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<uint64_t> m_image_acquired_ticks;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
std::unique_ptr<SystemOverlay> m_system_overlay;
|
||||
vk::DescriptorSetLayout m_overlay_descriptors = nullptr;
|
||||
@@ -346,6 +360,40 @@ struct Presenter::Impl {
|
||||
std::atomic<uint64_t> presented_overlay_revision {0};
|
||||
};
|
||||
|
||||
vk::SurfaceFormatKHR Swapchain::SelectFormat() {
|
||||
const auto& formats = m_window.surface_capabilities.formats;
|
||||
auto& graphics = m_window.graphic_ctx;
|
||||
const auto hdr_format = std::ranges::find_if(formats, [&](const auto& candidate) {
|
||||
return candidate.colorSpace == vk::ColorSpaceKHR::eHdr10St2084EXT &&
|
||||
IsHdrSwapchainFormat(candidate.format) &&
|
||||
static_cast<bool>(
|
||||
graphics.GetFormatProperties(candidate.format).optimalTilingFeatures &
|
||||
vk::FormatFeatureFlagBits::eBlitDst);
|
||||
});
|
||||
m_hdr_supported = Config::HdrEnabled() && hdr_format != formats.end();
|
||||
m_hdr_active = m_hdr_requested && IsHdrSupported();
|
||||
if (m_hdr_active) {
|
||||
return *hdr_format;
|
||||
}
|
||||
if (Config::HdrEnabled() && !IsHdrSupported()) {
|
||||
LOGF("HDR10 surface unavailable; using SDR output\n");
|
||||
}
|
||||
if (formats.size() == 1 && formats.front().format == vk::Format::eUndefined) {
|
||||
return {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
}
|
||||
for (const auto& candidate: formats) {
|
||||
if (candidate.colorSpace != vk::ColorSpaceKHR::eSrgbNonlinear) {
|
||||
continue;
|
||||
}
|
||||
switch (candidate.format) {
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Unorm: return candidate;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
EXIT("no supported UNORM swapchain format\n");
|
||||
}
|
||||
|
||||
void Swapchain::Create() {
|
||||
auto& graphics = m_window.graphic_ctx;
|
||||
EXIT_IF(graphics.device == nullptr);
|
||||
@@ -385,18 +433,7 @@ void Swapchain::Create() {
|
||||
? vk::CompositeAlphaFlagBitsKHR::eOpaque
|
||||
: vk::CompositeAlphaFlagBitsKHR::eInherit;
|
||||
|
||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
if (surface.formats.size() != 1 || surface.formats.front().format != vk::Format::eUndefined) {
|
||||
const auto it = std::find_if(surface.formats.begin(), surface.formats.end(),
|
||||
[](const vk::SurfaceFormatKHR& candidate) {
|
||||
return candidate.format == vk::Format::eB8G8R8A8Unorm ||
|
||||
candidate.format == vk::Format::eR8G8B8A8Unorm;
|
||||
});
|
||||
if (it == surface.formats.end()) {
|
||||
EXIT("no supported UNORM swapchain format\n");
|
||||
}
|
||||
format = *it;
|
||||
}
|
||||
const auto format = SelectFormat();
|
||||
m_format = format.format;
|
||||
const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||
if (!static_cast<bool>(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
@@ -436,6 +473,7 @@ void Swapchain::Create() {
|
||||
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
|
||||
"vkCreateSwapchainKHR");
|
||||
EXIT_IF(m_handle == nullptr);
|
||||
LOGF("Vulkan presentation: %s\n", m_hdr_active ? "HDR10 (PQ)" : "SDR");
|
||||
|
||||
m_images = EnumerateVulkan<vk::Image>(
|
||||
"vkGetSwapchainImagesKHR", [&](uint32_t* count, vk::Image* images) {
|
||||
@@ -464,6 +502,7 @@ void Swapchain::Create() {
|
||||
vk::SemaphoreCreateInfo semaphore_info {};
|
||||
semaphore_info.sType = vk::StructureType::eSemaphoreCreateInfo;
|
||||
m_image_acquired.resize(m_images.size());
|
||||
m_image_acquired_ticks.assign(m_images.size(), 0);
|
||||
m_render_complete.resize(m_images.size());
|
||||
for (size_t i = 0; i < m_images.size(); i++) {
|
||||
RequireVulkanSuccess(
|
||||
@@ -532,6 +571,7 @@ void Swapchain::Destroy() {
|
||||
m_images.clear();
|
||||
m_image_views.clear();
|
||||
m_image_acquired.clear();
|
||||
m_image_acquired_ticks.clear();
|
||||
m_render_complete.clear();
|
||||
}
|
||||
|
||||
@@ -557,8 +597,11 @@ bool Swapchain::NeedsResize() const {
|
||||
m_window_extent.height != m_window.graphic_ctx.screen_height;
|
||||
}
|
||||
|
||||
Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
Swapchain::Status Swapchain::AcquireNextImage(CommandScheduler& scheduler) {
|
||||
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
|
||||
// Repeated frames can wrap the semaphore ring without acquiring a new source frame.
|
||||
// Wait for the submission that consumed this slot before signaling it again.
|
||||
scheduler.Wait(m_image_acquired_ticks[m_frame_index]);
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
const auto result = m_window.graphic_ctx.device.acquireNextImageKHR(
|
||||
m_handle, std::numeric_limits<uint64_t>::max(), m_image_acquired[m_frame_index], nullptr,
|
||||
@@ -692,9 +735,11 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
||||
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
|
||||
write.pImageInfo = ℑ
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_overlay_layout, 0, 1, &write);
|
||||
const uint32_t premultiplied = layer.premultiplied_alpha;
|
||||
const uint32_t flags = (layer.premultiplied_alpha ? 1u : 0u) |
|
||||
(m_hdr_active ? 2u : 0u) | (frame.hdr ? 4u : 0u) |
|
||||
(frame.image.format == vk::Format::eR16G16B16A16Sfloat ? 8u : 0u);
|
||||
command.pushConstants(m_overlay_layout, vk::ShaderStageFlagBits::eFragment, 0,
|
||||
sizeof(premultiplied), &premultiplied);
|
||||
sizeof(flags), &flags);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eGraphics, m_overlay_pipeline);
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = m_image_views[m_image_index];
|
||||
@@ -828,7 +873,9 @@ uint64_t Swapchain::Submit(CommandScheduler& scheduler) {
|
||||
SubmitInfo submit;
|
||||
submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer);
|
||||
submit.AddSignal(m_render_complete[m_image_index]);
|
||||
return scheduler.Submit(submit);
|
||||
const auto tick = scheduler.Submit(submit);
|
||||
m_image_acquired_ticks[m_frame_index] = tick;
|
||||
return tick;
|
||||
}
|
||||
|
||||
Swapchain::Status Swapchain::Present() {
|
||||
@@ -868,7 +915,7 @@ Presenter::Presenter(WindowContext& window): m_impl(std::make_unique<Impl>(windo
|
||||
|
||||
Presenter::~Presenter() = default;
|
||||
|
||||
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info) {
|
||||
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info, bool hdr) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
auto frame_format = info.pixel_format;
|
||||
@@ -878,6 +925,8 @@ Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo
|
||||
default: break;
|
||||
}
|
||||
auto* frame = m_impl->frames.Acquire({info.extent.width, info.extent.height}, frame_format);
|
||||
frame->hdr = hdr;
|
||||
frame->blank = false;
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& image = m_impl->ResolveSurface(info);
|
||||
if (image.backing.format == vk::Format::eUndefined) {
|
||||
@@ -894,6 +943,8 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto format = m_impl->frames.GetFormat();
|
||||
auto* frame = m_impl->frames.Acquire({width, height}, format);
|
||||
frame->hdr = false;
|
||||
frame->blank = true;
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
frame->Configure(m_impl->window.graphic_ctx, {width, height}, format);
|
||||
vk::ClearColorValue clear {};
|
||||
@@ -922,6 +973,10 @@ bool Presenter::IsGuestPaused() const noexcept {
|
||||
return m_impl->window.loop.paused.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool Presenter::IsHdrSupported() const noexcept {
|
||||
return m_impl->swapchain.IsHdrSupported();
|
||||
}
|
||||
|
||||
bool Presenter::NeedsSystemOverlayRefresh() const noexcept {
|
||||
const auto visual = GetSystemOverlayVisualState();
|
||||
return visual.active ||
|
||||
@@ -967,12 +1022,16 @@ void Presenter::Impl::Present() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||
// Keep black/blank flips in the current color space; only real main-bus frames switch it.
|
||||
const auto* main_frame = layers[0].frame;
|
||||
const bool hdr_changed = main_frame != nullptr && !main_frame->blank &&
|
||||
swapchain.SetHdr(main_frame->hdr);
|
||||
// Some window systems keep presenting an old swapchain after a resize.
|
||||
if (swapchain.NeedsResize()) {
|
||||
if (hdr_changed || swapchain.NeedsResize()) {
|
||||
RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
auto status = swapchain.AcquireNextImage(present_scheduler);
|
||||
if (status != Swapchain::Status::Success) {
|
||||
RecoverSwapchain(status);
|
||||
continue;
|
||||
|
||||
@@ -63,6 +63,8 @@ vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noe
|
||||
features.shaderBufferInt64Atomics = VK_TRUE;
|
||||
features.storageBuffer8BitAccess = VK_TRUE;
|
||||
features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -215,6 +217,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features features12 {};
|
||||
vk::PhysicalDeviceVulkan11Features features11 {};
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
|
||||
#if defined(__APPLE__)
|
||||
features12.pNext = &depth_clip_control;
|
||||
#else
|
||||
@@ -222,7 +225,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
fragment_barycentric.pNext = &depth_clip_control;
|
||||
features12.pNext = &fragment_barycentric;
|
||||
#endif
|
||||
features11.pNext = features12.pNext;
|
||||
robustness2.pNext = features12.pNext;
|
||||
features11.pNext = &robustness2;
|
||||
features12.pNext = &features11;
|
||||
features13.pNext = &features12;
|
||||
device_features2.pNext = &features13;
|
||||
@@ -284,7 +288,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
check_feature(features12.shaderSampledImageArrayNonUniformIndexing,
|
||||
"shaderSampledImageArrayNonUniformIndexing",
|
||||
required_features12.shaderSampledImageArrayNonUniformIndexing);
|
||||
check_feature(features12.shaderStorageImageArrayNonUniformIndexing,
|
||||
"shaderStorageImageArrayNonUniformIndexing",
|
||||
required_features12.shaderStorageImageArrayNonUniformIndexing);
|
||||
check_feature(features12.shaderStorageBufferArrayNonUniformIndexing,
|
||||
"shaderStorageBufferArrayNonUniformIndexing",
|
||||
required_features12.shaderStorageBufferArrayNonUniformIndexing);
|
||||
check_feature(features13.robustImageAccess, "robustImageAccess");
|
||||
check_feature(robustness2.nullDescriptor, "nullDescriptor");
|
||||
check_feature(features13.dynamicRendering, "dynamicRendering",
|
||||
required_features13.dynamicRendering);
|
||||
check_feature(features13.synchronization2, "synchronization2",
|
||||
@@ -446,14 +457,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
vk::PhysicalDeviceVulkan13Features supported_features13 {};
|
||||
supported_features13.pNext = &supported_features12;
|
||||
|
||||
const auto robustness2_ext_enabled =
|
||||
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT supported_robustness2 {};
|
||||
if (robustness2_ext_enabled) {
|
||||
supported_robustness2.pNext = supported_features13.pNext;
|
||||
supported_features13.pNext = &supported_robustness2;
|
||||
}
|
||||
supported_robustness2.pNext = supported_features13.pNext;
|
||||
supported_features13.pNext = &supported_robustness2;
|
||||
|
||||
const bool mesh_extension = HasExtension(device_extensions, VK_EXT_MESH_SHADER_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT supported_mesh {};
|
||||
@@ -478,6 +484,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
provoking_vertex.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &provoking_vertex;
|
||||
}
|
||||
const bool maintenance8_extension =
|
||||
HasExtension(device_extensions, VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceMaintenance8FeaturesKHR maintenance8 {};
|
||||
if (maintenance8_extension) {
|
||||
maintenance8.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &maintenance8;
|
||||
}
|
||||
const bool image_atomic_int64_extension =
|
||||
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
|
||||
@@ -522,6 +535,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
subgroup_size_control.pNext = &graphics.mesh_shader_properties;
|
||||
}
|
||||
physical_device.getProperties2(&properties2);
|
||||
EXIT_NOT_IMPLEMENTED(!(properties11.subgroupSupportedOperations & vk::SubgroupFeatureFlagBits::eVote));
|
||||
|
||||
graphics.subgroup_size = properties11.subgroupSize;
|
||||
graphics.min_subgroup_size = subgroup_size_control.minSubgroupSize;
|
||||
@@ -542,6 +556,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
graphics.attachment_feedback_loop_enabled =
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||
LOGF("Vulkan maintenance8 support: %s\n",
|
||||
graphics.maintenance8_enabled ? "true" : "false");
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
graphics.attachment_feedback_loop_enabled ? "true" : "false");
|
||||
if (graphics.mesh_shader_enabled) {
|
||||
@@ -597,27 +614,19 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
fragment_barycentric.fragmentShaderBarycentric = VK_TRUE;
|
||||
robustness2.pNext = &fragment_barycentric;
|
||||
#endif
|
||||
if (robustness2_ext_enabled) {
|
||||
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
|
||||
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
|
||||
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
|
||||
}
|
||||
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
|
||||
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
|
||||
robustness2.nullDescriptor = VK_TRUE;
|
||||
|
||||
auto features13 = WindowContext::RequiredVulkan13Features();
|
||||
#if defined(__APPLE__)
|
||||
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
|
||||
: static_cast<void*>(&features12);
|
||||
#else
|
||||
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
|
||||
: static_cast<void*>(&fragment_barycentric);
|
||||
#endif
|
||||
features13.pNext = &robustness2;
|
||||
features13.robustImageAccess = supported_features13.robustImageAccess;
|
||||
features13.subgroupSizeControl =
|
||||
graphics.compute_subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
|
||||
|
||||
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
|
||||
features13.robustImageAccess == VK_TRUE ? "true" : "false",
|
||||
robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
|
||||
robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
|
||||
|
||||
vk::DeviceCreateInfo create_info {};
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features {};
|
||||
@@ -638,6 +647,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
image_atomic_int64.sparseImageInt64Atomics = VK_FALSE;
|
||||
create_info.pNext = &image_atomic_int64;
|
||||
}
|
||||
if (graphics.maintenance8_enabled) {
|
||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &maintenance8;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -671,6 +684,11 @@ static void VulkanGetExtensions(VulkanExtensions& r) {
|
||||
});
|
||||
|
||||
r.enable_validation_layers = Config::VulkanValidationEnabled();
|
||||
if (Config::HdrEnabled() &&
|
||||
HasExtension(r.available_extensions, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME) &&
|
||||
!HasExtension(r.required_extensions, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) {
|
||||
r.required_extensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (HasExtension(r.available_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
|
||||
r.required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
@@ -938,7 +956,7 @@ void WindowContext::CreateVulkan() {
|
||||
std::vector<const char*> device_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
||||
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
"VK_KHR_maintenance1"};
|
||||
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, "VK_KHR_maintenance1"};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
|
||||
@@ -1001,7 +1019,7 @@ void WindowContext::CreateVulkan() {
|
||||
device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||
for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
|
||||
@@ -326,6 +326,19 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
|
||||
const auto button = ControllerButtonToPadButton(f.button);
|
||||
if (button != 0) {
|
||||
Controller::SetButton(f.id, button, f.down);
|
||||
} else if (f.button == SDL_GAMEPAD_BUTTON_BACK) {
|
||||
// Pads without a touchpad (Switch, Xbox, ...) have no way to click it, which
|
||||
// blocks games that require it. Use their Back/Select button as a centered click.
|
||||
auto* pad = SDL_GetGamepadFromID(f.id);
|
||||
if (pad != nullptr && SDL_GetNumGamepadTouchpads(pad) == 0) {
|
||||
if (f.down) {
|
||||
Controller::SetTouchPad(f.id, 0, true, 0.5f, 0.5f);
|
||||
}
|
||||
Controller::SetButton(f.id, Controller::PAD_BUTTON_TOUCH_PAD, f.down);
|
||||
if (f.up) {
|
||||
Controller::SetTouchPad(f.id, 0, false, 0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
@@ -467,6 +468,7 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
Decoder::DecodeInstruction(joined_code, front_words - 1u, result.instructions.back());
|
||||
Decoder::Program back_program;
|
||||
Decoder::DecodeProgram(back, back_program);
|
||||
result.has_swap_pc |= back_program.has_swap_pc;
|
||||
const auto back_pc = front_words * sizeof(uint32_t);
|
||||
for (auto& inst: back_program.instructions) {
|
||||
// A back-stage PC-relative data reference requires its guest code address.
|
||||
@@ -479,11 +481,227 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
return result;
|
||||
}
|
||||
|
||||
void PrepareCallTarget(ShaderSource& source, const CompileOptions& options) {
|
||||
using namespace IR;
|
||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||
EXIT_NOT_IMPLEMENTED(source.call.has_value() || options.stage != ShaderType::Compute);
|
||||
source.call = ShaderSource::Call {.instruction = i};
|
||||
}
|
||||
EXIT_IF(options.input_info.compute == nullptr);
|
||||
const auto& call = source.decoded.instructions[source.call->instruction];
|
||||
EXIT_NOT_IMPLEMENTED(call.src0.kind != Decoder::OperandKind::Sgpr ||
|
||||
call.dst.kind != Decoder::OperandKind::Sgpr ||
|
||||
call.src0.reg != call.dst.reg || call.dst.reg >= 105u);
|
||||
const auto graph = CFG::BuildGraph(source.decoded);
|
||||
Program query;
|
||||
query.stage = options.stage;
|
||||
query.user_data_base = options.user_data_base;
|
||||
query.user_data_count = static_cast<uint32_t>(options.user_data.size());
|
||||
query.wave_size = options.wave_size;
|
||||
auto& block = *query.block_storage.emplace_back(std::make_unique<Block>());
|
||||
query.blocks.push_back(&block);
|
||||
struct Producer { bool ready = false; std::map<uint32_t, Value> values; };
|
||||
std::map<uint32_t, Producer> producers;
|
||||
std::function<Value(uint32_t, uint32_t)> Register;
|
||||
std::function<Value(uint32_t, Value)> Resolve = [&](uint32_t before, Value value) -> Value {
|
||||
value = value.Resolve();
|
||||
auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr) return value;
|
||||
uint32_t code = UINT32_MAX;
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::GetScalarRegister: code = static_cast<uint32_t>(inst->Arg(0).ScalarRegister()); break;
|
||||
case ValueOpcode::GetVccLo: code = 106u; break;
|
||||
case ValueOpcode::GetVccHi: code = 107u; break;
|
||||
default: break;
|
||||
}
|
||||
if (code != UINT32_MAX) {
|
||||
const auto result = Register(before, code);
|
||||
inst->ReplaceUsesWith(result);
|
||||
return result;
|
||||
}
|
||||
for (size_t i = 0; i < inst->NumArgs(); ++i) inst->SetArg(i, Resolve(before, inst->Arg(i)));
|
||||
return value;
|
||||
};
|
||||
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code);
|
||||
if (index == UINT32_MAX) {
|
||||
EXIT_IF(code < query.user_data_base || code - query.user_data_base >= query.user_data_count);
|
||||
return IREmitter(&block).GetUserData(static_cast<ScalarReg>(code));
|
||||
}
|
||||
auto [entry, inserted] = producers.try_emplace(index);
|
||||
auto& producer = entry->second;
|
||||
if (inserted) {
|
||||
Block translated;
|
||||
Frontend::Translator translator(query, &translated, 1u,
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0u,
|
||||
!options.input_info.compute->async_compute);
|
||||
translator.TranslateInstruction(source.decoded.instructions[index]);
|
||||
for (const auto& inst: translated) {
|
||||
uint32_t destination = UINT32_MAX;
|
||||
Value value;
|
||||
switch (inst.GetOpcode()) {
|
||||
case ValueOpcode::SetScalarRegister:
|
||||
destination = static_cast<uint32_t>(inst.Arg(0).ScalarRegister()); value = inst.Arg(1); break;
|
||||
case ValueOpcode::SetVccLo: destination = 106u; value = inst.Arg(0); break;
|
||||
case ValueOpcode::SetVccHi: destination = 107u; value = inst.Arg(0); break;
|
||||
default: break;
|
||||
}
|
||||
if (destination != UINT32_MAX) producer.values.insert_or_assign(destination, value);
|
||||
}
|
||||
for (auto& [destination, value]: producer.values) value = Resolve(index, value);
|
||||
for (auto& inst: translated) inst.SetParent(&block);
|
||||
block.Instructions().splice(block.end(), translated.Instructions());
|
||||
producer.ready = true;
|
||||
}
|
||||
EXIT_IF(!producer.ready || !producer.values.contains(code));
|
||||
return producer.values.at(code).Resolve();
|
||||
};
|
||||
for (uint32_t i = 0; i < 2u; ++i) {
|
||||
const auto value = Register(source.call->instruction, call.src0.reg + i);
|
||||
EXIT_IF(!ValidateRuntimeValue(query, value, RuntimeValueType::Integer));
|
||||
auto& root = query.value_storage.emplace_back(ValueOpcode::ReferenceU32);
|
||||
root.SetArg(0, value);
|
||||
source.call->target[i] = Value(&root);
|
||||
}
|
||||
RewriteToSsa(query.blocks);
|
||||
ConstantPropagationPass(query.blocks, query.wave_size);
|
||||
RemoveIdentities(query);
|
||||
EliminateDeadCode(query.blocks);
|
||||
for (auto& value: source.call->target) value = value.Instruction()->Arg(0).Resolve();
|
||||
query.value_storage.clear();
|
||||
for (auto& inst: block) {
|
||||
inst.SetParent(nullptr);
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 || inst.GetOpcode() == ValueOpcode::ReadConstBuffer)
|
||||
inst.SetFlags(SrtReadFlags {.index = inst.Flags<MemoryFlags>().index});
|
||||
}
|
||||
query.value_storage.splice(query.value_storage.end(), block.Instructions());
|
||||
source.call_targets = std::move(static_cast<ResourcePlan&>(query));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ShaderSource PrepareShaderSource(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
EXIT_IF(code.empty());
|
||||
ShaderSource source;
|
||||
if (!options.back_code.empty()) {
|
||||
source.decoded = DecodeFusedProgram(code, options.back_code, source.code);
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
source.decoded = Decoder::DecodeFrontProgram(code);
|
||||
auto& handoff = source.decoded.instructions.back();
|
||||
handoff.opcode = Decoder::Opcode::S_ENDPGM;
|
||||
handoff.src_count = 0;
|
||||
} else {
|
||||
Decoder::DecodeProgram(code, source.decoded);
|
||||
}
|
||||
if (source.decoded.has_swap_pc) {
|
||||
PrepareCallTarget(source, options);
|
||||
source.code.assign(code.begin(), code.end());
|
||||
source.decoded.code = source.code;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtRuntime& runtime) {
|
||||
auto& reads = source.reads;
|
||||
reads.clear();
|
||||
IR::SrtReadCapture capture(runtime, reads);
|
||||
const auto clean = IR::CleanRuntime(capture.ObservedRuntime());
|
||||
IR::SrtWalker walker(source.call_targets, clean);
|
||||
uint32_t low = 0, high = 0;
|
||||
const auto& call = source.decoded.instructions[source.call->instruction];
|
||||
if (!walker.Evaluate(source.call->target[0], low) || !walker.Evaluate(source.call->target[1], high))
|
||||
EXIT("shader call at pc 0x%08x has an unavailable scalar target", call.pc);
|
||||
// SWAPPC reads the old aliased pair and ignores the target's low two bits.
|
||||
const auto address = ((uint64_t {high} << 32u) | low) & ~uint64_t {3};
|
||||
EXIT_IF(address == 0u);
|
||||
const auto query_reads = reads.size();
|
||||
if (source.linked) {
|
||||
auto& linked = *source.linked;
|
||||
const auto function = std::span(linked.code).subspan(source.code.size());
|
||||
if (clean.read_memory(clean.userdata, address, linked.observed_function) &&
|
||||
std::ranges::equal(function, linked.observed_function)) {
|
||||
return linked.decoded;
|
||||
}
|
||||
reads.resize(query_reads);
|
||||
}
|
||||
ShaderSource::Linked linked {.code = source.code,
|
||||
.decoded = source.decoded};
|
||||
const auto& last = source.decoded.instructions.back();
|
||||
EXIT_NOT_IMPLEMENTED(last.opcode != Decoder::Opcode::S_ENDPGM);
|
||||
// Keep the caller's data/footer bytes at their native offsets. The leaf's virtual CFG
|
||||
// PCs follow decoded instructions; its physical code is independently read and owned.
|
||||
const auto function_pc = last.pc + last.word_count * sizeof(uint32_t);
|
||||
for (uint32_t index = 0;;) {
|
||||
// The observed queue helper is a scalar leaf. Scalar encodings need at most one literal.
|
||||
EXIT_IF(index >= 64u || address > UINT64_MAX - (uint64_t {index} + 2u) * sizeof(uint32_t));
|
||||
std::array<uint32_t, 2> words {};
|
||||
if (!clean.read_memory(clean.userdata, address + uint64_t {index} * sizeof(uint32_t),
|
||||
std::span(words).first(1)))
|
||||
EXIT("shader call at pc 0x%08x cannot read leaf code", call.pc);
|
||||
const auto family = Decoder::GetInstructionFamily(words[0]);
|
||||
EXIT_NOT_IMPLEMENTED(family != Decoder::Family::SOP1 && family != Decoder::Family::SOP2 &&
|
||||
family != Decoder::Family::SOPC && family != Decoder::Family::SOPK &&
|
||||
family != Decoder::Family::SOPP);
|
||||
Decoder::Instruction inst;
|
||||
Decoder::DecodeInstruction(words, 0u, inst);
|
||||
if (inst.word_count == 2u) {
|
||||
if (!clean.read_memory(clean.userdata, address + uint64_t {index + 1u} * sizeof(uint32_t),
|
||||
std::span(words).last(1)))
|
||||
EXIT("shader call at pc 0x%08x cannot read leaf literal", call.pc);
|
||||
inst = {};
|
||||
Decoder::DecodeInstruction(words, 0u, inst);
|
||||
}
|
||||
inst.pc = index * sizeof(uint32_t);
|
||||
if (Decoder::IsDirectBranch(inst.opcode)) inst.branch_target += inst.pc;
|
||||
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||
index += inst.word_count;
|
||||
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_SWAPPC_B64 ||
|
||||
inst.opcode == Decoder::Opcode::S_GETPC_B64 ||
|
||||
inst.opcode == Decoder::Opcode::S_ENDPGM);
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) || CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
||||
const bool returns = inst.opcode == Decoder::Opcode::S_SETPC_B64;
|
||||
if (returns) {
|
||||
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr || inst.src0.reg != call.dst.reg);
|
||||
inst.opcode = Decoder::Opcode::S_BRANCH;
|
||||
inst.branch_target = call.pc + call.word_count * sizeof(uint32_t);
|
||||
} else if (Decoder::IsDirectBranch(inst.opcode)) {
|
||||
EXIT_IF(inst.branch_target > UINT32_MAX - function_pc);
|
||||
inst.branch_target += function_pc;
|
||||
}
|
||||
inst.pc += function_pc;
|
||||
linked.decoded.instructions.push_back(std::move(inst));
|
||||
if (returns) break;
|
||||
}
|
||||
const auto function_end = linked.decoded.instructions.back().pc +
|
||||
linked.decoded.instructions.back().word_count * sizeof(uint32_t);
|
||||
for (size_t i = source.decoded.instructions.size(); i + 1u < linked.decoded.instructions.size(); ++i) {
|
||||
const auto& inst = linked.decoded.instructions[i];
|
||||
if (Decoder::IsDirectBranch(inst.opcode))
|
||||
EXIT_IF(inst.branch_target < function_pc || inst.branch_target >= function_end);
|
||||
}
|
||||
reads.resize(query_reads);
|
||||
reads.emplace_back(address, (linked.code.size() - source.code.size()) * sizeof(uint32_t));
|
||||
linked.decoded.instructions[source.call->instruction].branch_target = function_pc;
|
||||
linked.decoded.code = linked.code;
|
||||
linked.observed_function.resize(linked.code.size() - source.code.size());
|
||||
source.linked = std::move(linked);
|
||||
++source.revision;
|
||||
return source.linked->decoded;
|
||||
}
|
||||
|
||||
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
if (code.empty()) {
|
||||
EXIT("shader recompiler input is empty\n");
|
||||
auto source = PrepareShaderSource(code, options);
|
||||
return TranslateProgram(source.decoded, options);
|
||||
}
|
||||
|
||||
TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileOptions& options) {
|
||||
EXIT_IF(decoded.instructions.empty());
|
||||
if (decoded.has_swap_pc) {
|
||||
for (const auto& inst: decoded.instructions) {
|
||||
if (inst.opcode == Decoder::Opcode::S_SWAPPC_B64 && inst.branch_target == UINT32_MAX)
|
||||
EXIT("shader call at pc 0x%08x must be linked before translation", inst.pc);
|
||||
}
|
||||
}
|
||||
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
|
||||
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh &&
|
||||
@@ -494,34 +712,11 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
}
|
||||
|
||||
const auto compile_begin = std::chrono::steady_clock::now();
|
||||
const auto phase_ms = [&compile_begin]() {
|
||||
const auto phase_ms = [&compile_begin]() {
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - compile_begin)
|
||||
.count());
|
||||
std::chrono::steady_clock::now() - compile_begin).count());
|
||||
};
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " code_words=%" PRIu64 " decode\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(code.size()));
|
||||
|
||||
Decoder::Program decoded;
|
||||
std::vector<uint32_t> joined_code;
|
||||
if (!options.back_code.empty()) {
|
||||
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
decoded = Decoder::DecodeFrontProgram(code);
|
||||
// The separately compiled hull half runs in the next Vulkan stage.
|
||||
auto& handoff = decoded.instructions.back();
|
||||
handoff.opcode = Decoder::Opcode::S_ENDPGM;
|
||||
handoff.src_count = 0;
|
||||
} else {
|
||||
Decoder::DecodeProgram(code, decoded);
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(decoded.instructions.size()), phase_ms());
|
||||
|
||||
std::string decoded_dump;
|
||||
if (options.dump_ir) {
|
||||
decoded_dump = Decoder::ProgramToString(decoded);
|
||||
@@ -594,18 +789,24 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
IR::RewriteToSsa(ir.blocks);
|
||||
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
read_lane_stats.rewritten_reads);
|
||||
const auto simplify = [&ir] {
|
||||
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::RemoveIdentities(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
};
|
||||
simplify();
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0 || read_lane_stats.rewritten_reductions != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 " reductions=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
|
||||
}
|
||||
// Cleanup also exposes profitable folds by removing dead predicate consumers.
|
||||
simplify();
|
||||
if (decoded.has_swap_pc) {
|
||||
// Leaf returns are resolved CFG edges; native PC values must not escape into GPU data.
|
||||
for (const auto* block: ir.blocks)
|
||||
for (const auto& inst: *block)
|
||||
EXIT_NOT_IMPLEMENTED(inst.GetOpcode() == IR::ValueOpcode::GetShaderBase);
|
||||
}
|
||||
LowerTessellationMemory(ir, options);
|
||||
std::string cfg_dump;
|
||||
@@ -616,7 +817,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
}
|
||||
IR::TrackResources(ir, decoded, native_cfg);
|
||||
IR::TrackResources(ir, decoded, native_cfg, options.input_info);
|
||||
TranslateResult result;
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
@@ -657,7 +858,11 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
const auto resource = inst.Flags<uint32_t>();
|
||||
first = resource < ir.info.images.size() &&
|
||||
ir.info.images[resource].indirect_root == resource ? 1u : 0u;
|
||||
} else if (op != IR::ValueOpcode::GetSamplerResource) {
|
||||
} else if (op == IR::ValueOpcode::GetSamplerResource) {
|
||||
const auto resource = inst.Flags<uint32_t>();
|
||||
first = resource < ir.info.samplers.size() &&
|
||||
!ir.info.samplers[resource].indirect_resources.empty() ? 1u : 0u;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
for (size_t index = first; index < inst.NumArgs(); index++) {
|
||||
@@ -666,24 +871,22 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
}
|
||||
}
|
||||
ir.value_storage.clear();
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::RemoveIdentities(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
|
||||
IR::CollectShaderInfo(ir, options.input_info);
|
||||
IR::AllocateBindings(ir, push_data_start_dword,
|
||||
ir.stage == ShaderType::Compute && options.input_info.compute != nullptr &&
|
||||
options.input_info.compute->lds_storage);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
LOGF("%s native IR (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword,
|
||||
options.maintenance8_enabled, options.emit_debug_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -18,6 +21,8 @@ struct CompileOptions {
|
||||
uint64_t shader_hash = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
bool emit_debug_info = false;
|
||||
bool maintenance8_enabled = false;
|
||||
const char* dump_label = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
std::span<const uint32_t> back_code;
|
||||
@@ -37,6 +42,32 @@ struct CompileResult {
|
||||
IR::Program program;
|
||||
};
|
||||
|
||||
// Decoded source and typed scalar call queries are retained once per cache source.
|
||||
struct ShaderSource {
|
||||
struct Call {
|
||||
uint32_t instruction;
|
||||
std::array<IR::Value, 2> target;
|
||||
};
|
||||
struct Linked {
|
||||
std::vector<uint32_t> code;
|
||||
Decoder::Program decoded;
|
||||
std::vector<uint32_t> observed_function;
|
||||
};
|
||||
std::vector<uint32_t> code;
|
||||
Decoder::Program decoded;
|
||||
IR::ResourcePlan call_targets;
|
||||
std::optional<Call> call;
|
||||
std::optional<Linked> linked;
|
||||
uint64_t revision = 0;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
||||
};
|
||||
|
||||
[[nodiscard]] ShaderSource PrepareShaderSource(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtRuntime& runtime);
|
||||
[[nodiscard]] TranslateResult TranslateProgram(const Decoder::Program& decoded,
|
||||
const CompileOptions& options);
|
||||
|
||||
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
|
||||
|
||||
@@ -318,7 +318,7 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
|
||||
}
|
||||
}
|
||||
ConstantPropagationPass(program.blocks, program.wave_size);
|
||||
RemoveIdentities(program.blocks);
|
||||
RemoveIdentities(program);
|
||||
EliminateDeadCode(program.blocks);
|
||||
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
|
||||
options.stage == ShaderType::Local ? "LS"
|
||||
|
||||
@@ -160,6 +160,14 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_
|
||||
AppendInstruction(m_entry_points, spv::OpEntryPoint, operands);
|
||||
}
|
||||
|
||||
uint32_t Builder::AddString(const char* text) {
|
||||
const auto id = AllocateId();
|
||||
std::vector<uint32_t> operands = {id};
|
||||
AppendString(operands, text);
|
||||
AppendInstruction(m_debug, spv::OpString, operands);
|
||||
return id;
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
@@ -171,10 +179,12 @@ void Builder::AddFunction(std::span<const uint32_t> words) {
|
||||
}
|
||||
|
||||
DeferredPhi Builder::AddDeferredPhi(uint32_t type, uint32_t result, size_t incoming_count) {
|
||||
std::vector<uint32_t> words {spv::OpPhi, type, result};
|
||||
words.resize(words.size() + incoming_count * 2u);
|
||||
const DeferredPhi phi {m_functions.size()};
|
||||
AddFunction(words);
|
||||
const auto word_count = static_cast<uint32_t>(3u + incoming_count * 2u);
|
||||
m_functions.push_back((word_count << spv::WordCountShift) | spv::OpPhi);
|
||||
m_functions.push_back(type);
|
||||
m_functions.push_back(result);
|
||||
m_functions.resize(m_functions.size() + incoming_count * 2u);
|
||||
m_unpatched_phi_incomings += incoming_count;
|
||||
return phi;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ public:
|
||||
AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...);
|
||||
}
|
||||
|
||||
uint32_t AddString(const char* text);
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
@@ -19,92 +19,14 @@ namespace {
|
||||
}
|
||||
|
||||
void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
|
||||
std::array<std::vector<uint32_t>, KindCount> expected;
|
||||
std::array<bool, KindCount> present {};
|
||||
const auto Dense = [](size_t size) {
|
||||
std::vector<uint32_t> values(size);
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
values[i] = i;
|
||||
}
|
||||
return values;
|
||||
std::vector<bool> live_buffers(program.info.buffers.size());
|
||||
bool uses_lds = false;
|
||||
bool uses_gds = false;
|
||||
const auto uses_mapping = [](const auto& resource) {
|
||||
return resource.indirect_root != UINT32_MAX;
|
||||
};
|
||||
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
|
||||
const auto index = static_cast<size_t>(kind);
|
||||
present[index] = true;
|
||||
expected[index] = std::move(resources);
|
||||
};
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = IR::DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(*kind)] = true;
|
||||
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::Dynamic;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
}
|
||||
expected[static_cast<size_t>(*kind)].insert(expected[static_cast<size_t>(*kind)].end(),
|
||||
count, i);
|
||||
}
|
||||
if (!program.info.samplers.empty()) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
auto& buffers = expected[static_cast<size_t>(Kind::Buffers)];
|
||||
const auto shared = IR::CollectMemoryResources(program, buffers);
|
||||
present[static_cast<size_t>(Kind::Buffers)] = !buffers.empty();
|
||||
if (shared.gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (shared.lds && lds_storage) {
|
||||
Expect(Kind::SharedMemory);
|
||||
}
|
||||
if (program.info.uses_dma) {
|
||||
Expect(Kind::BdaPagetable);
|
||||
Expect(Kind::FaultBuffer);
|
||||
}
|
||||
if (IR::UsesFlattenedSrt(program)) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
|
||||
Expect(Kind::ShaderData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
if (kind >= KindCount || seen[kind] || !present[kind] ||
|
||||
binding.resources != expected[kind]) {
|
||||
Fail(program, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
seen[kind] = true;
|
||||
}
|
||||
for (size_t i = 0; i < KindCount; i++) {
|
||||
if (present[i] != seen[i]) {
|
||||
Fail(program, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
const auto user_data_dwords = program.bindings.user_data_registers.size();
|
||||
const bool has_dispatch_threads = program.bindings.dispatch_thread_dword != IR::PushData::NoStart;
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
(has_dispatch_threads && (program.stage != ShaderType::Compute ||
|
||||
program.bindings.dispatch_thread_dword != user_data_dwords)) ||
|
||||
program.bindings.memory_offset_dword != user_data_dwords + (has_dispatch_threads ? 3u : 0u) ||
|
||||
program.bindings.memory_offset_count != buffers.size() ||
|
||||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
|
||||
bool uses_flattened_srt = std::ranges::any_of(program.info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(program.info.images, uses_mapping);
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
return !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
@@ -141,6 +63,39 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
uses_flattened_srt |= op == IR::ValueOpcode::ReadConst;
|
||||
if (IR::BufferAccessOf(op) != IR::BufferAccess::None ||
|
||||
IR::SharedAccessOf(op) != IR::SharedAccess::None ||
|
||||
IR::AddressOpcodeInfoOf(op).access != IR::AddressAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "typed shader contains invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[index];
|
||||
if (!memory.planning_only) {
|
||||
uses_lds |= memory.kind == IR::ResourceKind::FlatLocal;
|
||||
if (IR::SharedAccessOf(op) != IR::SharedAccess::None) {
|
||||
if (memory.kind != IR::ResourceKind::Lds && memory.kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
uses_gds |= memory.kind == IR::ResourceKind::Gds;
|
||||
uses_lds |= memory.kind == IR::ResourceKind::Lds;
|
||||
} else if (memory.kind == IR::ResourceKind::Buffer ||
|
||||
memory.kind == IR::ResourceKind::ScalarBuffer) {
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "typed shader contains an invalid buffer resource");
|
||||
}
|
||||
live_buffers[memory.resource] = true;
|
||||
for (const auto child: program.info.buffers[memory.resource].indirect_resources) {
|
||||
if (child >= program.info.buffers.size()) {
|
||||
Fail(program, "typed shader contains an invalid indirect buffer resource");
|
||||
}
|
||||
live_buffers[child] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::GetBufferResource:
|
||||
@@ -151,6 +106,11 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
Fail(program, "typed buffer handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetBufferResourceIndex:
|
||||
if (dense >= program.info.buffers.size()) {
|
||||
Fail(program, "typed buffer selector has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetAddressResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
break;
|
||||
@@ -186,163 +146,66 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
}
|
||||
}
|
||||
}
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
std::array<uint32_t, static_cast<size_t>(Kind::Count)> expected {};
|
||||
auto& buffer_count = expected[static_cast<size_t>(Kind::Buffers)];
|
||||
for (uint32_t index = 0; index < program.info.buffers.size(); ++index) {
|
||||
const auto slot = live_buffers[index] ? buffer_count++ : UINT32_MAX;
|
||||
if (program.info.buffers[index].live != live_buffers[index] ||
|
||||
program.info.buffers[index].descriptor_index != slot) {
|
||||
Fail(program, "native buffer descriptor index does not match shader topology");
|
||||
}
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
if (image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
}
|
||||
auto& count = expected[static_cast<size_t>(*kind)];
|
||||
if (image.descriptor_index != count) {
|
||||
Fail(program, "native image descriptor index does not match shader topology");
|
||||
}
|
||||
count += image.mip_count;
|
||||
}
|
||||
expected[static_cast<size_t>(Kind::Samplers)] = static_cast<uint32_t>(program.info.samplers.size());
|
||||
expected[static_cast<size_t>(Kind::Gds)] = uses_gds;
|
||||
expected[static_cast<size_t>(Kind::SharedMemory)] = uses_lds && lds_storage;
|
||||
expected[static_cast<size_t>(Kind::BdaPagetable)] = program.info.uses_dma;
|
||||
expected[static_cast<size_t>(Kind::FaultBuffer)] = program.info.uses_dma;
|
||||
expected[static_cast<size_t>(Kind::FlattenedSrt)] = uses_flattened_srt;
|
||||
expected[static_cast<size_t>(Kind::ShaderData)] =
|
||||
program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData();
|
||||
uint64_t descriptor_mask = 0;
|
||||
for (uint32_t index = 0; index < expected.size(); ++index) {
|
||||
if (expected[index] != 0) descriptor_mask |= uint64_t{1} << index;
|
||||
}
|
||||
if (program.bindings.descriptor_counts != expected ||
|
||||
program.bindings.descriptor_mask != descriptor_mask) {
|
||||
Fail(program, "native descriptor counts do not match shader topology");
|
||||
}
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
const auto& registers = program.info.user_data_registers;
|
||||
const bool has_dispatch_threads = program.bindings.dispatch_thread_dword != IR::PushData::NoStart;
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
(has_dispatch_threads && (program.stage != ShaderType::Compute ||
|
||||
program.bindings.dispatch_thread_dword != registers.size())) ||
|
||||
program.bindings.memory_offset_dword != registers.size() + (has_dispatch_threads ? 3u : 0u) ||
|
||||
!std::is_sorted(registers.begin(), registers.end()) ||
|
||||
std::adjacent_find(registers.begin(), registers.end()) != registers.end()) {
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program& program) {
|
||||
SpirvRequirements requirements {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
requirements.float64 |= inst.GetType() == IR::Type::F64;
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
|
||||
inst.GetType() == IR::Type::U64) {
|
||||
requirements.buffer_int64_atomics = true;
|
||||
}
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "address operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
const auto kind = memory.kind;
|
||||
if (kind == IR::ResourceKind::Scratch || kind == IR::ResourceKind::FlatLocal) {
|
||||
if (program.scratch_dwords == 0) {
|
||||
Fail(program, "scratch operation has no per-thread storage");
|
||||
}
|
||||
requirements.function_scratch = true;
|
||||
if (kind == IR::ResourceKind::FlatLocal && program.stage != ShaderType::Compute &&
|
||||
program.stage != ShaderType::Mesh) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
} else if (address_access == IR::AddressAccess::Write) {
|
||||
Fail(program, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
|
||||
} else {
|
||||
// Descriptor stores can alias coherent physical-address loads.
|
||||
requirements.coherent_buffers |= memory.coherent;
|
||||
}
|
||||
}
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind == IR::ResourceKind::IndirectBuffer &&
|
||||
inst.GetOpcode() != IR::ValueOpcode::ReadConstBuffer) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
if (memory.kind == IR::ResourceKind::Buffer) {
|
||||
requirements.coherent_buffers |= memory.coherent;
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "buffer operation has invalid resource metadata");
|
||||
}
|
||||
const auto bits = StorageBufferElementBits(program, memory);
|
||||
requirements.buffer_u8 |= bits == 8u;
|
||||
requirements.buffer_u16 |= bits == 16u;
|
||||
if ((program.info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
|
||||
if (program.stage != ShaderType::Compute) {
|
||||
Fail(program, "buffer ADD_TID is only valid for compute shaders");
|
||||
}
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto shared_access = IR::SharedAccessOf(inst.GetOpcode());
|
||||
if (shared_access != IR::SharedAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Atomic &&
|
||||
IR::SharedComponentCount(inst.GetOpcode()) == 2u) {
|
||||
if (kind != IR::ResourceKind::Lds || program.stage != ShaderType::Compute) {
|
||||
Fail(program, "64-bit shared atomics require compute LDS");
|
||||
}
|
||||
requirements.shared_int64_atomics = true;
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh &&
|
||||
kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Append ||
|
||||
shared_access == IR::SharedAccess::Consume) {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::StoreCompletion: requirements.subgroup_barrier = true; break;
|
||||
case IR::ValueOpcode::BvhIntersect: requirements.bvh = true; break;
|
||||
case IR::ValueOpcode::ConditionRef:
|
||||
requirements.subgroup_ballot |=
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
break;
|
||||
case IR::ValueOpcode::Ballot: requirements.subgroup_ballot = true; break;
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::DppMoveU32) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::Permlane16U32: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::PermuteU32:
|
||||
case IR::ValueOpcode::BpermuteU32: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::LaneId:
|
||||
requirements.subgroup_local_invocation_id |=
|
||||
program.stage != ShaderType::TessellationControl;
|
||||
break;
|
||||
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
|
||||
case IR::ValueOpcode::ImageGatherRaw:
|
||||
requirements.image_gather_extended = true;
|
||||
break;
|
||||
case IR::ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (index >= program.export_info.size()) {
|
||||
Fail(program, "attribute export has invalid metadata");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
program.export_info[index].vm) {
|
||||
requirements.pixel_valid_mask = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return requirements;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
ShaderStageInputInfo input_info) {
|
||||
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
|
||||
uint32_t push_data_start_dword, bool maintenance8_enabled,
|
||||
bool emit_debug_info) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
@@ -352,19 +215,26 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete) {
|
||||
!program.shader_info_complete) {
|
||||
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
|
||||
}
|
||||
ValidateNativeProgram(program, program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage);
|
||||
IR::ValidateProgram(program, true);
|
||||
EmitterState state(program, input_info);
|
||||
program.bindings = {.push_data_start_dword = push_data_start_dword};
|
||||
EmitterState state(program, input_info, maintenance8_enabled);
|
||||
if (emit_debug_info) {
|
||||
char name[64];
|
||||
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
|
||||
program.shader_hash);
|
||||
state.debug_source = state.builder.AddString(name);
|
||||
}
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u
|
||||
? 2u
|
||||
: 1u;
|
||||
DefineModule(state);
|
||||
ValidateNativeProgram(program, program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage);
|
||||
EmitProgram(state);
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.program.stage), state.main_func,
|
||||
"main", state.interface_variables);
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
ShaderStageInputInfo input_info);
|
||||
// Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently.
|
||||
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
|
||||
uint32_t push_data_start_dword = 0,
|
||||
bool maintenance8_enabled = false,
|
||||
bool emit_debug_info = false);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
|
||||
@@ -56,33 +56,14 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
|
||||
return signed_value ? Unary(state, spv::OpBitcast, TypeU32(state), high) : high;
|
||||
}
|
||||
|
||||
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
|
||||
bool max_value) {
|
||||
const auto ab = signed_value ? EmitMinMaxI32Value(state, a, b, max_value)
|
||||
: EmitMinMaxU32Value(state, a, b, max_value);
|
||||
return signed_value ? EmitMinMaxI32Value(state, ab, c, max_value)
|
||||
: EmitMinMaxU32Value(state, ab, c, max_value);
|
||||
}
|
||||
|
||||
uint32_t EmitMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value) {
|
||||
const auto minimum = EmitMinMax3(state, a, b, c, signed_value, false);
|
||||
const auto maximum = EmitMinMax3(state, a, b, c, signed_value, true);
|
||||
const auto ab = Binary(state, spv::OpIAdd, TypeU32(state), a, b);
|
||||
const auto abc = Binary(state, spv::OpIAdd, TypeU32(state), ab, c);
|
||||
return Binary(state, spv::OpISub, TypeU32(state),
|
||||
Binary(state, spv::OpISub, TypeU32(state), abc, minimum), maximum);
|
||||
}
|
||||
|
||||
uint32_t EmitFMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool max_value) {
|
||||
return EmitMinMaxF32Value(state, EmitMinMaxF32Value(state, a, b, max_value), c, max_value);
|
||||
}
|
||||
|
||||
uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
std::initializer_list<uint32_t> args) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {spv::OpExtInst, type, result, GlslStd450(state), opcode};
|
||||
words.insert(words.end(), args.begin(), args.end());
|
||||
state.builder.AddFunction(words);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpExtInst, type, result, GlslStd450(state), opcode, args);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -229,10 +210,23 @@ uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value
|
||||
}
|
||||
|
||||
uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
const auto low = EmitF32ToF16RtzBits(state, arg0);
|
||||
const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
EmitF32ToF16RtzBits(state, arg1), ConstantU32(state, 16));
|
||||
return Binary(state, spv::OpBitwiseOr, TypeU32(state), low, high);
|
||||
const auto pair = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, arg1);
|
||||
const auto packed = EmitPackHalf2x16(state, pair);
|
||||
const auto back = EmitExt(state, TypeF32Vector(state, 2), GLSLstd450UnpackHalf2x16, {packed});
|
||||
auto result = packed;
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
const auto rounded = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), rounded, back, i);
|
||||
const auto above =
|
||||
Binary(state, spv::OpFOrdGreaterThan, TypeBool(state),
|
||||
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {rounded}),
|
||||
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {i == 0 ? arg0 : arg1}));
|
||||
result = Binary(state, spv::OpISub, TypeU32(state), result,
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, i == 0 ? 1u : 0x10000u), ConstantU32(state, 0)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitFPSaturate32(EmitterState& state, uint32_t arg0) {
|
||||
@@ -248,14 +242,6 @@ uint32_t EmitUMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMulHigh(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = arg0;
|
||||
const auto neg = Unary(state, spv::OpSNegate, TypeU32(state), value);
|
||||
const auto negative =
|
||||
Binary(state, spv::OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), negative, neg, value);
|
||||
}
|
||||
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0) {
|
||||
// Vulkan bit-count operands remain 32-bit, including when counting a U64.
|
||||
const auto unpacked = Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), arg0);
|
||||
@@ -264,58 +250,30 @@ uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0) {
|
||||
return Binary(state, spv::OpIAdd, TypeU32(state), pair.low, pair.high);
|
||||
}
|
||||
|
||||
uint32_t EmitFindILsb32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindILsb, {arg0});
|
||||
return Unary(state, spv::OpBitcast, TypeU32(state), value);
|
||||
}
|
||||
|
||||
uint32_t EmitFindUMsb32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindUMsb, {arg0});
|
||||
return Unary(state, spv::OpBitcast, TypeU32(state), value);
|
||||
}
|
||||
|
||||
uint32_t EmitSMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxI32Value(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitSMax32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxI32Value(state, arg0, arg1, true);
|
||||
}
|
||||
|
||||
uint32_t EmitUMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxU32Value(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitUMax32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxU32Value(state, arg0, arg1, true);
|
||||
}
|
||||
|
||||
uint32_t EmitSMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, true, false);
|
||||
return EmitSMin32(state, arg0, EmitSMin32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitSMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, true, true);
|
||||
return EmitSMax32(state, arg0, EmitSMax32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitUMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, false, false);
|
||||
return EmitUMin32(state, arg0, EmitUMin32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitUMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, false, true);
|
||||
return EmitUMax32(state, arg0, EmitUMax32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitSMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMed3(state, arg0, arg1, arg2, true);
|
||||
const auto high_min = EmitSMin32(state, EmitSMax32(state, arg0, arg1), arg2);
|
||||
return EmitSMax32(state, EmitSMin32(state, arg0, arg1), high_min);
|
||||
}
|
||||
|
||||
uint32_t EmitUMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMed3(state, arg0, arg1, arg2, false);
|
||||
}
|
||||
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0) {
|
||||
return EmitNative<spv::OpFUnordNotEqual, IR::Type::U1>(state, arg0, arg0);
|
||||
const auto high_min = EmitUMin32(state, EmitUMax32(state, arg0, arg1), arg2);
|
||||
return EmitUMax32(state, EmitUMin32(state, arg0, arg1), high_min);
|
||||
}
|
||||
|
||||
uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
|
||||
@@ -9,13 +9,6 @@ uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), ret, value,
|
||||
ConstantU32(state, shift));
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
|
||||
uint32_t constant) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
@@ -23,94 +16,12 @@ uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t va
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant),
|
||||
value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, f32);
|
||||
|
||||
const auto sign = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 16), 0x8000u);
|
||||
const auto exp = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 23), 0xffu);
|
||||
const auto mant = EmitAndConstant(state, bits, 0x007fffffu);
|
||||
|
||||
const auto half_exp = state.builder.AllocateId();
|
||||
const auto normal_exp = state.builder.AllocateId();
|
||||
const auto normal_mant = EmitShiftRightConstant(state, mant, 13);
|
||||
const auto normal_payload = state.builder.AllocateId();
|
||||
const auto normal = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp,
|
||||
ConstantU32(state, 10));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp,
|
||||
normal_mant);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload);
|
||||
|
||||
const auto mant_with_hidden = EmitOrU32(state, mant, ConstantU32(state, 0x00800000u));
|
||||
const auto raw_sub_shift = EmitSubConstantMinusU32(state, 126, exp);
|
||||
const auto exp_lt_103 = EmitCompareU32Constant(state, spv::OpULessThan, exp, 103);
|
||||
const auto exp_gt_112 = EmitCompareU32Constant(state, spv::OpUGreaterThan, exp, 112);
|
||||
const auto sub_shift_low =
|
||||
EmitSelectValueU32(state, exp_lt_103, ConstantU32(state, 31), raw_sub_shift);
|
||||
const auto sub_shift =
|
||||
EmitSelectValueU32(state, exp_gt_112, ConstantU32(state, 14), sub_shift_low);
|
||||
const auto sub_mant = state.builder.AllocateId();
|
||||
const auto subnormal = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden,
|
||||
sub_shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant);
|
||||
|
||||
const auto nan_payload =
|
||||
EmitOrU32(state, EmitShiftRightConstant(state, mant, 13), ConstantU32(state, 0x0200u));
|
||||
const auto nan =
|
||||
EmitOrU32(state, sign, EmitOrU32(state, ConstantU32(state, 0x7c00u), nan_payload));
|
||||
const auto inf = EmitOrU32(state, sign, ConstantU32(state, 0x7c00u));
|
||||
const auto max_finite = EmitOrU32(state, sign, ConstantU32(state, 0x7bffu));
|
||||
const auto mant_zero = EmitCompareU32Constant(state, spv::OpIEqual, mant, 0);
|
||||
const auto special = EmitSelectValueU32(state, mant_zero, inf, nan);
|
||||
|
||||
const auto exp_le_112 = EmitCompareU32Constant(state, spv::OpULessThanEqual, exp, 112);
|
||||
const auto exp_ge_143 = EmitCompareU32Constant(state, spv::OpUGreaterThanEqual, exp, 143);
|
||||
const auto exp_eq_255 = EmitCompareU32Constant(state, spv::OpIEqual, exp, 255);
|
||||
const auto finite0 = EmitSelectValueU32(state, exp_le_112, subnormal, normal);
|
||||
const auto finite1 = EmitSelectValueU32(state, exp_lt_103, sign, finite0);
|
||||
const auto finite2 = EmitSelectValueU32(state, exp_ge_143, max_finite, finite1);
|
||||
return EmitAndConstant(state, EmitSelectValueU32(state, exp_eq_255, special, finite2), 0xffffu);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
|
||||
F32Class cls;
|
||||
cls.bits = bits;
|
||||
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
|
||||
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
|
||||
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
|
||||
const auto exponent_max =
|
||||
EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0x7f800000u);
|
||||
const auto mantissa_nonzero = EmitCompareU32Constant(state, spv::OpINotEqual, mantissa_bits, 0);
|
||||
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
|
||||
cls.zero = EmitCompareU32Constant(state, spv::OpIEqual, abs_bits, 0);
|
||||
cls.bits = bits;
|
||||
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
|
||||
cls.nan = EmitCompareU32Constant(state, spv::OpUGreaterThan, abs_bits, 0x7f800000u);
|
||||
cls.zero = EmitCompareU32Constant(state, spv::OpIEqual, abs_bits, 0);
|
||||
return cls;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,21 +59,6 @@ uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource) {
|
||||
const auto* descriptor = IR::FindBinding(state.program.bindings, kind);
|
||||
if (descriptor == nullptr) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource, "descriptor group was not allocated");
|
||||
}
|
||||
const auto found =
|
||||
std::find(descriptor->resources.begin(), descriptor->resources.end(), resource);
|
||||
if (found == descriptor->resources.end()) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource,
|
||||
"resource is absent from descriptor group");
|
||||
}
|
||||
return static_cast<uint32_t>(found - descriptor->resources.begin());
|
||||
}
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
@@ -152,43 +137,62 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capability capability) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(capability);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
const auto image = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, ImageType(state, state.program.info.images.at(resource)),
|
||||
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
image, pointer);
|
||||
if (array_index != 0u) {
|
||||
DecorateNonUniformDescriptor(state, image,
|
||||
IR::ImageBindingResourceClass(kind) == IR::ImageResourceClass::Storage
|
||||
? spv::CapabilityStorageImageArrayNonUniformIndexing
|
||||
: spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto sampler_type = state.builder.Type(spv::OpTypeSampler);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, sampler_type);
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t array_index) {
|
||||
if (sampler >= state.program.info.samplers.size()) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Samplers, sampler,
|
||||
"sampler resource index is out of range");
|
||||
}
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, pointer_type, state.sampler_variable, ConstantU32(state, array_index),
|
||||
state, state.sampler_pointer_type, state.sampler_variable,
|
||||
array_index == 0u ? ConstantU32(state, sampler) : array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, sampler_type, sampler_id, pointer);
|
||||
state.builder.AddFunction(spv::OpLoad, state.sampler_type, sampler_id, pointer);
|
||||
if (array_index != 0u)
|
||||
DecorateNonUniformDescriptor(state, sampler_id, spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
uint32_t array_index, bool sampler_dynamic) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
|
||||
const auto kind = *IR::DescriptorBindingForImage(image_resource);
|
||||
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
|
||||
if (sampled_type == 0) {
|
||||
sampled_type = state.builder.Type(
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
if (array_index != 0u) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, sampled_image, spv::DecorationNonUniform);
|
||||
if (array_index != 0u || sampler_dynamic) {
|
||||
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
}
|
||||
return sampled_image;
|
||||
}
|
||||
@@ -200,24 +204,23 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
if (array_index == 0u) {
|
||||
array_index = ConstantU32(state, ResourceForDescriptor(state, *kind, resource) + mip);
|
||||
array_index = ConstantU32(state, image.descriptor_index + mip);
|
||||
}
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
|
||||
array_index, *kind, resource,
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel) {
|
||||
uint32_t texel, uint32_t array_index) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip),
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
|
||||
coord, texel);
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
|
||||
@@ -223,7 +223,7 @@ uint32_t Boxes(EmitterState& s, uint32_t first, uint32_t second, uint32_t half,
|
||||
} // namespace
|
||||
|
||||
void DefineBvhIntersect(EmitterState& s) {
|
||||
if (!s.requirements.bvh) return;
|
||||
if (!s.program.info.bvh) return;
|
||||
const auto u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), wide = TypeU64(s);
|
||||
const auto vec4 = TypeU32Vector(s, 4), vec3 = TypeF32Vector(s, 3);
|
||||
const auto signature = s.builder.Type(spv::OpTypeFunction, vec4, vec4, wide, f, vec3, vec3, vec3);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace {
|
||||
|
||||
bool UserDataDwordIndex(const EmitterState& state, IR::ScalarReg reg, uint32_t& dword_index) {
|
||||
const auto register_index = IR::RegIndex(reg);
|
||||
const auto& registers = state.program.bindings.user_data_registers;
|
||||
const auto& registers = state.program.info.user_data_registers;
|
||||
const auto found = std::lower_bound(registers.begin(), registers.end(), register_index);
|
||||
if (found == registers.end() || *found != register_index) {
|
||||
return false;
|
||||
@@ -177,6 +177,13 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
return value;
|
||||
};
|
||||
if (input->per_vertex) {
|
||||
if (PixelParameterIsFlat(state, attr)) {
|
||||
const auto value = load_per_vertex(
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
|
||||
? IR::StageInputKind::BaryCoordNoPerspective
|
||||
: IR::StageInputKind::BaryCoordSmooth;
|
||||
@@ -231,8 +238,12 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
|
||||
return value;
|
||||
};
|
||||
|
||||
const auto selected_vertex = (mode + 1u) % 3u;
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
uint32_t selected_vertex = (mode + 1u) % 3u;
|
||||
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
|
||||
selected_vertex =
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
|
||||
}
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
|
||||
const auto delta = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
|
||||
@@ -293,50 +304,46 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
f32[0], f32[1], f32[2], f32[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t raw[4] = {
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
}
|
||||
const auto packed = ExportRawComponent(ctx, data, pair);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) == 0u) {
|
||||
if (exp.compr || exp.en != 0xfu) {
|
||||
uint32_t raw[4] = {
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
const auto packed = ExportRawComponent(ctx, data, pair);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) == 0u) {
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
raw[component] = ExportRawComponent(ctx, data, component);
|
||||
} else {
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
raw[component] = ExportRawComponent(ctx, data, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
}
|
||||
if (uint_output) {
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t f32[4] {};
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
f32[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32[component], raw[component]);
|
||||
return data;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
|
||||
f32[1], f32[2], f32[3]);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
||||
return vector;
|
||||
}
|
||||
|
||||
@@ -461,7 +468,7 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& exp = ctx.Export(inst);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
if (state.program.stage == ShaderType::Pixel && exp.vm && state.requirements.pixel_valid_mask &&
|
||||
if (state.program.stage == ShaderType::Pixel && exp.vm && state.program.info.pixel_valid_mask &&
|
||||
state.pixel_valid_mask_variable != 0) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), value, exec, ConstantU32(state, 1),
|
||||
@@ -560,6 +567,28 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
mapping.Map(3));
|
||||
value = mapped;
|
||||
}
|
||||
if ((state.input_info.pixel->target_packed_unorm_mask & (1u << exp.index)) != 0u) {
|
||||
if (exp.compr || MrtOutputMode(state, exp) != 9u ||
|
||||
(mapping.ApplyMask(exp.en) & 7u) != 7u) {
|
||||
EXIT("unsupported packed UNorm EXP: export=%u mode=%u en=0x%x compr=%u\n",
|
||||
exp.index, MrtOutputMode(state, exp), exp.en, static_cast<uint32_t>(exp.compr));
|
||||
}
|
||||
const auto info = Format::GetFormatInfo(Prospero::BufferFormat::k11_11_10UNorm);
|
||||
const auto rounding = (state.input_info.pixel->target_round_mode & (1u << exp.index)) != 0
|
||||
? UnormRounding::Truncate : UnormRounding::HalfUp;
|
||||
const auto raw = Unary(state, spv::OpBitcast, TypeU32Vector(state, 4), value);
|
||||
auto packed = ConstantU32(state, 0);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
const auto encoded = EncodePackedColorComponent(
|
||||
state, info, component, ExportRawComponent(ctx, raw, component), 32u, rounding);
|
||||
packed = component == 0u ? encoded
|
||||
: Binary(state, spv::OpBitwiseOr, TypeU32(state), packed, encoded);
|
||||
}
|
||||
value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), value,
|
||||
packed, ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0));
|
||||
}
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Position &&
|
||||
state.input_info.vertex->clip_space.enabled) {
|
||||
@@ -647,17 +676,11 @@ uint32_t EmitLaneId(EmitterState& state) {
|
||||
|
||||
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto result = state.builder.AllocateId();
|
||||
const auto index = inst.Arg(0).U32();
|
||||
if (state.program.stage != ShaderType::Mesh || index >= IR::PushData::MeshDrawDwordCount) {
|
||||
ctx.Fail(inst, "invalid mesh draw parameter");
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
|
||||
state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, index));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), result, pointer);
|
||||
return result;
|
||||
return EmitPushDataLoad(state, ConstantU32(state, index));
|
||||
}
|
||||
|
||||
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg) {
|
||||
@@ -714,17 +737,22 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
// Scalar branches include lanes disabled by EXEC, which the branch body may restore.
|
||||
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Result(inst);
|
||||
const auto kind = inst.Flags<CFG::BranchCondition>();
|
||||
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto reference = zero ? ctx.Ballot(IR::Value(true))
|
||||
: ConstantU32CompositeZero(ctx.state, 4);
|
||||
const auto compare = Binary(ctx.state, zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBoolVector(ctx.state, 4), ballot, reference);
|
||||
return Unary(ctx.state, zero ? spv::OpAll : spv::OpAny, TypeBool(ctx.state), compare);
|
||||
// Zero-kind predicates are already inverted by translation.
|
||||
auto predicate = ctx.Arg(inst, 0);
|
||||
if (ctx.other_half != nullptr) {
|
||||
predicate = Binary(ctx.state, zero ? spv::OpLogicalAnd : spv::OpLogicalOr,
|
||||
TypeBool(ctx.state), predicate, ctx.HalfArg(inst, 0, 1));
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpGroupNonUniformAll : spv::OpGroupNonUniformAny,
|
||||
TypeBool(ctx.state), result,
|
||||
ConstantU32(ctx.state, spv::ScopeSubgroup), predicate);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
@@ -741,6 +769,18 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
auto value = ctx.Arg(inst, 0);
|
||||
if (ctx.other_half != nullptr) {
|
||||
value = EmitUMin32(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto hit = state.builder.AllocateId();
|
||||
|
||||
@@ -115,6 +115,16 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& fla
|
||||
if (control == 0x141u) {
|
||||
return EmitDppMirrorTargetLane(state, subid, true);
|
||||
}
|
||||
// row_share:N reads lane N of the caller's row of 16.
|
||||
if (control >= 0x150u && control <= 0x15fu) {
|
||||
const auto row = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
|
||||
ConstantU32(state, 0xfffffff0u));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row,
|
||||
ConstantU32(state, control & 0xfu));
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
if (control >= 0x160u && control <= 0x16fu) {
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid,
|
||||
|
||||
@@ -220,6 +220,10 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
Prospero::TextureNumericClass numeric_class, bool dref,
|
||||
const IR::MemoryInfo& mem, bool gather = false) {
|
||||
auto value_class = numeric_class;
|
||||
const auto conversion = ctx.state.program.info.images[mem.resource].conversion_format;
|
||||
if (conversion != Prospero::BufferFormat::kInvalid) {
|
||||
value_class = Prospero::SampledTextureNumericClass(conversion);
|
||||
}
|
||||
if (dref) {
|
||||
value_class = Prospero::TextureNumericClass::Float;
|
||||
}
|
||||
@@ -269,20 +273,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
result, packed[0], packed[1], packed[2], packed[3]);
|
||||
return result;
|
||||
}
|
||||
uint32_t component[4] {};
|
||||
for (uint32_t index = 0; index < 4u; index++) {
|
||||
if (dref) {
|
||||
component[index] = index == 0u ? FloatBits(ctx, value) : ConstantU32(ctx.state, 0);
|
||||
continue;
|
||||
}
|
||||
const auto scalar = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index);
|
||||
component[index] = SampledComponentBits(ctx, scalar, value_class);
|
||||
}
|
||||
if (!dref)
|
||||
return value_class == Prospero::TextureNumericClass::Uint
|
||||
? value : Unary(ctx.state, spv::OpBitcast, TypeU32Vector(ctx.state, 4), value);
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
const auto zero = ConstantU32(ctx.state, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
component[0], component[1], component[2], component[3]);
|
||||
FloatBits(ctx, value), zero, zero, zero);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -323,25 +320,26 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
return vector;
|
||||
}
|
||||
|
||||
uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
const ImageSampleLayout& layout, ImageDimension dimension) {
|
||||
uint32_t PackedOffset(ValueEmitContext& ctx, IR::Value value, ImageDimension dimension,
|
||||
uint32_t& mask) {
|
||||
const auto components = ImageDimensionInfoFor(dimension).spatial_components;
|
||||
const auto zero = ConstantI32(ctx.state, 0);
|
||||
if (layout.offset == NoImageComponent || mem.image_address_components <= layout.offset) {
|
||||
if (components == 1u) return zero;
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
|
||||
result, zero, zero, zero);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
|
||||
result, zero, zero);
|
||||
uint32_t values[3] {};
|
||||
if (value.IsImmediate()) {
|
||||
// RDNA2 packs signed six-bit texel offsets into the low bits of each byte.
|
||||
for (uint32_t index = 0; index < components; index++) {
|
||||
const auto bits = (value.U32() >> (index * 8u)) & 0x3fu;
|
||||
values[index] = ConstantI32(ctx.state, static_cast<int32_t>(bits ^ 0x20u) - 0x20);
|
||||
}
|
||||
return result;
|
||||
mask |= spv::ImageOperandsConstOffsetMask;
|
||||
return components == 1u
|
||||
? values[0]
|
||||
: ctx.state.builder.Constant(spv::OpConstantComposite,
|
||||
TypeI32Vector(ctx.state, components),
|
||||
std::span(values).first(components));
|
||||
}
|
||||
const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state),
|
||||
AddressU32(ctx, mem, address, layout.offset));
|
||||
uint32_t values[3] = {zero, zero, zero};
|
||||
ctx.state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
mask |= spv::ImageOperandsOffsetMask;
|
||||
const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), ctx.Def(value));
|
||||
for (uint32_t index = 0; index < components; index++) {
|
||||
values[index] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index],
|
||||
@@ -350,13 +348,8 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
|
||||
}
|
||||
if (components == 1u) return values[0];
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
|
||||
result, values[0], values[1], values[2]);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
|
||||
result, values[0], values[1]);
|
||||
}
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, components),
|
||||
result, std::span(values).first(components));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -388,17 +381,29 @@ uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) {
|
||||
|
||||
Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const IR::MemoryInfo& mem) {
|
||||
const auto format = state.program.info.images[mem.resource].conversion_format;
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
const auto format = image.conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
EXIT_IF(Prospero::RemapTextureFormat(format) == format || info.component_count == 0u ||
|
||||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
EXIT_IF(info.type != Format::ComponentType::Uscaled &&
|
||||
(info.type != Format::ComponentType::Uint || !info.packed_bitfield ||
|
||||
EXIT_IF(!raw_signed && info.type != Format::ComponentType::Uscaled &&
|
||||
((info.type != Format::ComponentType::Uint &&
|
||||
info.type != Format::ComponentType::Unorm) || !info.packed_bitfield ||
|
||||
info.byte_size != sizeof(uint32_t)));
|
||||
return info;
|
||||
}
|
||||
|
||||
uint32_t ExtractPackedImageComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t packed, uint32_t type, uint32_t component) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, type, result, packed,
|
||||
ConstantU32(state, info.component_bit_offset[component]),
|
||||
ConstantU32(state, info.component_bits[component]));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t ImageGatherSource(const EmitterState& state, const IR::MemoryInfo& mem) {
|
||||
const auto component = ImageGatherComponent(mem.dmask);
|
||||
const auto info = ImageConversionFormat(state, mem);
|
||||
@@ -422,13 +427,13 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
}
|
||||
uint32_t components[4] {};
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
components[component] = ctx.state.builder.AllocateId();
|
||||
if (info.packed_bitfield) {
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, scalar_type,
|
||||
components[component], packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, info.component_bits[component]));
|
||||
components[component] =
|
||||
ExtractPackedImageComponent(ctx.state, info, packed, scalar_type, component);
|
||||
components[component] =
|
||||
NormalizeFormatComponent(ctx.state, info, component, components[component]);
|
||||
} else {
|
||||
components[component] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
@@ -445,8 +450,9 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 1u)
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
selected[component] = info.packed_bitfield
|
||||
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
@@ -458,7 +464,8 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
return result;
|
||||
return info.type == Format::ComponentType::Unorm
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
|
||||
}
|
||||
|
||||
uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t gathered) {
|
||||
@@ -488,21 +495,74 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
|
||||
return Binary(ctx.state, spv::OpFMul, vector_type, gathered, scales);
|
||||
}
|
||||
|
||||
uint32_t values[4] {};
|
||||
for (uint32_t lane = 0; lane < 4u; lane++) {
|
||||
const auto packed = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered,
|
||||
lane);
|
||||
values[lane] = ctx.state.builder.AllocateId();
|
||||
const auto physical = (selector - 4u) % info.component_count;
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
|
||||
packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[physical]),
|
||||
ConstantU32(ctx.state, info.component_bits[physical]));
|
||||
return ExtractPackedImageComponent(ctx.state, info, gathered, TypeU32Vector(ctx.state, 4),
|
||||
(selector - 4u) % info.component_count);
|
||||
}
|
||||
|
||||
uint32_t SamplePackedUnormLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t coord) {
|
||||
auto& state = ctx.state;
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
const auto info = ImageConversionFormat(state, mem);
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto gathered = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpImageGather, TypeU32Vector(state, 4), gathered, sampled,
|
||||
coord, ConstantU32(state, 0u));
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto size = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32Vector(state, 2), size,
|
||||
LoadImageDescriptor(state, mem.resource), ConstantU32(state, 0u));
|
||||
const auto size_f32 = Unary(state, spv::OpConvertUToF, TypeF32Vector(state, 2), size);
|
||||
const auto half = ConstantF32Value(state, 0.5f);
|
||||
const auto halves = state.builder.Constant(spv::OpConstantComposite,
|
||||
TypeF32Vector(state, 2), half, half);
|
||||
const auto fractions = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpExtInst, TypeF32Vector(state, 2), fractions, GlslStd450(state), GLSLstd450Fract,
|
||||
Binary(state, spv::OpFSub, TypeF32Vector(state, 2),
|
||||
Binary(state, spv::OpFMul, TypeF32Vector(state, 2), coord, size_f32), halves));
|
||||
uint32_t fraction[2];
|
||||
uint32_t complement[2];
|
||||
for (uint32_t axis = 0; axis < 2u; axis++) {
|
||||
fraction[axis] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), fraction[axis],
|
||||
fractions, axis);
|
||||
complement[axis] = Binary(state, spv::OpFSub, TypeF32(state),
|
||||
ConstantF32Value(state, 1.0f), fraction[axis]);
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
values[0], values[1], values[2], values[3]);
|
||||
// Gather order is (x0, y1), (x1, y1), (x1, y0), (x0, y0).
|
||||
const auto weights = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpCompositeConstruct, TypeF32Vector(state, 4), weights,
|
||||
Binary(state, spv::OpFMul, TypeF32(state), complement[0], fraction[1]),
|
||||
Binary(state, spv::OpFMul, TypeF32(state), fraction[0], fraction[1]),
|
||||
Binary(state, spv::OpFMul, TypeF32(state), fraction[0], complement[1]),
|
||||
Binary(state, spv::OpFMul, TypeF32(state), complement[0], complement[1]));
|
||||
uint32_t decoded[4] {};
|
||||
uint32_t selected[4] {ZeroF32(state), ZeroF32(state), ZeroF32(state), ZeroF32(state)};
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
if ((mem.dmask & (1u << component)) == 0u) continue;
|
||||
const auto selector = (image.shader_swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = ConstantF32Value(state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
const auto physical = (selector - 4u) % info.component_count;
|
||||
if (decoded[physical] == 0u) {
|
||||
const auto texels = Unary(
|
||||
state, spv::OpConvertUToF, TypeF32Vector(state, 4),
|
||||
ExtractPackedImageComponent(state, info, gathered,
|
||||
TypeU32Vector(state, 4), physical));
|
||||
decoded[physical] = Binary(
|
||||
state, spv::OpFMul, TypeF32(state),
|
||||
Binary(state, spv::OpDot, TypeF32(state), texels, weights),
|
||||
ConstantF32Value(state, 1.0f / ((1u << info.component_bits[physical]) - 1u)));
|
||||
}
|
||||
selected[component] = decoded[physical];
|
||||
}
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), result,
|
||||
selected[0], selected[1], selected[2], selected[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -548,47 +608,23 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
|
||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
|
||||
EXIT_IF(info.type != Format::ComponentType::Uint || !info.packed_bitfield);
|
||||
|
||||
auto packed = ConstantU32(ctx.state, 0u);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
const auto value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, texel,
|
||||
component);
|
||||
const auto maximum =
|
||||
ConstantU32(ctx.state, info.component_bits[component] == 32u
|
||||
? UINT32_MAX
|
||||
: (1u << info.component_bits[component]) - 1u);
|
||||
const auto within =
|
||||
Binary(ctx.state, spv::OpULessThan, TypeBool(ctx.state), value, maximum);
|
||||
const auto clamped = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), clamped, within, value,
|
||||
maximum);
|
||||
const auto shifted =
|
||||
info.component_bit_offset[component] == 0u
|
||||
? clamped
|
||||
: Binary(ctx.state, spv::OpShiftLeftLogical, TypeU32(ctx.state), clamped,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]));
|
||||
packed = Binary(ctx.state, spv::OpBitwiseOr, TypeU32(ctx.state), packed, shifted);
|
||||
}
|
||||
const auto zero = ConstantU32(ctx.state, 0u);
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
packed, zero, zero, zero);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) {
|
||||
const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle;
|
||||
uint32_t values[4] {};
|
||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||
const bool converted = info.format != Prospero::BufferFormat::kInvalid;
|
||||
const bool packed = info.packed_bitfield;
|
||||
EXIT_IF(converted && info.type != Format::ComponentType::Sint &&
|
||||
(!packed || (info.type != Format::ComponentType::Uint &&
|
||||
info.type != Format::ComponentType::Unorm)));
|
||||
const auto zero = ConstantU32(ctx.state, 0);
|
||||
uint32_t values[4] {zero, zero, zero, zero};
|
||||
auto packed_value = zero;
|
||||
const auto dmask = mem.dmask != 0u ? mem.dmask : 1u;
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
for (uint32_t component = 0; component < (converted ? info.component_count : 4u); component++) {
|
||||
const auto source = InverseSwizzle(swizzle, component);
|
||||
uint32_t raw = ConstantU32(ctx.state, 0);
|
||||
if (source < 4u && ((dmask >> source) & 1u) != 0u) {
|
||||
const bool selected = source < 4u && ((dmask >> source) & 1u) != 0u;
|
||||
uint32_t raw = zero;
|
||||
if (selected) {
|
||||
const auto packed_index = DmaskComponentIndex(dmask, source);
|
||||
raw = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
|
||||
@@ -598,21 +634,209 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
|
||||
raw = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), raw,
|
||||
ConstantU32(ctx.state, 16u));
|
||||
}
|
||||
raw = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), raw,
|
||||
ConstantU32(ctx.state, 0xffffu));
|
||||
if (!packed) {
|
||||
raw = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), raw,
|
||||
ConstantU32(ctx.state, 0xffffu));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (converted) {
|
||||
if (!selected) continue;
|
||||
if (!packed) {
|
||||
values[component] =
|
||||
EncodeFormattedStoreComponent(ctx.state, info, component, raw, mem.data_bits);
|
||||
continue;
|
||||
}
|
||||
const auto value =
|
||||
EncodePackedColorComponent(ctx.state, info, component, raw, mem.data_bits);
|
||||
packed_value = packed_value == zero ? value
|
||||
: Binary(ctx.state, spv::OpBitwiseOr, TypeU32(ctx.state), packed_value, value);
|
||||
continue;
|
||||
}
|
||||
values[component] = integer ? raw
|
||||
: mem.data_bits == 16u
|
||||
? EmitF16BitsToF32(ctx.state, raw)
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), raw);
|
||||
}
|
||||
if (packed) values[0] = packed_value;
|
||||
const auto texel = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
integer ? TypeU32Vector(ctx.state, 4)
|
||||
: TypeF32Vector(ctx.state, 4),
|
||||
texel, values[0], values[1], values[2], values[3]);
|
||||
return PackImageTexel(ctx, mem, texel);
|
||||
return texel;
|
||||
}
|
||||
|
||||
template <typename Emit>
|
||||
auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type,
|
||||
const Emit& emit) {
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
const auto& image = state.program.info.images.at(mem.resource);
|
||||
if (image.indirect_root != mem.resource) return emit(mem.resource, 0u);
|
||||
const auto* handle = inst.Arg(0).ResolveInstruction();
|
||||
const auto* source = image.source < state.program.descriptor_sources.size()
|
||||
? &state.program.descriptor_sources[image.source]
|
||||
: nullptr;
|
||||
if (handle == nullptr || source == nullptr || !source->indirect_descriptor.has_value() ||
|
||||
handle->NumArgs() == 0u) {
|
||||
ctx.Fail(inst, "has invalid indirect image key provenance");
|
||||
}
|
||||
const auto key = ctx.Def(handle->Arg(0));
|
||||
if (state.flattened_srt_variable == 0 ||
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
|
||||
struct ImageRun {
|
||||
uint32_t first;
|
||||
uint32_t count;
|
||||
uint32_t resource;
|
||||
uint32_t slot_bias;
|
||||
};
|
||||
std::vector<ImageRun> runs;
|
||||
for (uint32_t ordinal = 0; ordinal < image.indirect_resources.size(); ++ordinal) {
|
||||
const auto resource = image.indirect_resources[ordinal];
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(candidate);
|
||||
if (!runs.empty()) {
|
||||
auto& run = runs.back();
|
||||
const auto& first = state.program.info.images[run.resource];
|
||||
const auto next_slot = run.slot_bias + ordinal;
|
||||
if (candidate.dimension == first.dimension && candidate.cube == first.cube &&
|
||||
IR::DescriptorBindingForImage(first) == kind &&
|
||||
(inst.GetOpcode() != IR::ValueOpcode::ImageWrite ||
|
||||
candidate.shader_swizzle == first.shader_swizzle) &&
|
||||
candidate.mip_count == 1u && first.mip_count == 1u &&
|
||||
(ordinal == 1u || candidate.descriptor_index == next_slot)) {
|
||||
// Native roots precede appended children; only the root may have a slot gap.
|
||||
if (ordinal == 1u)
|
||||
run.slot_bias = candidate.descriptor_index - ordinal;
|
||||
++run.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
runs.push_back({ordinal, 1u, resource,
|
||||
candidate.descriptor_index - ordinal});
|
||||
}
|
||||
const auto EmitRun = [&](uint32_t index) {
|
||||
const auto& run = runs[index];
|
||||
if (run.count == 1u) return emit(run.resource, 0u);
|
||||
auto slot = Binary(state, spv::OpIAdd, TypeU32(state), selected,
|
||||
ConstantU32(state, run.slot_bias));
|
||||
if (run.first == 0u) {
|
||||
const auto root_slot = image.descriptor_index;
|
||||
if (root_slot != run.slot_bias) {
|
||||
const auto is_root = Binary(state, spv::OpIEqual, TypeBool(state), selected,
|
||||
ConstantU32(state, 0u));
|
||||
const auto root_or_child = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), root_or_child,
|
||||
is_root, ConstantU32(state, root_slot), slot);
|
||||
slot = root_or_child;
|
||||
}
|
||||
}
|
||||
return emit(run.resource, slot);
|
||||
};
|
||||
auto run_index = ConstantU32(state, 0u);
|
||||
for (uint32_t index = 1; index < runs.size(); ++index) {
|
||||
const auto at_or_after = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state),
|
||||
selected, ConstantU32(state, runs[index].first));
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next, at_or_after,
|
||||
ConstantU32(state, index), run_index);
|
||||
run_index = next;
|
||||
}
|
||||
return runs.size() == 1u
|
||||
? EmitRun(0u)
|
||||
: EmitIndexSwitch(state, run_index, static_cast<uint32_t>(runs.size()),
|
||||
result_type, EmitRun);
|
||||
}
|
||||
|
||||
template <typename Emit>
|
||||
uint32_t EmitImageResult(ValueEmitContext& ctx, const IR::Inst& inst, const Emit& emit) {
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
const bool gather = inst.GetOpcode() == IR::ValueOpcode::ImageGatherRaw;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
const bool scalar = dref && !gather;
|
||||
const bool mixed = std::ranges::any_of(image.indirect_resources, [&](uint32_t resource) {
|
||||
return state.program.info.images[resource].numeric_class != image.numeric_class;
|
||||
});
|
||||
const auto native_type = [&](uint32_t resource) {
|
||||
return scalar ? TypeF32(state) : ImageVectorType(
|
||||
state, dref ? Prospero::TextureNumericClass::Float
|
||||
: state.program.info.images[resource].numeric_class, 4);
|
||||
};
|
||||
const auto result_bits = [&](uint32_t resource, uint32_t value) {
|
||||
auto candidate_mem = mem;
|
||||
candidate_mem.resource = resource;
|
||||
if (!scalar) {
|
||||
value = gather ? UnpackImageGather(ctx, candidate_mem, value)
|
||||
: UnpackImageTexel(ctx, candidate_mem, value);
|
||||
}
|
||||
return ResultVector(ctx, value,
|
||||
dref ? Prospero::TextureNumericClass::Float
|
||||
: state.program.info.images[resource].numeric_class,
|
||||
scalar, candidate_mem, gather);
|
||||
};
|
||||
// RDNA returns register bits. Convert per typed run only when the join mixes
|
||||
// numeric classes; homogeneous accesses keep their single conversion after it.
|
||||
const auto value = EmitImageAccess(
|
||||
ctx, inst, mixed ? TypeU32Vector(state, 4) : native_type(mem.resource),
|
||||
[&](uint32_t resource, uint32_t array_index) {
|
||||
const auto native = emit(resource, array_index, native_type(resource));
|
||||
return mixed ? result_bits(resource, native) : native;
|
||||
});
|
||||
return mixed ? value : result_bits(mem.resource, value);
|
||||
}
|
||||
|
||||
struct ImageSampler {
|
||||
uint32_t id = 0;
|
||||
bool dynamic = false;
|
||||
};
|
||||
|
||||
std::array<ImageSampler, 2> LoadImageSamplers(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
const auto& samplers = state.program.info.samplers;
|
||||
const auto& root = samplers.at(mem.sampler);
|
||||
std::array<ImageSampler, 2> result {};
|
||||
const auto load = [&](const IR::ImageResource& image) {
|
||||
const bool integer = image.numeric_class != Prospero::TextureNumericClass::Float;
|
||||
auto& sampler = result[integer];
|
||||
if (sampler.id != 0u) return;
|
||||
uint32_t index = mem.sampler;
|
||||
if (root.integer_border != integer) {
|
||||
const auto found = std::ranges::find_if(samplers, [&](const auto& candidate) {
|
||||
return candidate.snapshot_index == root.snapshot_index &&
|
||||
candidate.integer_border == integer;
|
||||
});
|
||||
if (found == samplers.end()) ctx.Fail(inst, "has no typed sampler variant");
|
||||
index = static_cast<uint32_t>(found - samplers.begin());
|
||||
}
|
||||
uint32_t array_index = 0;
|
||||
const auto& candidates = samplers[index].indirect_resources;
|
||||
if (!candidates.empty()) {
|
||||
const auto* handle = inst.Arg(1).ResolveInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
|
||||
ctx.Fail(inst, "has invalid finite sampler key provenance");
|
||||
auto selected = ctx.Def(handle->Arg(0));
|
||||
if (samplers[index].indirect_root != IR::SamplerResource::NoIndirectSampler)
|
||||
selected = EmitIndirectResourceIndex(state, selected,
|
||||
samplers[index].indirect_mapping_offset,
|
||||
samplers[index].indirect_search_iterations, 0u);
|
||||
array_index = EmitIndexSwitch(
|
||||
state, selected, static_cast<uint32_t>(candidates.size()),
|
||||
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, candidates[ordinal]); });
|
||||
}
|
||||
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
|
||||
};
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
load(image);
|
||||
for (const auto candidate: image.indirect_resources) load(state.program.info.images[candidate]);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -631,6 +855,22 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Define(inst, QueryDimensions(ctx, mem, *address));
|
||||
return;
|
||||
}
|
||||
if (image_info.needs_sampler &&
|
||||
image.conversion_format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||
if (op != IR::ValueOpcode::ImageSampleRaw ||
|
||||
image.dimension != ImageDimension::Dim2D ||
|
||||
mem.image_dimension != ImageDimension::Dim2D || mem.data_bits != 32u ||
|
||||
mem.image_sample_flags != Decoder::ImageSampleFlagLevelZero ||
|
||||
image.indirect_root != IR::ImageResource::NoIndirectImage ||
|
||||
!state.program.info.samplers.at(mem.sampler).indirect_resources.empty()) {
|
||||
ctx.Fail(inst, "packed UNorm sampling requires a direct 2D sample_lz");
|
||||
return;
|
||||
}
|
||||
const auto coord = CoordF32(ctx, mem, *address, Layout(mem).coord, 2u);
|
||||
ctx.Define(inst, ResultVector(ctx, SamplePackedUnormLz(ctx, mem, coord),
|
||||
Prospero::TextureNumericClass::Float, false, mem));
|
||||
return;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto dimension = image.dimension;
|
||||
@@ -656,45 +896,49 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageRead) {
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const auto condition = ctx.Arg(inst, 2);
|
||||
const auto condition = ctx.Arg(inst, 2);
|
||||
ctx.Define(
|
||||
inst,
|
||||
EmitValueOrDefaultIfCondition(
|
||||
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
|
||||
[&]() {
|
||||
const auto descriptor = LoadImageDescriptor(state, mem.resource);
|
||||
const auto color = state.builder.AllocateId();
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, dimension_info.coordinate_components));
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpImageFetch,
|
||||
ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsLodMask,
|
||||
LodU32(ctx, mem, *address, dimension));
|
||||
}
|
||||
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
|
||||
false, mem);
|
||||
return EmitImageResult(
|
||||
ctx, inst, [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto dimension = candidate.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto descriptor = LoadImageDescriptor(state, resource, 0u, array_index);
|
||||
const auto fetched = state.builder.AllocateId();
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageFetch, result_type, fetched,
|
||||
descriptor, coord, spv::ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, dimension_info.coordinate_components));
|
||||
} else {
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageFetch, result_type, fetched,
|
||||
descriptor, coord, spv::ImageOperandsLodMask,
|
||||
LodU32(ctx, mem, *address, mem.image_dimension));
|
||||
}
|
||||
return fetched;
|
||||
});
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageWrite) {
|
||||
const bool uint_image = image.numeric_class == Prospero::TextureNumericClass::Uint;
|
||||
const auto dimension = image.dimension;
|
||||
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto mip_lod =
|
||||
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, dimension)
|
||||
: 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
|
||||
EmitStorageImageWrite(state, mem.resource, mip_lod, coord, texel);
|
||||
EmitImageAccess(ctx, inst, 0u, [&](uint32_t resource, uint32_t array_index) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
auto selected = mem;
|
||||
selected.resource = resource;
|
||||
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
|
||||
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
|
||||
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -704,21 +948,28 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto layout = Layout(mem);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
IR::Value offset_value;
|
||||
if (layout.offset != NoImageComponent) {
|
||||
const auto value = address->Arg(layout.offset).Resolve();
|
||||
offset_value = IR::ResolveActiveU32(value, inst.Arg(inst.NumArgs() - 1u).Resolve());
|
||||
if (offset_value.IsEmpty()) offset_value = value;
|
||||
}
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
Prospero::BufferFormat::kInvalid) {
|
||||
ctx.Fail(inst, "uses depth comparison with a converted image");
|
||||
return;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
dimension_info.coordinate_components, image.cube);
|
||||
if (dimension == ImageDimension::Dim1D) {
|
||||
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
if (image.indirect_root == mem.resource || dref ||
|
||||
!HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
ctx.Fail(inst, "has an unsupported 1D gather variant");
|
||||
return;
|
||||
}
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
dimension_info.coordinate_components, image.cube);
|
||||
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, coord, numeric_class);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
|
||||
numeric_class, false, mem, true));
|
||||
@@ -728,9 +979,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "has an unsupported 1D-array gather");
|
||||
return;
|
||||
}
|
||||
const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
|
||||
: numeric_class;
|
||||
const auto result_type = ImageVectorType(state, result_numeric_class, 4);
|
||||
uint32_t component_or_dref;
|
||||
if (dref) {
|
||||
component_or_dref = layout.dref != NoImageComponent
|
||||
@@ -739,37 +987,40 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
} else {
|
||||
component_or_dref = ConstantU32(state, ImageGatherSource(state, mem));
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
uint32_t offset = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
operand_mask = spv::ImageOperandsConstOffsetsMask;
|
||||
offset = HorizontalOffsets(state, dimension);
|
||||
} else if (layout.offset != NoImageComponent) {
|
||||
operand_mask = spv::ImageOperandsOffsetMask;
|
||||
offset = PackedOffset(ctx, mem, *address, layout, dimension);
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto EmitGather = [&](uint32_t mip) {
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, sampler_id, mip);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {
|
||||
static_cast<uint32_t>(dref ? spv::OpImageDrefGather : spv::OpImageGather), result_type,
|
||||
sample, sampled, coord, component_or_dref};
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.push_back(offset);
|
||||
const auto samplers = LoadImageSamplers(ctx, inst);
|
||||
const auto EmitGather = [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto sampler = samplers[candidate.numeric_class != Prospero::TextureNumericClass::Float];
|
||||
const auto dimension = candidate.dimension;
|
||||
const auto coord = CoordF32(
|
||||
ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(dimension).coordinate_components, candidate.cube);
|
||||
uint32_t operand_mask = 0;
|
||||
uint32_t offset = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
operand_mask = spv::ImageOperandsConstOffsetsMask;
|
||||
offset = HorizontalOffsets(state, dimension);
|
||||
} else if (layout.offset != NoImageComponent) {
|
||||
offset = PackedOffset(ctx, offset_value, dimension, operand_mask);
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
return sample;
|
||||
const auto EmitMip = [&](uint32_t mip) {
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler.id, mip, array_index,
|
||||
sampler.dynamic);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
const std::array operands {operand_mask, offset};
|
||||
state.builder.AddFunction(
|
||||
dref ? spv::OpImageDrefGather : spv::OpImageGather, result_type,
|
||||
sample, sampled, coord, component_or_dref,
|
||||
std::span(operands).first(operand_mask != 0u ? 2u : 0u));
|
||||
return sample;
|
||||
};
|
||||
return candidate.mip_mode == IR::ImageMipMode::Dynamic && candidate.mip_count > 1u
|
||||
? EmitIndexSwitch(
|
||||
state, GatherMip(ctx, inst, mem, *address, layout, candidate.mip_count),
|
||||
candidate.mip_count, result_type, EmitMip)
|
||||
: EmitMip(0u);
|
||||
};
|
||||
const auto sample = image.mip_mode == IR::ImageMipMode::Dynamic && image.mip_count > 1u
|
||||
? EmitIndexSwitch(
|
||||
state, GatherMip(ctx, inst, mem, *address, layout,
|
||||
image.mip_count),
|
||||
image.mip_count, result_type, EmitGather)
|
||||
: EmitGather(0);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
|
||||
result_numeric_class, false, mem, true));
|
||||
ctx.Define(inst, EmitImageResult(ctx, inst, EmitGather));
|
||||
return;
|
||||
}
|
||||
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
|
||||
@@ -782,147 +1033,61 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
} else if (dref) {
|
||||
opcode = spv::OpImageSampleDrefImplicitLod;
|
||||
}
|
||||
uint32_t result_type = ImageVectorType(state, numeric_class, 4);
|
||||
uint32_t dref_value = 0;
|
||||
if (dref) {
|
||||
result_type = TypeF32(state);
|
||||
dref_value = ZeroF32(state);
|
||||
if (layout.dref != NoImageComponent) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
std::vector<uint32_t> operands;
|
||||
std::array<uint32_t, 4> operands {};
|
||||
size_t operand_count = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operand_mask |= spv::ImageOperandsGradMask;
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_x, dimension_info.spatial_components));
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components));
|
||||
operands[0] = spv::ImageOperandsGradMask;
|
||||
operands[1] = CoordF32(ctx, mem, *address, layout.grad_x, dimension_info.spatial_components);
|
||||
operands[2] = CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components);
|
||||
operand_count = 3;
|
||||
} else if (explicit_lod) {
|
||||
operand_mask |= spv::ImageOperandsLodMask;
|
||||
auto lod = ZeroF32(state);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||
}
|
||||
operands.push_back(lod);
|
||||
operands[0] = spv::ImageOperandsLodMask;
|
||||
operands[1] = lod;
|
||||
operand_count = 2;
|
||||
} else if (layout.bias != NoImageComponent) {
|
||||
operand_mask |= spv::ImageOperandsBiasMask;
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
operands[0] = spv::ImageOperandsBiasMask;
|
||||
operands[1] = AddressF32(ctx, mem, *address, layout.bias);
|
||||
operand_count = 2;
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto EmitSample = [&](uint32_t resource, uint32_t array_index = 0u) {
|
||||
if (layout.offset != NoImageComponent &&
|
||||
!offset_value.IsImmediate() && !state.maintenance8_enabled) {
|
||||
ctx.Fail(inst, "requires maintenance8 for a dynamic sample texel offset");
|
||||
}
|
||||
const auto samplers = LoadImageSamplers(ctx, inst);
|
||||
const auto EmitSample = [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto sampler = samplers[candidate.numeric_class != Prospero::TextureNumericClass::Float];
|
||||
auto sample_operands = operands;
|
||||
auto sample_operand_count = operand_count;
|
||||
if (layout.offset != NoImageComponent) {
|
||||
sample_operand_count = std::max(size_t{1}, sample_operand_count);
|
||||
sample_operands[sample_operand_count++] = PackedOffset(
|
||||
ctx, offset_value, candidate.dimension, sample_operands[0]);
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
sample_operands.push_back(dref_value);
|
||||
}
|
||||
if (operand_mask != 0u) {
|
||||
sample_operands.push_back(operand_mask);
|
||||
sample_operands.insert(sample_operands.end(), operands.begin(), operands.end());
|
||||
}
|
||||
state.builder.AddFunction(opcode, sample_operands);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler.id, 0u, array_index,
|
||||
sampler.dynamic);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
state.builder.AddFunction(opcode, result_type, sample, sampled, coord,
|
||||
std::span(&dref_value, dref ? 1u : 0u),
|
||||
std::span<const uint32_t>(sample_operands).first(sample_operand_count));
|
||||
return sample;
|
||||
};
|
||||
if (image.indirect_root != mem.resource) {
|
||||
const auto sample = EmitSample(mem.resource);
|
||||
auto result = sample;
|
||||
if (!dref) {
|
||||
result = UnpackImageTexel(ctx, mem, sample);
|
||||
}
|
||||
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
|
||||
return;
|
||||
}
|
||||
const auto* handle = image_arg.ResolveInstruction();
|
||||
const auto* source = image.source < state.program.descriptor_sources.size()
|
||||
? &state.program.descriptor_sources[image.source]
|
||||
: nullptr;
|
||||
if (handle == nullptr || source == nullptr || !source->indirect_descriptor.has_value() ||
|
||||
handle->NumArgs() == 0u) {
|
||||
ctx.Fail(inst, "has invalid indirect image key provenance");
|
||||
return;
|
||||
}
|
||||
const auto key = ctx.Def(handle->Arg(0));
|
||||
if (state.flattened_srt_variable == 0 ||
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
return;
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
|
||||
struct SampleRun {
|
||||
uint32_t first;
|
||||
uint32_t count;
|
||||
uint32_t resource;
|
||||
uint32_t slot_bias;
|
||||
};
|
||||
std::vector<SampleRun> runs;
|
||||
for (uint32_t ordinal = 0; ordinal < image.indirect_resources.size(); ++ordinal) {
|
||||
const auto resource = image.indirect_resources[ordinal];
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(candidate);
|
||||
const auto& binding = *IR::FindBinding(state.program.bindings, kind);
|
||||
if (!runs.empty()) {
|
||||
auto& run = runs.back();
|
||||
const auto& first = state.program.info.images[run.resource];
|
||||
const auto next_slot = run.slot_bias + ordinal;
|
||||
if (candidate.dimension == first.dimension && candidate.cube == first.cube &&
|
||||
IR::DescriptorBindingForImage(first) == kind &&
|
||||
candidate.mip_count == 1u && first.mip_count == 1u &&
|
||||
(ordinal == 1u || (next_slot < binding.resources.size() &&
|
||||
binding.resources[next_slot] == resource))) {
|
||||
// Native roots precede appended children; only the root may have a slot gap.
|
||||
if (ordinal == 1u)
|
||||
run.slot_bias = ResourceForDescriptor(state, kind, resource) - ordinal;
|
||||
++run.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
runs.push_back({ordinal, 1u, resource,
|
||||
ResourceForDescriptor(state, kind, resource) - ordinal});
|
||||
}
|
||||
const auto EmitRun = [&](uint32_t index) {
|
||||
const auto& run = runs[index];
|
||||
if (run.count == 1u) return EmitSample(run.resource);
|
||||
auto slot = Binary(state, spv::OpIAdd, TypeU32(state), selected,
|
||||
ConstantU32(state, run.slot_bias));
|
||||
if (run.first == 0u) {
|
||||
const auto kind = *IR::DescriptorBindingForImage(image);
|
||||
const auto root_slot = ResourceForDescriptor(state, kind, mem.resource);
|
||||
if (root_slot != run.slot_bias) {
|
||||
const auto is_root = Binary(state, spv::OpIEqual, TypeBool(state), selected,
|
||||
ConstantU32(state, 0u));
|
||||
const auto root_or_child = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), root_or_child,
|
||||
is_root, ConstantU32(state, root_slot), slot);
|
||||
slot = root_or_child;
|
||||
}
|
||||
}
|
||||
return EmitSample(run.resource, slot);
|
||||
};
|
||||
auto run_index = ConstantU32(state, 0u);
|
||||
for (uint32_t index = 1; index < runs.size(); ++index) {
|
||||
const auto at_or_after = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state),
|
||||
selected, ConstantU32(state, runs[index].first));
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next, at_or_after,
|
||||
ConstantU32(state, index), run_index);
|
||||
run_index = next;
|
||||
}
|
||||
auto result = runs.size() == 1u
|
||||
? EmitRun(0u)
|
||||
: EmitIndexSwitch(state, run_index, static_cast<uint32_t>(runs.size()),
|
||||
result_type, EmitRun);
|
||||
if (!dref) {
|
||||
result = UnpackImageTexel(ctx, mem, result);
|
||||
}
|
||||
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
|
||||
ctx.Define(inst, EmitImageResult(ctx, inst, EmitSample));
|
||||
return;
|
||||
}
|
||||
if (image_info.access == IR::ImageAccess::Atomic) {
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
#include <bit>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
uint32_t EmitIdentity(ValueEmitContext& ctx, uint32_t value);
|
||||
|
||||
// Operand signatures distinguish SPIR-V IDs (uint32_t), raw literals/values
|
||||
// (IR::Value), and instructions whose metadata or operand loads must stay lazy.
|
||||
#define EMIT_NATIVE(name, opcode, type, ...) \
|
||||
inline constexpr auto Emit##name = EmitNative<spv::opcode, IR::Type::type, __VA_ARGS__>;
|
||||
EMIT_NATIVE(BitCastU16F16, OpBitcast, U32, uint32_t)
|
||||
inline constexpr auto EmitBitCastU16F16 = EmitIdentity;
|
||||
inline constexpr auto EmitBitCastF16U16 = EmitBitCastU16F16;
|
||||
inline constexpr auto EmitConvertU32U16 = EmitBitCastU16F16;
|
||||
inline constexpr auto EmitConvertU32U8 = EmitBitCastU16F16;
|
||||
inline constexpr auto EmitBitCastU32F32 = EmitBitCastU16F16;
|
||||
EMIT_NATIVE(BitCastU32F32, OpBitcast, U32, uint32_t)
|
||||
EMIT_NATIVE(BitCastF32U32, OpBitcast, F32, uint32_t)
|
||||
EMIT_NATIVE(BitCastU64F64, OpBitcast, U64, uint32_t)
|
||||
EMIT_NATIVE(BitCastF64U64, OpBitcast, F64, uint32_t)
|
||||
@@ -23,13 +25,8 @@ uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitConvertF32F16 = EmitF16BitsToF32;
|
||||
uint32_t EmitConvertS32F32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0);
|
||||
template <IR::Type type>
|
||||
uint32_t EmitConvertSigned32ToFloat(EmitterState& state, uint32_t arg0) {
|
||||
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
|
||||
return EmitNative<spv::OpConvertSToF, type>(state, signed_value);
|
||||
}
|
||||
inline constexpr auto EmitConvertF32S32 = EmitConvertSigned32ToFloat<IR::Type::F32>;
|
||||
inline constexpr auto EmitConvertF64S32 = EmitConvertSigned32ToFloat<IR::Type::F64>;
|
||||
EMIT_NATIVE(ConvertF32S32, OpConvertSToF, F32, uint32_t)
|
||||
EMIT_NATIVE(ConvertF64S32, OpConvertSToF, F64, uint32_t)
|
||||
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
|
||||
@@ -69,7 +66,7 @@ EMIT_NATIVE(IMul64, OpIMul, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IAddCarry32, OpIAddCarry, U32x2, uint32_t, uint32_t)
|
||||
uint32_t EmitSMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitIAbs32 = EmitGlsl<GLSLstd450SAbs, IR::Type::U32, uint32_t>;
|
||||
EMIT_NATIVE(ShiftLeftLogical32, OpShiftLeftLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightLogical32, OpShiftRightLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightArithmetic32, OpShiftRightArithmetic, U32, uint32_t, uint32_t)
|
||||
@@ -96,13 +93,13 @@ EMIT_NATIVE(BitwiseAnd64, OpBitwiseAnd, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitReverse32, OpBitReverse, U32, uint32_t)
|
||||
EMIT_NATIVE(BitCount32, OpBitCount, U32, uint32_t)
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitFindILsb32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitFindUMsb32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitFindILsb32 = EmitGlsl<GLSLstd450FindILsb, IR::Type::U32, uint32_t>;
|
||||
inline constexpr auto EmitFindUMsb32 = EmitGlsl<GLSLstd450FindUMsb, IR::Type::U32, uint32_t>;
|
||||
uint32_t EmitFindUMsb64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitSMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitSMax32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMax32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
inline constexpr auto EmitSMin32 = EmitGlsl<GLSLstd450SMin, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitSMax32 = EmitGlsl<GLSLstd450SMax, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitUMin32 = EmitGlsl<GLSLstd450UMin, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitUMax32 = EmitGlsl<GLSLstd450UMax, IR::Type::U32, uint32_t, uint32_t>;
|
||||
uint32_t EmitSMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
uint32_t EmitSMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
uint32_t EmitUMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
@@ -151,6 +148,15 @@ inline constexpr auto EmitFPOrdEqual32 = EmitFloatCompare32<spv::OpFOrdEqual>;
|
||||
EMIT_NATIVE(FPOrdEqual64, OpFOrdEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdLessThanEqual64, OpFOrdLessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdGreaterThanEqual64, OpFOrdGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdNotEqual64, OpFOrdNotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdLessThan64, OpFOrdLessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPOrdGreaterThan64, OpFOrdGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordEqual64, OpFUnordEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordNotEqual64, OpFUnordNotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordLessThan64, OpFUnordLessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordGreaterThan64, OpFUnordGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordLessThanEqual64, OpFUnordLessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPUnordGreaterThanEqual64, OpFUnordGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
inline constexpr auto EmitFPUnordEqual32 = EmitFloatCompare32<spv::OpFUnordEqual>;
|
||||
inline constexpr auto EmitFPOrdNotEqual32 = EmitFloatCompare32<spv::OpFOrdNotEqual>;
|
||||
inline constexpr auto EmitFPUnordNotEqual32 = EmitFloatCompare32<spv::OpFUnordNotEqual>;
|
||||
@@ -162,7 +168,8 @@ inline constexpr auto EmitFPOrdLessThanEqual32 = EmitFloatCompare32<spv::OpFOrdL
|
||||
inline constexpr auto EmitFPUnordLessThanEqual32 = EmitFloatCompare32<spv::OpFUnordLessThanEqual>;
|
||||
inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32<spv::OpFOrdGreaterThanEqual>;
|
||||
inline constexpr auto EmitFPUnordGreaterThanEqual32 = EmitFloatCompare32<spv::OpFUnordGreaterThanEqual>;
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(FPIsNan32, OpIsNan, U1, uint32_t)
|
||||
EMIT_NATIVE(FPIsNan64, OpIsNan, U1, uint32_t)
|
||||
inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
|
||||
inline constexpr auto EmitFPCmpClass16 = EmitClassMaskF16;
|
||||
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
|
||||
@@ -202,7 +209,6 @@ uint32_t EmitFPSin(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitFPCos(EmitterState& state, uint32_t arg0);
|
||||
#undef EMIT_NATIVE
|
||||
|
||||
uint32_t EmitIdentity(ValueEmitContext& ctx, uint32_t value);
|
||||
void EmitVoid(ValueEmitContext&);
|
||||
inline constexpr auto EmitReference = EmitVoid;
|
||||
inline constexpr auto EmitReferenceU32 = EmitVoid;
|
||||
@@ -232,6 +238,7 @@ uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
@@ -240,6 +247,7 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& in
|
||||
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
|
||||
inline constexpr auto EmitGetSrtResource = EmitVoid;
|
||||
inline constexpr auto EmitGetBufferResource = EmitGetSrtResource;
|
||||
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitGetAddressResource = EmitGetSrtResource;
|
||||
inline constexpr auto EmitGetScratchResource = EmitGetSrtResource;
|
||||
inline constexpr auto EmitGetImageResource = EmitGetSrtResource;
|
||||
@@ -316,6 +324,8 @@ inline constexpr auto EmitSharedAtomicDec32 = EmitSharedIncDec;
|
||||
inline constexpr auto EmitSharedAtomicSMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicUMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicSMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicUMax64 = EmitSharedAtomic64;
|
||||
inline constexpr auto EmitSharedAtomicCmpSwap64 = EmitSharedAtomic64;
|
||||
inline constexpr auto EmitSharedAtomicUMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicAnd32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicOr32 = EmitAtomic32;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -58,43 +57,39 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
|
||||
|
||||
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
|
||||
|
||||
struct SpirvRequirements {
|
||||
bool bvh = false;
|
||||
bool subgroup_ballot = false;
|
||||
bool subgroup_barrier = false;
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
bool function_scratch = false;
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
bool buffer_u8 = false;
|
||||
bool buffer_u16 = false;
|
||||
bool shared_int64_atomics = false;
|
||||
bool coherent_buffers = false;
|
||||
bool float64 = false;
|
||||
struct BufferDefinition {
|
||||
uint32_t variable = 0;
|
||||
uint32_t type = 0;
|
||||
uint32_t element_type = 0;
|
||||
uint32_t pointer_type = 0;
|
||||
uint32_t element_pointer_type = 0;
|
||||
};
|
||||
|
||||
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
|
||||
struct ImageDefinition {
|
||||
uint32_t variable = 0;
|
||||
uint32_t type = 0;
|
||||
uint32_t pointer_type = 0;
|
||||
uint32_t sampled_type = 0;
|
||||
};
|
||||
|
||||
struct EmitterState {
|
||||
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
|
||||
EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_, bool maintenance8_enabled_)
|
||||
: builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u),
|
||||
program(program_), input_info(input_info_),
|
||||
requirements(AnalyzeProgramRequirements(program_)) {
|
||||
program(program_), input_info(input_info_), maintenance8_enabled(maintenance8_enabled_) {
|
||||
if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) {
|
||||
lds_storage_class = spv::StorageClassWorkgroup;
|
||||
}
|
||||
if (IR::FindBinding(program.bindings, IR::DescriptorBindingKind::SharedMemory) != nullptr) {
|
||||
if (program.info.uses_lds && program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage) {
|
||||
lds_storage_class = spv::StorageClassStorageBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
IR::Program& program;
|
||||
ShaderStageInputInfo input_info;
|
||||
bool maintenance8_enabled;
|
||||
uint32_t debug_source = 0;
|
||||
uint32_t void_type = 0;
|
||||
uint32_t bool_type = 0;
|
||||
uint32_t u32_type = 0;
|
||||
@@ -113,17 +108,16 @@ struct EmitterState {
|
||||
uint32_t tess_inner_variable = 0;
|
||||
uint32_t tess_patch_base = 0;
|
||||
|
||||
const SpirvRequirements requirements;
|
||||
uint32_t lane_count = 1;
|
||||
uint32_t lane_half = 0;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
uint32_t storage_buffer_u8_variable = 0;
|
||||
uint32_t storage_buffer_u16_variable = 0;
|
||||
uint32_t storage_buffer_u64_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
std::array<BufferDefinition, 4> storage_buffers {};
|
||||
std::vector<uint32_t> memory_byte_offsets;
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t indirect_format_function = 0;
|
||||
uint32_t indirect_format_geometry_function = 0;
|
||||
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions;
|
||||
uint32_t bvh_intersect_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
@@ -135,8 +129,10 @@ struct EmitterState {
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<uint32_t, IR::ImageBindingCount> image_variables {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> images {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_pointer_type = 0;
|
||||
uint32_t main_func = 0;
|
||||
uint32_t mesh_guest_func = 0;
|
||||
uint32_t mesh_allocation = 0;
|
||||
@@ -156,11 +152,11 @@ struct EmitterState {
|
||||
uint32_t layer_variable = 0;
|
||||
uint32_t viewport_index_variable = 0;
|
||||
uint32_t depth_variable = 0;
|
||||
uint32_t object_primitive_id_variable = 0;
|
||||
uint32_t sample_mask_variable = 0;
|
||||
std::vector<InputBinding> inputs;
|
||||
std::vector<OutputBinding> outputs;
|
||||
std::vector<uint32_t> interface_variables;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
};
|
||||
|
||||
uint32_t TypeVoid(EmitterState& state);
|
||||
@@ -180,8 +176,6 @@ uint32_t TypeI32Vector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeF32Vector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypePointer(EmitterState& state, spv::StorageClass storage_class, uint32_t pointee);
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElement(EmitterState& state, uint32_t bits);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state, uint32_t bits = 32);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state, uint32_t bits = 32);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
@@ -191,6 +185,11 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl
|
||||
inline void EmitLabel(EmitterState& state, uint32_t label) {
|
||||
state.current_label = label;
|
||||
state.builder.AddFunction(spv::OpLabel, label);
|
||||
if (state.debug_source != 0 && state.current_block != nullptr) {
|
||||
// Source lines encode the containing native block's byte offset plus one.
|
||||
state.builder.AddFunction(spv::OpLine, state.debug_source,
|
||||
state.current_block->start_pc + 1u, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TypeId(EmitterState& state, IR::Type type);
|
||||
@@ -255,12 +254,10 @@ struct ValueEmitContext {
|
||||
const IR::Inst* ImageAddress(IR::Value value);
|
||||
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
||||
const IR::ExportInfo& Export(const IR::Inst& inst) const;
|
||||
uint32_t Label(const IR::Block* block) const;
|
||||
[[noreturn]] void Fail(const char* reason) const;
|
||||
[[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const;
|
||||
|
||||
EmitterState& state;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
@@ -316,9 +313,6 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* reason);
|
||||
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource);
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
@@ -339,13 +333,14 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip = 0,
|
||||
uint32_t array_index = 0);
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t array_index = 0);
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id,
|
||||
uint32_t mip = 0, uint32_t array_index = 0);
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel);
|
||||
uint32_t texel, uint32_t array_index);
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
@@ -409,11 +404,13 @@ inline constexpr auto EmitAddU32 = EmitNative<spv::OpIAdd, IR::Type::U32, uint32
|
||||
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs);
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index);
|
||||
uint32_t EmitPushDataLoad(EmitterState& state, uint32_t index);
|
||||
|
||||
uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem);
|
||||
|
||||
Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem);
|
||||
uint32_t StorageBufferElementBits(const IR::Program& program, const IR::MemoryInfo& mem);
|
||||
|
||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
|
||||
@@ -426,16 +423,19 @@ struct MemoryResourceAccess {
|
||||
uint32_t length = 0;
|
||||
uint32_t byte_offset = 0;
|
||||
uint32_t element_bits = 32;
|
||||
bool non_uniform = false;
|
||||
spv::MemoryAccessMask memory_access = spv::MemoryAccessMaskNone;
|
||||
};
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource);
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t array_index = 0u);
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable, uint32_t pointer_type);
|
||||
BufferDefinition& buffer,
|
||||
uint32_t array_index = 0);
|
||||
|
||||
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index);
|
||||
@@ -458,6 +458,16 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits);
|
||||
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t raw);
|
||||
|
||||
enum class UnormRounding { RoundEven, HalfUp, Truncate };
|
||||
|
||||
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data, uint32_t data_bits,
|
||||
UnormRounding rounding = UnormRounding::RoundEven);
|
||||
|
||||
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data, uint32_t data_bits,
|
||||
UnormRounding rounding = UnormRounding::RoundEven);
|
||||
|
||||
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode);
|
||||
|
||||
uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t pointer,
|
||||
@@ -478,21 +488,11 @@ inline constexpr auto EmitSelectValueU32 =
|
||||
|
||||
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask);
|
||||
|
||||
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift);
|
||||
|
||||
inline constexpr auto EmitOrU32 = EmitNative<spv::OpBitwiseOr, IR::Type::U32, uint32_t, uint32_t>;
|
||||
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
|
||||
uint32_t constant);
|
||||
|
||||
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value);
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32);
|
||||
|
||||
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value);
|
||||
|
||||
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value);
|
||||
|
||||
inline constexpr auto EmitBitcastF32ToU32 = EmitNative<spv::OpBitcast, IR::Type::U32, uint32_t>;
|
||||
|
||||
inline constexpr auto EmitBitcastU32ToF32 = EmitNative<spv::OpBitcast, IR::Type::F32, uint32_t>;
|
||||
@@ -534,6 +534,8 @@ uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
|
||||
void EmitProgram(EmitterState& state);
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
|
||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
|
||||
void DefineBvhIntersect(EmitterState& state);
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||
|
||||
|
||||
@@ -249,11 +249,11 @@ uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint3
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t prefix = 0) {
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
const auto resource = ResourceForDescriptor(ctx.state, IR::DescriptorBindingKind::Buffers,
|
||||
mem.resource);
|
||||
const auto prefix = ctx.state.memory_byte_offsets[resource];
|
||||
const auto resource = ctx.state.program.info.buffers.at(mem.resource).descriptor_index;
|
||||
if (prefix == 0u) prefix = ctx.state.memory_byte_offsets[resource];
|
||||
const auto address = Binary(ctx.state, spv::OpIAdd, TypeU32(ctx.state),
|
||||
BufferByteAddress(ctx, inst, mem), prefix);
|
||||
// The host binding prefix must not wrap an out-of-range guest address into the buffer.
|
||||
@@ -274,15 +274,10 @@ uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
|
||||
return ScratchByteAddress(ctx, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
|
||||
}
|
||||
|
||||
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
auto address = ByteAddress(ctx, inst, mem);
|
||||
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
|
||||
// RDNA2 DS region addresses retain bits [15:2] after adding the byte offset.
|
||||
address = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), address,
|
||||
ConstantU32(ctx.state, 0xffffu));
|
||||
}
|
||||
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t prefix = 0) {
|
||||
return Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state),
|
||||
address, ConstantU32(ctx.state, 2));
|
||||
ByteAddress(ctx, inst, mem, prefix), ConstantU32(ctx.state, 2));
|
||||
}
|
||||
|
||||
uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
|
||||
@@ -291,9 +286,99 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
|
||||
uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
|
||||
uint32_t address, uint32_t index, uint32_t bits, bool sign_extend);
|
||||
|
||||
template <typename Fn>
|
||||
auto EmitBufferAccess(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem,
|
||||
uint32_t components, Fn&& emit) {
|
||||
auto& state = ctx.state;
|
||||
constexpr bool has_result = !std::is_void_v<
|
||||
std::invoke_result_t<Fn, const IR::MemoryInfo&, const MemoryResourceAccess&>>;
|
||||
const auto access = [&](uint32_t resource) {
|
||||
mem.resource = resource;
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
const auto& buffer = state.program.info.buffers[resource];
|
||||
if (buffer.packed_stride == 0u &&
|
||||
buffer.source < state.program.descriptor_sources.size() &&
|
||||
state.program.descriptor_sources[buffer.source].indirect_descriptor) {
|
||||
// Empty candidates still carry the format/swizzle needed for OOB loads.
|
||||
return emit(mem, MemoryResourceAccess{});
|
||||
}
|
||||
}
|
||||
return emit(mem, PrepareMemoryResourceAccess(state, mem));
|
||||
};
|
||||
if (mem.kind != IR::ResourceKind::Buffer ||
|
||||
state.program.info.buffers[mem.resource].indirect_root != mem.resource) {
|
||||
return access(mem.resource);
|
||||
}
|
||||
const auto root_resource = mem.resource;
|
||||
const auto& buffer = state.program.info.buffers[root_resource];
|
||||
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||
const auto selected = ctx.Arg(handle, 0);
|
||||
const auto representative = SharedIndirectBufferResource(state, root_resource);
|
||||
const auto shared = representative != UINT32_MAX;
|
||||
const auto empty_root = shared && buffer.packed_stride == 0u;
|
||||
const auto selected_access = [&]() {
|
||||
if (shared) {
|
||||
const auto root_slot = buffer.descriptor_index;
|
||||
const auto child_slot = state.program.info.buffers[buffer.indirect_resources[1]].descriptor_index;
|
||||
auto slot = EmitAddU32(state, selected, ConstantU32(state, child_slot - 1u));
|
||||
if (!empty_root && child_slot != root_slot + 1u) {
|
||||
slot = Select(state, TypeU32(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
|
||||
ConstantU32(state, root_slot), slot);
|
||||
}
|
||||
mem.resource = representative;
|
||||
return emit(mem, PrepareMemoryResourceAccess(state, mem, slot));
|
||||
}
|
||||
return EmitIndexSwitch(state, selected, buffer.indirect_resources.size(),
|
||||
has_result ? TypeU32Composite(state, components) : 0u,
|
||||
[&](uint32_t item) { return access(buffer.indirect_resources[item]); });
|
||||
};
|
||||
if constexpr (!has_result) {
|
||||
if (!shared) return selected_access();
|
||||
}
|
||||
auto valid = Binary(state, spv::OpULessThan, TypeBool(state), selected,
|
||||
ConstantU32(state, buffer.indirect_resources.size()));
|
||||
if constexpr (has_result) {
|
||||
return EmitValueOrDefaultIfCondition(state, valid, TypeU32Composite(state, components),
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
if (!empty_root) return selected_access();
|
||||
return EmitValueOrDefaultIfCondition(state,
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
|
||||
TypeU32Composite(state, components), access(root_resource), selected_access);
|
||||
});
|
||||
} else {
|
||||
if (empty_root) valid = AndCondition(state, valid,
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)));
|
||||
EmitIfCondition(state, valid, selected_access);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BufferIndexInBounds(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
|
||||
uint32_t payload_in_bounds) {
|
||||
if (mem.kind != IR::ResourceKind::Buffer) return payload_in_bounds;
|
||||
auto& state = ctx.state;
|
||||
const auto& buffer = state.program.info.buffers[mem.resource];
|
||||
const auto& sources = state.program.descriptor_sources;
|
||||
if (buffer.source >= sources.size() || !sources[buffer.source].indirect_descriptor ||
|
||||
!sources[buffer.source].indirect_descriptor->indexed) return payload_in_bounds;
|
||||
const auto bytes = Binary(state, spv::OpIMul, TypeU32(state), resource.length,
|
||||
ConstantU32(state, resource.element_bits / 8u));
|
||||
const auto available = Binary(state, spv::OpISub, TypeU32(state), bytes, resource.byte_offset);
|
||||
// Test the original index before multiplication can wrap. A truncated binding
|
||||
// may omit final-record padding; the payload check below still checks every byte.
|
||||
const auto last_record = Binary(state, spv::OpUDiv, TypeU32(state),
|
||||
Binary(state, spv::OpISub, TypeU32(state), available, ConstantU32(state, 1u)),
|
||||
ConstantU32(state, buffer.packed_stride & 0x3fffu));
|
||||
return AndCondition(state, payload_in_bounds,
|
||||
AndCondition(state,
|
||||
Binary(state, spv::OpUGreaterThan, TypeBool(state), bytes, resource.byte_offset),
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), ctx.Arg(inst, 1), last_record)));
|
||||
}
|
||||
|
||||
uint32_t LoadWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource) {
|
||||
const auto index = DwordIndex(ctx, inst, mem);
|
||||
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index),
|
||||
[&]() { return LoadWordInBounds(ctx, resource, index); });
|
||||
@@ -309,7 +394,7 @@ uint32_t LoadWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
|
||||
uint32_t LoadSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t bits,
|
||||
bool sign_extend) {
|
||||
const auto address = ByteAddress(ctx, inst, mem);
|
||||
const auto address = ByteAddress(ctx, inst, mem, resource.byte_offset);
|
||||
const auto index = Binary(
|
||||
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
|
||||
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
|
||||
@@ -333,7 +418,8 @@ uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess&
|
||||
uint32_t value;
|
||||
if (resource.element_bits < 32u) {
|
||||
const auto loaded = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeStorageBufferElement(ctx.state, bits), loaded,
|
||||
ctx.state.builder.AddFunction(spv::OpLoad,
|
||||
ctx.state.storage_buffers[bits == 8u ? 0 : 1].element_type, loaded,
|
||||
EmitMemoryElementPointer(ctx.state, resource, index),
|
||||
resource.memory_access);
|
||||
value = Unary(ctx.state, spv::OpUConvert, TypeU32(ctx.state), loaded);
|
||||
@@ -438,7 +524,8 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
if (resource.element_bits < 32u) {
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpStore, pointer,
|
||||
Unary(ctx.state, spv::OpUConvert, TypeStorageBufferElement(ctx.state, bits), data),
|
||||
Unary(ctx.state, spv::OpUConvert,
|
||||
ctx.state.storage_buffers[bits == 8u ? 0 : 1].element_type, data),
|
||||
resource.memory_access);
|
||||
return;
|
||||
}
|
||||
@@ -469,20 +556,23 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
|
||||
void StoreSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t bits, uint32_t data) {
|
||||
const auto address = ByteAddress(ctx, inst, mem);
|
||||
if (resource.object_pointer == 0u) return;
|
||||
const auto address = ByteAddress(ctx, inst, mem, resource.byte_offset);
|
||||
const auto index = Binary(
|
||||
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
|
||||
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
|
||||
EmitIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
ctx.state, BufferIndexInBounds(ctx, inst, mem, resource,
|
||||
EmitMemoryElementInBounds(ctx.state, resource, index)), [&]() {
|
||||
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
|
||||
});
|
||||
}
|
||||
|
||||
void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem, uint32_t bits) {
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
StoreSubwordPrepared(ctx, inst, mem, resource, bits, ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
|
||||
StoreSubwordPrepared(ctx, inst, selected, resource, bits, ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -539,58 +629,20 @@ void StoreLocalFlat(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
|
||||
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t data) {
|
||||
const auto index = DwordIndex(ctx, inst, mem);
|
||||
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
if (resource.object_pointer == 0u) return;
|
||||
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
|
||||
const auto in_bounds = BufferIndexInBounds(ctx, inst, mem, resource,
|
||||
EmitMemoryElementInBounds(ctx.state, resource, index));
|
||||
EmitIfCondition(ctx.state, in_bounds, [&]() {
|
||||
StoreWordInBounds(ctx, resource, index, data);
|
||||
});
|
||||
}
|
||||
|
||||
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
|
||||
auto& state = ctx.state;
|
||||
EmitIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto data = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto store = [&](uint32_t resource) {
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
const auto& buffer = state.program.info.buffers[resource];
|
||||
if (buffer.packed_stride == 0u &&
|
||||
buffer.source < state.program.descriptor_sources.size() &&
|
||||
state.program.descriptor_sources[buffer.source].indirect_descriptor) return;
|
||||
}
|
||||
mem.resource = resource;
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
StoreWordPrepared(ctx, inst, mem, access, data);
|
||||
};
|
||||
if (mem.kind != IR::ResourceKind::Buffer ||
|
||||
state.program.info.buffers[mem.resource].indirect_root != mem.resource) {
|
||||
store(mem.resource);
|
||||
return;
|
||||
}
|
||||
const auto& buffer = state.program.info.buffers[mem.resource];
|
||||
const auto* handle = inst.Arg(0).ResolveInstruction();
|
||||
if (handle == nullptr || buffer.indirect_resources.empty() ||
|
||||
state.flattened_srt_variable == 0 || buffer.indirect_search_iterations == 0u) {
|
||||
ctx.Fail(inst, "has no indirect buffer runtime mapping");
|
||||
return;
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, ctx.Arg(*handle, 0), buffer.indirect_mapping_offset,
|
||||
buffer.indirect_search_iterations, UINT32_MAX);
|
||||
const auto merge = state.builder.AllocateId();
|
||||
std::vector<uint32_t> labels(buffer.indirect_resources.size());
|
||||
std::vector<uint32_t> branches {spv::OpSwitch, selected, merge};
|
||||
for (uint32_t i = 0; i < labels.size(); ++i) {
|
||||
labels[i] = state.builder.AllocateId();
|
||||
branches.push_back(i);
|
||||
branches.push_back(labels[i]);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(branches);
|
||||
for (uint32_t i = 0; i < labels.size(); ++i) {
|
||||
EmitLabel(state, labels[i]);
|
||||
store(buffer.indirect_resources[i]);
|
||||
state.builder.AddFunction(spv::OpBranch, merge);
|
||||
}
|
||||
EmitLabel(state, merge);
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
|
||||
StoreWordPrepared(ctx, inst, selected, resource, ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -598,7 +650,12 @@ template <typename Fn>
|
||||
uint32_t EmitAtomicAccess(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, Fn&& operation) {
|
||||
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto index = DwordIndex(ctx, inst, mem);
|
||||
auto index = DwordIndex(ctx, inst, mem);
|
||||
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
|
||||
// Keep region-address truncation local to DS atomics.
|
||||
index = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), index,
|
||||
ConstantU32(ctx.state, 0x3fffu));
|
||||
}
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
@@ -648,15 +705,15 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
|
||||
PreparedFormattedMemory plan;
|
||||
plan.info = info;
|
||||
plan.resource = resource;
|
||||
const auto start = ByteAddress(ctx, inst, mem);
|
||||
const auto start = ByteAddress(ctx, inst, mem, resource.byte_offset);
|
||||
// Check the whole, unaligned format span before aligning the actual memory address.
|
||||
const auto end = Binary(ctx.state, spv::OpIAdd, TypeU32(ctx.state), start,
|
||||
ConstantU32(ctx.state, info.byte_size - 1u));
|
||||
const auto last_index = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), end,
|
||||
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
|
||||
plan.in_bounds = AndCondition(
|
||||
plan.in_bounds = BufferIndexInBounds(ctx, inst, mem, resource, AndCondition(
|
||||
ctx.state, Binary(ctx.state, spv::OpUGreaterThanEqual, TypeBool(ctx.state), end, start),
|
||||
EmitMemoryElementInBounds(ctx.state, plan.resource, last_index));
|
||||
EmitMemoryElementInBounds(ctx.state, plan.resource, last_index)));
|
||||
plan.base = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), start,
|
||||
ConstantU32(ctx.state, ~(std::min(4u, info.byte_size) - 1u)));
|
||||
return plan;
|
||||
@@ -708,98 +765,86 @@ uint32_t FormattedOutOfBoundsValue(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
return ConstructU32Composite(ctx.state, components, values);
|
||||
}
|
||||
|
||||
uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource);
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
const auto value = EmitValueOrDefaultIfCondition(
|
||||
ctx.state, plan.in_bounds, TypeU32(ctx.state),
|
||||
FormattedOutOfBoundsValue(ctx, mem, plan, 1u),
|
||||
[&]() { return LoadFormattedInBounds(ctx, mem, plan, 0u); });
|
||||
if (mem.data_bits != 16u) {
|
||||
return value;
|
||||
uint32_t LoadFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
|
||||
const Format::BufferFormatInfo& info, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto count = mem.data_bits == 16u ? mem.component_count : components;
|
||||
const auto plan = resource.object_pointer == 0u ? PreparedFormattedMemory{.info = info}
|
||||
: PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
const auto out_of_bounds = FormattedOutOfBoundsValue(ctx, mem, plan, count);
|
||||
const auto value = resource.object_pointer == 0u ? out_of_bounds : EmitValueOrDefaultIfCondition(
|
||||
state, plan.in_bounds, TypeU32Composite(state, count), out_of_bounds, [&]() {
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < count; component++) {
|
||||
values[component] = LoadFormattedInBounds(ctx, mem, plan, component);
|
||||
}
|
||||
return ConstructU32Composite(state, count, values);
|
||||
});
|
||||
if (mem.data_bits != 16u) return value;
|
||||
const bool integer = info.type == Format::ComponentType::Uint ||
|
||||
info.type == Format::ComponentType::Sint;
|
||||
const auto scalar = [&](uint32_t component) {
|
||||
if (component >= count) return integer ? ConstantU32(state, 0u) : ConstantF32(state, 0);
|
||||
auto result = value;
|
||||
if (count != 1u) {
|
||||
result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result, value,
|
||||
component);
|
||||
}
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
return EmitUMin32(ctx.state, value, ConstantU32(ctx.state, 0xffffu));
|
||||
return EmitUMin32(state, result, ConstantU32(state, 0xffffu));
|
||||
}
|
||||
if (info.type == Format::ComponentType::Sint) {
|
||||
const auto lower = EmitSMax32(ctx.state, value, ConstantU32(ctx.state, 0xffff8000u));
|
||||
return EmitSMin32(ctx.state, lower, ConstantU32(ctx.state, 0x7fffu));
|
||||
const auto lower = EmitSMax32(state, result, ConstantU32(state, 0xffff8000u));
|
||||
return EmitSMin32(state, lower, ConstantU32(state, 0x7fffu));
|
||||
}
|
||||
return EmitConvertF16F32(ctx.state, EmitBitCastF32U32(ctx.state, value));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data, uint32_t data_bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto bits = info.component_bits[component];
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else if (info.type == Format::ComponentType::Sint) {
|
||||
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
return EmitBitCastF32U32(state, result);
|
||||
};
|
||||
std::array<uint32_t, 4> packed {};
|
||||
for (uint32_t word = 0; word < components; word++) {
|
||||
const auto low = scalar(word * 2u);
|
||||
const auto high = scalar(word * 2u + 1u);
|
||||
if (integer) {
|
||||
packed[word] = Binary(
|
||||
state, spv::OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, 0xffffu)),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), high, ConstantU32(state, 16u)));
|
||||
} else {
|
||||
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
||||
const auto pair = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair,
|
||||
low, high);
|
||||
packed[word] = EmitPackHalf2x16(state, pair);
|
||||
}
|
||||
}
|
||||
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
||||
info.type == Format::ComponentType::Snorm;
|
||||
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
||||
info.type == Format::ComponentType::Sscaled;
|
||||
if (normalized || scaled) {
|
||||
// Two-bit packed channels are unsigned even in signed formats.
|
||||
const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm ||
|
||||
info.type == Format::ComponentType::Sscaled);
|
||||
const auto max_value = static_cast<float>((1u << (bits - (is_signed ? 1u : 0u))) - 1u);
|
||||
const auto lower = normalized ? (is_signed ? -1.0f : 0.0f)
|
||||
: (is_signed ? -max_value - 1.0f : 0.0f);
|
||||
const auto upper = normalized ? 1.0f : max_value;
|
||||
auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan,
|
||||
ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data));
|
||||
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
|
||||
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
|
||||
if (normalized) {
|
||||
value = EmitFPRoundEven32(
|
||||
state, EmitFPMul32(state, value, ConstantF32Value(state, max_value)));
|
||||
}
|
||||
// Float-to-integer conversion truncates scaled values toward zero.
|
||||
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
|
||||
is_signed ? TypeI32(state) : TypeU32(state), value);
|
||||
return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value;
|
||||
}
|
||||
if (bits == 16u && info.type == Format::ComponentType::Float) {
|
||||
const auto pair = EmitCompositeConstructF32x2(
|
||||
state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f));
|
||||
return EmitPackHalf2x16(state, pair);
|
||||
}
|
||||
return data;
|
||||
return ConstructU32Composite(state, components, packed);
|
||||
}
|
||||
|
||||
void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
|
||||
const Format::BufferFormatInfo& info, uint32_t data,
|
||||
uint32_t components) {
|
||||
if (resource.object_pointer == 0u) return;
|
||||
// RDNA2 formatted stores transfer the entire format, zero-filling missing VGPRs.
|
||||
const auto count = mem.data_bits == 16u ? mem.component_count : components;
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
auto packed = ConstantU32(ctx.state, 0);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
auto value = ConstantU32(ctx.state, 0);
|
||||
if (component < components) {
|
||||
if (component < count) {
|
||||
const auto word = mem.data_bits == 16u ? component / 2u : component;
|
||||
value = data;
|
||||
if (components != 1u) {
|
||||
value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
|
||||
value, data, component);
|
||||
value, data, word);
|
||||
}
|
||||
if (mem.data_bits == 16u && (component & 1u) != 0u)
|
||||
value = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), value,
|
||||
ConstantU32(ctx.state, 16u));
|
||||
}
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value, mem.data_bits);
|
||||
value = EncodeFormattedStoreComponent(ctx.state, info, component, value, mem.data_bits);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.packed_bitfield) {
|
||||
packed = EmitBitFieldInsert(
|
||||
@@ -822,19 +867,6 @@ void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
});
|
||||
}
|
||||
|
||||
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
const auto data = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data);
|
||||
return;
|
||||
}
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, data, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectScalarBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||
@@ -878,12 +910,22 @@ struct IndirectBufferAccess {
|
||||
uint32_t raw_index_in_bounds;
|
||||
};
|
||||
|
||||
IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
std::array<uint32_t, 8> IndirectBufferArgs(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||
const auto word1 = ctx.Arg(handle, 1);
|
||||
const auto records = ctx.Arg(handle, 2);
|
||||
const auto word3 = ctx.Arg(handle, 3);
|
||||
auto offset = ctx.Arg(inst, 2);
|
||||
if (ctx.Memory(inst).offset != 0u) {
|
||||
const auto immediate = ConstantU32(state, ctx.Memory(inst).offset);
|
||||
offset = offset == ConstantU32(state, 0) ? immediate
|
||||
: Binary(state, spv::OpIAdd, TypeU32(state), offset, immediate);
|
||||
}
|
||||
return {ctx.Arg(handle, 0), ctx.Arg(handle, 1), ctx.Arg(handle, 2), ctx.Arg(handle, 3),
|
||||
ctx.Arg(inst, 1), offset, ctx.Arg(inst, 3), BufferLane(state)};
|
||||
}
|
||||
|
||||
IndirectBufferAccess PrepareIndirectBuffer(EmitterState& state,
|
||||
const std::array<uint32_t, 8>& args, bool formatted) {
|
||||
const auto& [word0, word1, records, word3, input_index, offset, soffset, lane] = args;
|
||||
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
|
||||
return EmitBitFieldUExtract(state, word, ConstantU32(state, first), ConstantU32(state, count));
|
||||
};
|
||||
@@ -893,14 +935,12 @@ IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst
|
||||
const auto stride = field(word1, 16, 14);
|
||||
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
|
||||
const auto index = Binary(
|
||||
state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
|
||||
Select(state, TypeU32(state), nonzero(field(word3, 23, 1)), BufferLane(state),
|
||||
state, spv::OpIAdd, TypeU32(state), input_index,
|
||||
Select(state, TypeU32(state), nonzero(field(word3, 23, 1)), lane,
|
||||
ConstantU32(state, 0)));
|
||||
const auto soffset = ctx.Arg(inst, 3);
|
||||
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
|
||||
ctx.Memory(inst).offset, stride, swizzle,
|
||||
const auto address = CalculateBufferAddress(state, index, offset, soffset, 0u, stride, swizzle,
|
||||
field(word3, 21, 2));
|
||||
const auto base = PackU64(state, ctx.Arg(handle, 0), field(word1, 0, 16));
|
||||
const auto base = PackU64(state, word0, field(word1, 0, 16));
|
||||
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
|
||||
return {
|
||||
.address = Binary(state, spv::OpIAdd, TypeU64(state), base,
|
||||
@@ -909,7 +949,7 @@ IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst
|
||||
.stride = stride,
|
||||
.swizzle = swizzle,
|
||||
.format = field(word3, 12, 7),
|
||||
.selector = ctx.Memory(inst).formatted ? field(word3, 0, 3) : 0u,
|
||||
.selector = formatted ? field(word3, 0, 3) : 0u,
|
||||
.mode = field(word3, 28, 2),
|
||||
.records = records,
|
||||
.index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records),
|
||||
@@ -925,10 +965,9 @@ uint32_t IndirectBufferInBounds(EmitterState& state, const IndirectBufferAccess&
|
||||
state, spv::OpIAdd, TypeU32(state), buffer.offset, ConstantU32(state, displacement));
|
||||
const auto has_payload = [&](uint32_t size) {
|
||||
return AndCondition(
|
||||
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size,
|
||||
ConstantU32(state, bytes)),
|
||||
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, bytes),
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
|
||||
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, bytes))));
|
||||
Binary(state, spv::OpISub, TypeU32(state), size, bytes)));
|
||||
};
|
||||
const auto structured = AndCondition(
|
||||
state, buffer.index_in_bounds,
|
||||
@@ -955,9 +994,9 @@ uint32_t IndirectBufferInBounds(EmitterState& state, const IndirectBufferAccess&
|
||||
return in_bounds;
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& buffer,
|
||||
uint32_t bits, uint32_t components, bool coherent) {
|
||||
auto& state = ctx.state;
|
||||
const auto buffer = PrepareIndirectBuffer(ctx, inst);
|
||||
const auto valid_format = Binary(state, spv::OpINotEqual, TypeBool(state), buffer.format,
|
||||
ConstantU32(state, 0));
|
||||
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||
@@ -969,8 +1008,20 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_
|
||||
ConstantU64(state, component * 4u));
|
||||
values[component] = EmitValueOrZeroIfCondition(
|
||||
state, AndCondition(state, valid_format,
|
||||
IndirectBufferInBounds(state, buffer, component * 4u, 4u, false)),
|
||||
[&]() { return LoadBdaDword(ctx, address); });
|
||||
IndirectBufferInBounds(state, buffer, component * 4u,
|
||||
ConstantU32(state, bits / 8u), false)),
|
||||
[&]() {
|
||||
const auto value = LoadBdaDword(ctx, address, coherent);
|
||||
if (bits == 32u) return value;
|
||||
const auto byte = Binary(
|
||||
state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
Unary(state, spv::OpUConvert, TypeU32(state), buffer.address),
|
||||
ConstantU32(state, bits == 8u ? 3u : 2u));
|
||||
return EmitBitFieldUExtract(
|
||||
state, value,
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, ConstantU32(state, 3u)),
|
||||
ConstantU32(state, bits));
|
||||
});
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
}
|
||||
@@ -978,100 +1029,109 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_
|
||||
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto buffer = PrepareIndirectBuffer(ctx, inst);
|
||||
return EmitIndexSwitch(
|
||||
state, buffer.format, static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u,
|
||||
TypeU32(state), [&](uint32_t format) {
|
||||
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
|
||||
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
|
||||
const auto constant = Select(
|
||||
state, TypeU32(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), buffer.selector, ConstantU32(state, 1)),
|
||||
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
|
||||
ConstantU32(state, 4)),
|
||||
TypeU32(state), constant, [&]() {
|
||||
const auto in_bounds = IndirectBufferInBounds(state, buffer, 0u, info.byte_size, true);
|
||||
const auto base = Binary(
|
||||
state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||
ConstantU64(state, ~(uint64_t(std::min(4u, info.byte_size)) - 1u)));
|
||||
const auto load = [&](uint32_t component, uint32_t bits) {
|
||||
const auto offset = Format::GetFormatComponentByteOffset(info, component);
|
||||
const auto address = offset == 0u
|
||||
? base
|
||||
: Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
base, ConstantU64(state, offset));
|
||||
return LoadBda(ctx, address, in_bounds, bits);
|
||||
};
|
||||
const auto emit_component = [&](uint32_t component) {
|
||||
return LoadFormattedComponent(
|
||||
ctx, info, {FormattedSourceKind::Memory, component},
|
||||
[&](uint32_t source) { return load(source, 32u); }, load);
|
||||
};
|
||||
if (info.component_count == 1u) return emit_component(0u);
|
||||
const auto component = Binary(
|
||||
state, spv::OpUMod, TypeU32(state),
|
||||
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)),
|
||||
ConstantU32(state, info.component_count));
|
||||
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
|
||||
emit_component);
|
||||
});
|
||||
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
|
||||
const auto geometry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
|
||||
state.indirect_format_geometry_function, buffer.format);
|
||||
const auto field = [&](uint32_t index) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
|
||||
return value;
|
||||
};
|
||||
const auto bytes = field(0);
|
||||
const auto component = Binary(
|
||||
state, spv::OpUMod, TypeU32(state),
|
||||
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
|
||||
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
|
||||
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||
Unary(state, spv::OpNot, TypeU64(state), alignment));
|
||||
const auto address = Binary(
|
||||
state, spv::OpIAdd, TypeU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
|
||||
const auto memory = AndCondition(
|
||||
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
|
||||
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
|
||||
ConstantU32(state, 4)));
|
||||
const auto raw = EmitValueOrZeroIfCondition(
|
||||
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
|
||||
[&]() {
|
||||
// Format alignment and component spacing keep every selected component within one DWORD.
|
||||
const auto word = LoadBdaDword(
|
||||
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
|
||||
ConstantU64(state, ~uint64_t {3})));
|
||||
const auto byte = Binary(
|
||||
state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
|
||||
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
|
||||
ConstantU32(state, 3)));
|
||||
});
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
|
||||
state.indirect_format_function, buffer.format, buffer.selector,
|
||||
component, raw);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto mem = ctx.Memory(inst);
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
|
||||
return LoadIndirectFormattedX(ctx, inst);
|
||||
}
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
const auto mem = ctx.Memory(inst);
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
return LoadIndirectBuffer(ctx, inst, components);
|
||||
const auto function = state.indirect_buffer_load_functions.at(
|
||||
{mem.data_bits, components, uint32_t(mem.coherent)});
|
||||
const auto args = IndirectBufferArgs(ctx, inst);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
|
||||
result, function, args);
|
||||
return result;
|
||||
}
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, plan.in_bounds, TypeU32Composite(state, components),
|
||||
FormattedOutOfBoundsValue(ctx, mem, plan, components), [&]() {
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
values[component] = LoadFormattedInBounds(ctx, mem, plan, component);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
});
|
||||
}
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
values[component] =
|
||||
LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
return EmitBufferAccess(ctx, inst, mem, components, [&](const auto& selected, const auto& resource) {
|
||||
const auto info = Format::GetFormatInfo(
|
||||
selected.formatted ? BufferFormat(ctx, selected) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
return LoadFormattedPrepared(ctx, inst, selected, resource, info, components);
|
||||
}
|
||||
if (resource.object_pointer == 0u) return ConstantU32CompositeZero(state, components);
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
values[component] =
|
||||
LoadWordPrepared(ctx, inst, RebaseRawComponent(selected, component), resource);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
void StoreBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
EmitIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto composite = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, composite, components);
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
|
||||
component);
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
|
||||
}
|
||||
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
|
||||
const auto info = Format::GetFormatInfo(
|
||||
selected.formatted ? BufferFormat(ctx, selected) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
StoreFormattedPrepared(ctx, inst, selected, resource, info, composite, components);
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
auto data = composite;
|
||||
if (components != 1u) {
|
||||
data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
|
||||
component);
|
||||
}
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(selected, component), resource, data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1128,6 +1188,124 @@ uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
|
||||
auto& state = ctx.state;
|
||||
for (const auto* block: state.program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto components = IR::BufferComponentCount(inst.GetOpcode());
|
||||
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
|
||||
const auto& memory = ctx.Memory(inst);
|
||||
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
|
||||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
|
||||
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
|
||||
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
|
||||
if (!inserted) continue;
|
||||
const auto function = entry->second = state.builder.AllocateId();
|
||||
const auto type = TypeU32(state);
|
||||
const auto result_type = TypeU32Composite(state, components);
|
||||
const auto signature = state.builder.Type(
|
||||
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
|
||||
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
|
||||
std::to_string(components) + (memory.coherent ? "_coherent" : "");
|
||||
state.builder.AddName(function, name.c_str());
|
||||
state.builder.AddFunction(spv::OpFunction, result_type, function,
|
||||
spv::FunctionControlMaskNone, signature);
|
||||
std::array<uint32_t, 8> args;
|
||||
for (auto& arg: args) {
|
||||
arg = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
|
||||
}
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
|
||||
memory.data_bits, components, memory.coherent);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
||||
auto& state = ctx.state;
|
||||
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
|
||||
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
|
||||
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
|
||||
const auto& memory = ctx.Memory(inst);
|
||||
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
|
||||
});
|
||||
});
|
||||
if (!needed) return;
|
||||
|
||||
const auto type = TypeU32(state);
|
||||
constexpr auto format_count =
|
||||
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
|
||||
{
|
||||
const auto result_type = TypeU32Composite(state, 4);
|
||||
const auto function_type = state.builder.Type(spv::OpTypeFunction, result_type, type);
|
||||
state.indirect_format_geometry_function = state.builder.AllocateId();
|
||||
const auto format = state.builder.AllocateId();
|
||||
state.builder.AddName(state.indirect_format_geometry_function,
|
||||
"indirect_buffer_format_geometry");
|
||||
state.builder.AddFunction(spv::OpFunction, result_type,
|
||||
state.indirect_format_geometry_function,
|
||||
spv::FunctionControlMaskNone, function_type);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, format);
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
const auto result = EmitIndexSwitch(
|
||||
state, format, format_count, result_type, [&](uint32_t format) {
|
||||
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
|
||||
return state.builder.Constant(
|
||||
spv::OpConstantComposite, result_type, ConstantU32(state, info.byte_size),
|
||||
ConstantU32(state, std::max(1u, info.component_count)),
|
||||
ConstantU32(state, info.packed_bitfield ? 0u : info.component_bits[0] / 8u),
|
||||
ConstantU32(state, info.byte_size == 0u ? 0u : std::min(4u, info.byte_size) - 1u));
|
||||
});
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type, type, type, type);
|
||||
state.indirect_format_function = state.builder.AllocateId();
|
||||
const auto format = state.builder.AllocateId();
|
||||
const auto selector = state.builder.AllocateId();
|
||||
const auto component = state.builder.AllocateId();
|
||||
const auto raw = state.builder.AllocateId();
|
||||
state.builder.AddName(state.indirect_format_function, "convert_indirect_buffer_format");
|
||||
state.builder.AddFunction(spv::OpFunction, type, state.indirect_format_function,
|
||||
spv::FunctionControlMaskNone, function_type);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, format);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, selector);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, component);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, raw);
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
const auto result = EmitIndexSwitch(
|
||||
state, format, format_count, TypeU32(state), [&](uint32_t format) {
|
||||
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
|
||||
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
|
||||
const auto constant = Select(
|
||||
state, TypeU32(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, 1)),
|
||||
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), selector,
|
||||
ConstantU32(state, 4)),
|
||||
TypeU32(state), constant, [&]() {
|
||||
const auto emit_component = [&](uint32_t component) {
|
||||
return LoadFormattedComponent(
|
||||
ctx, info, {FormattedSourceKind::Memory, component},
|
||||
[&](uint32_t) { return raw; },
|
||||
[&](uint32_t, uint32_t bits) {
|
||||
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), raw,
|
||||
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
|
||||
});
|
||||
};
|
||||
if (!info.packed_bitfield) return emit_component(0u);
|
||||
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
|
||||
emit_component);
|
||||
});
|
||||
});
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
@@ -1200,8 +1378,8 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU64(state), ConstantU64(state, 0), [&]() {
|
||||
const auto resource = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_u64_variable, TypeStorageBufferPointer(state, 64));
|
||||
const auto resource =
|
||||
PrepareStorageBufferResourceAccess(state, mem, state.storage_buffers[3]);
|
||||
const auto byte_address = ByteAddress(ctx, inst, mem);
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte_address,
|
||||
ConstantU32(state, 3u));
|
||||
@@ -1221,32 +1399,48 @@ void EmitSharedAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
EnsureLdsStorage(state);
|
||||
EmitIfCondition(state, ctx.Arg(inst, 2), [&]() {
|
||||
const auto address = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
ByteAddress(ctx, inst, mem), ConstantU32(state, 0xfff8u));
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address,
|
||||
ConstantU32(state, 3u));
|
||||
const auto in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index,
|
||||
ConstantU32(state, LdsDwordCount(state) / 2u));
|
||||
EmitIfCondition(state, in_bounds, [&]() {
|
||||
auto native_index = index;
|
||||
auto semantics = spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
if (state.lds_storage_class == spv::StorageClassStorageBuffer) {
|
||||
native_index = EmitAddU32(state, index, EmitBinaryU32(
|
||||
state, spv::OpShiftRightLogical, state.lds_base_dwords, ConstantU32(state, 1)));
|
||||
semantics = spv::MemorySemanticsUniformMemoryMask;
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto address = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
ByteAddress(ctx, inst, mem), ConstantU32(state, 0xfff8u));
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address,
|
||||
ConstantU32(state, 3u));
|
||||
const auto in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index,
|
||||
ConstantU32(state, LdsDwordCount(state) / 2u));
|
||||
const auto active = EmitLogicalAndBool(state, ctx.Arg(inst, inst.NumArgs() - 1), in_bounds);
|
||||
const auto emit = [&]() {
|
||||
auto native_index = index;
|
||||
auto semantics = spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
if (state.lds_storage_class == spv::StorageClassStorageBuffer) {
|
||||
native_index = EmitAddU32(state, index, EmitBinaryU32(
|
||||
state, spv::OpShiftRightLogical, state.lds_base_dwords, ConstantU32(state, 1)));
|
||||
semantics = spv::MemorySemanticsUniformMemoryMask;
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, state.lds_storage_class, TypeU64(state)),
|
||||
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
|
||||
const auto old = state.builder.AllocateId();
|
||||
const auto opcode = SpirvAtomicOpcode(inst.GetOpcode());
|
||||
const auto scope = ConstantU32(state, spv::ScopeWorkgroup);
|
||||
const auto acquire_release =
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | semantics);
|
||||
if (opcode == spv::OpAtomicCompareExchange) {
|
||||
// RDNA2 DS compares DATA and stores DATA2, opposite to BUFFER atomics.
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, state.lds_storage_class, TypeU64(state)),
|
||||
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
|
||||
const auto value = ctx.Arg(inst, 1);
|
||||
state.builder.AddFunction(
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeU64(state), state.builder.AllocateId(),
|
||||
pointer, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | semantics), value);
|
||||
});
|
||||
});
|
||||
opcode, TypeU64(state), old, pointer, scope, acquire_release,
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireMask | semantics),
|
||||
ctx.Arg(inst, 2), ctx.Arg(inst, 1));
|
||||
} else {
|
||||
state.builder.AddFunction(opcode, TypeU64(state), old, pointer, scope,
|
||||
acquire_release, ctx.Arg(inst, 1));
|
||||
}
|
||||
return old;
|
||||
};
|
||||
if (inst.HasUses()) {
|
||||
ctx.Define(inst, EmitValueOrDefaultIfCondition(
|
||||
state, active, TypeU64(state), ConstantU64(state, 0), emit));
|
||||
} else {
|
||||
EmitIfCondition(state, active, emit);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EmitBufferFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
@@ -1376,6 +1570,13 @@ void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}));
|
||||
}
|
||||
|
||||
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto& buffer = ctx.state.program.info.buffers.at(inst.Flags<uint32_t>());
|
||||
if (buffer.indirect_resources.empty()) return ConstantU32(ctx.state, 0u);
|
||||
return EmitIndirectResourceIndex(ctx.state, ctx.Arg(inst, 0), buffer.indirect_mapping_offset,
|
||||
buffer.indirect_search_iterations, UINT32_MAX);
|
||||
}
|
||||
|
||||
void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
@@ -1387,7 +1588,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (mem.kind == IR::ResourceKind::FlatLocal)
|
||||
value = LoadLocalFlat(ctx, inst);
|
||||
else if (buffer_components > 1u ||
|
||||
(buffer_components == 1u && mem.kind == IR::ResourceKind::IndirectBuffer && !mem.formatted))
|
||||
(buffer_components == 1u && mem.kind == IR::ResourceKind::IndirectBuffer) ||
|
||||
(op == IR::ValueOpcode::LoadBufferU32 && mem.formatted))
|
||||
value = LoadBuffer(ctx, inst, buffer_components);
|
||||
else if (shared_components > 1u)
|
||||
value = LoadWideShared(ctx, inst, shared_components);
|
||||
@@ -1397,9 +1599,6 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
mem.kind != IR::ResourceKind::Scratch)
|
||||
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
|
||||
address_info.data_bits, mem.coherent);
|
||||
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
|
||||
value = mem.kind == IR::ResourceKind::IndirectBuffer ? LoadIndirectFormattedX(ctx, inst)
|
||||
: FormattedLoad(ctx, inst, mem);
|
||||
else if (inst.GetType() == IR::Type::U8)
|
||||
value = LoadSubword(ctx, inst, mem, 8, false);
|
||||
else if (inst.GetType() == IR::Type::U16)
|
||||
@@ -1418,11 +1617,11 @@ void EmitStoreMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (mem.kind == IR::ResourceKind::FlatLocal)
|
||||
StoreLocalFlat(ctx, inst);
|
||||
else if (buffer_components > 1u)
|
||||
StoreWideBuffer(ctx, inst, buffer_components);
|
||||
StoreBuffer(ctx, inst, buffer_components);
|
||||
else if (shared_components > 1u)
|
||||
StoreWideShared(ctx, inst, shared_components);
|
||||
else if (op == IR::ValueOpcode::StoreBufferU32 && mem.formatted)
|
||||
FormattedStore(ctx, inst, mem);
|
||||
StoreBuffer(ctx, inst, 1u);
|
||||
else if (type == IR::Type::U8)
|
||||
StoreSubword(ctx, inst, mem, 8);
|
||||
else if (type == IR::Type::U16)
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
if (state.program.bindings.UsesPushData()) {
|
||||
dword_index += state.program.bindings.push_data_start_dword;
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
|
||||
pointer, state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, dword_index));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
uint32_t EmitPushDataLoad(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
|
||||
pointer, state.push_constant_variable, ConstantU32(state, 0), index);
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint32_t EmitShaderDataDwordLoadAt(EmitterState& state, uint32_t dword_index) {
|
||||
if (state.program.bindings.UsesPushData()) return EmitPushDataLoad(state, dword_index);
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.shader_data_storage_variable,
|
||||
ConstantU32(state, 0), ConstantU32(state, dword_index));
|
||||
ConstantU32(state, 0), dword_index);
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
if (state.program.bindings.UsesPushData()) {
|
||||
dword_index += state.program.bindings.push_data_start_dword;
|
||||
}
|
||||
return EmitShaderDataDwordLoadAt(state, ConstantU32(state, dword_index));
|
||||
}
|
||||
|
||||
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(opcode, TypeU32(state), ret, lhs, rhs);
|
||||
@@ -102,23 +109,51 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
return state.program.info.buffers[mem.resource].descriptor_format;
|
||||
}
|
||||
|
||||
uint32_t StorageBufferElementBits(const IR::Program& program, const IR::MemoryInfo& mem) {
|
||||
if (!mem.formatted) return mem.data_bits;
|
||||
const auto format = mem.typed ? Format::DecodeTBufferFormat(mem.data_format, mem.number_format)
|
||||
: program.info.buffers[mem.resource].descriptor_format;
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
return info.type == Format::ComponentType::Unknown || info.packed_bitfield
|
||||
? 32u : info.component_bits[0];
|
||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource) {
|
||||
const auto& buffers = state.program.info.buffers;
|
||||
const auto& root = buffers[resource];
|
||||
if (root.indirect_root != resource || root.descriptor_index == UINT32_MAX ||
|
||||
root.indirect_resources.size() < 2u) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
const auto first = root.packed_stride == 0u ? 1u : 0u;
|
||||
const auto representative = root.indirect_resources[first];
|
||||
const auto& shared = buffers[representative];
|
||||
if (shared.packed_stride == 0u) return UINT32_MAX;
|
||||
uint32_t next_child_slot = UINT32_MAX;
|
||||
for (uint32_t ordinal = first; ordinal < root.indirect_resources.size(); ++ordinal) {
|
||||
const auto& candidate = buffers[root.indirect_resources[ordinal]];
|
||||
if (candidate.packed_stride != shared.packed_stride ||
|
||||
candidate.descriptor_format != shared.descriptor_format ||
|
||||
candidate.descriptor_swizzle != shared.descriptor_swizzle ||
|
||||
candidate.descriptor_index == UINT32_MAX ||
|
||||
(ordinal > 1u && candidate.descriptor_index != next_child_slot)) return UINT32_MAX;
|
||||
if (ordinal != 0u) next_child_slot = candidate.descriptor_index + 1u;
|
||||
}
|
||||
return representative;
|
||||
}
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state) {
|
||||
for (uint32_t i = 0; i < state.program.bindings.memory_offset_count; i++) {
|
||||
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||
state.memory_byte_offsets.resize(count);
|
||||
std::vector<bool> dynamic_offsets(count);
|
||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||
for (const auto candidate: state.program.info.buffers[resource].indirect_resources) {
|
||||
dynamic_offsets[state.program.info.buffers[candidate].descriptor_index] = true;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i += 4u) {
|
||||
const auto lanes = std::min(4u, count - i);
|
||||
if (std::all_of(dynamic_offsets.begin() + i, dynamic_offsets.begin() + i + lanes,
|
||||
[](bool dynamic) { return dynamic; })) continue;
|
||||
const auto word =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.memory_offset_dword + i / 4u);
|
||||
const auto shift = ConstantU32(state, (i % 4u) * 8u);
|
||||
state.memory_byte_offsets[i] = EmitBinaryU32(
|
||||
state, spv::OpBitwiseAnd, EmitBinaryU32(state, spv::OpShiftRightLogical, word, shift),
|
||||
ConstantU32(state, 0xffu));
|
||||
for (uint32_t lane = 0; lane < lanes; ++lane) {
|
||||
if (dynamic_offsets[i + lane]) continue;
|
||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +181,7 @@ void EnsureLdsStorage(EmitterState& state) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
return variable;
|
||||
};
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
if (state.program.info.shared_int64_atomics) {
|
||||
state.lds_variable = define(TypeU32(state), 4u);
|
||||
state.lds_u64_variable = define(TypeU64(state), 8u);
|
||||
state.builder.AddName(state.lds_u64_variable, "lds_qwords");
|
||||
@@ -160,28 +195,51 @@ void EnsureLdsStorage(EmitterState& state) {
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type) {
|
||||
if (variable == 0) {
|
||||
BufferDefinition& buffer,
|
||||
uint32_t array_index) {
|
||||
if (buffer.variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
if (buffer.pointer_type == 0) {
|
||||
buffer.pointer_type = TypePointer(state, spv::StorageClassStorageBuffer, buffer.type);
|
||||
}
|
||||
const bool non_uniform = array_index != 0u;
|
||||
const auto static_index = state.program.info.buffers.at(mem.resource).descriptor_index;
|
||||
if (!non_uniform) array_index = ConstantU32(state, static_index);
|
||||
MemoryResourceAccess access {
|
||||
.kind = mem.kind,
|
||||
.non_uniform = non_uniform,
|
||||
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index));
|
||||
access.byte_offset = state.memory_byte_offsets[array_index];
|
||||
state.builder.AddFunction(spv::OpAccessChain, buffer.pointer_type, access.object_pointer,
|
||||
buffer.variable, array_index);
|
||||
if (non_uniform) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
||||
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, offsets_start),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, shift, ConstantU32(state, 8));
|
||||
} else {
|
||||
access.byte_offset = state.memory_byte_offsets[static_index];
|
||||
}
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
access.object_pointer, 0);
|
||||
return access;
|
||||
}
|
||||
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem) {
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t array_index) {
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
switch (mem.kind) {
|
||||
case IR::ResourceKind::Lds:
|
||||
@@ -214,12 +272,14 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
const auto bits = mem.kind == IR::ResourceKind::Buffer
|
||||
? StorageBufferElementBits(state.program, mem) : 32u;
|
||||
const auto variable = bits == 8u ? state.storage_buffer_u8_variable
|
||||
: bits == 16u ? state.storage_buffer_u16_variable
|
||||
: state.storage_buffer_variable;
|
||||
access = PrepareStorageBufferResourceAccess(
|
||||
state, mem, variable, TypeStorageBufferPointer(state, bits));
|
||||
? IR::StorageBufferElementBits(state.program, mem) : 32u;
|
||||
auto* buffer = &state.storage_buffers[2];
|
||||
if (bits == 8u) {
|
||||
buffer = &state.storage_buffers[0];
|
||||
} else if (bits == 16u) {
|
||||
buffer = &state.storage_buffers[1];
|
||||
}
|
||||
access = PrepareStorageBufferResourceAccess(state, mem, *buffer, array_index);
|
||||
access.element_bits = bits;
|
||||
return access;
|
||||
}
|
||||
@@ -245,7 +305,7 @@ uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAcces
|
||||
TypeStorageBufferElementPointer(state, 32));
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
if (access.kind == IR::ResourceKind::Lds && state.requirements.shared_int64_atomics) {
|
||||
if (access.kind == IR::ResourceKind::Lds && state.program.info.shared_int64_atomics) {
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, ConstantU32(state, 0), index);
|
||||
return pointer;
|
||||
@@ -264,6 +324,9 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
|
||||
ConstantU32(state, 0), index);
|
||||
if (access.non_uniform) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, pointer, spv::DecorationNonUniform);
|
||||
}
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@@ -366,8 +429,82 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data, uint32_t data_bits,
|
||||
UnormRounding rounding) {
|
||||
EXIT_IF(rounding != UnormRounding::RoundEven && info.type != Format::ComponentType::Unorm);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else if (info.type == Format::ComponentType::Sint) {
|
||||
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else {
|
||||
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
||||
}
|
||||
}
|
||||
if (info.type == Format::ComponentType::Sint && bits < 32u) {
|
||||
const auto maximum = (1u << (bits - 1u)) - 1u;
|
||||
data = EmitSMax32(state, data, ConstantU32(state, ~maximum));
|
||||
data = EmitSMin32(state, data, ConstantU32(state, maximum));
|
||||
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), data,
|
||||
ConstantU32(state, (1u << bits) - 1u));
|
||||
}
|
||||
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
||||
info.type == Format::ComponentType::Snorm;
|
||||
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
||||
info.type == Format::ComponentType::Sscaled;
|
||||
if (normalized || scaled) {
|
||||
// Two-bit packed channels are unsigned even in signed formats.
|
||||
const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm ||
|
||||
info.type == Format::ComponentType::Sscaled);
|
||||
const auto max_value = static_cast<float>((1u << (bits - (is_signed ? 1u : 0u))) - 1u);
|
||||
const auto lower = normalized ? (is_signed ? -1.0f : 0.0f)
|
||||
: (is_signed ? -max_value - 1.0f : 0.0f);
|
||||
const auto upper = normalized ? 1.0f : max_value;
|
||||
auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan,
|
||||
ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data));
|
||||
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
|
||||
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
|
||||
if (normalized) {
|
||||
value = EmitFPMul32(state, value, ConstantF32Value(state, max_value));
|
||||
if (rounding == UnormRounding::RoundEven) {
|
||||
value = EmitFPRoundEven32(state, value);
|
||||
} else if (rounding == UnormRounding::HalfUp) {
|
||||
value = EmitFPAdd32(state, value, ConstantF32Value(state, 0.5f));
|
||||
}
|
||||
}
|
||||
// Float-to-integer conversion truncates scaled values toward zero.
|
||||
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
|
||||
is_signed ? TypeI32(state) : TypeU32(state), value);
|
||||
return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value;
|
||||
}
|
||||
if (bits == 16u && info.type == Format::ComponentType::Float) {
|
||||
const auto pair = EmitCompositeConstructF32x2(
|
||||
state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f));
|
||||
return EmitPackHalf2x16(state, pair);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data, uint32_t data_bits,
|
||||
UnormRounding rounding) {
|
||||
auto value = EncodeFormattedStoreComponent(state, info, component, data, data_bits, rounding);
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
value = EmitUMin32(state, value,
|
||||
ConstantU32(state, (1u << info.component_bits[component]) - 1u));
|
||||
}
|
||||
if (info.component_bit_offset[component] != 0u) {
|
||||
value = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), value,
|
||||
ConstantU32(state, info.component_bit_offset[component]));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::SharedAtomicCmpSwap64:
|
||||
case IR::ValueOpcode::ImageAtomicCompareSwap32:
|
||||
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
|
||||
case IR::ValueOpcode::ImageAtomicSwap32:
|
||||
@@ -395,7 +532,8 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::ImageAtomicUMax32:
|
||||
case IR::ValueOpcode::ImageAtomicUMax64:
|
||||
case IR::ValueOpcode::BufferAtomicUMax32:
|
||||
case IR::ValueOpcode::SharedAtomicUMax32: return spv::OpAtomicUMax;
|
||||
case IR::ValueOpcode::SharedAtomicUMax32:
|
||||
case IR::ValueOpcode::SharedAtomicUMax64: return spv::OpAtomicUMax;
|
||||
case IR::ValueOpcode::ImageAtomicAnd32:
|
||||
case IR::ValueOpcode::ImageAtomicAnd64:
|
||||
case IR::ValueOpcode::BufferAtomicAnd32:
|
||||
|
||||
@@ -120,32 +120,39 @@ uint32_t TypeFunction(EmitterState& state) {
|
||||
return state.function_type;
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferElement(EmitterState& state, uint32_t bits) {
|
||||
return bits == 32u ? TypeU32(state)
|
||||
: bits == 64u ? TypeU64(state)
|
||||
: state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
}
|
||||
|
||||
uint32_t StorageRuntimeArrayType(EmitterState& state, uint32_t bits) {
|
||||
return state.builder.DecoratedType(
|
||||
spv::OpTypeRuntimeArray,
|
||||
{{spv::OpDecorate, {spv::DecorationArrayStride, bits / 8u}}},
|
||||
TypeStorageBufferElement(state, bits));
|
||||
}
|
||||
|
||||
uint32_t StorageBufferType(EmitterState& state, uint32_t bits = 32) {
|
||||
return state.builder.DecoratedType(spv::OpTypeStruct,
|
||||
{{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
|
||||
{spv::OpDecorate, {spv::DecorationBlock}}},
|
||||
StorageRuntimeArrayType(state, bits));
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state, uint32_t bits) {
|
||||
return TypePointer(state, spv::StorageClassStorageBuffer, StorageBufferType(state, bits));
|
||||
uint32_t StorageBufferType(EmitterState& state, BufferDefinition& buffer, uint32_t bits = 32) {
|
||||
if (buffer.type == 0) {
|
||||
if (bits == 32u) {
|
||||
buffer.element_type = TypeU32(state);
|
||||
} else if (bits == 64u) {
|
||||
buffer.element_type = TypeU64(state);
|
||||
} else {
|
||||
buffer.element_type = state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
}
|
||||
const auto array = state.builder.DecoratedType(
|
||||
spv::OpTypeRuntimeArray,
|
||||
{{spv::OpDecorate, {spv::DecorationArrayStride, bits / 8u}}}, buffer.element_type);
|
||||
buffer.type = state.builder.DecoratedType(
|
||||
spv::OpTypeStruct, {{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
|
||||
{spv::OpDecorate, {spv::DecorationBlock}}}, array);
|
||||
}
|
||||
return buffer.type;
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state, uint32_t bits) {
|
||||
return TypePointer(state, spv::StorageClassStorageBuffer, TypeStorageBufferElement(state, bits));
|
||||
auto* buffer = &state.storage_buffers[2];
|
||||
if (bits == 8u) {
|
||||
buffer = &state.storage_buffers[0];
|
||||
} else if (bits == 16u) {
|
||||
buffer = &state.storage_buffers[1];
|
||||
} else if (bits == 64u) {
|
||||
buffer = &state.storage_buffers[3];
|
||||
}
|
||||
if (buffer->element_pointer_type == 0) {
|
||||
buffer->element_pointer_type =
|
||||
TypePointer(state, spv::StorageClassStorageBuffer, buffer->element_type);
|
||||
}
|
||||
return buffer->element_pointer_type;
|
||||
}
|
||||
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state) {
|
||||
@@ -200,14 +207,54 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
}
|
||||
|
||||
void DefineDescriptors(EmitterState& state) {
|
||||
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
auto& info = state.program.info;
|
||||
auto& bindings = state.program.bindings;
|
||||
auto& counts = bindings.descriptor_counts;
|
||||
auto& buffer_count = counts[static_cast<size_t>(Kind::Buffers)];
|
||||
for (auto& buffer: info.buffers) {
|
||||
buffer.descriptor_index = buffer.live ? buffer_count++ : UINT32_MAX;
|
||||
}
|
||||
std::array<const IR::ImageResource*, IR::ImageBindingCount> image_types {};
|
||||
for (auto& image: info.images) {
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
if (!kind.has_value() || image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
EXIT("shader image has an invalid descriptor type or mip count");
|
||||
}
|
||||
auto& count = counts[static_cast<size_t>(*kind)];
|
||||
image.descriptor_index = count;
|
||||
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = ℑ
|
||||
count += image.mip_count;
|
||||
}
|
||||
counts[static_cast<size_t>(Kind::Samplers)] = static_cast<uint32_t>(info.samplers.size());
|
||||
counts[static_cast<size_t>(Kind::Gds)] = info.uses_gds;
|
||||
counts[static_cast<size_t>(Kind::SharedMemory)] =
|
||||
info.uses_lds && state.lds_storage_class == spv::StorageClassStorageBuffer;
|
||||
counts[static_cast<size_t>(Kind::BdaPagetable)] = info.uses_dma;
|
||||
counts[static_cast<size_t>(Kind::FaultBuffer)] = info.uses_dma;
|
||||
counts[static_cast<size_t>(Kind::FlattenedSrt)] = info.uses_flattened_srt;
|
||||
bindings.memory_offset_dword = static_cast<uint32_t>(info.user_data_registers.size());
|
||||
if (info.uses_dispatch_threads) {
|
||||
bindings.dispatch_thread_dword = bindings.memory_offset_dword;
|
||||
bindings.memory_offset_dword += 3u;
|
||||
}
|
||||
bindings.push_data_start_dword =
|
||||
IR::PushData::StartFor(bindings.push_data_start_dword, bindings.ShaderDataDwords());
|
||||
counts[static_cast<size_t>(Kind::ShaderData)] =
|
||||
bindings.ShaderDataDwords() != 0 && !bindings.UsesPushData();
|
||||
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh ||
|
||||
state.object_primitive_id_variable != 0) {
|
||||
const auto type = PushConstantBlockType(state);
|
||||
state.push_constant_variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant);
|
||||
state.builder.AddName(type, "BufferResource");
|
||||
state.builder.AddName(state.push_constant_variable, "vsharp");
|
||||
}
|
||||
for (const auto& binding: state.program.bindings.descriptors) {
|
||||
for (uint32_t index = 0; index < counts.size(); ++index) {
|
||||
if (counts[index] == 0) continue;
|
||||
bindings.descriptor_mask |= uint64_t{1} << index;
|
||||
const auto kind = static_cast<Kind>(index);
|
||||
const auto Define = [&](uint32_t type, const char* name,
|
||||
spv::StorageClass storage = spv::StorageClassStorageBuffer) {
|
||||
const auto variable =
|
||||
@@ -215,69 +262,66 @@ void DefineDescriptors(EmitterState& state) {
|
||||
state.builder.AddName(variable, name);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding,
|
||||
IR::NativeBinding(state.program.stage, binding.kind));
|
||||
IR::NativeBinding(state.program.stage, kind));
|
||||
return variable;
|
||||
};
|
||||
const auto ArrayType = [&](uint32_t type) {
|
||||
return state.builder.Type(
|
||||
spv::OpTypeArray, type,
|
||||
ConstantU32(state, static_cast<uint32_t>(binding.resources.size())));
|
||||
ConstantU32(state, counts[index]));
|
||||
};
|
||||
switch (binding.kind) {
|
||||
case IR::DescriptorBindingKind::Buffers:
|
||||
state.storage_buffer_variable =
|
||||
Define(ArrayType(StorageBufferType(state)), "buffers");
|
||||
if (state.requirements.buffer_u8) {
|
||||
state.storage_buffer_u8_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 8)), "buffers_u8");
|
||||
}
|
||||
if (state.requirements.buffer_u16) {
|
||||
state.storage_buffer_u16_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 16)), "buffers_u16");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
state.storage_buffer_u64_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 64)), "buffers_u64");
|
||||
}
|
||||
for (const auto variable: {state.storage_buffer_variable, state.storage_buffer_u8_variable,
|
||||
state.storage_buffer_u16_variable, state.storage_buffer_u64_variable}) {
|
||||
if (variable == 0) continue;
|
||||
if (state.storage_buffer_u8_variable != 0 || state.storage_buffer_u16_variable != 0 ||
|
||||
state.storage_buffer_u64_variable != 0) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
switch (kind) {
|
||||
case IR::DescriptorBindingKind::Buffers: {
|
||||
const std::array used {info.buffer_u8, info.buffer_u16, true, info.buffer_int64_atomics};
|
||||
constexpr std::array names {"buffers_u8", "buffers_u16", "buffers", "buffers_u64"};
|
||||
for (uint32_t alias = 0; alias < used.size(); ++alias) {
|
||||
if (!used[alias]) continue;
|
||||
auto& buffer = state.storage_buffers[alias];
|
||||
const auto type = StorageBufferType(state, buffer, 8u << alias);
|
||||
buffer.variable = Define(ArrayType(type), names[alias]);
|
||||
if (info.buffer_u8 || info.buffer_u16 || info.buffer_int64_atomics) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, buffer.variable, spv::DecorationAliased);
|
||||
}
|
||||
// RDNA2 stores publish to L2 even without GLC; every alias of the buffer
|
||||
// must participate in visibility for cache-bypassing polling loads.
|
||||
if (state.requirements.coherent_buffers) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationCoherent);
|
||||
if (info.coherent_buffers) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, buffer.variable, spv::DecorationCoherent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::DescriptorBindingKind::BdaPagetable:
|
||||
state.bda_pagetable_variable = Define(StorageBufferType(state, 64), "bda_pagetable");
|
||||
state.bda_pagetable_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[3], 64), "bda_pagetable");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::FaultBuffer:
|
||||
state.fault_buffer_variable = Define(StorageBufferType(state), "fault_buffer");
|
||||
state.fault_buffer_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "fault_buffer");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::ShaderData:
|
||||
state.shader_data_storage_variable =
|
||||
Define(StorageBufferType(state), "shader_data");
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "shader_data");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::FlattenedSrt:
|
||||
state.flattened_srt_variable = Define(StorageBufferType(state), "flattened_srt");
|
||||
state.flattened_srt_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "flattened_srt");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::Samplers:
|
||||
state.sampler_variable = Define(ArrayType(state.builder.Type(spv::OpTypeSampler)),
|
||||
"samplers", spv::StorageClassUniformConstant);
|
||||
state.sampler_type = state.builder.Type(spv::OpTypeSampler);
|
||||
state.sampler_pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, state.sampler_type);
|
||||
state.sampler_variable = Define(ArrayType(state.sampler_type), "samplers",
|
||||
spv::StorageClassUniformConstant);
|
||||
break;
|
||||
case IR::DescriptorBindingKind::Gds:
|
||||
state.gds_variable = Define(StorageBufferType(state), "gds");
|
||||
state.gds_variable = Define(StorageBufferType(state, state.storage_buffers[2]), "gds");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::SharedMemory:
|
||||
state.lds_variable = Define(StorageBufferType(state), "lds_dwords");
|
||||
state.lds_variable = Define(StorageBufferType(state, state.storage_buffers[2]), "lds_dwords");
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.lds_variable, spv::DecorationCoherent);
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
state.lds_u64_variable = Define(StorageBufferType(state, 64), "lds_qwords");
|
||||
if (state.program.info.shared_int64_atomics) {
|
||||
state.lds_u64_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[3], 64), "lds_qwords");
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.lds_u64_variable, spv::DecorationCoherent);
|
||||
for (const auto variable: {state.lds_variable, state.lds_u64_variable}) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
@@ -285,13 +329,16 @@ void DefineDescriptors(EmitterState& state) {
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
EXIT_IF(IR::ImageBindingResourceClass(binding.kind) ==
|
||||
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
|
||||
IR::ImageResourceClass::None);
|
||||
const auto& image = state.program.info.images.at(binding.resources.front());
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(binding.kind));
|
||||
state.image_variables[IR::ImageBindingIndex(binding.kind)] =
|
||||
Define(ArrayType(ImageType(state, image)), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
|
||||
auto& definition = state.images[IR::ImageBindingIndex(kind)];
|
||||
definition.type = ImageType(state, image);
|
||||
definition.variable = Define(ArrayType(definition.type), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
definition.pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, definition.type);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(image.resource_class ==
|
||||
@@ -335,11 +382,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value) {
|
||||
|
||||
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components) {
|
||||
EXIT_IF(components < 1u || components > 4u);
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
if (components == 1u) return zero;
|
||||
std::vector<uint32_t> values(components, zero);
|
||||
const std::array values {zero, zero, zero, zero};
|
||||
return state.builder.Constant(spv::OpConstantComposite, TypeU32Composite(state, components),
|
||||
values);
|
||||
std::span(values).first(components));
|
||||
}
|
||||
|
||||
uint32_t GlslStd450(EmitterState& state) {
|
||||
@@ -437,7 +484,7 @@ void DefineInputs(EmitterState& state) {
|
||||
state.inputs.push_back({{kind, 0, components, name}});
|
||||
}
|
||||
};
|
||||
if (IR::FindBinding(state.program.bindings, IR::DescriptorBindingKind::SharedMemory) != nullptr) {
|
||||
if (state.program.info.uses_lds && state.lds_storage_class == spv::StorageClassStorageBuffer) {
|
||||
add_builtin(IR::StageInputKind::WorkgroupId, 3, "gl_WorkGroupID");
|
||||
add_builtin(IR::StageInputKind::NumWorkgroups, 3, "gl_NumWorkGroups");
|
||||
}
|
||||
@@ -500,6 +547,11 @@ void DefineInputs(EmitterState& state) {
|
||||
}
|
||||
input.variable_id =
|
||||
DefineInterfaceVariable(state, type, spv::StorageClassInput, input.debug_name.c_str());
|
||||
if (state.program.stage == ShaderType::Pixel && input.kind == IR::StageInputKind::PrimitiveId) {
|
||||
// Fragment PrimitiveId accepts Tessellation, which the device already enables.
|
||||
state.builder.RequireCapability(spv::CapabilityTessellation);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationFlat);
|
||||
}
|
||||
if (input.kind == IR::StageInputKind::Layer || input.kind == IR::StageInputKind::SampleId) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationFlat);
|
||||
}
|
||||
@@ -524,7 +576,7 @@ void DefineInputs(EmitterState& state) {
|
||||
builtin);
|
||||
}
|
||||
}
|
||||
if (state.requirements.subgroup_local_invocation_id) {
|
||||
if (state.program.info.subgroup_local_invocation_id) {
|
||||
const auto variable = DefineInterfaceVariable(state, TypeU32(state), spv::StorageClassInput,
|
||||
"gl_SubgroupInvocationID");
|
||||
state.subgroup_local_invocation_id_variable = variable;
|
||||
@@ -611,13 +663,21 @@ void DefineOutputs(EmitterState& state) {
|
||||
BuiltIn(state.sample_mask_variable, SampleMaskArrayType(state), "gl_SampleMask",
|
||||
spv::BuiltInSampleMask);
|
||||
break;
|
||||
case IR::StageOutputKind::ObjectPrimitiveId:
|
||||
binding.variable_id = DefineInterfaceVariable(
|
||||
state, TypeU32(state), spv::StorageClassOutput, binding.debug_name.c_str());
|
||||
state.object_primitive_id_variable = binding.variable_id;
|
||||
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
|
||||
spv::DecorationLocation, binding.location);
|
||||
break;
|
||||
case IR::StageOutputKind::Parameter:
|
||||
case IR::StageOutputKind::Mrt: {
|
||||
const bool uint_output =
|
||||
binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
||||
state.input_info.pixel->target_output_mode[binding.index] == 7u;
|
||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
@@ -647,10 +707,10 @@ void DefineModule(EmitterState& state) {
|
||||
DefineOutputs(state);
|
||||
DefineTessellationInterfaces(state);
|
||||
DefineDescriptors(state);
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.lds_variable = state.builder.AllocateId();
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.scratch_variable[half] = state.builder.AllocateId();
|
||||
}
|
||||
@@ -675,11 +735,11 @@ void DefineModule(EmitterState& state) {
|
||||
|
||||
state.builder.RequireCapability(spv::CapabilityShader);
|
||||
state.builder.RequireCapability(spv::CapabilitySignedZeroInfNanPreserve);
|
||||
if (state.requirements.buffer_u8) {
|
||||
if (state.program.info.buffer_u8) {
|
||||
state.builder.RequireExtension("SPV_KHR_8bit_storage");
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer8BitAccess);
|
||||
}
|
||||
if (state.requirements.buffer_u16) {
|
||||
if (state.program.info.buffer_u16) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
|
||||
}
|
||||
if (state.program.info.uses_dma) {
|
||||
@@ -687,11 +747,11 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics || state.requirements.shared_int64_atomics) {
|
||||
if (state.program.info.buffer_int64_atomics || state.program.info.shared_int64_atomics) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityInt64Atomics);
|
||||
}
|
||||
if (state.requirements.shared_int64_atomics &&
|
||||
if (state.program.info.shared_int64_atomics &&
|
||||
state.lds_storage_class == spv::StorageClassWorkgroup) {
|
||||
state.builder.RequireVersion(0x00010400u);
|
||||
state.builder.RequireExtension("SPV_KHR_workgroup_memory_explicit_layout");
|
||||
@@ -714,21 +774,28 @@ void DefineModule(EmitterState& state) {
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||
}
|
||||
if (state.requirements.image_gather_extended) {
|
||||
if (state.program.info.image_gather_extended) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
}
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_barrier ||
|
||||
state.requirements.subgroup_ballot || state.requirements.subgroup_shuffle ||
|
||||
state.requirements.subgroup_local_invocation_id) {
|
||||
if (state.lane_count == 2 || state.program.info.subgroup_barrier ||
|
||||
state.program.info.subgroup_ballot || state.program.info.subgroup_vote ||
|
||||
state.program.info.subgroup_shuffle ||
|
||||
state.program.info.subgroup_arithmetic || state.program.info.subgroup_local_invocation_id) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniform);
|
||||
}
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_ballot) {
|
||||
if (state.lane_count == 2 || state.program.info.subgroup_ballot) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformBallot);
|
||||
}
|
||||
if (state.requirements.subgroup_shuffle) {
|
||||
if (state.program.info.subgroup_vote) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformVote);
|
||||
}
|
||||
if (state.program.info.subgroup_shuffle) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformShuffle);
|
||||
}
|
||||
if (state.requirements.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
if (state.program.info.subgroup_arithmetic) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformArithmetic);
|
||||
}
|
||||
if (state.program.info.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
state.builder.RequireCapability(spv::CapabilityComputeDerivativeGroupQuadsKHR);
|
||||
state.builder.RequireExtension("SPV_KHR_compute_shader_derivatives");
|
||||
}
|
||||
@@ -751,7 +818,7 @@ void DefineModule(EmitterState& state) {
|
||||
// contract prevents host compilers from treating synthesized IEEE values as finite.
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
|
||||
32u);
|
||||
if (state.requirements.float64) {
|
||||
if (state.program.info.float64) {
|
||||
EXIT_NOT_IMPLEMENTED(state.program.stage == ShaderType::Compute &&
|
||||
state.input_info.compute->float_mode != 0xc0);
|
||||
// Use native rounding for MODE=0xc0, consistent with ordinary FP32 arithmetic.
|
||||
@@ -763,8 +830,8 @@ void DefineModule(EmitterState& state) {
|
||||
// state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDenormPreserve, 64u);
|
||||
}
|
||||
if (const auto* cs = ShaderWorkgroupInput(state.program.stage, state.input_info)) {
|
||||
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_x = state.program.info.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.program.info.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_z = 1u;
|
||||
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
|
||||
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
|
||||
@@ -772,7 +839,7 @@ void DefineModule(EmitterState& state) {
|
||||
if (state.lane_count == 2) {
|
||||
local_x = ((local_x * local_y * local_z + 63u) / 64u) * 32u;
|
||||
local_y = local_z = 1u;
|
||||
if (state.requirements.compute_derivatives) {
|
||||
if (state.program.info.compute_derivatives) {
|
||||
local_y = local_x / 2u;
|
||||
local_x = 2u;
|
||||
}
|
||||
@@ -791,14 +858,14 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeEarlyFragmentTests);
|
||||
}
|
||||
}
|
||||
if (state.requirements.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
if (state.program.info.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDerivativeGroupQuadsKHR);
|
||||
}
|
||||
state.builder.AddName(state.main_func, "main");
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.builder.AddName(state.scratch_variable[half], "scratch_dwords");
|
||||
}
|
||||
|
||||
@@ -83,63 +83,58 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
}
|
||||
}
|
||||
|
||||
const IR::Block* TargetBlock(const IR::Program& program, uint32_t id) {
|
||||
const auto found = std::ranges::find_if(
|
||||
program.block_info, [&](const IR::BlockInfo& info) { return info.id == id; });
|
||||
if (found == program.block_info.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return program.blocks[static_cast<size_t>(found - program.block_info.begin())];
|
||||
}
|
||||
|
||||
void EmitReturn(ValueEmitContext& ctx) {
|
||||
EmitKillIfPixelValidMaskInactive(ctx.state);
|
||||
if (ctx.state.object_primitive_id_variable != 0) {
|
||||
auto& state = ctx.state;
|
||||
const auto primitive = EmitGetBuiltin(
|
||||
ctx, IR::Value {static_cast<uint32_t>(IR::StageInputKind::PrimitiveId)}, IR::Value {0u});
|
||||
const auto object = EmitPushDataLoad(state, ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpStore, state.object_primitive_id_variable,
|
||||
EmitAddU32(state, object, primitive));
|
||||
}
|
||||
ctx.state.builder.AddFunction(spv::OpReturn);
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& program = ctx.state.program;
|
||||
const auto& term = info.terminator;
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
const auto emit_merge = [&]() {
|
||||
if (term.loop_header) {
|
||||
const auto* merge = TargetBlock(program, term.merge_block);
|
||||
const auto* cont = TargetBlock(program, term.continue_block);
|
||||
const auto* merge = term.merge_block;
|
||||
const auto* cont = term.continue_block;
|
||||
if (merge != nullptr && cont != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpLoopMerge, ctx.Label(merge), ctx.Label(cont),
|
||||
spv::LoopControlMaskNone);
|
||||
ctx.state.builder.AddFunction(spv::OpLoopMerge, merge->Definition(),
|
||||
cont->Definition(), spv::LoopControlMaskNone);
|
||||
}
|
||||
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
term.merge_block != UINT32_MAX) {
|
||||
if (const auto* merge = TargetBlock(program, term.merge_block); merge != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, ctx.Label(merge),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
term.merge_block != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, term.merge_block->Definition(),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
};
|
||||
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch: {
|
||||
const auto* target = TargetBlock(program, term.true_block);
|
||||
const auto* target = term.true_block;
|
||||
if (target == nullptr) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(target));
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, target->Definition());
|
||||
return;
|
||||
}
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
const auto* true_block = TargetBlock(program, term.true_block);
|
||||
const auto* false_block = TargetBlock(program, term.false_block);
|
||||
if (true_block == nullptr || false_block == nullptr || info.condition.IsEmpty()) {
|
||||
const auto* true_block = term.true_block;
|
||||
const auto* false_block = term.false_block;
|
||||
if (true_block == nullptr || false_block == nullptr || block->condition.IsEmpty()) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
const auto condition = ctx.Def(info.condition);
|
||||
const auto condition = ctx.Def(block->condition);
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
|
||||
ctx.Label(true_block), ctx.Label(false_block));
|
||||
true_block->Definition(), false_block->Definition());
|
||||
return;
|
||||
}
|
||||
default: EmitReturn(ctx); return;
|
||||
@@ -147,8 +142,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
}
|
||||
|
||||
void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* from, uint32_t target) {
|
||||
const auto* block = TargetBlock(ctx.state.program, target);
|
||||
const IR::Block* from, const IR::Block* block) {
|
||||
if (block != nullptr) {
|
||||
StoreDispatcherPhiEdge(ctx, dispatcher, from, block);
|
||||
if (ctx.other_half != nullptr) {
|
||||
@@ -158,41 +152,37 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
||||
}
|
||||
|
||||
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::BlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block->id);
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
const auto selected = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), selected,
|
||||
ctx.Def(block->condition),
|
||||
ConstantU32(ctx.state, term.true_block->id),
|
||||
ConstantU32(ctx.state, term.false_block->id));
|
||||
return selected;
|
||||
}
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
for (const auto target: term.indirect_targets) {
|
||||
for (const auto* target: block->ImmSuccessors()) {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, target);
|
||||
}
|
||||
uint32_t selected = ConstantU32(ctx.state, UINT32_MAX);
|
||||
if (!info.indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(info.indirect_target);
|
||||
const auto& values = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_values
|
||||
: term.indirect_target_pcs;
|
||||
const auto& targets = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_targets
|
||||
: term.indirect_targets;
|
||||
for (size_t index = 0; index < std::min(values.size(), targets.size()); index++) {
|
||||
if (!block->indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(block->indirect_target);
|
||||
for (const auto& branch: term.cases) {
|
||||
const auto match = ctx.state.builder.AllocateId();
|
||||
const auto next = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpIEqual, TypeBool(ctx.state), match,
|
||||
selector, ConstantU32(ctx.state, values[index]));
|
||||
selector, ConstantU32(ctx.state, branch.value));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), next, match,
|
||||
ConstantU32(ctx.state, targets[index]), selected);
|
||||
ConstantU32(ctx.state, branch.target->id),
|
||||
selected);
|
||||
selected = next;
|
||||
}
|
||||
}
|
||||
@@ -244,8 +234,9 @@ void Invoke(Return (*emit)(Context&, Args...), ValueEmitContext& ctx, const IR::
|
||||
|
||||
void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.half != 0 && (inst.GetOpcode() == IR::ValueOpcode::Ballot ||
|
||||
inst.GetOpcode() == IR::ValueOpcode::ReadFirstLane)) {
|
||||
// Both operations already combine both emulated halves into one whole-wave result.
|
||||
inst.GetOpcode() == IR::ValueOpcode::ReadFirstLane ||
|
||||
inst.GetOpcode() == IR::ValueOpcode::SubgroupUMinU32)) {
|
||||
// These operations combine both emulated halves into one whole-wave result.
|
||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
||||
return;
|
||||
}
|
||||
@@ -267,7 +258,7 @@ void EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& s
|
||||
}
|
||||
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
||||
const auto* predecessor = inst.PhiBlock(index);
|
||||
if (predecessor == nullptr || !ctx.state.labels.contains(predecessor)) {
|
||||
if (predecessor == nullptr || predecessor->Definition() == 0) {
|
||||
ctx.Fail(inst, "has a predecessor outside the structured function");
|
||||
}
|
||||
}
|
||||
@@ -301,7 +292,7 @@ void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionSt
|
||||
template <typename EmitInstruction>
|
||||
void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&& emit_instruction) {
|
||||
ctx.state.current_block = block;
|
||||
EmitLabel(ctx.state, ctx.Label(block));
|
||||
EmitLabel(ctx.state, block->Definition());
|
||||
bool emitted_non_phi = false;
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
|
||||
@@ -342,14 +333,14 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
|
||||
void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
const auto& program = ctx.state.program;
|
||||
StructuredFunctionState structured;
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(program.blocks.front()));
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, program.blocks.front()->Definition());
|
||||
for (size_t index = 0; index < program.blocks.size(); index++) {
|
||||
const auto* block = program.blocks[index];
|
||||
EmitBlock(ctx, block, [&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitStructuredInstruction(lane, structured, inst);
|
||||
});
|
||||
structured.block_exit_labels.emplace(block, ctx.state.current_label);
|
||||
EmitStructuredTerminator(ctx, block, program.block_info[index]);
|
||||
EmitStructuredTerminator(ctx, block);
|
||||
}
|
||||
PatchStructuredPhis(ctx, structured);
|
||||
}
|
||||
@@ -357,15 +348,15 @@ void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) {
|
||||
auto& state = ctx.state;
|
||||
const auto* entry = state.program.blocks.front();
|
||||
state.builder.AddFunction(spv::OpBranch, ctx.Label(entry));
|
||||
state.builder.AddFunction(spv::OpBranch, entry->Definition());
|
||||
EmitBlock(ctx, entry, [&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto initial_pc =
|
||||
EmitDispatcherNextPc(ctx, dispatcher, entry, state.program.block_info.front());
|
||||
const auto initial_pc = EmitDispatcherNextPc(ctx, dispatcher, entry);
|
||||
const auto initial_parent = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
|
||||
|
||||
state.current_block = nullptr;
|
||||
EmitLabel(state, dispatcher.header_label);
|
||||
const auto pc = state.builder.AllocateId();
|
||||
const auto next_pc = state.builder.AllocateId();
|
||||
@@ -384,8 +375,8 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
spv::SelectionControlMaskNone);
|
||||
std::vector<uint32_t> words {spv::OpSwitch, pc, dispatcher.after_switch_label};
|
||||
for (size_t index = 1; index < state.program.blocks.size(); index++) {
|
||||
words.push_back(state.program.block_info[index].id);
|
||||
words.push_back(ctx.Label(state.program.blocks[index]));
|
||||
words.push_back(state.program.blocks[index]->id);
|
||||
words.push_back(state.program.blocks[index]->Definition());
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
std::vector<uint32_t> next_pc_words {spv::OpPhi, TypeU32(state), next_pc,
|
||||
@@ -396,12 +387,12 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
[&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index],
|
||||
state.program.block_info[index]);
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index]);
|
||||
next_pc_words.push_back(selected);
|
||||
next_pc_words.push_back(state.current_label);
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
|
||||
}
|
||||
state.current_block = nullptr;
|
||||
EmitLabel(state, dispatcher.after_switch_label);
|
||||
state.builder.AddFunction(next_pc_words);
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.continue_label);
|
||||
@@ -551,23 +542,23 @@ uint32_t ValueEmitContext::Shuffle(const IR::Inst& inst, size_t index, uint32_t
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
|
||||
if (const auto found = definitions.find(&inst); found != definitions.end()) {
|
||||
return found->second;
|
||||
if (const auto id = inst.Definition(half); id != 0) {
|
||||
return id;
|
||||
}
|
||||
const auto id = state.builder.AllocateId();
|
||||
definitions.emplace(&inst, id);
|
||||
inst.SetDefinition(half, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
|
||||
if (const auto found = definitions.find(&inst); found != definitions.end()) {
|
||||
if (found->second != value) {
|
||||
if (const auto id = inst.Definition(half); id != 0) {
|
||||
if (id != value) {
|
||||
state.builder.AddFunction(spv::OpCopyObject, TypeId(state, inst.GetType()),
|
||||
found->second, value);
|
||||
id, value);
|
||||
}
|
||||
return found->second;
|
||||
return id;
|
||||
}
|
||||
definitions.emplace(&inst, value);
|
||||
inst.SetDefinition(half, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -595,10 +586,6 @@ const IR::ExportInfo& ValueEmitContext::Export(const IR::Inst& inst) const {
|
||||
return state.program.export_info.at(inst.Flags<IR::ExportFlags>().index);
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Label(const IR::Block* block) const {
|
||||
return state.labels.at(block);
|
||||
}
|
||||
|
||||
[[noreturn]] void ValueEmitContext::Fail(const char* reason) const {
|
||||
EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n",
|
||||
state.program.shader_hash, static_cast<unsigned>(state.program.stage), reason);
|
||||
@@ -622,13 +609,16 @@ void EmitProgram(EmitterState& state) {
|
||||
high.half = 1;
|
||||
}
|
||||
std::optional<DispatcherFunctionState> dispatcher;
|
||||
if (state.program.stage == ShaderType::Pixel && state.requirements.pixel_valid_mask) {
|
||||
if (state.program.stage == ShaderType::Pixel && state.program.info.pixel_valid_mask) {
|
||||
state.pixel_valid_mask_variable = state.builder.AllocateId();
|
||||
state.builder.AddName(state.pixel_valid_mask_variable, "pixel_valid_mask_active");
|
||||
}
|
||||
for (const auto* block: program.blocks) {
|
||||
const auto label = state.builder.AllocateId();
|
||||
state.labels.emplace(block, label);
|
||||
// Re-emission may use a different host subgroup width and ID allocation order.
|
||||
for (const auto& inst: *block) {
|
||||
inst.ResetDefinitions();
|
||||
}
|
||||
block->SetDefinition(state.builder.AllocateId());
|
||||
}
|
||||
if (state.program.dispatcher_fallback) {
|
||||
auto& dispatch = dispatcher.emplace();
|
||||
@@ -669,8 +659,8 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < program.blocks.size(); index++) {
|
||||
mark_cross_block(program.block_info[index].condition, program.blocks[index]);
|
||||
mark_cross_block(program.block_info[index].indirect_target, program.blocks[index]);
|
||||
mark_cross_block(program.blocks[index]->condition, program.blocks[index]);
|
||||
mark_cross_block(program.blocks[index]->indirect_target, program.blocks[index]);
|
||||
}
|
||||
dispatch.header_label = state.builder.AllocateId();
|
||||
dispatch.select_label = state.builder.AllocateId();
|
||||
@@ -686,29 +676,26 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
DefineIndirectBufferLoads(ctx);
|
||||
DefineIndirectBufferFormats(ctx);
|
||||
DefineBvhIntersect(state);
|
||||
for (const auto* block: program.blocks) {
|
||||
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32;
|
||||
})) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
if (state.lane_count == 2) {
|
||||
high.scratch_u32_variable = state.builder.AllocateId();
|
||||
}
|
||||
break;
|
||||
if (program.info.uses_swizzle) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
if (state.lane_count == 2) {
|
||||
high.scratch_u32_variable = state.builder.AllocateId();
|
||||
}
|
||||
}
|
||||
state.builder.AddFunction(spv::OpFunction, TypeVoid(state),
|
||||
state.mesh_guest_func != 0 ? state.mesh_guest_func : state.main_func,
|
||||
spv::FunctionControlMaskNone, TypeFunction(state));
|
||||
EmitLabel(state, state.entry_label);
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpVariable,
|
||||
TypeU32ArrayPointer(state, spv::StorageClassFunction, LdsDwordCount(state)),
|
||||
state.lds_variable, spv::StorageClassFunction);
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpVariable,
|
||||
@@ -770,6 +757,7 @@ void EmitProgram(EmitterState& state) {
|
||||
EmitStructuredFunction(ctx);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
state.current_block = nullptr;
|
||||
if (state.program.stage == ShaderType::Mesh) {
|
||||
EmitMeshEntryPoint(state);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::CFG {
|
||||
@@ -568,9 +567,8 @@ bool ResolveSetpcTargets(const Decoder::Program& program, uint32_t setpc_index,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidTarget(uint32_t target, const std::set<uint32_t>& instruction_pcs, uint32_t first_pc,
|
||||
uint32_t end_pc) {
|
||||
return target == end_pc || (target >= first_pc && instruction_pcs.contains(target));
|
||||
bool IsValidTarget(uint32_t target, std::span<const Instruction> instructions, uint32_t end_pc) {
|
||||
return target == end_pc || std::ranges::binary_search(instructions, target, {}, &Instruction::pc);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> AllBlockIds(uint32_t count) {
|
||||
@@ -1455,9 +1453,7 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
const auto first_pc = program.instructions.front().pc;
|
||||
const auto end_pc = ProgramEndPc(program);
|
||||
|
||||
std::set<uint32_t> instruction_pcs;
|
||||
for (const auto& inst: program.instructions) {
|
||||
instruction_pcs.insert(inst.pc);
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::UnsupportedInstruction, UINT32_MAX,
|
||||
@@ -1466,23 +1462,22 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint32_t> labels;
|
||||
labels.insert(first_pc);
|
||||
labels.insert(end_pc);
|
||||
std::vector<uint32_t> labels {first_pc, end_pc};
|
||||
|
||||
std::map<uint32_t, SetpcTargetInfo> setpc_targets;
|
||||
for (uint32_t i = 0; i < program.instructions.size(); i++) {
|
||||
const auto& inst = program.instructions[i];
|
||||
const auto next_pc = InstructionEndPc(inst);
|
||||
if (Decoder::IsDirectBranch(inst.opcode)) {
|
||||
if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) {
|
||||
if (Decoder::IsDirectBranch(inst.opcode) ||
|
||||
(inst.opcode == Opcode::S_SWAPPC_B64 && inst.branch_target != UINT32_MAX)) {
|
||||
if (!IsValidTarget(inst.branch_target, program.instructions, end_pc)) {
|
||||
ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, inst.branch_target));
|
||||
}
|
||||
labels.insert(inst.branch_target);
|
||||
labels.push_back(inst.branch_target);
|
||||
if (next_pc <= end_pc) {
|
||||
labels.insert(next_pc);
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
} else if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||
SetpcTargetInfo target_info;
|
||||
@@ -1495,30 +1490,32 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
? std::span<const uint32_t>(target_info.target_pcs)
|
||||
: std::span<const uint32_t>(&target_info.target, 1);
|
||||
for (const auto target: target_pcs) {
|
||||
if (!IsValidTarget(target, instruction_pcs, first_pc, end_pc)) {
|
||||
if (!IsValidTarget(target, program.instructions, end_pc)) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, target));
|
||||
}
|
||||
labels.insert(target);
|
||||
labels.push_back(target);
|
||||
}
|
||||
setpc_targets.emplace(inst.pc, std::move(target_info));
|
||||
if (next_pc <= end_pc) {
|
||||
labels.insert(next_pc);
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
} else if (inst.opcode == Opcode::S_ENDPGM) {
|
||||
labels.insert(next_pc);
|
||||
} else if (inst.opcode == Opcode::S_ENDPGM || inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> sorted_labels(labels.begin(), labels.end());
|
||||
for (uint32_t i = 0; i < sorted_labels.size(); i++) {
|
||||
const auto start = sorted_labels[i];
|
||||
SortUnique(labels);
|
||||
graph.blocks.reserve(labels.size());
|
||||
for (uint32_t i = 0; i < labels.size(); i++) {
|
||||
const auto start = labels[i];
|
||||
if (start > end_pc) {
|
||||
continue;
|
||||
}
|
||||
if (start != end_pc && !instruction_pcs.contains(start)) {
|
||||
const auto begin = std::ranges::lower_bound(program.instructions, start, {}, &Instruction::pc);
|
||||
if (start != end_pc && (begin == program.instructions.end() || begin->pc != start)) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidLabel, UINT32_MAX,
|
||||
fmt::format("CFG label does not start on an instruction: 0x{:08x}", start));
|
||||
@@ -1527,11 +1524,8 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
BasicBlock block;
|
||||
block.id = static_cast<uint32_t>(graph.blocks.size());
|
||||
block.start_pc = start;
|
||||
block.end_pc = i + 1u < sorted_labels.size() ? sorted_labels[i + 1u] : end_pc;
|
||||
block.inst_begin = static_cast<uint32_t>(
|
||||
std::lower_bound(program.instructions.begin(), program.instructions.end(), start,
|
||||
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
|
||||
program.instructions.begin());
|
||||
block.end_pc = i + 1u < labels.size() ? labels[i + 1u] : end_pc;
|
||||
block.inst_begin = static_cast<uint32_t>(begin - program.instructions.begin());
|
||||
block.inst_end = static_cast<uint32_t>(
|
||||
std::lower_bound(program.instructions.begin(), program.instructions.end(), block.end_pc,
|
||||
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
|
||||
@@ -1541,10 +1535,10 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
|
||||
graph.entry_block = 0;
|
||||
|
||||
std::map<uint32_t, uint32_t> pc_to_block;
|
||||
for (const auto& block: graph.blocks) {
|
||||
pc_to_block.emplace(block.start_pc, block.id);
|
||||
}
|
||||
const auto block_at_pc = [&](uint32_t pc) {
|
||||
const auto found = std::ranges::lower_bound(graph.blocks, pc, {}, &BasicBlock::start_pc);
|
||||
return found != graph.blocks.end() && found->start_pc == pc ? found->id : UINT32_MAX;
|
||||
};
|
||||
|
||||
for (auto& block: graph.blocks) {
|
||||
block.terminator = {};
|
||||
@@ -1557,6 +1551,9 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
const auto next_pc = InstructionEndPc(last);
|
||||
if (last.opcode == Opcode::S_ENDPGM) {
|
||||
block.terminator.kind = TerminatorKind::Return;
|
||||
} else if (last.opcode == Opcode::S_SWAPPC_B64 && last.branch_target != UINT32_MAX) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.true_block = block_at_pc(last.branch_target);
|
||||
} else if (last.opcode == Opcode::S_SETPC_B64) {
|
||||
const auto& target_info = setpc_targets.at(last.pc);
|
||||
if (target_info.indirect) {
|
||||
@@ -1566,7 +1563,7 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
block.terminator.indirect_selector_code = target_info.selector_code;
|
||||
for (const auto target_pc: target_info.target_pcs) {
|
||||
block.terminator.indirect_target_pcs.push_back(target_pc);
|
||||
block.terminator.indirect_targets.push_back(pc_to_block.at(target_pc));
|
||||
block.terminator.indirect_targets.push_back(block_at_pc(target_pc));
|
||||
}
|
||||
const auto selector_count = std::min(target_info.selector_values.size(),
|
||||
target_info.selector_target_pcs.size());
|
||||
@@ -1574,35 +1571,35 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
block.terminator.indirect_selector_values.push_back(
|
||||
target_info.selector_values[i]);
|
||||
block.terminator.indirect_selector_targets.push_back(
|
||||
pc_to_block.at(target_info.selector_target_pcs[i]));
|
||||
block_at_pc(target_info.selector_target_pcs[i]));
|
||||
}
|
||||
} else {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = pc_to_block.at(target_info.target);
|
||||
block.terminator.true_block = block_at_pc(target_info.target);
|
||||
}
|
||||
} else if (last.opcode == Opcode::S_BRANCH) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = pc_to_block.at(last.branch_target);
|
||||
block.terminator.true_block = block_at_pc(last.branch_target);
|
||||
} else if (Decoder::IsConditionalBranch(last.opcode)) {
|
||||
block.terminator.kind = TerminatorKind::ConditionalBranch;
|
||||
block.terminator.condition = ConditionForOpcode(last.opcode);
|
||||
block.terminator.true_block = pc_to_block.at(last.branch_target);
|
||||
const auto fallthrough = pc_to_block.find(next_pc);
|
||||
if (fallthrough == pc_to_block.end()) {
|
||||
block.terminator.true_block = block_at_pc(last.branch_target);
|
||||
const auto fallthrough = block_at_pc(next_pc);
|
||||
if (fallthrough == UINT32_MAX) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::MissingFallthrough, block.id,
|
||||
fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block",
|
||||
last.pc));
|
||||
}
|
||||
block.terminator.false_block = fallthrough->second;
|
||||
block.terminator.false_block = fallthrough;
|
||||
} else {
|
||||
auto next = pc_to_block.find(block.end_pc);
|
||||
if (next != pc_to_block.end() && block.end_pc != block.start_pc) {
|
||||
const auto next = block_at_pc(block.end_pc);
|
||||
if (next != UINT32_MAX && block.end_pc != block.start_pc) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = next->second;
|
||||
block.terminator.true_block = next;
|
||||
} else {
|
||||
block.terminator.kind = TerminatorKind::Return;
|
||||
}
|
||||
@@ -1683,6 +1680,36 @@ Graph Structurize(const Graph& graph) {
|
||||
return GotoStructurizer(graph).Run();
|
||||
}
|
||||
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
||||
uint32_t destination = UINT32_MAX;
|
||||
// Scalar ALU pair widths are not all decoded. The typed producer must confirm this word.
|
||||
return InstructionWritesScalarCode(inst, code) ||
|
||||
(ScalarOperandCode(inst.dst, destination) && code == destination + 1u);
|
||||
}
|
||||
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code) {
|
||||
EXIT_IF(before >= program.instructions.size());
|
||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||
return block.inst_begin <= before && before < block.inst_end;
|
||||
});
|
||||
EXIT_IF(found == graph.blocks.end());
|
||||
const auto* block = &*found;
|
||||
for (size_t depth = 0; depth < graph.blocks.size(); ++depth) {
|
||||
for (uint32_t i = before; i > block->inst_begin;) {
|
||||
const auto& inst = program.instructions[--i];
|
||||
EXIT_IF(inst.opcode == Opcode::S_SWAPPC_B64);
|
||||
if (MayWriteScalarRegister(inst, code)) return i;
|
||||
}
|
||||
if (block->id == graph.entry_block) return UINT32_MAX;
|
||||
EXIT_IF(block->predecessors.size() != 1u);
|
||||
block = graph.FindBlock(block->predecessors[0]);
|
||||
EXIT_IF(block == nullptr);
|
||||
before = block->inst_end;
|
||||
}
|
||||
EXIT("scalar shader call source has cyclic reaching definitions");
|
||||
}
|
||||
|
||||
std::string BranchConditionToString(BranchCondition condition) {
|
||||
switch (condition) {
|
||||
case BranchCondition::Always: return "always";
|
||||
|
||||
@@ -113,6 +113,9 @@ struct Graph {
|
||||
};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code);
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code);
|
||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
|
||||
@@ -185,6 +185,7 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
|
||||
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod},
|
||||
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
|
||||
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
|
||||
{0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod},
|
||||
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero},
|
||||
{0x57u, Opcode::IMAGE_GATHER4_LZ_O,
|
||||
@@ -351,6 +352,9 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
if (a16) {
|
||||
inst.image_sample_flags |= ImageSampleFlagA16;
|
||||
}
|
||||
if ((sample != nullptr || gather != nullptr) && (word0 & (1u << 12u)) != 0u) {
|
||||
inst.image_sample_flags |= ImageSampleFlagUnnormalized;
|
||||
}
|
||||
inst.image_dimension = dimension;
|
||||
inst.image_r128 = r128;
|
||||
inst.image_nsa_dwords = nsa_dwords;
|
||||
|
||||
@@ -42,11 +42,17 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x0eu, Opcode::BUFFER_LOAD_DWORDX4, 4, 32},
|
||||
{0x0fu, Opcode::BUFFER_LOAD_DWORDX3, 3, 32},
|
||||
{0x18u, Opcode::BUFFER_STORE_BYTE, 1, 8},
|
||||
{0x19u, Opcode::BUFFER_STORE_BYTE_D16_HI, 1, 8},
|
||||
{0x1au, Opcode::BUFFER_STORE_SHORT, 1, 16},
|
||||
{0x1bu, Opcode::BUFFER_STORE_SHORT_D16_HI, 1, 16},
|
||||
{0x1cu, Opcode::BUFFER_STORE_DWORD, 1, 32},
|
||||
{0x1du, Opcode::BUFFER_STORE_DWORDX2, 2, 32},
|
||||
{0x1eu, Opcode::BUFFER_STORE_DWORDX4, 4, 32},
|
||||
{0x1fu, Opcode::BUFFER_STORE_DWORDX3, 3, 32},
|
||||
{0x20u, Opcode::BUFFER_LOAD_UBYTE_D16, 1, 8},
|
||||
{0x24u, Opcode::BUFFER_LOAD_SHORT_D16, 1, 16},
|
||||
{0x25u, Opcode::BUFFER_LOAD_SHORT_D16_HI, 1, 16},
|
||||
{0x27u, Opcode::BUFFER_STORE_FORMAT_D16_HI_X, 1, 16, false, false, true},
|
||||
{0x30u, Opcode::BUFFER_ATOMIC_SWAP, 1, 32},
|
||||
{0x31u, Opcode::BUFFER_ATOMIC_CMPSWAP, 1, 32},
|
||||
{0x32u, Opcode::BUFFER_ATOMIC_ADD, 1, 32},
|
||||
@@ -64,7 +70,9 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
|
||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
@@ -114,7 +122,8 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
{0x3fu, Opcode::DS_ORDERED_COUNT, 1, 32},
|
||||
{0x40u, Opcode::DS_ADD_U64, 2, 32}, {0x4au, Opcode::DS_OR_B64, 2, 32},
|
||||
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
|
||||
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
|
||||
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x68u, Opcode::DS_MAX_RTN_U64, 2, 32},
|
||||
{0x70u, Opcode::DS_CMPST_RTN_B64, 2, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
|
||||
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
|
||||
{0xa0u, Opcode::DS_WRITE_B8_D16_HI, 1, 8},
|
||||
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
|
||||
@@ -190,6 +199,8 @@ bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
case Opcode::DS_MIN_RTN_U32:
|
||||
case Opcode::DS_MAX_U32:
|
||||
case Opcode::DS_MAX_RTN_U32:
|
||||
case Opcode::DS_MAX_RTN_U64:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
case Opcode::DS_OR_B32:
|
||||
@@ -209,6 +220,7 @@ uint32_t DsSourceCount(Opcode opcode) {
|
||||
case Opcode::DS_WRITE2ST64_B32:
|
||||
case Opcode::DS_WRITE2_B64:
|
||||
case Opcode::DS_WRITE2ST64_B64:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_MSKOR_B32: return 3u;
|
||||
case Opcode::DS_MIN_F32:
|
||||
case Opcode::DS_MAX_F32:
|
||||
@@ -298,8 +310,20 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X) {
|
||||
inst.dst.sdwa_sel = 4u;
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_XYZW) {
|
||||
inst.data_components = 4u;
|
||||
}
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) {
|
||||
inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u;
|
||||
} else if (inst.opcode == Opcode::BUFFER_STORE_BYTE_D16_HI) {
|
||||
inst.dst.sdwa_sel = 2u;
|
||||
} else if (inst.opcode == Opcode::BUFFER_STORE_SHORT_D16_HI ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_HI_X) {
|
||||
inst.dst.sdwa_sel = 5u;
|
||||
}
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
@@ -371,7 +395,7 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
|
||||
if (lds != 0 || inst.glc || inst.slc || seg == 3u) {
|
||||
if (lds != 0 || inst.glc || (inst.slc && !IsFlatStoreOpcode(inst.opcode)) || seg == 3u) {
|
||||
SetUnsupported(inst, Family::FLAT, opcode, "FLAT modifiers or segment are not implemented");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,12 +47,17 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
|
||||
{0x0au, Opcode::S_WQM_B64},
|
||||
{0x0bu, Opcode::S_BREV_B32},
|
||||
{0x0cu, Opcode::S_BREV_B64},
|
||||
{0x0du, Opcode::S_BCNT0_I32_B32},
|
||||
{0x0eu, Opcode::S_BCNT0_I32_B64},
|
||||
{0x0fu, Opcode::S_BCNT1_I32_B32},
|
||||
{0x10u, Opcode::S_BCNT1_I32_B64},
|
||||
{0x11u, Opcode::S_FF0_I32_B32},
|
||||
{0x12u, Opcode::S_FF0_I32_B64},
|
||||
{0x13u, Opcode::S_FF1_I32_B32},
|
||||
{0x14u, Opcode::S_FF1_I32_B64},
|
||||
{0x15u, Opcode::S_FLBIT_I32_B32},
|
||||
{0x16u, Opcode::S_FLBIT_I32_B64},
|
||||
{0x19u, Opcode::S_SEXT_I32_I8},
|
||||
{0x1au, Opcode::S_SEXT_I32_I16},
|
||||
{0x1bu, Opcode::S_BITSET0_B32},
|
||||
{0x1cu, Opcode::S_BITSET0_B64},
|
||||
@@ -60,6 +65,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
|
||||
{0x1eu, Opcode::S_BITSET1_B64},
|
||||
{0x1fu, Opcode::S_GETPC_B64},
|
||||
{0x20u, Opcode::S_SETPC_B64},
|
||||
{0x21u, Opcode::S_SWAPPC_B64},
|
||||
{0x24u, Opcode::S_AND_SAVEEXEC_B64},
|
||||
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
|
||||
{0x2du, Opcode::S_QUADMASK_B64},
|
||||
@@ -87,7 +93,7 @@ constexpr OpcodeMap SOPK_OPCODE_LIST[] = {
|
||||
{0x08u, Opcode::S_CMP_LE_I32}, {0x09u, Opcode::S_CMP_EQ_U32}, {0x0au, Opcode::S_CMP_LG_U32},
|
||||
{0x0bu, Opcode::S_CMP_GT_U32}, {0x0cu, Opcode::S_CMP_GE_U32}, {0x0du, Opcode::S_CMP_LT_U32},
|
||||
{0x0eu, Opcode::S_CMP_LE_U32}, {0x0fu, Opcode::S_ADD_I32}, {0x10u, Opcode::S_MULK_I32},
|
||||
{0x13u, Opcode::S_SETREG_B32}, {0x17u, Opcode::S_WAITCNT_VSCNT}, {0x18u, Opcode::S_WAITCNT},
|
||||
{0x12u, Opcode::S_GETREG_B32}, {0x13u, Opcode::S_SETREG_B32}, {0x17u, Opcode::S_WAITCNT_VSCNT}, {0x18u, Opcode::S_WAITCNT},
|
||||
{0x19u, Opcode::S_WAITCNT}, {0x1au, Opcode::S_WAITCNT},
|
||||
{0x1bu, Opcode::S_SUBVECTOR_LOOP_BEGIN}, {0x1cu, Opcode::S_SUBVECTOR_LOOP_END},
|
||||
};
|
||||
@@ -110,7 +116,9 @@ constexpr OpcodeMap SOPP_OPCODE_LIST[] = {
|
||||
{0x12u, Opcode::S_TRAP},
|
||||
{0x16u, Opcode::S_TTRACEDATA},
|
||||
{0x17u, Opcode::S_CBRANCH_CDBGSYS},
|
||||
{0x18u, Opcode::S_CBRANCH_CDBGUSER},
|
||||
{0x19u, Opcode::S_CBRANCH_CDBGSYS_OR_USER},
|
||||
{0x1au, Opcode::S_CBRANCH_CDBGSYS_AND_USER},
|
||||
{0x20u, Opcode::S_INST_PREFETCH},
|
||||
{0x23u, Opcode::S_WAITCNT_DEPCTR},
|
||||
};
|
||||
@@ -154,6 +162,10 @@ void DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::S_SWAPPC_B64:
|
||||
inst.branch_target = UINT32_MAX;
|
||||
inst.data_dwords = 2;
|
||||
break;
|
||||
case Opcode::S_GETPC_B64:
|
||||
inst.src_count = 0;
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
@@ -201,7 +213,7 @@ void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x1fu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
const auto imm = opcode >= 0x09u && opcode <= 0x0eu
|
||||
const auto imm = (opcode >= 0x09u && opcode <= 0x0eu) || opcode == 0x12u
|
||||
? static_cast<int32_t>(word & 0xffffu)
|
||||
: static_cast<int32_t>(static_cast<int16_t>(word & 0xffffu));
|
||||
|
||||
@@ -221,6 +233,7 @@ void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::S_GETREG_B32:
|
||||
case Opcode::S_MOVK_I32: DecodeScalarDestination(sdst, pc, inst.dst); return;
|
||||
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
|
||||
case Opcode::S_SUBVECTOR_LOOP_END:
|
||||
|
||||
@@ -143,6 +143,7 @@ std::string FormatMimg(const Instruction& inst) {
|
||||
case Opcode::IMAGE_GATHER4_L:
|
||||
case Opcode::IMAGE_GATHER4_LZ:
|
||||
case Opcode::IMAGE_GATHER4_C:
|
||||
case Opcode::IMAGE_GATHER4_C_L:
|
||||
case Opcode::IMAGE_GATHER4_C_LZ:
|
||||
case Opcode::IMAGE_GATHER4_LZ_O:
|
||||
case Opcode::IMAGE_GATHER4_C_O:
|
||||
@@ -186,7 +187,9 @@ bool IsConditionalBranch(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
// Conditional shader debugging is disabled.
|
||||
case Opcode::S_CBRANCH_CDBGSYS:
|
||||
case Opcode::S_CBRANCH_CDBGSYS_OR_USER: return false;
|
||||
case Opcode::S_CBRANCH_CDBGUSER:
|
||||
case Opcode::S_CBRANCH_CDBGSYS_OR_USER:
|
||||
case Opcode::S_CBRANCH_CDBGSYS_AND_USER: return false;
|
||||
case Opcode::S_CBRANCH_SCC0:
|
||||
case Opcode::S_CBRANCH_SCC1:
|
||||
case Opcode::S_CBRANCH_VCCZ:
|
||||
@@ -389,6 +392,7 @@ Program DecodeFrontProgram(std::span<const uint32_t> front) {
|
||||
while (front_words < front.size()) {
|
||||
auto& inst = result.instructions.emplace_back();
|
||||
DecodeInstruction(front, front_words, inst);
|
||||
result.has_swap_pc |= inst.opcode == Opcode::S_SWAPPC_B64;
|
||||
front_words += inst.word_count;
|
||||
if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||
EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Sgpr || inst.src0.reg != 6u);
|
||||
@@ -406,6 +410,7 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
|
||||
program.instructions.clear();
|
||||
program.instructions.reserve(code.size());
|
||||
program.code = code;
|
||||
program.has_swap_pc = false;
|
||||
|
||||
std::vector<bool> branch_targets;
|
||||
for (uint32_t word_index = 0; word_index < code.size();) {
|
||||
@@ -413,6 +418,7 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
|
||||
DecodeInstruction(code, word_index, program.instructions.back());
|
||||
|
||||
const auto& inst = program.instructions.back();
|
||||
program.has_swap_pc |= inst.opcode == Opcode::S_SWAPPC_B64;
|
||||
word_index += inst.word_count;
|
||||
|
||||
if (IsDirectBranch(inst.opcode)) {
|
||||
@@ -503,9 +509,14 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
OperandToString(inst.dst).c_str(),
|
||||
OperandToString(inst.src0).c_str()));
|
||||
case Opcode::S_ABS_I32:
|
||||
case Opcode::S_SEXT_I32_I8:
|
||||
case Opcode::S_SEXT_I32_I16:
|
||||
case Opcode::S_BREV_B32:
|
||||
case Opcode::S_BREV_B64:
|
||||
case Opcode::S_BCNT0_I32_B32:
|
||||
case Opcode::S_BCNT0_I32_B64:
|
||||
case Opcode::S_FF0_I32_B32:
|
||||
case Opcode::S_FF0_I32_B64:
|
||||
case Opcode::S_BCNT1_I32_B32:
|
||||
case Opcode::S_FLBIT_I32_B32:
|
||||
case Opcode::S_FF1_I32_B32:
|
||||
@@ -567,7 +578,9 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::S_CBRANCH_EXECZ:
|
||||
case Opcode::S_CBRANCH_EXECNZ:
|
||||
case Opcode::S_CBRANCH_CDBGSYS:
|
||||
case Opcode::S_CBRANCH_CDBGUSER:
|
||||
case Opcode::S_CBRANCH_CDBGSYS_OR_USER:
|
||||
case Opcode::S_CBRANCH_CDBGSYS_AND_USER:
|
||||
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} 0x{:08x}", inst.pc,
|
||||
magic_enum::enum_name(inst.opcode),
|
||||
inst.branch_target));
|
||||
@@ -599,6 +612,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::IMAGE_GATHER4_L:
|
||||
case Opcode::IMAGE_GATHER4_LZ:
|
||||
case Opcode::IMAGE_GATHER4_C:
|
||||
case Opcode::IMAGE_GATHER4_C_L:
|
||||
case Opcode::IMAGE_GATHER4_C_LZ:
|
||||
case Opcode::IMAGE_GATHER4_LZ_O:
|
||||
case Opcode::IMAGE_GATHER4_C_O:
|
||||
@@ -619,19 +633,27 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Opcode::BUFFER_LOAD_UBYTE_D16:
|
||||
case Opcode::BUFFER_LOAD_USHORT:
|
||||
case Opcode::BUFFER_LOAD_SHORT_D16:
|
||||
case Opcode::BUFFER_LOAD_SHORT_D16_HI:
|
||||
case Opcode::BUFFER_LOAD_DWORD:
|
||||
case Opcode::BUFFER_LOAD_DWORDX2:
|
||||
case Opcode::BUFFER_LOAD_DWORDX3:
|
||||
case Opcode::BUFFER_LOAD_DWORDX4:
|
||||
case Opcode::BUFFER_STORE_BYTE:
|
||||
case Opcode::BUFFER_STORE_BYTE_D16_HI:
|
||||
case Opcode::BUFFER_STORE_SHORT:
|
||||
case Opcode::BUFFER_STORE_SHORT_D16_HI:
|
||||
case Opcode::BUFFER_STORE_DWORD:
|
||||
case Opcode::BUFFER_STORE_DWORDX2:
|
||||
case Opcode::BUFFER_STORE_DWORDX3:
|
||||
@@ -694,6 +716,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_MIN_RTN_U32:
|
||||
case Opcode::DS_MAX_U32:
|
||||
case Opcode::DS_MAX_RTN_U32:
|
||||
case Opcode::DS_MAX_RTN_U64:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
case Opcode::DS_OR_B32:
|
||||
|
||||
@@ -45,12 +45,17 @@ enum class Opcode {
|
||||
S_CMOV_B64,
|
||||
S_MOVK_I32,
|
||||
S_ABS_I32,
|
||||
S_SEXT_I32_I8,
|
||||
S_SEXT_I32_I16,
|
||||
S_ABSDIFF_I32,
|
||||
S_BREV_B32,
|
||||
S_BREV_B64,
|
||||
S_BCNT0_I32_B32,
|
||||
S_BCNT0_I32_B64,
|
||||
S_BCNT1_I32_B32,
|
||||
S_BCNT1_I32_B64,
|
||||
S_FF0_I32_B32,
|
||||
S_FF0_I32_B64,
|
||||
S_FF1_I32_B32,
|
||||
S_FF1_I32_B64,
|
||||
S_FLBIT_I32_B32,
|
||||
@@ -59,6 +64,7 @@ enum class Opcode {
|
||||
S_QUADMASK_B64,
|
||||
S_GETPC_B64,
|
||||
S_SETPC_B64,
|
||||
S_SWAPPC_B64,
|
||||
S_SUBVECTOR_LOOP_BEGIN,
|
||||
S_SUBVECTOR_LOOP_END,
|
||||
S_AND_SAVEEXEC_B32,
|
||||
@@ -372,10 +378,38 @@ enum class Opcode {
|
||||
V_CMP_NEQ_F32,
|
||||
V_CMP_NLT_F32,
|
||||
V_CMP_TRU_F32,
|
||||
V_CMP_F_F64,
|
||||
V_CMP_LT_F64,
|
||||
V_CMP_EQ_F64,
|
||||
V_CMP_LE_F64,
|
||||
V_CMP_GT_F64,
|
||||
V_CMP_LG_F64,
|
||||
V_CMP_GE_F64,
|
||||
V_CMP_O_F64,
|
||||
V_CMP_U_F64,
|
||||
V_CMP_NGE_F64,
|
||||
V_CMP_NLG_F64,
|
||||
V_CMP_NGT_F64,
|
||||
V_CMP_NLE_F64,
|
||||
V_CMP_NEQ_F64,
|
||||
V_CMP_NLT_F64,
|
||||
V_CMP_TRU_F64,
|
||||
V_CMPX_F_F64,
|
||||
V_CMPX_LT_F64,
|
||||
V_CMPX_EQ_F64,
|
||||
V_CMPX_LE_F64,
|
||||
V_CMPX_GT_F64,
|
||||
V_CMPX_LG_F64,
|
||||
V_CMPX_GE_F64,
|
||||
V_CMPX_O_F64,
|
||||
V_CMPX_U_F64,
|
||||
V_CMPX_NGE_F64,
|
||||
V_CMPX_NLG_F64,
|
||||
V_CMPX_NGT_F64,
|
||||
V_CMPX_NLE_F64,
|
||||
V_CMPX_NEQ_F64,
|
||||
V_CMPX_NLT_F64,
|
||||
V_CMPX_TRU_F64,
|
||||
V_CMPX_LT_F32,
|
||||
V_CMPX_EQ_F32,
|
||||
V_CMPX_LE_F32,
|
||||
@@ -400,6 +434,7 @@ enum class Opcode {
|
||||
V_CMP_T_I32,
|
||||
V_CMP_CLASS_F32,
|
||||
V_CMPX_CLASS_F32,
|
||||
V_CMP_CLASS_F16,
|
||||
V_CMPX_CLASS_F16,
|
||||
V_CMP_LT_I16,
|
||||
V_CMP_EQ_I16,
|
||||
@@ -456,11 +491,14 @@ enum class Opcode {
|
||||
V_CMP_NE_U32,
|
||||
V_CMP_GE_U32,
|
||||
V_CMP_T_U32,
|
||||
// Keep V_CMP_F_I64 to V_CMPX_T_U64 contiguous: VectorAluOps.cpp tests this range.
|
||||
V_CMP_F_I64,
|
||||
V_CMP_EQ_I64,
|
||||
V_CMP_LT_I64,
|
||||
V_CMP_EQ_I64,
|
||||
V_CMP_LE_I64,
|
||||
V_CMP_GT_I64,
|
||||
V_CMP_NE_I64,
|
||||
V_CMP_GE_I64,
|
||||
V_CMP_T_I64,
|
||||
V_CMP_F_U64,
|
||||
V_CMP_LT_U64,
|
||||
@@ -474,7 +512,9 @@ enum class Opcode {
|
||||
V_CMPX_LT_I64,
|
||||
V_CMPX_EQ_I64,
|
||||
V_CMPX_LE_I64,
|
||||
V_CMPX_GT_I64,
|
||||
V_CMPX_NE_I64,
|
||||
V_CMPX_GE_I64,
|
||||
V_CMPX_T_I64,
|
||||
V_CMPX_F_U64,
|
||||
V_CMPX_LT_U64,
|
||||
@@ -507,21 +547,29 @@ enum class Opcode {
|
||||
BUFFER_LOAD_FORMAT_XYZ,
|
||||
BUFFER_LOAD_FORMAT_XYZW,
|
||||
BUFFER_LOAD_FORMAT_D16_X,
|
||||
BUFFER_LOAD_FORMAT_D16_XYZW,
|
||||
BUFFER_STORE_FORMAT_X,
|
||||
BUFFER_STORE_FORMAT_XY,
|
||||
BUFFER_STORE_FORMAT_XYZ,
|
||||
BUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_STORE_FORMAT_D16_X,
|
||||
BUFFER_STORE_FORMAT_D16_HI_X,
|
||||
BUFFER_STORE_FORMAT_D16_XYZW,
|
||||
BUFFER_LOAD_UBYTE,
|
||||
BUFFER_LOAD_UBYTE_D16,
|
||||
BUFFER_LOAD_SBYTE,
|
||||
BUFFER_LOAD_USHORT,
|
||||
BUFFER_LOAD_SSHORT,
|
||||
BUFFER_LOAD_SHORT_D16,
|
||||
BUFFER_LOAD_SHORT_D16_HI,
|
||||
BUFFER_LOAD_DWORD,
|
||||
BUFFER_LOAD_DWORDX2,
|
||||
BUFFER_LOAD_DWORDX3,
|
||||
BUFFER_LOAD_DWORDX4,
|
||||
BUFFER_STORE_BYTE,
|
||||
BUFFER_STORE_BYTE_D16_HI,
|
||||
BUFFER_STORE_SHORT,
|
||||
BUFFER_STORE_SHORT_D16_HI,
|
||||
BUFFER_STORE_DWORD,
|
||||
BUFFER_STORE_DWORDX2,
|
||||
BUFFER_STORE_DWORDX3,
|
||||
@@ -582,6 +630,8 @@ enum class Opcode {
|
||||
DS_MIN_RTN_U32,
|
||||
DS_MAX_U32,
|
||||
DS_MAX_RTN_U32,
|
||||
DS_MAX_RTN_U64,
|
||||
DS_CMPST_RTN_B64,
|
||||
DS_AND_B32,
|
||||
DS_AND_RTN_B32,
|
||||
DS_OR_B32,
|
||||
@@ -650,6 +700,7 @@ enum class Opcode {
|
||||
IMAGE_GATHER4_L,
|
||||
IMAGE_GATHER4_LZ,
|
||||
IMAGE_GATHER4_C,
|
||||
IMAGE_GATHER4_C_L,
|
||||
IMAGE_GATHER4_C_LZ,
|
||||
IMAGE_GATHER4_LZ_O,
|
||||
IMAGE_GATHER4_C_O,
|
||||
@@ -672,8 +723,11 @@ enum class Opcode {
|
||||
S_CBRANCH_EXECZ,
|
||||
S_CBRANCH_EXECNZ,
|
||||
S_CBRANCH_CDBGSYS,
|
||||
S_CBRANCH_CDBGUSER,
|
||||
S_CBRANCH_CDBGSYS_OR_USER,
|
||||
S_CBRANCH_CDBGSYS_AND_USER,
|
||||
S_SENDMSG,
|
||||
S_GETREG_B32,
|
||||
S_SETREG_B32,
|
||||
S_SLEEP,
|
||||
S_TRAP,
|
||||
@@ -717,6 +771,7 @@ enum ImageSampleFlag : uint32_t {
|
||||
ImageSampleFlagCd = 1u << 8u,
|
||||
ImageSampleFlagGatherHorizontal = 1u << 9u,
|
||||
ImageSampleFlagAdjust = 1u << 10u,
|
||||
ImageSampleFlagUnnormalized = 1u << 11u,
|
||||
};
|
||||
|
||||
enum class ImageDimension : uint32_t {
|
||||
@@ -812,6 +867,7 @@ struct Instruction {
|
||||
struct Program {
|
||||
std::span<const uint32_t> code;
|
||||
std::vector<Instruction> instructions;
|
||||
bool has_swap_pc = false;
|
||||
};
|
||||
|
||||
// Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs.
|
||||
|
||||
@@ -17,7 +17,6 @@ enum class Vop2SdwaProfile {
|
||||
IntegerFullDestination,
|
||||
IntegerPartialDestination,
|
||||
ReverseRightShift,
|
||||
ReverseLogicalLeft,
|
||||
Bitwise,
|
||||
Count,
|
||||
};
|
||||
@@ -36,10 +35,10 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
|
||||
{0x05u, Opcode::V_SUBREV_F32},
|
||||
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
|
||||
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0fu, Opcode::V_MIN_F32},
|
||||
{0x10u, Opcode::V_MAX_F32},
|
||||
{0x11u, Opcode::V_MIN_I32},
|
||||
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x12u, Opcode::V_MAX_I32, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x13u, Opcode::V_MIN_U32, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x14u, Opcode::V_MAX_U32, Vop2SdwaProfile::IntegerFullDestination},
|
||||
@@ -48,7 +47,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
|
||||
{0x17u, Opcode::V_ASHR_I32},
|
||||
{0x18u, Opcode::V_ASHRREV_I32, Vop2SdwaProfile::ReverseRightShift},
|
||||
{0x19u, Opcode::V_LSHL_B32},
|
||||
{0x1au, Opcode::V_LSHLREV_B32, Vop2SdwaProfile::ReverseLogicalLeft},
|
||||
{0x1au, Opcode::V_LSHLREV_B32, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x1bu, Opcode::V_AND_B32, Vop2SdwaProfile::Bitwise},
|
||||
{0x1cu, Opcode::V_OR_B32, Vop2SdwaProfile::Bitwise},
|
||||
{0x1du, Opcode::V_XOR_B32, Vop2SdwaProfile::Bitwise},
|
||||
@@ -236,8 +235,23 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32},
|
||||
{0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32},
|
||||
{0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32},
|
||||
{0x22u, Opcode::V_CMP_EQ_F64, false}, {0x23u, Opcode::V_CMP_LE_F64, false},
|
||||
{0x33u, Opcode::V_CMPX_LE_F64, false}, {0x36u, Opcode::V_CMPX_GE_F64, false},
|
||||
{0x20u, Opcode::V_CMP_F_F64, false}, {0x21u, Opcode::V_CMP_LT_F64, false},
|
||||
{0x22u, Opcode::V_CMP_EQ_F64, false}, {0x23u, Opcode::V_CMP_LE_F64, false},
|
||||
{0x24u, Opcode::V_CMP_GT_F64, false}, {0x25u, Opcode::V_CMP_LG_F64, false},
|
||||
{0x26u, Opcode::V_CMP_GE_F64, false}, {0x27u, Opcode::V_CMP_O_F64, false},
|
||||
{0x28u, Opcode::V_CMP_U_F64, false}, {0x29u, Opcode::V_CMP_NGE_F64, false},
|
||||
{0x2au, Opcode::V_CMP_NLG_F64, false}, {0x2bu, Opcode::V_CMP_NGT_F64, false},
|
||||
{0x2cu, Opcode::V_CMP_NLE_F64, false}, {0x2du, Opcode::V_CMP_NEQ_F64, false},
|
||||
{0x2eu, Opcode::V_CMP_NLT_F64, false}, {0x2fu, Opcode::V_CMP_TRU_F64, false},
|
||||
{0x30u, Opcode::V_CMPX_F_F64, false},
|
||||
{0x31u, Opcode::V_CMPX_LT_F64, false}, {0x32u, Opcode::V_CMPX_EQ_F64, false},
|
||||
{0x33u, Opcode::V_CMPX_LE_F64, false}, {0x34u, Opcode::V_CMPX_GT_F64, false},
|
||||
{0x35u, Opcode::V_CMPX_LG_F64, false}, {0x36u, Opcode::V_CMPX_GE_F64, false},
|
||||
{0x37u, Opcode::V_CMPX_O_F64, false}, {0x38u, Opcode::V_CMPX_U_F64, false},
|
||||
{0x39u, Opcode::V_CMPX_NGE_F64, false}, {0x3au, Opcode::V_CMPX_NLG_F64, false},
|
||||
{0x3bu, Opcode::V_CMPX_NGT_F64, false}, {0x3cu, Opcode::V_CMPX_NLE_F64, false},
|
||||
{0x3du, Opcode::V_CMPX_NEQ_F64, false}, {0x3eu, Opcode::V_CMPX_NLT_F64, false},
|
||||
{0x3fu, Opcode::V_CMPX_TRU_F64, false},
|
||||
{0x80u, Opcode::V_CMP_F_I32}, {0x81u, Opcode::V_CMP_LT_I32},
|
||||
{0x82u, Opcode::V_CMP_EQ_I32}, {0x83u, Opcode::V_CMP_LE_I32},
|
||||
{0x84u, Opcode::V_CMP_GT_I32}, {0x85u, Opcode::V_CMP_NE_I32},
|
||||
@@ -245,7 +259,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x88u, Opcode::V_CMP_CLASS_F32}, {0x89u, Opcode::V_CMP_LT_I16},
|
||||
{0x8au, Opcode::V_CMP_EQ_I16}, {0x8bu, Opcode::V_CMP_LE_I16},
|
||||
{0x8cu, Opcode::V_CMP_GT_I16}, {0x8du, Opcode::V_CMP_NE_I16},
|
||||
{0x8eu, Opcode::V_CMP_GE_I16}, {0x91u, Opcode::V_CMPX_LT_I32},
|
||||
{0x8eu, Opcode::V_CMP_GE_I16}, {0x8fu, Opcode::V_CMP_CLASS_F16, false},
|
||||
{0x91u, Opcode::V_CMPX_LT_I32},
|
||||
{0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32},
|
||||
{0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32},
|
||||
{0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32},
|
||||
@@ -262,25 +277,26 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32},
|
||||
{0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32},
|
||||
{0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32},
|
||||
{0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false},
|
||||
{0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false},
|
||||
{0xa5u, Opcode::V_CMP_NE_I64, false},
|
||||
{0xa0u, Opcode::V_CMP_F_I64, false}, {0xa7u, Opcode::V_CMP_T_I64, false},
|
||||
{0xb0u, Opcode::V_CMPX_F_I64, false}, {0xb1u, Opcode::V_CMPX_LT_I64, false},
|
||||
{0xb2u, Opcode::V_CMPX_EQ_I64, false}, {0xb3u, Opcode::V_CMPX_LE_I64, false},
|
||||
{0xb7u, Opcode::V_CMPX_T_I64, false},
|
||||
{0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32},
|
||||
{0xc7u, Opcode::V_CMP_T_U32}, {0xd1u, Opcode::V_CMPX_LT_U32},
|
||||
{0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32},
|
||||
{0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32},
|
||||
{0xd6u, Opcode::V_CMPX_GE_U32}, {0xe1u, Opcode::V_CMP_LT_U64, false},
|
||||
{0xe0u, Opcode::V_CMP_F_U64, false}, {0xe7u, Opcode::V_CMP_T_U64, false},
|
||||
{0xf0u, Opcode::V_CMPX_F_U64, false}, {0xf1u, Opcode::V_CMPX_LT_U64, false},
|
||||
{0xf2u, Opcode::V_CMPX_EQ_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false},
|
||||
{0xf6u, Opcode::V_CMPX_GE_U64, false}, {0xf7u, Opcode::V_CMPX_T_U64, false},
|
||||
{0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe3u, Opcode::V_CMP_LE_U64, false},
|
||||
{0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false},
|
||||
{0xe6u, Opcode::V_CMP_GE_U64, false}, {0xf3u, Opcode::V_CMPX_LE_U64, false},
|
||||
{0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16},
|
||||
{0xd6u, Opcode::V_CMPX_GE_U32}, {0xa0u, Opcode::V_CMP_F_I64, false},
|
||||
{0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa2u, Opcode::V_CMP_EQ_I64, false},
|
||||
{0xa3u, Opcode::V_CMP_LE_I64, false}, {0xa4u, Opcode::V_CMP_GT_I64, false},
|
||||
{0xa5u, Opcode::V_CMP_NE_I64, false}, {0xa6u, Opcode::V_CMP_GE_I64, false},
|
||||
{0xa7u, Opcode::V_CMP_T_I64, false}, {0xb0u, Opcode::V_CMPX_F_I64, false},
|
||||
{0xb1u, Opcode::V_CMPX_LT_I64, false}, {0xb2u, Opcode::V_CMPX_EQ_I64, false},
|
||||
{0xb3u, Opcode::V_CMPX_LE_I64, false}, {0xb4u, Opcode::V_CMPX_GT_I64, false},
|
||||
{0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xb6u, Opcode::V_CMPX_GE_I64, false},
|
||||
{0xb7u, Opcode::V_CMPX_T_I64, false}, {0xe0u, Opcode::V_CMP_F_U64, false},
|
||||
{0xe1u, Opcode::V_CMP_LT_U64, false}, {0xe2u, Opcode::V_CMP_EQ_U64, false},
|
||||
{0xe3u, Opcode::V_CMP_LE_U64, false}, {0xe4u, Opcode::V_CMP_GT_U64, false},
|
||||
{0xe5u, Opcode::V_CMP_NE_U64, false}, {0xe6u, Opcode::V_CMP_GE_U64, false},
|
||||
{0xe7u, Opcode::V_CMP_T_U64, false}, {0xf0u, Opcode::V_CMPX_F_U64, false},
|
||||
{0xf1u, Opcode::V_CMPX_LT_U64, false}, {0xf2u, Opcode::V_CMPX_EQ_U64, false},
|
||||
{0xf3u, Opcode::V_CMPX_LE_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false},
|
||||
{0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xf6u, Opcode::V_CMPX_GE_U64, false},
|
||||
{0xf7u, Opcode::V_CMPX_T_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16},
|
||||
{0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16},
|
||||
{0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16},
|
||||
{0xceu, Opcode::V_CMP_GE_F16}, {0xe9u, Opcode::V_CMP_NGE_F16},
|
||||
@@ -578,7 +594,7 @@ struct Vop1SdwaRule {
|
||||
|
||||
constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
|
||||
{Opcode::V_MOV_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
|
||||
SdwaSelWords() | SdwaSelFull(), false},
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
|
||||
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
@@ -759,9 +775,10 @@ void DecodeVop1Sdwa(uint32_t pc, std::span<const uint32_t> code, uint32_t word_i
|
||||
|
||||
void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) {
|
||||
operand.dpp = true;
|
||||
if (encoding == 233u) {
|
||||
operand.dpp8 = true;
|
||||
operand.dpp_ctrl = modifier >> 8u;
|
||||
if (encoding == 233u || encoding == 234u) {
|
||||
operand.dpp8 = true;
|
||||
operand.dpp_ctrl = modifier >> 8u;
|
||||
operand.dpp_fetch_inactive = encoding == 234u;
|
||||
return;
|
||||
}
|
||||
operand.negate = ((modifier >> 20u) & 0x1u) != 0u;
|
||||
@@ -878,10 +895,38 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMP_F_F32:
|
||||
case Opcode::V_CMP_LT_F32:
|
||||
case Opcode::V_CMP_EQ_F32:
|
||||
case Opcode::V_CMP_F_F64:
|
||||
case Opcode::V_CMP_LT_F64:
|
||||
case Opcode::V_CMP_EQ_F64:
|
||||
case Opcode::V_CMP_LE_F64:
|
||||
case Opcode::V_CMP_GT_F64:
|
||||
case Opcode::V_CMP_LG_F64:
|
||||
case Opcode::V_CMP_GE_F64:
|
||||
case Opcode::V_CMP_O_F64:
|
||||
case Opcode::V_CMP_U_F64:
|
||||
case Opcode::V_CMP_NGE_F64:
|
||||
case Opcode::V_CMP_NLG_F64:
|
||||
case Opcode::V_CMP_NGT_F64:
|
||||
case Opcode::V_CMP_NLE_F64:
|
||||
case Opcode::V_CMP_NEQ_F64:
|
||||
case Opcode::V_CMP_NLT_F64:
|
||||
case Opcode::V_CMP_TRU_F64:
|
||||
case Opcode::V_CMPX_F_F64:
|
||||
case Opcode::V_CMPX_LT_F64:
|
||||
case Opcode::V_CMPX_EQ_F64:
|
||||
case Opcode::V_CMPX_LE_F64:
|
||||
case Opcode::V_CMPX_GT_F64:
|
||||
case Opcode::V_CMPX_LG_F64:
|
||||
case Opcode::V_CMPX_GE_F64:
|
||||
case Opcode::V_CMPX_O_F64:
|
||||
case Opcode::V_CMPX_U_F64:
|
||||
case Opcode::V_CMPX_NGE_F64:
|
||||
case Opcode::V_CMPX_NLG_F64:
|
||||
case Opcode::V_CMPX_NGT_F64:
|
||||
case Opcode::V_CMPX_NLE_F64:
|
||||
case Opcode::V_CMPX_NEQ_F64:
|
||||
case Opcode::V_CMPX_NLT_F64:
|
||||
case Opcode::V_CMPX_TRU_F64:
|
||||
case Opcode::V_CMP_LE_F32:
|
||||
case Opcode::V_CMP_GT_F32:
|
||||
case Opcode::V_CMP_LG_F32:
|
||||
@@ -929,6 +974,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMPX_NEQ_F16:
|
||||
case Opcode::V_CMPX_NLT_F16:
|
||||
case Opcode::V_CMP_CLASS_F32:
|
||||
case Opcode::V_CMP_CLASS_F16:
|
||||
case Opcode::V_CMPX_CLASS_F16:
|
||||
case Opcode::V_CMPX_CLASS_F32: return true;
|
||||
default: return false;
|
||||
@@ -994,8 +1040,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
|
||||
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
|
||||
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
|
||||
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
|
||||
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
// Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write.
|
||||
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
};
|
||||
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
|
||||
static_cast<size_t>(Vop2SdwaProfile::Count));
|
||||
@@ -1199,10 +1245,10 @@ VopcSdwaFields DecodeVopcSdwaFields(uint32_t modifier) {
|
||||
}
|
||||
|
||||
bool SupportsVopcSdwa(Opcode opcode) {
|
||||
return opcode != Opcode::UNSUPPORTED && opcode != Opcode::V_CMP_NE_I64 &&
|
||||
opcode != Opcode::V_CMP_EQ_F64 &&
|
||||
opcode != Opcode::V_CMP_LE_F64 && opcode != Opcode::V_CMPX_LE_F64 &&
|
||||
opcode != Opcode::V_CMPX_GE_F64;
|
||||
// SDWA operand handling is not implemented for 64-bit compares.
|
||||
return opcode != Opcode::UNSUPPORTED &&
|
||||
(opcode < Opcode::V_CMP_F_F64 || opcode > Opcode::V_CMPX_TRU_F64) &&
|
||||
(opcode < Opcode::V_CMP_F_I64 || opcode > Opcode::V_CMPX_T_U64);
|
||||
}
|
||||
|
||||
void DecodeVopcSdwa(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
@@ -1535,8 +1581,22 @@ void ApplyNativeVop3SourceModifiers(Instruction& inst, uint32_t abs, uint32_t ne
|
||||
|
||||
bool IsVopcCompareExec(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_CMPX_F_F64:
|
||||
case Opcode::V_CMPX_LT_F64:
|
||||
case Opcode::V_CMPX_EQ_F64:
|
||||
case Opcode::V_CMPX_LE_F64:
|
||||
case Opcode::V_CMPX_GT_F64:
|
||||
case Opcode::V_CMPX_LG_F64:
|
||||
case Opcode::V_CMPX_GE_F64:
|
||||
case Opcode::V_CMPX_O_F64:
|
||||
case Opcode::V_CMPX_U_F64:
|
||||
case Opcode::V_CMPX_NGE_F64:
|
||||
case Opcode::V_CMPX_NLG_F64:
|
||||
case Opcode::V_CMPX_NGT_F64:
|
||||
case Opcode::V_CMPX_NLE_F64:
|
||||
case Opcode::V_CMPX_NEQ_F64:
|
||||
case Opcode::V_CMPX_NLT_F64:
|
||||
case Opcode::V_CMPX_TRU_F64:
|
||||
case Opcode::V_CMPX_LT_F32:
|
||||
case Opcode::V_CMPX_EQ_F32:
|
||||
case Opcode::V_CMPX_LE_F32:
|
||||
@@ -1569,7 +1629,9 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_LT_I64:
|
||||
case Opcode::V_CMPX_EQ_I64:
|
||||
case Opcode::V_CMPX_LE_I64:
|
||||
case Opcode::V_CMPX_GT_I64:
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_GE_I64:
|
||||
case Opcode::V_CMPX_T_I64:
|
||||
case Opcode::V_CMPX_F_U64:
|
||||
case Opcode::V_CMPX_LT_U64:
|
||||
@@ -1623,7 +1685,8 @@ void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
return;
|
||||
}
|
||||
switch (src0) {
|
||||
case 233u: DecodeVop2Dpp(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
|
||||
case 233u:
|
||||
case 234u: DecodeVop2Dpp(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
|
||||
case 249u: DecodeVop2Sdwa(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
|
||||
case 250u: DecodeVop2Dpp(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
|
||||
default: break;
|
||||
@@ -1658,7 +1721,8 @@ void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
return;
|
||||
}
|
||||
switch (src0) {
|
||||
case 233u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 233u:
|
||||
case 234u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 249u: DecodeVop1Sdwa(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 250u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
default: break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <bit>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
@@ -27,7 +28,8 @@ void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool valu
|
||||
}
|
||||
|
||||
void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
IR::Type type, bool scalar, bool cmpx) {
|
||||
IR::Type type, bool scalar, bool cmpx, bool swap_operands) {
|
||||
// The equality compares share their IR opcode with the unsigned ones.
|
||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 ||
|
||||
@@ -42,8 +44,12 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
||||
}
|
||||
return ReadOperand(operand, type);
|
||||
};
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {read(inst.src0), read(inst.src1)})), scalar,
|
||||
cmpx);
|
||||
auto lhs = read(inst.src0);
|
||||
auto rhs = read(inst.src1);
|
||||
if (swap_operands) {
|
||||
std::swap(lhs, rhs);
|
||||
}
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
@@ -65,11 +71,14 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx) {
|
||||
const auto lhs = IR::F32(ReadOperand(inst.src0, IR::Type::F32));
|
||||
const auto rhs = IR::F32(ReadOperand(inst.src1, IR::Type::F32));
|
||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {lhs})),
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {rhs})));
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
||||
bool is64) {
|
||||
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
||||
const auto is_nan_op = is64 ? IR::ValueOpcode::FPIsNan64 : IR::ValueOpcode::FPIsNan32;
|
||||
const auto lhs = ReadOperand(inst.src0, type);
|
||||
const auto rhs = ReadOperand(inst.src1, type);
|
||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
||||
IR::U1(ir.Emit(is_nan_op, {rhs})));
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,8 +221,12 @@ void Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
|
||||
const auto source = ExtractU64(ReadU64(inst.src0));
|
||||
void Translator::S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero) {
|
||||
auto source = ReadU32Pair(inst.src0);
|
||||
if (find_zero) {
|
||||
source[0] = ir.BitwiseNot(source[0]);
|
||||
source[1] = ir.BitwiseNot(source[1]);
|
||||
}
|
||||
const auto low_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[0]}));
|
||||
const auto high_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[1]}));
|
||||
const auto high_position = ir.IAdd(high_lsb, IR::U32(IR::Value(32u)));
|
||||
|
||||
@@ -187,7 +187,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
if (decoded.family == Decoder::Family::MUBUF || decoded.family == Decoder::Family::MTBUF) {
|
||||
const bool store_or_atomic =
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_FORMAT_X &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X) ||
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW) ||
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_BYTE &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_DWORDX4) ||
|
||||
(decoded.opcode >= Decoder::Opcode::TBUFFER_STORE_FORMAT_X &&
|
||||
@@ -578,11 +578,13 @@ void Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opc
|
||||
bool returns_value) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto address = ReadU32(inst.src0);
|
||||
const IR::Value value =
|
||||
memory.data_dwords == 2u ? IR::Value(ReadU64(inst.src1)) : IR::Value(ReadU32(inst.src1));
|
||||
const auto read = [&](const Decoder::Operand& operand) -> IR::Value {
|
||||
return memory.data_dwords == 2u ? IR::Value(ReadU64(operand)) : IR::Value(ReadU32(operand));
|
||||
};
|
||||
const auto value = read(inst.src1);
|
||||
const auto flags = AddMemoryInfo(memory, inst.pc);
|
||||
const auto result = inst.src_count == 3u
|
||||
? ir.Emit(opcode, {address, value, ReadU32(inst.src2), ir.GetExec()}, flags)
|
||||
? ir.Emit(opcode, {address, value, read(inst.src2), ir.GetExec()}, flags)
|
||||
: ir.Emit(opcode, {address, value, ir.GetExec()}, flags);
|
||||
if (returns_value) {
|
||||
WriteOperand(inst.dst, result);
|
||||
@@ -687,7 +689,8 @@ void Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto sampler = GetSamplerResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageSampleRaw, {resource, sampler, address},
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageSampleRaw,
|
||||
{resource, sampler, address, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
const bool dref = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u;
|
||||
if (dref && memory.data_bits != 16u) {
|
||||
@@ -714,7 +717,7 @@ void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
|
||||
has_lod ? resource.Instruction()->Arg(1) : IR::Value(0u),
|
||||
has_lod ? resource.Instruction()->Arg(3) : IR::Value(0u),
|
||||
has_lod ? sampler.Instruction()->Arg(1) : IR::Value(0u),
|
||||
has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u)},
|
||||
has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
for (uint32_t index = 0; index < memory.data_dwords; index++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
@@ -970,9 +973,12 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Decoder::Opcode::BUFFER_LOAD_UBYTE_D16:
|
||||
case Decoder::Opcode::BUFFER_LOAD_SBYTE:
|
||||
case Decoder::Opcode::BUFFER_LOAD_USHORT:
|
||||
case Decoder::Opcode::BUFFER_LOAD_SSHORT:
|
||||
case Decoder::Opcode::BUFFER_LOAD_SHORT_D16:
|
||||
case Decoder::Opcode::BUFFER_LOAD_SHORT_D16_HI:
|
||||
case Decoder::Opcode::BUFFER_LOAD_DWORD:
|
||||
case Decoder::Opcode::BUFFER_LOAD_DWORDX2:
|
||||
case Decoder::Opcode::BUFFER_LOAD_DWORDX3:
|
||||
@@ -982,6 +988,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XYZ:
|
||||
@@ -992,12 +999,16 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_STORE_DWORDX3:
|
||||
case Decoder::Opcode::BUFFER_STORE_DWORDX4:
|
||||
case Decoder::Opcode::BUFFER_STORE_BYTE:
|
||||
case Decoder::Opcode::BUFFER_STORE_BYTE_D16_HI:
|
||||
case Decoder::Opcode::BUFFER_STORE_SHORT:
|
||||
case Decoder::Opcode::BUFFER_STORE_SHORT_D16_HI:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
@@ -1070,6 +1081,10 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax32, false);
|
||||
case Decoder::Opcode::DS_MAX_RTN_U32:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax32, true);
|
||||
case Decoder::Opcode::DS_MAX_RTN_U64:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax64, true);
|
||||
case Decoder::Opcode::DS_CMPST_RTN_B64:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicCmpSwap64, true);
|
||||
case Decoder::Opcode::DS_AND_B32:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicAnd32, false);
|
||||
case Decoder::Opcode::DS_AND_RTN_B32:
|
||||
@@ -1148,6 +1163,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::IMAGE_GATHER4_L:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_LZ:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_C:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_C_L:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_C_LZ:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_LZ_O:
|
||||
case Decoder::Opcode::IMAGE_GATHER4_C_O:
|
||||
|
||||
@@ -10,13 +10,21 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_MOV_B64: S_MOV_B64(inst); return;
|
||||
case O::S_WQM_B32: S_WQM(inst, false); return;
|
||||
case O::S_WQM_B64: S_WQM(inst, true); return;
|
||||
case O::S_GETPC_B64: S_GETPC_B64(inst); return;
|
||||
case O::S_GETPC_B64:
|
||||
case O::S_SWAPPC_B64: S_GETPC_B64(inst); return;
|
||||
case O::S_SETPC_B64: return;
|
||||
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return;
|
||||
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return;
|
||||
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return;
|
||||
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return;
|
||||
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return;
|
||||
case O::S_GETREG_B32:
|
||||
if (!graphics_compute || (inst.src0.value != 0x1818u && inst.src0.value != 0x0a18u))
|
||||
EXIT("unsupported S_GETREG_B32 at pc 0x%08x: selector=0x%04x graphics_compute=%u",
|
||||
inst.pc, inst.src0.value, static_cast<unsigned>(graphics_compute));
|
||||
// HW_ID2 identifies graphics compute as QUEUE=6, ME=0.
|
||||
WriteOperand(inst.dst, IR::Value(inst.src0.value == 0x1818u ? 6u : 0u));
|
||||
return;
|
||||
case O::S_SETREG_B32: EmitControlNop(); return;
|
||||
case O::S_WAITCNT_VSCNT: S_WAITCNT_VSCNT(inst); return;
|
||||
case O::S_WAITCNT: return;
|
||||
@@ -134,10 +142,14 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
|
||||
case O::S_ABS_I32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::IAbs32, IR::Type::U32, false, false, true);
|
||||
case O::S_SEXT_I32_I16:
|
||||
case O::S_SEXT_I32_I8:
|
||||
case O::S_SEXT_I32_I16: {
|
||||
// Sign extension leaves SCC unchanged.
|
||||
const auto bits = inst.opcode == O::S_SEXT_I32_I8 ? 8u : 16u;
|
||||
WriteOperand(inst.dst, IR::U32(ir.Emit(IR::ValueOpcode::BitFieldSExtract,
|
||||
{ReadU32(inst.src0), IR::Value(0u), IR::Value(16u)})));
|
||||
{ReadU32(inst.src0), IR::Value(0u), IR::Value(bits)})));
|
||||
return;
|
||||
}
|
||||
case O::S_MUL_I32:
|
||||
case O::S_MULK_I32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::IMul32, IR::Type::U32, false, false, false);
|
||||
@@ -166,12 +178,29 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitReverse32, {value[0]}))});
|
||||
return;
|
||||
}
|
||||
case O::S_BCNT0_I32_B32:
|
||||
case O::S_FF0_I32_B32: {
|
||||
const auto value = ir.BitwiseNot(ReadU32(inst.src0));
|
||||
const bool count = inst.opcode == O::S_BCNT0_I32_B32;
|
||||
const auto result = IR::U32(ir.Emit(count ? IR::ValueOpcode::BitCount32
|
||||
: IR::ValueOpcode::FindILsb32, {value}));
|
||||
WriteOperand(inst.dst, result);
|
||||
if (count) {
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case O::S_BCNT1_I32_B32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::BitCount32, IR::Type::U32, false, false,
|
||||
true);
|
||||
case O::S_BCNT0_I32_B64:
|
||||
case O::S_BCNT1_I32_B64: {
|
||||
// Vulkan bit counts operate on 32-bit words; avoid packing only to split again.
|
||||
const auto value = ReadU32Pair(inst.src0);
|
||||
auto value = ReadU32Pair(inst.src0);
|
||||
if (inst.opcode == O::S_BCNT0_I32_B64) {
|
||||
value[0] = ir.BitwiseNot(value[0]);
|
||||
value[1] = ir.BitwiseNot(value[1]);
|
||||
}
|
||||
const auto low = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {value[0]}));
|
||||
const auto high = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {value[1]}));
|
||||
const auto result = ir.IAdd(low, high);
|
||||
@@ -209,7 +238,8 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseOr32, false, true, true);
|
||||
case O::S_XNOR_B32:
|
||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, true);
|
||||
case O::S_FF1_I32_B64: return S_FF1_I32_B64(inst);
|
||||
case O::S_FF0_I32_B64: return S_FF_I32_B64(inst, true);
|
||||
case O::S_FF1_I32_B64: return S_FF_I32_B64(inst, false);
|
||||
case O::S_FLBIT_I32_B32: return V_FFBH_32(inst, false);
|
||||
case O::S_FLBIT_I32_B64: return S_FLBIT_I32_B64(inst);
|
||||
|
||||
@@ -249,7 +279,9 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_CBRANCH_EXECZ:
|
||||
case O::S_CBRANCH_EXECNZ:
|
||||
case O::S_CBRANCH_CDBGSYS:
|
||||
case O::S_CBRANCH_CDBGUSER:
|
||||
case O::S_CBRANCH_CDBGSYS_OR_USER:
|
||||
case O::S_CBRANCH_CDBGSYS_AND_USER:
|
||||
case O::S_ENDPGM: return;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
|
||||
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user