Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc4333e313 | ||
|
|
b0c8229bd4 | ||
|
|
bc1c779972 | ||
|
|
0c2546e850 | ||
|
|
eb3cb47296 | ||
|
|
a5cfdcfd32 | ||
|
|
ae67e2db51 | ||
|
|
1703836d06 | ||
|
|
b65dd18224 | ||
|
|
2f6dccb10b | ||
|
|
08a7d7fbed | ||
|
|
2aa0d58ffb | ||
|
|
9bf311c6c7 | ||
|
|
e66a7f52b5 | ||
|
|
67f8276751 | ||
|
|
3ad21e35cc | ||
|
|
5eab0ec738 | ||
|
|
bfd4c34b88 | ||
|
|
6ff6e7551d | ||
|
|
25f3ba4eae | ||
|
|
4592e4c178 | ||
|
|
97bef2992f | ||
|
|
15452a5fee | ||
|
|
651ef73a03 | ||
|
|
5e837a9bea | ||
|
|
a0b99a8c21 | ||
|
|
8cfe1c4d34 | ||
|
|
f71da54b9b | ||
|
|
63207739bc | ||
|
|
143f640706 | ||
|
|
54b937c85f | ||
|
|
22610cb93e | ||
|
|
0386f33bf3 | ||
|
|
138d5b86c4 | ||
|
|
963aaeb3b6 | ||
|
|
77458260f1 | ||
|
|
41de2fd64e | ||
|
|
128c938514 | ||
|
|
a8e3c29b2e | ||
|
|
c528324d27 | ||
|
|
c67352d5d6 | ||
|
|
6c01401862 | ||
|
|
a833e1b735 | ||
|
|
1be1c6c4f9 | ||
|
|
69fdb03dc2 | ||
|
|
2efd31443f | ||
|
|
9eef2e1434 | ||
|
|
39faa62ebd | ||
|
|
7ff8aff5e6 | ||
|
|
64a554dea7 | ||
|
|
4b0282e343 | ||
|
|
ffc81377b2 | ||
|
|
0685bffb2f |
No files matched your search
@@ -113,7 +113,7 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher ^
|
||||
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
shader_recompiler_compute_tests ^
|
||||
resource_tracking_tests scalar_provenance_tests ^
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
--target launcher audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -388,7 +388,7 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
--parallel
|
||||
@@ -397,7 +397,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
@@ -325,6 +325,7 @@ 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)
|
||||
add_gpu_blit_shader(gpu_pssr_linear gpu_pssr_linear.comp)
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
@@ -522,6 +523,13 @@ add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ajm_resample_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/AjmResampleTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(ajm_resample_tests common fmt::fmt FFmpeg::ffmpeg LibAtrac9 opus)
|
||||
target_include_directories(ajm_resample_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||
)
|
||||
@@ -624,6 +632,19 @@ target_sources(shader_recompiler_compute_tests PRIVATE
|
||||
"${gpu_test_ms_depth_header}")
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
||||
add_library(kyty_test_gtest STATIC EXCLUDE_FROM_ALL
|
||||
"${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest/gmock-gtest-all.cc")
|
||||
target_include_directories(kyty_test_gtest PUBLIC "${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest")
|
||||
target_sources(virtual_memory_allocation_tests PRIVATE "${KYTY_TESTS_DIR}/TrinityTests.cpp")
|
||||
target_link_libraries(virtual_memory_allocation_tests kyty_test_gtest)
|
||||
|
||||
add_executable(psml_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PsmlTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/libPsml.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/symbolDatabase.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp")
|
||||
target_link_libraries(psml_tests common kyty_test_gtest)
|
||||
target_include_directories(psml_tests PRIVATE ${inc_headers})
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
@@ -668,6 +689,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME ajm_resample COMMAND $<TARGET_FILE:ajm_resample_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
|
||||
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||
@@ -684,6 +706,16 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
|
||||
add_test(NAME shader_recompiler_float_min_max
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
|
||||
add_test(NAME shader_recompiler_vop3_conversions
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only)
|
||||
add_test(NAME shader_recompiler_quadmask
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only)
|
||||
add_test(NAME shader_recompiler_integer16_ternary
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only)
|
||||
add_test(NAME shader_recompiler_mad_u16
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only)
|
||||
add_test(NAME shader_recompiler_mul_hi24
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mul-hi24-only)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
add_test(NAME graphics_pipeline_rasterization
|
||||
@@ -692,6 +724,10 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
||||
add_test(NAME virtual_memory_allocation
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||
add_test(NAME trinity_mode
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --trinity-only)
|
||||
add_test(NAME psml COMMAND $<TARGET_FILE:psml_tests>)
|
||||
add_test(NAME pssr COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --pssr-only)
|
||||
add_test(NAME guest_red_zone_patcher
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
||||
add_test(NAME command_scheduler_timeline
|
||||
@@ -700,8 +736,6 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME pm4_flip
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||
add_test(NAME depth_bits
|
||||
@@ -764,6 +798,7 @@ if(BUILD_TESTING)
|
||||
audio_out2_port_tests
|
||||
audio_out_timing_tests
|
||||
ngs2_sampler_tests
|
||||
ajm_resample_tests
|
||||
pad_haptics_tests
|
||||
controller_settings_tests
|
||||
host_input_tests
|
||||
|
||||
@@ -23,11 +23,8 @@ can change significantly between builds.
|
||||
|
||||
## Current Status
|
||||
|
||||
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.
|
||||
External low-level emulation modules are neither required nor planned.
|
||||
|
||||
Windows and Linux are the primary target platforms and receive the most testing.
|
||||
|
||||
@@ -90,6 +87,13 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
|
||||
<p align="center"><em>And many more...</em></p>
|
||||
|
||||
## Notable Forks
|
||||
|
||||
These community forks bring KytyPS5 to other platforms.
|
||||
|
||||
- [Cosmo/KytyPS5](https://github.com/Cosmo/KytyPS5) — Xbox Series X: Direct3D 12 renderer, sleek
|
||||
controller-first interface, and local multiplayer for up to four players.
|
||||
|
||||
## Contributing
|
||||
|
||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
|
||||
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
|
||||
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
|
||||
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
|
||||
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
|
||||
<g transform="translate(256 268) scale(0.86) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<g transform="translate(0 18)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
|
||||
<g fill="url(#body)" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M128 164 L137 128 L174 156 Z" fill="#0b0d12"/><path d="M384 164 L375 128 L338 156 Z" fill="#0b0d12"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="16"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" fill="none" stroke="#0b0d12" stroke-width="7"/>
|
||||
<path d="M168 230 Q194 202 220 230" fill="none" stroke="#0b0d12" stroke-width="15"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="16"/>
|
||||
<g fill="none" stroke="#0b0d12"><g stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" stroke-width="7"/>
|
||||
<path d="M168 230 Q194 202 220 230" stroke-width="15"/></g>
|
||||
<circle cx="322" cy="220" r="22" fill="#0b0d12"/>
|
||||
<path d="M238 252 L274 252 L256 276 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -105,10 +105,18 @@ bool HideCursorEnabled() {
|
||||
return g_config->hide_cursor_enabled;
|
||||
}
|
||||
|
||||
bool StretchEnabled() {
|
||||
return g_config->stretch_enabled;
|
||||
}
|
||||
|
||||
bool VrEnabled() {
|
||||
return g_config->vr_enabled;
|
||||
}
|
||||
|
||||
bool Ps5ProEnabled() {
|
||||
return g_config->pro_enabled;
|
||||
}
|
||||
|
||||
bool AmdCpuEnabled() {
|
||||
return g_config->amd_cpu_enabled;
|
||||
}
|
||||
@@ -129,6 +137,10 @@ bool IsNetworkConnected() {
|
||||
return g_config->network_connected;
|
||||
}
|
||||
|
||||
bool PsnEmulationEnabled() {
|
||||
return g_config->psn_emulation_enabled;
|
||||
}
|
||||
|
||||
bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
|
||||
@@ -64,10 +64,13 @@ struct ConfigOptions {
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool stretch_enabled = false;
|
||||
bool splash_enabled = true;
|
||||
bool vr_enabled = false;
|
||||
bool pro_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
bool network_connected = false;
|
||||
bool psn_emulation_enabled = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -125,9 +128,12 @@ int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
bool HdrEnabled();
|
||||
bool HideCursorEnabled();
|
||||
bool StretchEnabled();
|
||||
bool VrEnabled();
|
||||
bool Ps5ProEnabled();
|
||||
bool AmdCpuEnabled();
|
||||
bool IsNetworkConnected();
|
||||
bool PsnEmulationEnabled();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
@@ -92,6 +93,7 @@ public:
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void ProcessPssr(const Pssr::Request& request);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||
|
||||
void WaitForRewind(bool valid);
|
||||
|
||||
@@ -226,6 +226,7 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
|
||||
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
|
||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
||||
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
|
||||
g_cp_op_custom_func[Pm4::R_PSSR] = CpOpPssr;
|
||||
|
||||
return g_cp_op_custom_func;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ uint32_t CpOpPushMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
|
||||
uint32_t CpOpPopMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitRegMem64(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpFlip(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpPssr(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -1706,6 +1706,17 @@ KYTY_CP_OP_PARSER(CpOpFlip) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpPssr) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
const auto packet_dw = KYTY_PM4_LEN(cmd_id) - 1;
|
||||
EXIT_NOT_IMPLEMENTED(packet_dw < Pssr::RequestDwords || packet_dw > dw);
|
||||
Pssr::Request request {};
|
||||
std::memcpy(&request, buffer, sizeof(request));
|
||||
cp.ProcessPssr(request);
|
||||
return packet_dw;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpIndexType) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{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},
|
||||
{BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -92,12 +91,6 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -443,10 +436,7 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -866,9 +856,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from
|
||||
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they
|
||||
// are zero.
|
||||
// The GPU reads the records if the shaders allow; the VS prolog removes Vulkan's bases
|
||||
// from the index VGPRs and copies them only to the user SGPRs the packet patches.
|
||||
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
@@ -940,6 +929,11 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::ProcessPssr(const Pssr::Request& request) {
|
||||
EXIT_NOT_IMPLEMENTED(IsAsyncComputeQueue());
|
||||
m_renderer.GetRenderExecutor().ProcessPssr(CurrentBuffer(), request);
|
||||
}
|
||||
|
||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
||||
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
||||
@@ -960,11 +954,10 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
// Stall only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
}
|
||||
|
||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||
|
||||
@@ -31,8 +31,6 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> commands);
|
||||
@@ -84,7 +82,6 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -81,6 +81,7 @@ constexpr uint32_t R_WRITE_DATA = 0x15;
|
||||
constexpr uint32_t R_FLIP = 0x17;
|
||||
constexpr uint32_t R_DMA_DATA = 0x19;
|
||||
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
||||
constexpr uint32_t R_PSSR = 0x1B;
|
||||
|
||||
constexpr uint32_t R_NUM = 0x3F + 1;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics::Pssr {
|
||||
|
||||
struct Request {
|
||||
uint32_t output[8];
|
||||
uint32_t input[8];
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
constexpr uint32_t RequestDwords = sizeof(Request) / sizeof(uint32_t);
|
||||
static_assert(sizeof(Request) == 72);
|
||||
|
||||
} // namespace Libs::Graphics::Pssr
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
|
||||
@@ -259,6 +259,17 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t FirstTailLevel(const MipTailLayout& tail, uint32_t width, uint32_t height,
|
||||
uint32_t levels) {
|
||||
for (uint32_t level = 0; levels > 1 && level < levels; ++level) {
|
||||
if (ShiftCeil(width, level) <= tail.width_limit &&
|
||||
ShiftCeil(height, level) <= tail.height_limit && levels - level <= tail.max_levels) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
|
||||
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
|
||||
out = {bytes, 1, 1};
|
||||
@@ -336,23 +347,14 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
|
||||
TileSurfaceLayout result {};
|
||||
result.description = description;
|
||||
result.texture = texture;
|
||||
result.first_tail_level = description.levels;
|
||||
const uint32_t block_slices =
|
||||
volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth))
|
||||
: description.layers;
|
||||
|
||||
MipTailLayout tail {};
|
||||
const bool has_tail = GetMipTailLayout(block, tail);
|
||||
if (has_tail && description.levels > 1) {
|
||||
for (uint32_t level = 0; level < description.levels; ++level) {
|
||||
if (ShiftCeil(width0, level) <= tail.width_limit &&
|
||||
ShiftCeil(height0, level) <= tail.height_limit &&
|
||||
description.levels - level <= tail.max_levels) {
|
||||
result.first_tail_level = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.first_tail_level = GetMipTailLayout(block, tail)
|
||||
? FirstTailLevel(tail, width0, height0, description.levels)
|
||||
: description.levels;
|
||||
|
||||
for (uint32_t level = 0; level < result.first_tail_level; ++level) {
|
||||
auto& mip = result.mips[level];
|
||||
@@ -1213,44 +1215,52 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t block_width, uint32_t block_height,
|
||||
TileSizeAlign& total_size) {
|
||||
if (width == 0 || height == 0 || slices == 0) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
|
||||
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
|
||||
const uint64_t blocks = blocks_x * blocks_y;
|
||||
if (blocks > UINT32_MAX / 4096u / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
|
||||
total_size.align = 4096;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2, TileSizeOffset* mips) {
|
||||
total_size = {};
|
||||
if (levels != 1 || num_fragments_log2 != 0 ||
|
||||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
|
||||
TileBlockLayout block {};
|
||||
MipTailLayout tail {};
|
||||
if (slices == 0 || levels > 16 || num_fragments_log2 != 0 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth) ||
|
||||
!TileGetBlockLayout(tile == Prospero::TileMode::kDepth ? TileBlockFamily::Depth64KB
|
||||
: TileBlockFamily::RenderTarget64KB,
|
||||
bytes_per_element, block) ||
|
||||
!GetMipTailLayout(block, tail)) {
|
||||
return false;
|
||||
}
|
||||
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data.
|
||||
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. Each slice stores
|
||||
// its mips backwards through the first tail mip, which fits one block; later tail mips have
|
||||
// none. Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
|
||||
return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u),
|
||||
1u << (coverage_bits / 2u), total_size);
|
||||
uint64_t slice_size = 0;
|
||||
for (uint32_t level = std::min(FirstTailLevel(tail, width, height, levels) + 1u, levels);
|
||||
level-- > 0;) {
|
||||
const uint64_t size = uint64_t {ShiftCeil(width, level + (coverage_bits + 1u) / 2u)} *
|
||||
ShiftCeil(height, level + coverage_bits / 2u) * 4096u;
|
||||
if (mips != nullptr) {
|
||||
mips[level] = {.size = static_cast<uint32_t>(size),
|
||||
.offset = static_cast<uint32_t>(slice_size)};
|
||||
}
|
||||
slice_size += size;
|
||||
}
|
||||
if (slice_size == 0 || slice_size > UINT32_MAX / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size = {static_cast<uint32_t>(slice_size * slices), 4096};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size) {
|
||||
total_size = {};
|
||||
// Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels.
|
||||
return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size);
|
||||
const uint64_t blocks = uint64_t {ShiftCeil(width, 10)} * ShiftCeil(height, 9);
|
||||
if (levels != 1 || blocks == 0 || slices == 0 || blocks > UINT32_MAX / 4096u / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size = {static_cast<uint32_t>(blocks * slices * 4096u), 4096};
|
||||
return true;
|
||||
}
|
||||
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
|
||||
@@ -123,9 +123,11 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
// mips, when given, receives the size and offset within a slice of each level that has metadata.
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0,
|
||||
TileSizeOffset* mips = nullptr);
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size);
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -17,14 +18,19 @@ public:
|
||||
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
||||
it = std::prev(it);
|
||||
}
|
||||
uint64_t begin = address;
|
||||
uint64_t last = end;
|
||||
while (it != m_ranges.end() && it->first <= last) {
|
||||
begin = std::min(begin, it->first);
|
||||
last = std::max(last, it->second);
|
||||
it = m_ranges.erase(it);
|
||||
if (it == m_ranges.end() || it->first > address) {
|
||||
it = m_ranges.emplace_hint(it, address, end);
|
||||
} else {
|
||||
if (it->second >= end) {
|
||||
return;
|
||||
}
|
||||
it->second = end;
|
||||
}
|
||||
auto next = std::next(it);
|
||||
while (next != m_ranges.end() && next->first <= it->second) {
|
||||
it->second = std::max(it->second, next->second);
|
||||
next = m_ranges.erase(next);
|
||||
}
|
||||
m_ranges.emplace(begin, last);
|
||||
}
|
||||
|
||||
void Subtract(uint64_t address, uint64_t size) {
|
||||
|
||||
@@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
||||
}
|
||||
|
||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
const bool source_depth = source.info.IsDepth();
|
||||
const bool destination_depth = destination.info.IsDepth();
|
||||
if (source_depth == destination_depth) {
|
||||
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||
return false;
|
||||
}
|
||||
auto& depth = source_depth ? source : destination;
|
||||
auto& color = source_depth ? destination : source;
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
||||
auto& depth = source_wide ? source : destination;
|
||||
auto& narrow = source_wide ? destination : source;
|
||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
||||
color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) {
|
||||
narrow.info.bytes_per_block != sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
@@ -507,12 +506,10 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
|
||||
const auto layers = depth.info.resources.layers;
|
||||
const uint64_t depth_slice =
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
||||
const uint64_t color_slice =
|
||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
||||
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;
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||
const uint64_t narrow_slice =
|
||||
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||
const std::array depth_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
depth.info.pitch,
|
||||
@@ -520,29 +517,31 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
depth.info.extent}};
|
||||
const std::array color_copies {
|
||||
const std::array narrow_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
color.info.pitch,
|
||||
color.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
||||
narrow.info.pitch,
|
||||
narrow.info.extent.height,
|
||||
{narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor,
|
||||
0, 0, layers},
|
||||
{},
|
||||
color.info.extent}};
|
||||
narrow.info.extent}};
|
||||
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||
const TileManager::D16Layout promote_layout {
|
||||
.width = depth.info.extent.width,
|
||||
.height = depth.info.extent.height,
|
||||
.layers = layers,
|
||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
||||
.source_slice_stride = color_slice,
|
||||
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||
.source_slice_stride = narrow_slice,
|
||||
.target_slice_stride = depth_slice,
|
||||
};
|
||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
||||
if (source_depth) {
|
||||
if (source_wide) {
|
||||
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
||||
m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32,
|
||||
m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32,
|
||||
{.width = promote_layout.width,
|
||||
.height = promote_layout.height,
|
||||
.layers = promote_layout.layers,
|
||||
@@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
.target_row_stride = promote_layout.source_row_stride,
|
||||
.source_slice_stride = promote_layout.target_slice_stride,
|
||||
.target_slice_stride = promote_layout.source_slice_stride});
|
||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
||||
color_buffer.size);
|
||||
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
} else {
|
||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
promote_layout);
|
||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||
depth_buffer.size);
|
||||
@@ -1044,33 +1044,50 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
return;
|
||||
}
|
||||
const auto range = desc.info.metadata.range;
|
||||
const auto& view = desc.view_info;
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// Native color metadata must not retain a reused HTile clear flag.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1) {
|
||||
if (range.size == 0 || view.level_count != 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto layers = desc.info.TransferLayers();
|
||||
// These one-mip surfaces use complete 4 KiB color metadata blocks.
|
||||
// Color metadata slices consist of complete 4 KiB blocks.
|
||||
constexpr uint64_t MetadataBlockSize = 0x1000;
|
||||
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
|
||||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
|
||||
EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n");
|
||||
}
|
||||
const auto& view = desc.view_info;
|
||||
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : metadata_base_layer;
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
|
||||
const auto count =
|
||||
volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u) : view.layer_count;
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||
// Each slice holds the viewed mip's metadata in one run; DCC mips past the first tail mip
|
||||
// have none. FindImage moves base_level only for one-mip descriptors, which keep the slice.
|
||||
TileSizeOffset mip {.size = static_cast<uint32_t>(slice_size)};
|
||||
if (desc.info.metadata.kind == ImageMetadataKind::Dcc && desc.info.resources.levels > 1) {
|
||||
std::array<TileSizeOffset, 16> mips {};
|
||||
TileSizeAlign total {};
|
||||
(void)TileGetDccSize(desc.info.extent.width, desc.info.extent.height, layers,
|
||||
desc.info.bytes_per_block, desc.info.resources.levels,
|
||||
desc.info.tile_mode, total, 0, mips.data());
|
||||
mip = mips[view.base_level];
|
||||
}
|
||||
if (mip.size == 0) {
|
||||
return;
|
||||
}
|
||||
const GuestRange viewed {range.address + slice_size * first + mip.offset,
|
||||
slice_size * (count - 1) + mip.size};
|
||||
const auto run = [&](uint32_t slice) { return viewed.address + slice_size * slice; };
|
||||
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||
uint32_t keys = 0;
|
||||
@@ -1082,7 +1099,11 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
}
|
||||
}
|
||||
// No selected key to clear, or none left since the last check.
|
||||
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||
bool checked = true;
|
||||
for (uint32_t slice = 0; slice < count && checked; slice++) {
|
||||
checked = m_buffer_cache.IsColorMetadataChecked(run(slice), mip.size, keys);
|
||||
}
|
||||
if (checked) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
@@ -1107,9 +1128,9 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
const auto [buffer, offset] =
|
||||
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
|
||||
const auto predicates = m_tiler.CheckColorClear(
|
||||
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
|
||||
if (expand) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
|
||||
{buffer->Handle(), offset, viewed.size}, slice_size, count, keys, expand);
|
||||
for (uint32_t slice = 0; slice < count && expand; slice++) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(run(slice), mip.size, keys);
|
||||
}
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||
@@ -1142,7 +1163,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||
}
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
const auto address = run(slice);
|
||||
uint8_t code = 0;
|
||||
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
|
||||
EXIT("TextureCache: failed to read color metadata backing\n");
|
||||
@@ -1151,7 +1172,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
if (!DecodeColorClear(desc, code, clear.color)) {
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> bytes(slice_size);
|
||||
std::vector<uint8_t> bytes(mip.size);
|
||||
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
|
||||
EXIT("TextureCache: failed to read color metadata slice\n");
|
||||
}
|
||||
@@ -1175,7 +1196,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
// to overlapping image data. Invalidate the buffer before updating its backing.
|
||||
if (desc.type != BindingType::VideoOut) {
|
||||
std::fill(bytes.begin(), bytes.end(), uint8_t {0xff});
|
||||
m_buffer_cache.InvalidateMemory(address, slice_size);
|
||||
m_buffer_cache.InvalidateMemory(address, bytes.size());
|
||||
LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,15 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 };
|
||||
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 };
|
||||
|
||||
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
|
||||
enum class VideoOutCompression : uint8_t {
|
||||
Uncompressed,
|
||||
Dcc256_256_0,
|
||||
Dcc256_64_64,
|
||||
Dcc128_128_128,
|
||||
Unsupported
|
||||
};
|
||||
|
||||
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask };
|
||||
|
||||
@@ -99,6 +105,8 @@ struct ImageInfo {
|
||||
switch (guest_format) {
|
||||
case Prospero::BufferFormat::k10_11_11Float:
|
||||
return ColorTransform::Reverse10_11_11;
|
||||
case Prospero::BufferFormat::k2_10_10_10UNorm:
|
||||
return ColorTransform::Rotate2_10_10_10;
|
||||
default: return ColorTransform::None;
|
||||
}
|
||||
}
|
||||
@@ -289,6 +297,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
|
||||
uint64_t dcc_clear_color) noexcept {
|
||||
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
|
||||
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
|
||||
constexpr uint32_t DCC_128_128_128 = 0x00100024u;
|
||||
if (!compressed) {
|
||||
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
|
||||
? VideoOutCompression::Uncompressed
|
||||
@@ -300,6 +309,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
|
||||
switch (dcc_control) {
|
||||
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
|
||||
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
|
||||
case DCC_128_128_128: return VideoOutCompression::Dcc128_128_128;
|
||||
default: return VideoOutCompression::Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,12 @@ struct HostFormatInfo {
|
||||
|
||||
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
|
||||
Prospero::ChannelOrder order) {
|
||||
if (guest_format == Prospero::BufferFormat::k10_11_11Float) {
|
||||
switch (guest_format) {
|
||||
case Prospero::BufferFormat::k10_11_11Float:
|
||||
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
|
||||
case Prospero::BufferFormat::k2_10_10_10UNorm:
|
||||
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr};
|
||||
default: break;
|
||||
}
|
||||
if (order == Prospero::ChannelOrder::kAlt) {
|
||||
switch (guest_format) {
|
||||
|
||||
@@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
|
||||
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));
|
||||
((transform == ColorTransform::Reverse10_11_11 ||
|
||||
transform == ColorTransform::Rotate2_10_10_10) && 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);
|
||||
};
|
||||
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
||||
uint32_t element_bytes = 0;
|
||||
switch (transform) {
|
||||
case ColorTransform::SwapBgra16: element_bytes = 8; break;
|
||||
case ColorTransform::Reverse10_11_11: element_bytes = 4; break;
|
||||
case ColorTransform::Reverse10_11_11:
|
||||
case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break;
|
||||
default: EXIT("invalid color transform\n");
|
||||
}
|
||||
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
|
||||
@@ -628,19 +630,20 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand) {
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
|
||||
uint32_t keys, bool expand) {
|
||||
struct Check {
|
||||
uint32_t metadata_base;
|
||||
uint32_t words;
|
||||
uint32_t stride;
|
||||
uint32_t keys;
|
||||
uint32_t expand;
|
||||
};
|
||||
static_assert(sizeof(Check) <= sizeof(Conversion));
|
||||
const uint32_t max_slices =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX);
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % 4 != 0 || stride % 4 != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX || stride * (slices - 1) >= metadata.size);
|
||||
if (m_color_clear == nullptr) {
|
||||
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||
m_clear_predicates = std::make_unique<Buffer>(
|
||||
@@ -671,8 +674,9 @@ vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
|
||||
expand};
|
||||
const Check push {source.base,
|
||||
static_cast<uint32_t>((metadata.size - stride * (slices - 1)) / 4),
|
||||
static_cast<uint32_t>(stride / 4), keys, expand};
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch(slices, 1, 1);
|
||||
|
||||
@@ -56,11 +56,11 @@ public:
|
||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
||||
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
|
||||
static constexpr uint32_t ColorClearSlots = 8;
|
||||
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
|
||||
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
|
||||
// `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand);
|
||||
// metadata spans `slices` equal runs `stride` bytes apart. Returns the buffer whose dword
|
||||
// slice * ColorClearSlots + (key >> 5) is set when every byte of that run equals a key whose
|
||||
// bit `keys` selects; a hit expands to 0xff when `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
|
||||
uint32_t keys, bool expand);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
|
||||
@@ -934,43 +934,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
(pipeline_bind_point == vk::PipelineBindPoint::eCompute &&
|
||||
shader_stage != vk::ShaderStageFlagBits::eCompute));
|
||||
}
|
||||
for (const auto* reader: prepared_bindings) {
|
||||
const auto& reads = reader->runtime->resources->specialization_reads;
|
||||
if (reads.empty()) continue;
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
if (writer->runtime->module->info.has_address_writes) {
|
||||
EXIT("scalar resource reads cannot be proven disjoint from shader address writes\n");
|
||||
}
|
||||
}
|
||||
for (const auto [address, size]: reads) {
|
||||
for (const auto id: m_bound_images) {
|
||||
const auto& image = m_context.GetTextureCache().m_slot_images[id];
|
||||
if (!image.binding.shader_write && !image.binding.is_target) continue;
|
||||
for (const auto written: {image.info.data, image.info.stencil,
|
||||
image.info.metadata.range}) {
|
||||
if (written.size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written.size)) {
|
||||
EXIT("scalar resource reads overlap an image or attachment write\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
const auto& program = writer->runtime->module->info;
|
||||
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];
|
||||
// ADD_TID and swizzle move fixed-offset writes past write_extent.
|
||||
const bool linear = (resource.packed_stride & ((1u << 14u) | (1u << 20u))) == 0u;
|
||||
const auto written_size =
|
||||
linear ? std::min<uint64_t>(written.size, resource.write_extent) : written.size;
|
||||
if (written_size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written_size)) {
|
||||
EXIT("scalar resource reads overlap a shader buffer write\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pipeline.uses_image_heap) {
|
||||
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
|
||||
}
|
||||
|
||||
@@ -689,6 +689,7 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
if (!tess_active && !mesh_active) {
|
||||
vertex_info[0].indirect_offsets = state.indirect_offsets;
|
||||
vertex_info[0].indirect_draw = state.indirect_draw;
|
||||
}
|
||||
if (object_primitive_id) {
|
||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||
|
||||
@@ -139,6 +139,7 @@ struct DrawRenderState {
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
// Input: the user-data slots a GPU-read indirect draw patches.
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
bool indirect_draw = false;
|
||||
|
||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||
|
||||
@@ -150,15 +150,21 @@ private:
|
||||
friend class CommandScheduler;
|
||||
};
|
||||
|
||||
namespace Pssr {
|
||||
struct Request;
|
||||
}
|
||||
|
||||
class RenderExecutor {
|
||||
public:
|
||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||
~RenderExecutor();
|
||||
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, bool async_compute);
|
||||
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||
uint32_t mode, bool async_compute);
|
||||
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
|
||||
|
||||
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
||||
void FindBuffers(std::span<PreparedBindings* const> stages);
|
||||
@@ -219,6 +225,9 @@ private:
|
||||
std::vector<ImageId> m_bound_images;
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
vk::DescriptorSetLayout m_pssr_descriptor_layout = nullptr;
|
||||
vk::PipelineLayout m_pssr_pipeline_layout = nullptr;
|
||||
vk::Pipeline m_pssr_pipeline = nullptr;
|
||||
|
||||
friend class CommandProcessor;
|
||||
friend struct RenderExecutorTestAccess;
|
||||
|
||||
@@ -52,7 +52,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
auto vertex_offset = static_cast<int32_t>(index_offset);
|
||||
uint32_t instance_offset = 0;
|
||||
if (!vs_input_info.fetch_embedded) {
|
||||
if (!vs_input_info.has_dynamic_vertex_fetch) {
|
||||
return {vertex_offset, instance_offset};
|
||||
}
|
||||
|
||||
@@ -1251,16 +1251,26 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||
// their own offsets.
|
||||
// and quad-list draws for counts, tessellation draws for the index/SGPR prolog. Indirect records
|
||||
// carry their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
int32_t first_vertex,
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if ((indirect.address != 0 || source == DrawOffsetSource::IndirectArgs) &&
|
||||
ucfg.GetIndexOffset() != 0) {
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [&] {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"GPU: indirect draw ignores GE_INDX_OFFSET=0x{:08x}; geometry may be incorrect.\n",
|
||||
ucfg.GetIndexOffset()));
|
||||
});
|
||||
}
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
if (!vs.indirect_draw ||
|
||||
vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||
vs.indirect_offsets != indirect.offsets) {
|
||||
return std::nullopt;
|
||||
@@ -1269,7 +1279,7 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
|
||||
}
|
||||
if (source == DrawOffsetSource::IndirectArgs) {
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
return vs.has_dynamic_vertex_fetch ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
}
|
||||
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||
@@ -1326,7 +1336,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||
.indirect_draw = args.indirect.address != 0};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1431,7 +1442,8 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||
.indirect_draw = args.indirect.address != 0};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "gpu_blit_shaders/gpu_pssr_linear_spv.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
struct PssrParameters {
|
||||
uint32_t input_size[2];
|
||||
uint32_t output_size[2];
|
||||
uint32_t output_channels[4];
|
||||
};
|
||||
|
||||
bool IsInputFormat(Prospero::BufferFormat format) {
|
||||
return format == Prospero::BufferFormat::k11_11_10Float ||
|
||||
format == Prospero::BufferFormat::k10_11_11Float ||
|
||||
format == Prospero::BufferFormat::k16_16_16_16Float;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderExecutor::~RenderExecutor() {
|
||||
const auto device = m_context.GetGraphics().device;
|
||||
device.destroyPipeline(m_pssr_pipeline, nullptr);
|
||||
device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr);
|
||||
device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr);
|
||||
}
|
||||
|
||||
void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
ShaderTextureResource input_descriptor {}, output_descriptor {};
|
||||
std::copy_n(request.input, 8, input_descriptor.fields);
|
||||
std::copy_n(request.output, 8, output_descriptor.fields);
|
||||
const auto is_2d = [](const ShaderTextureResource& descriptor) {
|
||||
return !descriptor.IsNull() &&
|
||||
(descriptor.Type() == Prospero::ImageType::kColor2D ||
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DArray) &&
|
||||
descriptor.TileMode() != Prospero::TileMode::kDepth;
|
||||
};
|
||||
EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) ||
|
||||
!IsInputFormat(input_descriptor.Format()) ||
|
||||
output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float ||
|
||||
output_descriptor.TileMode() != Prospero::TileMode::kStandard256B ||
|
||||
output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 ||
|
||||
output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 ||
|
||||
output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0);
|
||||
const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >>
|
||||
input_descriptor.BaseLevel());
|
||||
const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >>
|
||||
input_descriptor.BaseLevel());
|
||||
EXIT_IF(request.width > input_width || request.height > input_height);
|
||||
PssrParameters parameters {{request.width, request.height},
|
||||
{output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u},
|
||||
{4, 4, 4, 4}};
|
||||
for (uint32_t component = 0; component < 3; ++component) {
|
||||
for (uint32_t source = 0; source < 4; ++source) {
|
||||
if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) {
|
||||
parameters.output_channels[component] = source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto resolve = [&](const uint32_t* words, bool output) {
|
||||
ShaderRecompiler::IR::ImageResource resource {};
|
||||
resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage
|
||||
: ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
||||
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
resource.read = !output;
|
||||
resource.written = output;
|
||||
ShaderRecompiler::IR::DescriptorValue value {};
|
||||
std::copy_n(words, 8, value.dwords.begin());
|
||||
value.dword_count = 8;
|
||||
auto binding = ResolveTexture(resource, value);
|
||||
binding.desc.view_info.level_count = 1;
|
||||
binding.desc.view_info.min_lod = 0;
|
||||
return binding;
|
||||
};
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
auto input = resolve(request.input, false);
|
||||
BindImage(input.image_id, false);
|
||||
auto output = resolve(request.output, true);
|
||||
BindImage(output.image_id, true);
|
||||
EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() &&
|
||||
output.desc.info.data.address < input.desc.info.data.End());
|
||||
const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) {
|
||||
auto& previous = cache.GetImage(binding.image_id);
|
||||
if (!previous.registered || previous.binding.needs_rebind) {
|
||||
previous.binding = {};
|
||||
binding = resolve(words, storage);
|
||||
BindImage(binding.image_id, storage);
|
||||
}
|
||||
binding.image_view = cache.FindTexture(binding.image_id, binding.desc);
|
||||
};
|
||||
acquire(input, request.input, false);
|
||||
acquire(output, request.output, true);
|
||||
auto& source = cache.GetImage(input.image_id);
|
||||
auto& destination = cache.GetImage(output.image_id);
|
||||
const auto& source_view = input.desc.view_info;
|
||||
const auto& destination_view = output.desc.view_info;
|
||||
EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() ||
|
||||
source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
(input.image_id == output.image_id &&
|
||||
source_view.base_level == destination_view.base_level &&
|
||||
source_view.base_layer == destination_view.base_layer));
|
||||
|
||||
auto& graphics = m_context.GetGraphics();
|
||||
if (m_pssr_pipeline == nullptr) {
|
||||
const std::array bindings {
|
||||
vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1,
|
||||
vk::ShaderStageFlagBits::eCompute},
|
||||
vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1,
|
||||
vk::ShaderStageFlagBits::eCompute}};
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = bindings.size();
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(
|
||||
&descriptor_info, nullptr, &m_pssr_descriptor_layout),
|
||||
"create upscaling descriptor layout");
|
||||
const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(PssrParameters)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_pssr_descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
layout_info.pPushConstantRanges = &push;
|
||||
RequireVulkanSuccess(graphics.device.createPipelineLayout(
|
||||
&layout_info, nullptr, &m_pssr_pipeline_layout),
|
||||
"create upscaling pipeline layout");
|
||||
const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device);
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
create.stage.module = shader;
|
||||
create.stage.pName = "main";
|
||||
create.layout = m_pssr_pipeline_layout;
|
||||
const auto result = graphics.device.createComputePipelines(
|
||||
nullptr, 1, &create, nullptr, &m_pssr_pipeline);
|
||||
graphics.device.destroyShaderModule(shader, nullptr);
|
||||
RequireVulkanSuccess(result, "create upscaling pipeline");
|
||||
}
|
||||
buffer.EndRendering();
|
||||
const auto command = buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead,
|
||||
ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1},
|
||||
command);
|
||||
destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
|
||||
ImageSubresourceRange {destination_view.base_level, 1,
|
||||
destination_view.base_layer, 1}, command);
|
||||
const std::array images {
|
||||
vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral},
|
||||
vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t i = 0; i < writes.size(); ++i) {
|
||||
writes[i].dstBinding = i;
|
||||
writes[i].descriptorCount = 1;
|
||||
writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage
|
||||
: vk::DescriptorType::eStorageImage;
|
||||
writes[i].pImageInfo = &images[i];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout,
|
||||
0, writes.size(), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline);
|
||||
command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute,
|
||||
0, sizeof(parameters), ¶meters);
|
||||
command.dispatch((parameters.output_size[0] + 7) / 8,
|
||||
(parameters.output_size[1] + 7) / 8, 1);
|
||||
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,33 @@
|
||||
#version 450 core
|
||||
#extension GL_EXT_samplerless_texture_functions : require
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
layout(binding = 0) uniform texture2D source;
|
||||
layout(binding = 1) writeonly uniform image2D destination;
|
||||
layout(push_constant) uniform Parameters {
|
||||
uvec2 input_size;
|
||||
uvec2 output_size;
|
||||
uvec4 output_channels;
|
||||
} parameters;
|
||||
|
||||
vec4 fetch_color(ivec2 coordinate) {
|
||||
return texelFetch(source, clamp(coordinate, ivec2(0), ivec2(parameters.input_size) - 1), 0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec2 output_coordinate = gl_GlobalInvocationID.xy;
|
||||
if (any(greaterThanEqual(output_coordinate, parameters.output_size))) return;
|
||||
vec2 input_coordinate = (vec2(output_coordinate) + 0.5) *
|
||||
vec2(parameters.input_size) / vec2(parameters.output_size) - 0.5;
|
||||
ivec2 lower = ivec2(floor(input_coordinate));
|
||||
vec2 weight = fract(input_coordinate);
|
||||
vec4 color = mix(mix(fetch_color(lower), fetch_color(lower + ivec2(1, 0)), weight.x),
|
||||
mix(fetch_color(lower + ivec2(0, 1)), fetch_color(lower + ivec2(1, 1)), weight.x),
|
||||
weight.y);
|
||||
vec4 stored = vec4(0.0);
|
||||
for (uint channel = 0; channel < 3; ++channel) {
|
||||
uint selected = parameters.output_channels[channel];
|
||||
if (selected < 4) stored[channel] = color[selected];
|
||||
}
|
||||
imageStore(destination, ivec2(output_coordinate), stored);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly.
|
||||
// ColorTransform values match imageInfo.h. Preserve the component bit fields exactly.
|
||||
uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
|
||||
switch (transform) {
|
||||
case 1u:
|
||||
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3u:
|
||||
if (to_host) {
|
||||
value.x = (value.x >> 2u) | (value.x << 30u);
|
||||
} else {
|
||||
value.x = (value.x << 2u) | (value.x >> 30u);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[];
|
||||
layout(push_constant, std430) uniform Check {
|
||||
uint metadata_base;
|
||||
uint words;
|
||||
uint stride;
|
||||
uint keys;
|
||||
uint expand;
|
||||
} params;
|
||||
@@ -20,7 +21,7 @@ shared uint mismatch;
|
||||
|
||||
void main() {
|
||||
const uint slice = gl_WorkGroupID.x;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.stride;
|
||||
// Each invocation reads, and may rewrite, only its own words.
|
||||
if (gl_LocalInvocationIndex == 0u) {
|
||||
key = metadata.data[base] & 0xffu;
|
||||
|
||||
@@ -151,7 +151,8 @@ bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanIm
|
||||
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
|
||||
|
||||
VmaAllocationCreateInfo alloc_info {};
|
||||
alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
alloc_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
alloc_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
|
||||
vk::Image::CType native_image = VK_NULL_HANDLE;
|
||||
const auto result = static_cast<vk::Result>(
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,7 +240,6 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -251,7 +250,6 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -955,7 +953,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1035,7 +1033,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1207,24 +1205,17 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
@@ -1320,7 +1311,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1703,12 +1694,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -290,7 +290,8 @@ public:
|
||||
private:
|
||||
vk::SurfaceFormatKHR SelectFormat();
|
||||
void Destroy();
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||
const vk::Rect2D& rect);
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
@@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() {
|
||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
}
|
||||
|
||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) {
|
||||
static vk::Rect2D FitAspect(const vk::Extent3D& source, const vk::Extent2D& target) {
|
||||
const uint64_t src_w = std::max(source.width, 1u);
|
||||
const uint64_t src_h = std::max(source.height, 1u);
|
||||
uint32_t w = target.width;
|
||||
uint32_t h = target.height;
|
||||
if (src_w * h > src_h * w) {
|
||||
h = std::max<uint32_t>(1, static_cast<uint32_t>(src_h * w / src_w));
|
||||
} else {
|
||||
w = std::max<uint32_t>(1, static_cast<uint32_t>(src_w * h / src_h));
|
||||
}
|
||||
return {{static_cast<int32_t>((target.width - w) / 2),
|
||||
static_cast<int32_t>((target.height - h) / 2)},
|
||||
{w, h}};
|
||||
}
|
||||
|
||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||
const vk::Rect2D& rect) {
|
||||
auto device = m_window.graphic_ctx.device;
|
||||
if (m_overlay_pipeline == nullptr) {
|
||||
vk::SamplerCreateInfo sampler {};
|
||||
@@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.beginRendering(&rendering);
|
||||
const vk::Viewport viewport {
|
||||
0.0f, 0.0f, static_cast<float>(m_extent.width), static_cast<float>(m_extent.height),
|
||||
0.0f, 1.0f};
|
||||
const vk::Rect2D scissor {{0, 0}, m_extent};
|
||||
const vk::Viewport viewport {static_cast<float>(rect.offset.x),
|
||||
static_cast<float>(rect.offset.y),
|
||||
static_cast<float>(rect.extent.width),
|
||||
static_cast<float>(rect.extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
command.setViewport(0, 1, &viewport);
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.setScissor(0, 1, &rect);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
}
|
||||
@@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||
nullptr, 0, nullptr, 1, &to_transfer);
|
||||
|
||||
const auto* aspect_source = source != nullptr ? source : overlay.frame;
|
||||
const auto game_rect = !Config::StretchEnabled() && aspect_source != nullptr
|
||||
? FitAspect(aspect_source->image.extent, m_extent)
|
||||
: vk::Rect2D {{0, 0}, m_extent};
|
||||
if (source == nullptr || game_rect.extent != m_extent) {
|
||||
const vk::ClearColorValue black {};
|
||||
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
|
||||
&black, 1, &to_transfer.subresourceRange);
|
||||
if (source != nullptr) {
|
||||
vk::ImageMemoryBarrier after_clear = to_transfer;
|
||||
after_clear.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
after_clear.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||
&after_clear);
|
||||
}
|
||||
}
|
||||
if (source != nullptr) {
|
||||
vk::ImageBlit region {};
|
||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
@@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
region.dstSubresource.mipLevel = 0;
|
||||
region.dstSubresource.baseArrayLayer = 0;
|
||||
region.dstSubresource.layerCount = 1;
|
||||
region.dstOffsets[1].x = static_cast<int>(m_extent.width);
|
||||
region.dstOffsets[1].y = static_cast<int>(m_extent.height);
|
||||
region.dstOffsets[0].x = game_rect.offset.x;
|
||||
region.dstOffsets[0].y = game_rect.offset.y;
|
||||
region.dstOffsets[1].x = game_rect.offset.x + static_cast<int>(game_rect.extent.width);
|
||||
region.dstOffsets[1].y = game_rect.offset.y + static_cast<int>(game_rect.extent.height);
|
||||
region.dstOffsets[1].z = 1;
|
||||
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
||||
®ion, vk::Filter::eLinear);
|
||||
} else {
|
||||
const vk::ClearColorValue black {};
|
||||
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
|
||||
&black, 1, &to_transfer.subresourceRange);
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier to_present {};
|
||||
@@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
&to_present);
|
||||
if (draw_attachment) {
|
||||
if (draw_overlay) {
|
||||
DrawOverlay(vk_command, overlay);
|
||||
DrawOverlay(vk_command, overlay, game_rect);
|
||||
if (draw_system_overlay) {
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
|
||||
@@ -814,7 +814,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
const auto& cfg = *selected_cfg;
|
||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
||||
options.input_info.vertex->fetch_embedded) {
|
||||
options.input_info.vertex->has_dynamic_vertex_fetch) {
|
||||
embedded_fetch = DetectEmbeddedVertexFetch(
|
||||
decoded, *options.input_info.vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
||||
|
||||
@@ -156,11 +156,14 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
|
||||
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
|
||||
}
|
||||
break;
|
||||
case Opcode::V_AND_B32:
|
||||
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) ||
|
||||
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
|
||||
value = constant(0u);
|
||||
case Opcode::V_AND_B32: {
|
||||
const auto mask = lhs.kind == Kind::PackedControlPoint ? rhs
|
||||
: rhs.kind == Kind::PackedControlPoint ? lhs
|
||||
: Address {};
|
||||
if (mask == constant(0xffu)) value = constant(0u);
|
||||
if (mask == constant(0x1f00u)) value = {Kind::Affine, 256u, 0u};
|
||||
break;
|
||||
}
|
||||
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
|
||||
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break;
|
||||
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
|
||||
|
||||
@@ -30,16 +30,7 @@ uint32_t NodeWord(EmitterState& s, uint32_t block, uint32_t word) {
|
||||
uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
|
||||
Vec3 origin, Vec3 direction) {
|
||||
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
|
||||
const auto zero = ConstantF32Value(s, 0.0f);
|
||||
const auto op = [&](spv::Op code, uint32_t x, uint32_t y) {
|
||||
const auto value = Binary(s, code, f, x, y);
|
||||
s.builder.AddAnnotation(spv::OpDecorate, value, spv::DecorationNoContraction);
|
||||
return value;
|
||||
};
|
||||
const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); };
|
||||
const auto either = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); };
|
||||
const auto both = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); };
|
||||
const auto abs = [&](uint32_t x) { return EmitGlsl<GLSLstd450FAbs, IR::Type::F32>(s, x); };
|
||||
const auto is_kind = [&](uint32_t n) { return cmp(spv::OpIEqual, kind, ConstantU32(s, n)); };
|
||||
// The four triangles are (0,1,2), (1,3,2), (2,3,4), (2,4,0).
|
||||
const std::array vertices {
|
||||
@@ -56,6 +47,28 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
|
||||
NodeWord(s, block, Binary(s, spv::OpIAdd, u, first, ConstantU32(s, axis))));
|
||||
}
|
||||
const auto flag = NodeWord(s, block, ConstantU32(s, 15));
|
||||
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag,
|
||||
Binary(s, spv::OpIMul, u, kind, ConstantU32(s, 8)));
|
||||
return EmitBvhTriangle(s, positions, origin, direction, remap,
|
||||
Binary(s, spv::OpIAdd, u, flag, kind), bary);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitBvhTriangle(EmitterState& s, const std::array<Vec3, 3>& positions,
|
||||
Vec3 origin, Vec3 direction, uint32_t remap,
|
||||
uint32_t triangle_id, uint32_t bary) {
|
||||
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
|
||||
const auto zero = ConstantF32Value(s, 0.0f);
|
||||
const auto op = [&](spv::Op code, uint32_t x, uint32_t y) {
|
||||
const auto value = Binary(s, code, f, x, y);
|
||||
s.builder.AddAnnotation(spv::OpDecorate, value, spv::DecorationNoContraction);
|
||||
return value;
|
||||
};
|
||||
const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); };
|
||||
const auto either = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); };
|
||||
const auto both = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); };
|
||||
const auto abs = [&](uint32_t x) { return EmitGlsl<GLSLstd450FAbs, IR::Type::F32>(s, x); };
|
||||
const auto y_largest = cmp(spv::OpFOrdLessThan, abs(direction[0]), abs(direction[1]));
|
||||
const auto z_largest = cmp(spv::OpFOrdLessThan,
|
||||
Select(s, f, y_largest, abs(direction[1]), abs(direction[0])), abs(direction[2]));
|
||||
@@ -112,8 +125,6 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
|
||||
cmp(spv::OpFOrdGreaterThan, projected[i][1], zero), right);
|
||||
missed = either(missed, both(cmp(spv::OpFOrdEqual, edge[i], zero), excluded));
|
||||
}
|
||||
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag,
|
||||
Binary(s, spv::OpIMul, u, kind, ConstantU32(s, 8)));
|
||||
const auto coordinate = [&](uint32_t shift) {
|
||||
const auto index = Binary(s, spv::OpBitwiseAnd, u,
|
||||
Binary(s, spv::OpShiftRightLogical, u, remap, ConstantU32(s, shift)), ConstantU32(s, 3));
|
||||
@@ -124,11 +135,13 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
|
||||
const std::array result {
|
||||
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32(s, 0x7f800000), numerator)),
|
||||
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32Value(s, 1.0f), denominator)),
|
||||
Select(s, u, bary, coordinate(0), Binary(s, spv::OpIAdd, u, flag, kind)),
|
||||
Select(s, u, bary, coordinate(0), triangle_id),
|
||||
Select(s, u, bary, coordinate(2), Select(s, u, missed, ConstantU32(s, 0), ConstantU32(s, 1))) };
|
||||
return Vector(s, TypeU32Vector(s, 4), result);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
|
||||
uint32_t half, uint32_t child) {
|
||||
const auto f = TypeF32(s);
|
||||
|
||||
@@ -859,6 +859,10 @@ uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMax, EmitUMax32>(ctx, inst);
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupOrU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitSubgroupReduceU32<spv::OpGroupNonUniformBitwiseOr, EmitOrU32>(ctx, inst);
|
||||
}
|
||||
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto hit = state.builder.AllocateId();
|
||||
|
||||
@@ -173,7 +173,7 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
|
||||
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, pointer);
|
||||
}
|
||||
|
||||
if (kind == VertexInputScalarKind::Uint) {
|
||||
if (kind == Format::ComponentType::Uint) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& i
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupOrU32(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);
|
||||
@@ -338,6 +339,9 @@ inline constexpr auto EmitDataConsume = EmitAppendConsume;
|
||||
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBvh8Intersect(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBvh8Extract(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBvhStack(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitImageQueryDimensions = EmitImage;
|
||||
inline constexpr auto EmitImageQueryLod = EmitImage;
|
||||
inline constexpr auto EmitImageRead = EmitImage;
|
||||
|
||||
@@ -118,8 +118,11 @@ struct EmitterState {
|
||||
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;
|
||||
std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
|
||||
uint32_t bvh_intersect_function = 0;
|
||||
uint32_t bvh8_result_type = 0;
|
||||
uint32_t bvh8_intersect_function = 0;
|
||||
uint32_t bvh_stack_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
@@ -169,6 +172,7 @@ uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeBvh8Result(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
uint32_t TypeF32(EmitterState& state);
|
||||
@@ -269,8 +273,6 @@ struct ValueEmitContext {
|
||||
uint32_t half = 0;
|
||||
};
|
||||
|
||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||
|
||||
constexpr uint32_t NoImageComponent = 0xffffffffu;
|
||||
|
||||
struct DppTargetLane {
|
||||
@@ -300,12 +302,11 @@ bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
|
||||
|
||||
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr);
|
||||
|
||||
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location);
|
||||
Format::ComponentType VertexParameterScalarKind(const EmitterState& state, uint32_t location);
|
||||
|
||||
uint32_t VertexParameterComponentCount(const InputBinding& input);
|
||||
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind);
|
||||
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, Format::ComponentType kind);
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
|
||||
@@ -540,9 +541,13 @@ uint32_t EmitF32BitsToF16RtzBits(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);
|
||||
void DefineTrinityBvhIntersect(EmitterState& state);
|
||||
uint32_t EmitBvhTriangle(EmitterState& state, const std::array<std::array<uint32_t, 3>, 3>& positions,
|
||||
std::array<uint32_t, 3> origin, std::array<uint32_t, 3> direction,
|
||||
uint32_t remap, uint32_t triangle_id, uint32_t bary);
|
||||
void DefineTrinityFunctions(EmitterState& state);
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||
|
||||
// These templates accept local lambdas from several emitter translation units.
|
||||
|
||||
@@ -1026,10 +1026,8 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
|
||||
return ConstructU32Composite(state, components, values);
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
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);
|
||||
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
state.indirect_format_function, buffer.format, buffer.selector,
|
||||
component, raw);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
@@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
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), [&]() {
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
const auto function = state.indirect_buffer_load_functions.at(
|
||||
{mem.data_bits, components, uint32_t(mem.coherent)});
|
||||
{mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)});
|
||||
const auto args = IndirectBufferArgs(ctx, inst);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
|
||||
@@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
|
||||
state.bda_pointer_function, 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;
|
||||
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
|
||||
state.bda_pointer_function, 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.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
|
||||
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
|
||||
std::array {memory.data_bits, components, uint32_t(memory.coherent),
|
||||
uint32_t(memory.formatted)}, 0u);
|
||||
if (!inserted) continue;
|
||||
if (memory.formatted && state.indirect_format_function == 0u) {
|
||||
DefineIndirectBufferFormats(ctx);
|
||||
}
|
||||
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" : "") +
|
||||
(memory.formatted ? "_formatted" : "");
|
||||
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 buffer = PrepareIndirectBuffer(state, args, memory.formatted);
|
||||
const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer)
|
||||
: LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
|
||||
@@ -402,47 +402,23 @@ uint32_t GlslStd450(EmitterState& state) {
|
||||
return state.builder.Import("GLSL.std.450");
|
||||
}
|
||||
|
||||
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
|
||||
Format::ComponentType VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
|
||||
if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) ||
|
||||
location >= ShaderVertexInputInfo::RES_MAX ||
|
||||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
|
||||
return VertexInputScalarKind::Float;
|
||||
}
|
||||
|
||||
switch (state.input_info.vertex->resources[location].Format()) {
|
||||
case Prospero::BufferFormat::k8UInt:
|
||||
case Prospero::BufferFormat::k16UInt:
|
||||
case Prospero::BufferFormat::k8_8UInt:
|
||||
case Prospero::BufferFormat::k32UInt:
|
||||
case Prospero::BufferFormat::k16_16UInt:
|
||||
case Prospero::BufferFormat::k8_8_8_8UInt:
|
||||
case Prospero::BufferFormat::k32_32UInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16UInt:
|
||||
case Prospero::BufferFormat::k32_32_32UInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32UInt: return VertexInputScalarKind::Uint;
|
||||
case Prospero::BufferFormat::k8SInt:
|
||||
case Prospero::BufferFormat::k16SInt:
|
||||
case Prospero::BufferFormat::k8_8SInt:
|
||||
case Prospero::BufferFormat::k32SInt:
|
||||
case Prospero::BufferFormat::k16_16SInt:
|
||||
case Prospero::BufferFormat::k8_8_8_8SInt:
|
||||
case Prospero::BufferFormat::k32_32SInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16SInt:
|
||||
case Prospero::BufferFormat::k32_32_32SInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32SInt: return VertexInputScalarKind::Sint;
|
||||
default: return VertexInputScalarKind::Float;
|
||||
return Format::ComponentType::Float;
|
||||
}
|
||||
return Format::GetFormatComponentType(state.input_info.vertex->resources[location].Format());
|
||||
}
|
||||
|
||||
uint32_t VertexParameterComponentCount(const InputBinding& input) {
|
||||
return std::clamp(input.component_count, 1u, 4u);
|
||||
}
|
||||
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind) {
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, Format::ComponentType kind) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint: return TypeI32(state);
|
||||
case VertexInputScalarKind::Uint: return TypeU32(state);
|
||||
case VertexInputScalarKind::Float:
|
||||
case Format::ComponentType::Sint: return TypeI32(state);
|
||||
case Format::ComponentType::Uint: return TypeU32(state);
|
||||
default: return TypeF32(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,8 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (ctx.half != 0 &&
|
||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
||||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||
op == Op::SubgroupUMaxU32 || op == Op::SubgroupOrU32 || op == Op::ReadConstBuffer ||
|
||||
op == Op::ReadRealtimeClock ||
|
||||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
||||
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
||||
@@ -414,6 +415,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
|
||||
case IR::Type::U32:
|
||||
case IR::Type::F16: return TypeU32(state);
|
||||
case IR::Type::U64: return TypeU64(state);
|
||||
case IR::Type::Bvh8Result: return TypeBvh8Result(state);
|
||||
case IR::Type::U32x2: return TypeU32Pair(state);
|
||||
case IR::Type::F32: return TypeF32(state);
|
||||
case IR::Type::F64: return TypeF64(state);
|
||||
@@ -675,8 +677,8 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
DefineIndirectBufferLoads(ctx);
|
||||
DefineIndirectBufferFormats(ctx);
|
||||
DefineBvhIntersect(state);
|
||||
DefineTrinityFunctions(state);
|
||||
if (program.info.uses_swizzle) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
if (state.lane_count == 2) {
|
||||
|
||||
@@ -30,11 +30,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (kind == Attribute::Factor) {
|
||||
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
||||
const auto index = inst.Arg(1).U32() / 4u;
|
||||
const auto outer = index < 3u;
|
||||
EXIT_NOT_IMPLEMENTED(index >= 4u);
|
||||
const bool quad = tess.domain == 2u;
|
||||
const auto outer_count = quad ? 4u : 3u;
|
||||
const bool outer = index < outer_count;
|
||||
EXIT_NOT_IMPLEMENTED(index >= (quad ? 6u : 4u));
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
|
||||
pointer, outer ? variable : state.tess_inner_variable,
|
||||
ConstantU32(state, outer ? index : index - 3u));
|
||||
ConstantU32(state, outer ? index : index - outer_count));
|
||||
return pointer;
|
||||
}
|
||||
auto address = ctx.Arg(inst, 1);
|
||||
@@ -128,20 +130,25 @@ void DefineTessellationInterfaces(EmitterState& state) {
|
||||
|
||||
void DefineTessellationExecutionModes(EmitterState& state) {
|
||||
const auto& tess = state.input_info.vertex->tess;
|
||||
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output.
|
||||
EXIT_NOT_IMPLEMENTED(tess.domain != 1u ||
|
||||
(tess.partitioning != 0u && tess.partitioning != 2u) ||
|
||||
tess.output_topology != 2u);
|
||||
// Triangle or quad domain, integer or fractional partitioning, triangle output.
|
||||
EXIT_NOT_IMPLEMENTED((tess.domain != 1u && tess.domain != 2u) ||
|
||||
(tess.partitioning != 0u && tess.partitioning != 2u && tess.partitioning != 3u) ||
|
||||
(tess.output_topology != 2u && tess.output_topology != 3u));
|
||||
state.builder.RequireCapability(spv::CapabilityTessellation);
|
||||
if (state.program.stage == ShaderType::TessellationControl) {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
|
||||
tess.output_control_points);
|
||||
} else {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
|
||||
state.builder.AddExecutionMode(state.main_func, tess.domain == 1u
|
||||
? spv::ExecutionModeTriangles
|
||||
: spv::ExecutionModeQuads);
|
||||
state.builder.AddExecutionMode(
|
||||
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
|
||||
: spv::ExecutionModeSpacingFractionalOdd);
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
|
||||
: tess.partitioning == 2u ? spv::ExecutionModeSpacingFractionalOdd
|
||||
: spv::ExecutionModeSpacingFractionalEven);
|
||||
state.builder.AddExecutionMode(state.main_func, tess.output_topology == 2u
|
||||
? spv::ExecutionModeVertexOrderCw
|
||||
: spv::ExecutionModeVertexOrderCcw);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
uint32_t Bits(EmitterState& s, uint32_t value, uint32_t offset, uint32_t count) {
|
||||
const auto result = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(s), result, value,
|
||||
ConstantU32(s, offset), ConstantU32(s, count));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t SetBits(EmitterState& s, uint32_t value, uint32_t field, uint32_t offset, uint32_t count) {
|
||||
const auto result = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpBitFieldInsert, TypeU32(s), result, value, field,
|
||||
ConstantU32(s, offset), ConstantU32(s, count));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Pair(EmitterState& s, uint32_t first, uint32_t second) {
|
||||
const auto result = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Pair(s), result, first, second);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t StackIndex(EmitterState& s, uint32_t control, uint32_t ring) {
|
||||
return EmitAddU32(s, Bits(s, control, 15, 14),
|
||||
Binary(s, spv::OpShiftLeftLogical, TypeU32(s), ring, ConstantU32(s, 5)));
|
||||
}
|
||||
|
||||
uint32_t StackPush(EmitterState& s, const MemoryResourceAccess& memory,
|
||||
uint32_t control, uint32_t node, uint32_t capacity) {
|
||||
const auto u = TypeU32(s), b = TypeBool(s), zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
|
||||
const auto count = Bits(s, control, 0, 5), parent = Bits(s, control, 5, 5), ring = Bits(s, control, 10, 5);
|
||||
const auto index = StackIndex(s, control, ring);
|
||||
EmitIfCondition(s, EmitMemoryElementInBounds(s, memory, index), [&] {
|
||||
s.builder.AddFunction(spv::OpStore, EmitMemoryElementPointer(s, memory, index), node);
|
||||
});
|
||||
control = Binary(s, spv::OpBitwiseOr, u, control,
|
||||
Select(s, u, Binary(s, spv::OpIEqual, b, count, capacity), ConstantU32(s, 1u << 30), zero));
|
||||
control = Select(s, u, Binary(s, spv::OpIEqual, b, parent, capacity),
|
||||
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, ~(1u << 29))), control);
|
||||
control = SetBits(s, control, EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
|
||||
EmitAddU32(s, count, one), capacity), 0, 5);
|
||||
control = SetBits(s, control, EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
|
||||
EmitAddU32(s, parent, one), capacity), 5, 5);
|
||||
const auto next = EmitAddU32(s, ring, one);
|
||||
return SetBits(s, control, Select(s, u, Binary(s, spv::OpIEqual, b, next, capacity), zero, next), 10, 5);
|
||||
}
|
||||
|
||||
uint32_t StackPop(EmitterState& s, const MemoryResourceAccess& memory,
|
||||
uint32_t control, uint32_t capacity) {
|
||||
const auto u = TypeU32(s), b = TypeBool(s), zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
|
||||
const auto count = Bits(s, control, 0, 5), parent = Bits(s, control, 5, 5), ring = Bits(s, control, 10, 5);
|
||||
const auto has_parent = Binary(s, spv::OpINotEqual, b, Bits(s, control, 29, 1), zero);
|
||||
const auto returning = Binary(s, spv::OpLogicalAnd, b, has_parent,
|
||||
Binary(s, spv::OpIEqual, b, parent, zero));
|
||||
control = SetBits(s, control, Select(s, u, returning, one, zero), 31, 1);
|
||||
control = Select(s, u, returning,
|
||||
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, ~(1u << 29))), control);
|
||||
const auto overflow = Binary(s, spv::OpINotEqual, b, Bits(s, control, 30, 1), zero);
|
||||
const auto empty = Pair(s,
|
||||
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, 0x5fffffff)),
|
||||
Select(s, u, overflow, ConstantU32(s, 0xffffffff), ConstantU32(s, 0xfffffffe)));
|
||||
return EmitValueOrDefaultIfCondition(s, Binary(s, spv::OpINotEqual, b, count, zero),
|
||||
TypeU32Pair(s), empty, [&] {
|
||||
control = SetBits(s, control, Binary(s, spv::OpISub, u, count, one), 0, 5);
|
||||
control = SetBits(s, control, Binary(s, spv::OpISub, u, parent, one), 5, 5);
|
||||
const auto previous = Select(s, u, Binary(s, spv::OpIEqual, b, ring, zero),
|
||||
Binary(s, spv::OpISub, u, capacity, one), Binary(s, spv::OpISub, u, ring, one));
|
||||
control = SetBits(s, control, previous, 10, 5);
|
||||
const auto index = StackIndex(s, control, previous);
|
||||
const auto node = EmitValueOrDefaultIfCondition(s,
|
||||
EmitMemoryElementInBounds(s, memory, index), u, zero, [&] {
|
||||
const auto value = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpLoad, u, value, EmitMemoryElementPointer(s, memory, index));
|
||||
return value;
|
||||
});
|
||||
const auto instance = Binary(s, spv::OpIEqual, b, Bits(s, node, 0, 3), ConstantU32(s, 6));
|
||||
control = EmitValueOrDefaultIfCondition(s, instance, u, control, [&] {
|
||||
const auto nested = Binary(s, spv::OpINotEqual, b, Bits(s, control, 29, 1), zero);
|
||||
const auto discarded = Binary(s, spv::OpBitwiseOr, u,
|
||||
SetBits(s, control, Bits(s, control, 5, 5), 0, 5), ConstantU32(s, 1u << 30));
|
||||
return Binary(s, spv::OpBitwiseOr, u,
|
||||
SetBits(s, Select(s, u, nested, discarded, control), zero, 5, 5), ConstantU32(s, 1u << 29));
|
||||
});
|
||||
return Pair(s, control, node);
|
||||
});
|
||||
}
|
||||
|
||||
void DefineBvhStack(EmitterState& s) {
|
||||
if (!s.program.info.bvh_stack) return;
|
||||
const auto u = TypeU32(s), b = TypeBool(s), vec4 = TypeU32Vector(s, 4), pair = TypeU32Pair(s);
|
||||
const auto zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
|
||||
const bool local = s.lds_storage_class == spv::StorageClassFunction;
|
||||
const bool buffer = s.lds_storage_class == spv::StorageClassStorageBuffer;
|
||||
std::vector<uint32_t> types {u, u, vec4, vec4, u};
|
||||
if (local) types.push_back(TypeU32ArrayPointer(s, spv::StorageClassFunction, LdsDwordCount(s)));
|
||||
if (buffer) types.push_back(u);
|
||||
const auto signature = s.builder.Type(spv::OpTypeFunction, pair, types);
|
||||
s.bvh_stack_function = s.builder.AllocateId();
|
||||
s.builder.AddName(s.bvh_stack_function, "trinity_bvh_stack");
|
||||
s.builder.AddFunction(spv::OpFunction, pair, s.bvh_stack_function, spv::FunctionControlMaskNone, signature);
|
||||
std::vector<uint32_t> args;
|
||||
for (const auto type: types) {
|
||||
args.push_back(s.builder.AllocateId());
|
||||
s.builder.AddFunction(spv::OpFunctionParameter, type, args.back());
|
||||
}
|
||||
const auto old_lds = s.lds_variable, old_base = s.lds_base_dwords;
|
||||
if (local) s.lds_variable = args.back();
|
||||
if (buffer) s.lds_base_dwords = args.back();
|
||||
const auto memory = PrepareMemoryResourceAccess(s, {.kind = IR::ResourceKind::Lds});
|
||||
const auto entry = s.builder.AllocateId();
|
||||
EmitLabel(s, entry);
|
||||
const auto last = args[1], capacity = args[4];
|
||||
const auto walkback = Binary(s, spv::OpINotEqual, b, Bits(s, last, 3, 29), ConstantU32(s, 0x1fffffff));
|
||||
const auto last4 = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpCompositeConstruct, vec4, last4, last, last, last, last);
|
||||
const auto found = [&](uint32_t values) {
|
||||
return Unary(s, spv::OpAny, b, Binary(s, spv::OpIEqual, TypeBoolVector(s, 4), values, last4));
|
||||
};
|
||||
const auto initial_valid = Binary(s, spv::OpLogicalOr, b, Unary(s, spv::OpLogicalNot, b, walkback),
|
||||
Binary(s, spv::OpLogicalOr, b, found(args[2]), found(args[3])));
|
||||
const auto header = s.builder.AllocateId(), body = s.builder.AllocateId();
|
||||
const auto cont = s.builder.AllocateId(), merge = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpBranch, header);
|
||||
EmitLabel(s, header);
|
||||
const auto index = s.builder.AllocateId(), control = s.builder.AllocateId();
|
||||
const auto valid = s.builder.AllocateId(), skip_low = s.builder.AllocateId(), skip_high = s.builder.AllocateId();
|
||||
const auto index_phi = s.builder.AddDeferredPhi(u, index, 2);
|
||||
const auto control_phi = s.builder.AddDeferredPhi(u, control, 2);
|
||||
const auto valid_phi = s.builder.AddDeferredPhi(b, valid, 2);
|
||||
const auto low_phi = s.builder.AddDeferredPhi(b, skip_low, 2);
|
||||
const auto high_phi = s.builder.AddDeferredPhi(b, skip_high, 2);
|
||||
const auto more = Binary(s, spv::OpLogicalAnd, b, valid,
|
||||
Binary(s, spv::OpULessThan, b, index, ConstantU32(s, 8)));
|
||||
s.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlDontUnrollMask);
|
||||
s.builder.AddFunction(spv::OpBranchConditional, more, body, merge);
|
||||
EmitLabel(s, body);
|
||||
const auto high = Binary(s, spv::OpUGreaterThanEqual, b, index, ConstantU32(s, 4));
|
||||
const auto high4 = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpCompositeConstruct, TypeBoolVector(s, 4), high4, high, high, high, high);
|
||||
const auto values = Select(s, vec4, high4, args[3], args[2]);
|
||||
const auto node = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpVectorExtractDynamic, u, node, values, Bits(s, index, 0, 2));
|
||||
const auto sentinel = Binary(s, spv::OpIEqual, b, Bits(s, node, 3, 29), ConstantU32(s, 0x1fffffff));
|
||||
const auto enabled = Unary(s, spv::OpLogicalNot, b, Select(s, b, high, skip_high, skip_low));
|
||||
const auto marker = Binary(s, spv::OpLogicalAnd, b, sentinel, enabled);
|
||||
const auto next_low = Binary(s, spv::OpLogicalOr, b, skip_low,
|
||||
Binary(s, spv::OpLogicalAnd, b, marker, Binary(s, spv::OpIEqual, b, Bits(s, node, 1, 1), zero)));
|
||||
const auto next_high = Binary(s, spv::OpLogicalOr, b, skip_high,
|
||||
Binary(s, spv::OpLogicalAnd, b, high,
|
||||
Binary(s, spv::OpLogicalAnd, b, marker, Binary(s, spv::OpIEqual, b, Bits(s, node, 2, 1), zero))));
|
||||
const auto next_valid = Unary(s, spv::OpLogicalNot, b,
|
||||
Binary(s, spv::OpLogicalAnd, b, walkback, Binary(s, spv::OpIEqual, b, node, last)));
|
||||
const auto push = Binary(s, spv::OpLogicalAnd, b, next_valid,
|
||||
Unary(s, spv::OpLogicalNot, b,
|
||||
Binary(s, spv::OpLogicalOr, b, sentinel, Select(s, b, high, next_high, next_low))));
|
||||
const auto next_control = EmitValueOrDefaultIfCondition(s, push, u, control, [&] {
|
||||
return StackPush(s, memory, control, node, capacity);
|
||||
});
|
||||
s.builder.AddFunction(spv::OpBranch, cont);
|
||||
EmitLabel(s, cont);
|
||||
const auto next_index = Binary(s, spv::OpISub, u, index, one);
|
||||
s.builder.AddFunction(spv::OpBranch, header);
|
||||
s.builder.PatchDeferredPhi(index_phi, 0, ConstantU32(s, 7), entry);
|
||||
s.builder.PatchDeferredPhi(index_phi, 1, next_index, cont);
|
||||
s.builder.PatchDeferredPhi(control_phi, 0, args[0], entry);
|
||||
s.builder.PatchDeferredPhi(control_phi, 1, next_control, cont);
|
||||
s.builder.PatchDeferredPhi(valid_phi, 0, initial_valid, entry);
|
||||
s.builder.PatchDeferredPhi(valid_phi, 1, next_valid, cont);
|
||||
s.builder.PatchDeferredPhi(low_phi, 0, ConstantBool(s, false), entry);
|
||||
s.builder.PatchDeferredPhi(low_phi, 1, next_low, cont);
|
||||
s.builder.PatchDeferredPhi(high_phi, 0, ConstantBool(s, false), entry);
|
||||
s.builder.PatchDeferredPhi(high_phi, 1, next_high, cont);
|
||||
EmitLabel(s, merge);
|
||||
s.builder.AddFunction(spv::OpReturnValue, StackPop(s, memory, control, capacity));
|
||||
s.builder.AddFunction(spv::OpFunctionEnd);
|
||||
if (local) s.lds_variable = old_lds;
|
||||
if (buffer) s.lds_base_dwords = old_base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DefineTrinityFunctions(EmitterState& s) {
|
||||
DefineBvhStack(s);
|
||||
DefineTrinityBvhIntersect(s);
|
||||
}
|
||||
|
||||
uint32_t EmitBvhStack(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& s = ctx.state;
|
||||
const auto capacity = ctx.Memory(inst).offset;
|
||||
if (s.bvh_stack_function == 0 || capacity < 8 || capacity > 31)
|
||||
ctx.Fail(inst, "invalid BVH stack function or capacity");
|
||||
const std::array values {ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)};
|
||||
return EmitValueOrDefaultIfCondition(s, ctx.Arg(inst, 4), TypeU32Pair(s),
|
||||
ConstantU32CompositeZero(s, 2), [&] {
|
||||
const auto result = s.builder.AllocateId();
|
||||
std::vector<uint32_t> call {spv::OpFunctionCall, TypeU32Pair(s), result,
|
||||
s.bvh_stack_function, values[0], values[1], values[2], values[3], ConstantU32(s, capacity)};
|
||||
if (s.lds_storage_class == spv::StorageClassFunction) call.push_back(s.lds_variable);
|
||||
if (s.lds_storage_class == spv::StorageClassStorageBuffer) call.push_back(s.lds_base_dwords);
|
||||
s.builder.AddFunction(call);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -0,0 +1,511 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t TypeBvh8Result(EmitterState& s) {
|
||||
if (s.bvh8_result_type == 0) {
|
||||
const auto v = TypeU32Vector(s, 4);
|
||||
s.bvh8_result_type = s.builder.Type(spv::OpTypeStruct, v, v, v, v, TypeU32(s));
|
||||
}
|
||||
return s.bvh8_result_type;
|
||||
}
|
||||
|
||||
namespace {
|
||||
using Vec3 = std::array<uint32_t, 3>;
|
||||
|
||||
struct Bvh8Emitter {
|
||||
EmitterState& s;
|
||||
const uint32_t u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), w = TypeU64(s);
|
||||
const uint32_t v4 = TypeU32Vector(s, 4), v3 = TypeF32Vector(s, 3);
|
||||
uint32_t triangle_function = 0, boxes_function = 0, decompress_function = 0;
|
||||
|
||||
uint32_t C(uint32_t x) { return ConstantU32(s, x); }
|
||||
uint32_t And(uint32_t x, uint32_t y) { return Binary(s, spv::OpBitwiseAnd, u, x, y); }
|
||||
uint32_t Or(uint32_t x, uint32_t y) { return Binary(s, spv::OpBitwiseOr, u, x, y); }
|
||||
uint32_t Add(uint32_t x, uint32_t y) { return EmitAddU32(s, x, y); }
|
||||
uint32_t Sub(uint32_t x, uint32_t y) { return Binary(s, spv::OpISub, u, x, y); }
|
||||
uint32_t Shl(uint32_t x, uint32_t n) { return Binary(s, spv::OpShiftLeftLogical, u, x, C(n)); }
|
||||
uint32_t Shr(uint32_t x, uint32_t n) { return Binary(s, spv::OpShiftRightLogical, u, x, C(n)); }
|
||||
uint32_t Eq(uint32_t x, uint32_t y) { return Binary(s, spv::OpIEqual, b, x, y); }
|
||||
uint32_t Both(uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); }
|
||||
uint32_t Either(uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); }
|
||||
uint32_t Not(uint32_t x) { return Unary(s, spv::OpLogicalNot, b, x); }
|
||||
uint32_t Bit(uint32_t x, uint32_t n) { return Not(Eq(And(x, C(1u << n)), C(0))); }
|
||||
uint32_t Choose(uint32_t condition, uint32_t x, uint32_t y) { return Select(s, u, condition, x, y); }
|
||||
uint32_t Float(uint32_t x) { return Unary(s, spv::OpBitcast, f, x); }
|
||||
uint32_t Raw(uint32_t x) { return Unary(s, spv::OpBitcast, u, x); }
|
||||
uint32_t Fop(spv::Op op, uint32_t x, uint32_t y) { return Binary(s, op, f, x, y); }
|
||||
uint32_t Fcmp(spv::Op op, uint32_t x, uint32_t y) { return Binary(s, op, b, x, y); }
|
||||
uint32_t Min(uint32_t x, uint32_t y) { return EmitGlsl<GLSLstd450FMin, IR::Type::F32>(s, x, y); }
|
||||
uint32_t Max(uint32_t x, uint32_t y) { return EmitGlsl<GLSLstd450FMax, IR::Type::F32>(s, x, y); }
|
||||
uint32_t Extract(uint32_t type, uint32_t value, uint32_t index) {
|
||||
const auto id = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpCompositeExtract, type, id, value, index);
|
||||
return id;
|
||||
}
|
||||
uint32_t Make(uint32_t type, std::span<const uint32_t> values) {
|
||||
const auto id = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpCompositeConstruct, type, id, values);
|
||||
return id;
|
||||
}
|
||||
uint32_t Make4(uint32_t x, uint32_t y, uint32_t z, uint32_t t) {
|
||||
return Make(v4, std::array{x, y, z, t});
|
||||
}
|
||||
Vec3 Unpack3(uint32_t value) {
|
||||
return {Extract(f, value, 0), Extract(f, value, 1), Extract(f, value, 2)};
|
||||
}
|
||||
uint32_t Word(uint32_t block, uint32_t index) {
|
||||
const auto offset = Unary(s, spv::OpUConvert, w, Shl(index, 2));
|
||||
const auto address = Binary(s, spv::OpIAdd, w, block, offset);
|
||||
const auto pointer = Unary(s, spv::OpConvertUToPtr, TypePhysicalU32Pointer(s), address);
|
||||
const auto id = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpLoad, u, id, pointer, spv::MemoryAccessAlignedMask, 4u);
|
||||
return id;
|
||||
}
|
||||
uint32_t Call(uint32_t type, uint32_t function, std::span<const uint32_t> args) {
|
||||
const auto id = s.builder.AllocateId();
|
||||
std::vector<uint32_t> call {spv::OpFunctionCall, type, id, function};
|
||||
call.insert(call.end(), args.begin(), args.end());
|
||||
s.builder.AddFunction(call);
|
||||
return id;
|
||||
}
|
||||
std::vector<uint32_t> Begin(uint32_t& function, const char* name, uint32_t result,
|
||||
std::span<const uint32_t> types) {
|
||||
function = s.builder.AllocateId();
|
||||
s.builder.AddName(function, name);
|
||||
s.builder.AddFunction(spv::OpFunction, result, function, spv::FunctionControlMaskNone,
|
||||
s.builder.Type(spv::OpTypeFunction, result, types));
|
||||
std::vector<uint32_t> args;
|
||||
for (auto type: types) {
|
||||
args.push_back(s.builder.AllocateId());
|
||||
s.builder.AddFunction(spv::OpFunctionParameter, type, args.back());
|
||||
}
|
||||
EmitLabel(s, s.builder.AllocateId());
|
||||
return args;
|
||||
}
|
||||
void End(uint32_t result) {
|
||||
s.builder.AddFunction(spv::OpReturnValue, result);
|
||||
s.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
uint32_t Result(const std::array<uint32_t, 10>& dst, const Vec3& org, const Vec3& dir,
|
||||
uint32_t instance) {
|
||||
const std::array groups {Make4(dst[0], dst[1], dst[2], dst[3]),
|
||||
Make4(dst[4], dst[5], dst[6], dst[7]), Make4(dst[8], dst[9], Raw(org[0]), Raw(org[1])),
|
||||
Make4(Raw(org[2]), Raw(dir[0]), Raw(dir[1]), Raw(dir[2])), instance};
|
||||
return Make(TypeBvh8Result(s), groups);
|
||||
}
|
||||
uint32_t NormalizeFlags(uint32_t flags) {
|
||||
flags = Choose(Bit(flags, 2), And(flags, C(~0x30u)), flags);
|
||||
return Or(flags, Choose(Eq(And(flags, C(0x30)), C(0x30)), C(0x100), C(0)));
|
||||
}
|
||||
|
||||
void DefineTriangle() {
|
||||
const auto a = Begin(triangle_function, "trinity_triangle", v4,
|
||||
std::array{w, u, u, u, v3, v3});
|
||||
const auto block = a[0], slot = a[1], flags = a[2], descriptor = a[3];
|
||||
const auto origin = Unpack3(a[4]), direction = Unpack3(a[5]);
|
||||
const auto flag_word = Word(block, C(15));
|
||||
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag_word, Shl(slot, 3));
|
||||
const auto bary = Bit(descriptor, 24);
|
||||
const auto pointer_flags = Both(bary, Bit(descriptor, 23));
|
||||
const auto tri_flags = Choose(pointer_flags, remap, C(0));
|
||||
const auto procedural = Bit(tri_flags, 6);
|
||||
const auto raw = EmitValueOrDefaultIfCondition(s, Not(procedural), v4,
|
||||
Make4(C(0), C(0x3f800000), C(0x3f800000), C(0x3f800000)), [&] {
|
||||
std::array<Vec3, 3> positions;
|
||||
const std::array vertices {slot, Choose(Eq(slot, C(0)), C(1), C(3)), C(2)};
|
||||
for (uint32_t vertex = 0; vertex < 3; ++vertex)
|
||||
for (uint32_t axis = 0; axis < 3; ++axis)
|
||||
positions[vertex][axis] = Float(Word(block,
|
||||
Add(Binary(s, spv::OpIMul, u, vertices[vertex], C(3)), C(axis))));
|
||||
return EmitBvhTriangle(s, positions, origin, direction, remap, Add(flag_word, slot), bary);
|
||||
});
|
||||
std::array<uint32_t, 4> result;
|
||||
for (uint32_t i = 0; i < 4; ++i) result[i] = Extract(u, raw, i);
|
||||
auto missed = Eq(result[0], C(0x7f800000));
|
||||
const auto flip = Both(Both(Not(procedural), Not(missed)),
|
||||
Binary(s, spv::OpLogicalNotEqual, b, Bit(flags, 3), Bit(tri_flags, 5)));
|
||||
for (uint32_t i = 0; i < 4; ++i)
|
||||
result[i] = Binary(s, spv::OpBitwiseXor, u, result[i],
|
||||
Choose(i < 2 ? flip : Both(flip, bary), C(0x80000000), C(0)));
|
||||
const auto front = Fcmp(spv::OpFOrdGreaterThan, Float(result[1]), ConstantF32Value(s, 0.0f));
|
||||
const auto opaque = Both(Either(Bit(tri_flags, 7), Bit(flags, 0)), Not(Bit(flags, 1)));
|
||||
missed = Either(missed, Either(Both(opaque, Bit(flags, 6)), Both(Not(opaque), Bit(flags, 7))));
|
||||
missed = Either(missed, Both(Both(Not(Bit(tri_flags, 4)), Not(procedural)),
|
||||
Select(s, b, front, Bit(flags, 5), Bit(flags, 4))));
|
||||
missed = Either(missed, Select(s, b, procedural, Bit(flags, 9), Bit(flags, 8)));
|
||||
const auto sign = [&](uint32_t value, uint32_t enabled) {
|
||||
return Choose(pointer_flags, Or(And(value, C(0x7fffffff)),
|
||||
Choose(enabled, C(0x80000000), C(0))), value);
|
||||
};
|
||||
result[2] = sign(result[2], Either(Not(opaque), procedural));
|
||||
result[3] = sign(result[3], Not(opaque));
|
||||
const std::array miss {C(0x7f800000), C(0x3f800000), Choose(bary, C(0), C(~0u)), C(0)};
|
||||
for (uint32_t i = 0; i < 4; ++i) result[i] = Choose(missed, miss[i], result[i]);
|
||||
End(Make(v4, result));
|
||||
}
|
||||
|
||||
uint32_t Instance(uint32_t block, uint32_t flags, uint32_t mask, Vec3 org, Vec3 dir) {
|
||||
const auto lo = Word(block, C(12)), hi = Word(block, C(13));
|
||||
const auto inst_flags = Shr(hi, 22);
|
||||
auto merged = Or(And(Or(flags, inst_flags), C(0x3f0)), And(inst_flags, C(4)));
|
||||
merged = Or(merged, And(Binary(s, spv::OpBitwiseXor, u, flags, inst_flags), C(8)));
|
||||
merged = Or(merged, Choose(Either(Bit(flags, 0), Both(Bit(inst_flags, 0), Not(Bit(flags, 1)))), C(1), C(0)));
|
||||
merged = Or(merged, Choose(Either(Bit(flags, 1), Both(Bit(inst_flags, 1), Not(Bit(flags, 0)))), C(2), C(0)));
|
||||
const auto user = Word(block, C(15));
|
||||
std::array<uint32_t, 10> dst;
|
||||
dst.fill(C(~0u));
|
||||
dst[2] = lo;
|
||||
dst[3] = Or(And(hi, C(0x3fffff)), Shl(merged, 22));
|
||||
dst[6] = And(user, C(0xffffff));
|
||||
dst[7] = Choose(Eq(And(mask, Shr(user, 24)), C(0)), C(~0u), Word(block, C(14)));
|
||||
Vec3 transformed_org, transformed_dir;
|
||||
for (uint32_t axis = 0; axis < 3; ++axis) {
|
||||
Vec3 row;
|
||||
for (uint32_t i = 0; i < 3; ++i) row[i] = Float(Word(block, C(axis * 4 + i)));
|
||||
const auto dot = [&](const Vec3& value) {
|
||||
return Fop(spv::OpFAdd, Fop(spv::OpFAdd, Fop(spv::OpFMul, row[0], value[0]),
|
||||
Fop(spv::OpFMul, row[1], value[1])), Fop(spv::OpFMul, row[2], value[2]));
|
||||
};
|
||||
transformed_org[axis] = Fop(spv::OpFAdd, dot(org), Float(Word(block, C(axis * 4 + 3))));
|
||||
transformed_dir[axis] = dot(dir);
|
||||
}
|
||||
return Result(dst, transformed_org, transformed_dir, C(1));
|
||||
}
|
||||
|
||||
void DefineDecompress();
|
||||
void DefineBoxes();
|
||||
uint32_t Boxes(uint32_t first, uint32_t second, uint32_t full, uint32_t compressed,
|
||||
uint32_t flags, uint32_t descriptor, uint32_t extent, uint32_t org, uint32_t dir);
|
||||
void DefineIntersect();
|
||||
};
|
||||
|
||||
void Bvh8Emitter::DefineDecompress() {
|
||||
const auto a = Begin(decompress_function, "trinity_shared_exponent", f, std::array{u, u, b});
|
||||
const auto exponent = a[0], packed = a[1], minimum = a[2];
|
||||
const auto negative = Bit(packed, 17), mantissa = And(packed, C(0x1ffff));
|
||||
const auto outward = Eq(Choose(negative, C(1), C(0)), Choose(minimum, C(1), C(0)));
|
||||
const auto nonzero = Not(Eq(mantissa, C(0)));
|
||||
const auto highest = EmitGlsl<GLSLstd450FindUMsb, IR::Type::U32>(s, mantissa);
|
||||
const auto shift = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s, Sub(C(16), highest), exponent);
|
||||
const auto decoded_exp = Choose(nonzero, Sub(exponent, shift),
|
||||
Choose(Both(outward, Binary(s, spv::OpUGreaterThan, b, exponent, C(17))), Sub(exponent, C(17)), C(0)));
|
||||
// Zero mantissas take a separate result, but their unused shifts must still be defined.
|
||||
const auto amount = Add(C(7), Choose(nonzero, shift, C(0)));
|
||||
const auto trail = Choose(Both(outward, Not(Eq(decoded_exp, C(255)))),
|
||||
Sub(Binary(s, spv::OpShiftLeftLogical, u, C(1), amount), C(1)), C(0));
|
||||
const auto fraction = Choose(nonzero,
|
||||
And(Or(Binary(s, spv::OpShiftLeftLogical, u, mantissa, amount), trail), C(0x7fffff)),
|
||||
Choose(outward, C(0x7fffff), C(0)));
|
||||
End(Float(Or(Or(fraction, Shl(decoded_exp, 23)), Choose(negative, C(0x80000000), C(0)))));
|
||||
}
|
||||
|
||||
void Bvh8Emitter::DefineBoxes() {
|
||||
const auto result_type = s.builder.Type(spv::OpTypeStruct, v4, v4);
|
||||
const auto a = Begin(boxes_function, "trinity_box_slot", result_type,
|
||||
std::array{w, w, b, b, u, u, f, v3, v3});
|
||||
const auto first = a[0], second = a[1], full = a[2], compressed = a[3];
|
||||
const auto flags = a[4], descriptor = a[5], extent = a[6];
|
||||
const auto org = Unpack3(a[7]), dir = Unpack3(a[8]);
|
||||
const auto zero = ConstantF32Value(s, 0.0f), infinity = ConstantF32(s, 0x7f800000);
|
||||
Vec3 inverse;
|
||||
for (uint32_t i = 0; i < 3; ++i) inverse[i] = Fop(spv::OpFDiv, ConstantF32Value(s, 1.0f), dir[i]);
|
||||
const auto packed = [&](uint32_t offset, uint32_t width) {
|
||||
const auto word = Shr(offset, 5), shift = And(offset, C(31));
|
||||
const auto lo = Binary(s, spv::OpShiftRightLogical, u, Word(first, word), shift);
|
||||
const auto next = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s, Add(word, C(1)), C(15));
|
||||
const auto hi = Binary(s, spv::OpShiftLeftLogical, u, Word(first, next), And(Sub(C(32), shift), C(31)));
|
||||
return And(Or(lo, Choose(Eq(shift, C(0)), C(0), hi)), C((1u << width) - 1));
|
||||
};
|
||||
const auto format = Choose(compressed, C(0), Choose(full, C(1), C(2)));
|
||||
const auto entry_label = s.current_label;
|
||||
const auto header = s.builder.AllocateId(), body = s.builder.AllocateId();
|
||||
const auto cont = s.builder.AllocateId(), merge = s.builder.AllocateId();
|
||||
const auto initial_nodes = Make4(C(~0u), C(~0u), C(~0u), C(~0u));
|
||||
const auto initial_keys = Make4(C(0x7f800000), C(0x7f800000), C(0x7f800000), C(0x7f800000));
|
||||
const auto base = And(Word(first, C(0)), C(0x1fffffff));
|
||||
s.builder.AddFunction(spv::OpBranch, header);
|
||||
EmitLabel(s, header);
|
||||
const auto child = s.builder.AllocateId(), prefix = s.builder.AllocateId();
|
||||
const auto nodes = s.builder.AllocateId(), keys = s.builder.AllocateId();
|
||||
const auto child_phi = s.builder.AddDeferredPhi(u, child, 2);
|
||||
const auto prefix_phi = s.builder.AddDeferredPhi(u, prefix, 2);
|
||||
const auto nodes_phi = s.builder.AddDeferredPhi(v4, nodes, 2);
|
||||
const auto keys_phi = s.builder.AddDeferredPhi(v4, keys, 2);
|
||||
const auto more = Binary(s, spv::OpULessThan, b, child, C(4));
|
||||
s.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlDontUnrollMask);
|
||||
s.builder.AddFunction(spv::OpBranchConditional, more, body, merge);
|
||||
EmitLabel(s, body);
|
||||
const auto kind = packed(Add(C(29), Binary(s, spv::OpIMul, u, child, C(3))), 3);
|
||||
const auto direct_node = Word(first, child);
|
||||
const auto node = Choose(compressed, Or(Shl(prefix, 3), kind), direct_node);
|
||||
const auto child_flags = Choose(compressed,
|
||||
And(packed(Add(C(41), Shl(child, 2)), 4), Choose(Eq(child, C(3)), C(7), C(15))), C(0));
|
||||
const auto step = Choose(Eq(kind, C(2)), C(4),
|
||||
Choose(Either(Either(Eq(kind, C(3)), Eq(kind, C(5))), Eq(kind, C(6))), C(2), C(1)));
|
||||
const auto next_prefix = Add(prefix, step);
|
||||
const auto bounds_type = s.builder.Type(spv::OpTypeStruct, v3, v3);
|
||||
const auto box = EmitIndexSwitch(s, format, 3, bounds_type, [&](uint32_t encoding) {
|
||||
std::array<uint32_t, 6> bounds;
|
||||
if (encoding == 0) {
|
||||
const auto yz = Word(first, C(2));
|
||||
const std::array exponents {Shr(Word(first, C(1)), 24), And(yz, C(255)), And(Shr(yz, 8), C(255))};
|
||||
for (uint32_t component = 0; component < 6; ++component) {
|
||||
const auto offset = Add(C(80 + component * 18), Binary(s, spv::OpIMul, u, child, C(108)));
|
||||
bounds[component] = Call(f, decompress_function,
|
||||
std::array{exponents[component % 3], packed(offset, 18), ConstantBool(s, component < 3)});
|
||||
}
|
||||
} else if (encoding == 1) {
|
||||
for (uint32_t component = 0; component < 6; ++component) {
|
||||
const auto word = Add(C(4 + component), Binary(s, spv::OpIMul, u, child, C(6)));
|
||||
const auto block = Select(s, w, Binary(s, spv::OpULessThan, b, word, C(16)), first, second);
|
||||
bounds[component] = Float(Word(block, And(word, C(15))));
|
||||
}
|
||||
} else {
|
||||
for (uint32_t pair = 0; pair < 3; ++pair) {
|
||||
const auto word = Add(C(4 + pair), Binary(s, spv::OpIMul, u, child, C(3)));
|
||||
const auto value = EmitGlsl<GLSLstd450UnpackHalf2x16, IR::Type::F32x2>(s, Word(first, word));
|
||||
bounds[pair * 2] = Extract(f, value, 0);
|
||||
bounds[pair * 2 + 1] = Extract(f, value, 1);
|
||||
}
|
||||
}
|
||||
return Make(bounds_type, std::array{Make(v3, std::span(bounds).first(3)),
|
||||
Make(v3, std::span(bounds).last(3))});
|
||||
});
|
||||
const auto lo_bound = Unpack3(Extract(v3, box, 0)), hi_bound = Unpack3(Extract(v3, box, 1));
|
||||
Vec3 near, far;
|
||||
auto valid = Not(Eq(And(child_flags, C(3)), C(3)));
|
||||
for (uint32_t axis = 0; axis < 3; ++axis) {
|
||||
valid = Both(valid, Fcmp(spv::OpFOrdLessThanEqual, lo_bound[axis], hi_bound[axis]));
|
||||
const auto lo = Fop(spv::OpFMul, Fop(spv::OpFSub, lo_bound[axis], org[axis]), inverse[axis]);
|
||||
const auto hi = Fop(spv::OpFMul, Fop(spv::OpFSub, hi_bound[axis], org[axis]), inverse[axis]);
|
||||
const auto forward = Fcmp(spv::OpFOrdGreaterThanEqual, inverse[axis], zero);
|
||||
near[axis] = Select(s, f, forward, lo, hi);
|
||||
far[axis] = Select(s, f, forward, hi, lo);
|
||||
}
|
||||
const auto entry = Max(Max(near[0], near[1]), near[2]);
|
||||
const auto exit = Min(Min(far[0], far[1]), far[2]);
|
||||
const auto exit_bits = Raw(Select(s, f, Fcmp(spv::OpFOrdEqual, exit, zero), zero, exit));
|
||||
const auto grown = Float(EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
|
||||
Add(exit_bits, And(Shr(descriptor, 23), C(255))), C(0x7f800000)));
|
||||
valid = Both(valid, Both(Fcmp(spv::OpFOrdGreaterThanEqual, exit, zero),
|
||||
Both(Fcmp(spv::OpFOrdLessThan, entry, extent), Fcmp(spv::OpFOrdLessThanEqual, entry, grown))));
|
||||
const auto opaque = Both(Either(Bit(child_flags, 0), Bit(flags, 0)), Not(Bit(flags, 1)));
|
||||
const auto nonopaque = Both(Either(Bit(child_flags, 1), Bit(flags, 1)), Not(Bit(flags, 0)));
|
||||
const auto culled = Either(Either(Both(opaque, Bit(flags, 6)), Both(nonopaque, Bit(flags, 7))),
|
||||
Either(Both(Bit(child_flags, 2), Bit(flags, 8)), Both(Bit(child_flags, 3), Bit(flags, 9))));
|
||||
valid = Both(valid, Not(culled));
|
||||
const auto heuristic = And(Shr(descriptor, 21), C(3));
|
||||
auto key = Max(entry, zero);
|
||||
key = Select(s, f, Eq(heuristic, C(1)), Unary(s, spv::OpFNegate, f, Fop(spv::OpFSub, exit, entry)), key);
|
||||
key = Select(s, f, Eq(heuristic, C(2)), Fop(spv::OpFAdd, entry, exit), key);
|
||||
const auto next_nodes = s.builder.AllocateId(), next_keys = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpVectorInsertDynamic, v4, next_nodes, nodes, Choose(valid, node, C(~0u)), child);
|
||||
s.builder.AddFunction(spv::OpVectorInsertDynamic, v4, next_keys, keys, Raw(Select(s, f, valid, key, infinity)), child);
|
||||
s.builder.AddFunction(spv::OpBranch, cont);
|
||||
EmitLabel(s, cont);
|
||||
const auto next_child = Add(child, C(1));
|
||||
s.builder.AddFunction(spv::OpBranch, header);
|
||||
s.builder.PatchDeferredPhi(child_phi, 0, C(0), entry_label);
|
||||
s.builder.PatchDeferredPhi(child_phi, 1, next_child, cont);
|
||||
s.builder.PatchDeferredPhi(prefix_phi, 0, base, entry_label);
|
||||
s.builder.PatchDeferredPhi(prefix_phi, 1, next_prefix, cont);
|
||||
s.builder.PatchDeferredPhi(nodes_phi, 0, initial_nodes, entry_label);
|
||||
s.builder.PatchDeferredPhi(nodes_phi, 1, next_nodes, cont);
|
||||
s.builder.PatchDeferredPhi(keys_phi, 0, initial_keys, entry_label);
|
||||
s.builder.PatchDeferredPhi(keys_phi, 1, next_keys, cont);
|
||||
EmitLabel(s, merge);
|
||||
End(Make(result_type, std::array{nodes, keys}));
|
||||
}
|
||||
|
||||
uint32_t Bvh8Emitter::Boxes(uint32_t first, uint32_t second, uint32_t full, uint32_t compressed,
|
||||
uint32_t flags, uint32_t descriptor, uint32_t extent,
|
||||
uint32_t org, uint32_t dir) {
|
||||
return Call(s.builder.Type(spv::OpTypeStruct, v4, v4), boxes_function,
|
||||
std::array{first, second, full, compressed, flags, descriptor, extent, org, dir});
|
||||
}
|
||||
|
||||
void Bvh8Emitter::DefineIntersect() {
|
||||
const auto result_type = TypeBvh8Result(s);
|
||||
const auto a = Begin(s.bvh8_intersect_function, "trinity_bvh_intersect", result_type,
|
||||
std::array{v4, w, u, f, u, v3, v3});
|
||||
const auto descriptor = a[0], encoded_base = a[1], node = a[2], extent = a[3], mask = a[4];
|
||||
const auto org = Unpack3(a[5]), dir = Unpack3(a[6]);
|
||||
std::array<uint32_t, 4> d;
|
||||
for (uint32_t i = 0; i < 4; ++i) d[i] = Extract(u, descriptor, i);
|
||||
const auto kind = And(node, C(7));
|
||||
const auto triangle = Binary(s, spv::OpULessThan, b, kind, C(2));
|
||||
const auto instance = Eq(kind, C(6));
|
||||
const auto full = Either(Eq(kind, C(2)), Eq(kind, C(5)));
|
||||
const auto big = Either(Eq(kind, C(2)), Eq(kind, C(3)));
|
||||
const auto compressed = Both(Not(full), Bit(d[3], 20));
|
||||
const auto flags = NormalizeFlags(Choose(Bit(d[3], 23),
|
||||
Unary(s, spv::OpUConvert, u, Binary(s, spv::OpShiftRightLogical, w, encoded_base, C(54))), C(0)));
|
||||
const auto base = Binary(s, spv::OpShiftLeftLogical, w, PackU64(s, d[0], And(d[1], C(255))), C(8));
|
||||
const auto lane_base = Binary(s, spv::OpShiftLeftLogical, w,
|
||||
Binary(s, spv::OpBitwiseAnd, w, encoded_base, ConstantU64(s, (uint64_t{1} << 54) - 1)), C(3));
|
||||
const auto offset = Binary(s, spv::OpShiftLeftLogical, w, Unary(s, spv::OpUConvert, w, Shr(node, 3)), C(6));
|
||||
const auto address = Binary(s, spv::OpIAdd, w, base, Binary(s, spv::OpIAdd, w, lane_base, offset));
|
||||
const auto capacity = Binary(s, spv::OpIAdd, w,
|
||||
PackU64(s, d[2], And(d[3], C(0x3ff))), ConstantU64(s, 1));
|
||||
const auto limit = Binary(s, spv::OpIAdd, w, base,
|
||||
Binary(s, spv::OpShiftLeftLogical, w, capacity, C(6)));
|
||||
auto valid = Both(Eq(Shr(d[3], 28), C(8)),
|
||||
Eq(And(Unary(s, spv::OpUConvert, u, encoded_base), C(7)), C(0)));
|
||||
valid = Both(valid, Both(Not(Eq(kind, C(7))), Either(Not(instance), Bit(d[3], 22))));
|
||||
valid = Both(valid, Not(Unary(s, spv::OpIsNan, b, extent)));
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
valid = Both(valid, Not(Either(Unary(s, spv::OpIsNan, b, org[i]), Unary(s, spv::OpIsInf, b, org[i]))));
|
||||
valid = Both(valid, Not(Unary(s, spv::OpIsNan, b, dir[i])));
|
||||
}
|
||||
const auto opaque = Bit(flags, 0), nonopaque = Bit(flags, 1);
|
||||
valid = Both(valid, Not(Either(Either(Both(opaque, Bit(flags, 6)), Both(nonopaque, Bit(flags, 7))),
|
||||
Both(Bit(flags, 6), Bit(flags, 7)))));
|
||||
const auto fetch_size = Select(s, w, full, ConstantU64(s, 128), ConstantU64(s, 64));
|
||||
const auto fetch = [&](uint32_t at, uint32_t enabled) {
|
||||
const auto end = Binary(s, spv::OpIAdd, w, at, fetch_size);
|
||||
const auto in_bounds = Both(Binary(s, spv::OpULessThanEqual, b, end, limit),
|
||||
Binary(s, spv::OpULessThanEqual, b, end, ConstantU64(s, (uint64_t{1} << 48) - 1)));
|
||||
return EmitValueOrDefaultIfCondition(s, Both(Both(valid, enabled), in_bounds), w,
|
||||
ConstantU64(s, 0), [&] { return GetBdaPointer(s, at); });
|
||||
};
|
||||
const auto first = fetch(address, ConstantBool(s, true));
|
||||
const auto present = [&](uint32_t pointer) { return Not(Binary(s, spv::OpIEqual, b, pointer, ConstantU64(s, 0))); };
|
||||
const auto tail = [&](uint32_t at, uint32_t pointer) {
|
||||
return EmitValueOrDefaultIfCondition(s, Both(full, present(pointer)), w, ConstantU64(s, 0), [&] {
|
||||
return GetBdaPointer(s, Binary(s, spv::OpIAdd, w, at, ConstantU64(s, 64)));
|
||||
});
|
||||
};
|
||||
const auto second = tail(address, first);
|
||||
const auto available = Both(present(first), Either(Not(full), present(second)));
|
||||
std::array<uint32_t, 10> invalid;
|
||||
invalid.fill(C(~0u));
|
||||
for (uint32_t slot = 0; slot < 2; ++slot) {
|
||||
invalid[slot * 4] = Choose(triangle, C(0x7f800000), C(~0u));
|
||||
invalid[slot * 4 + 1] = Choose(triangle, C(0x3f800000), C(~0u));
|
||||
invalid[slot * 4 + 3] = Choose(triangle, C(0), C(~0u));
|
||||
}
|
||||
const auto default_result = Result(invalid, org, dir, C(0));
|
||||
const auto value = EmitValueOrDefaultIfCondition(s, Both(instance, available), result_type, default_result, [&] {
|
||||
return Instance(first, flags, mask, org, dir);
|
||||
});
|
||||
const auto result = EmitValueOrDefaultIfCondition(s, Both(Not(instance), valid), result_type, value, [&] {
|
||||
const auto second_address = Binary(s, spv::OpIAdd, w, address, fetch_size);
|
||||
const auto next_first = fetch(second_address, big);
|
||||
const auto next_second = tail(second_address, next_first);
|
||||
const auto next_available = Both(present(next_first), Either(Not(full), present(next_second)));
|
||||
const auto triangles = EmitValueOrDefaultIfCondition(s, triangle, result_type, default_result, [&] {
|
||||
auto dst = invalid;
|
||||
for (uint32_t slot = 0; slot < 2; ++slot) {
|
||||
const auto active = Both(available, slot == 0 ? ConstantBool(s, true) : Eq(kind, C(1)));
|
||||
const auto absent = Both(available, Both(Eq(kind, C(0)), Bit(d[3], 24)));
|
||||
const auto miss = Make4(C(0x7f800000), C(0x3f800000),
|
||||
slot == 1 ? Choose(absent, C(0), C(~0u)) : C(~0u), C(0));
|
||||
const auto hit = EmitValueOrDefaultIfCondition(s, active, v4, miss, [&] {
|
||||
return Call(v4, triangle_function, std::array{first, C(slot), flags, d[3], a[5], a[6]});
|
||||
});
|
||||
for (uint32_t i = 0; i < 4; ++i) dst[slot * 4 + i] = Extract(u, hit, i);
|
||||
dst[8 + slot] = EmitValueOrDefaultIfCondition(s, active, u, C(~0u), [&] { return Word(first, C(14)); });
|
||||
}
|
||||
return Result(dst, org, dir, C(0));
|
||||
});
|
||||
return EmitValueOrDefaultIfCondition(s, Not(triangle), result_type, triangles, [&] {
|
||||
auto dst = invalid;
|
||||
const auto slot_type = s.builder.Type(spv::OpTypeStruct, v4, v4);
|
||||
const auto miss = Make(slot_type, std::array{Make4(C(~0u), C(~0u), C(~0u), C(~0u)),
|
||||
Make4(C(0x7f800000), C(0x7f800000), C(0x7f800000), C(0x7f800000))});
|
||||
std::array<uint32_t, 8> keys;
|
||||
for (uint32_t slot = 0; slot < 2; ++slot) {
|
||||
const auto hit = EmitValueOrDefaultIfCondition(s, slot == 0 ? available : next_available,
|
||||
slot_type, miss, [&] {
|
||||
return Boxes(slot == 0 ? first : next_first, slot == 0 ? second : next_second,
|
||||
full, compressed, flags, d[1], extent, a[5], a[6]);
|
||||
});
|
||||
const auto indices = Extract(v4, hit, 0), depths = Extract(v4, hit, 1);
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
dst[slot * 4 + i] = Extract(u, indices, i);
|
||||
keys[slot * 4 + i] = Extract(u, depths, i);
|
||||
}
|
||||
}
|
||||
const auto sort = Bit(d[1], 31), wide = Both(sort, Bit(d[3], 21)), tri_first = Bit(d[1], 20);
|
||||
constexpr std::array<std::pair<uint32_t, uint32_t>, 19> network {{{0,1}, {2,3}, {0,2}, {1,3}, {1,2},
|
||||
{4,5}, {6,7}, {4,6}, {5,7}, {5,6}, {0,4}, {1,5}, {1,4}, {2,6}, {3,7}, {3,6}, {2,4}, {3,5}, {3,4}}};
|
||||
const auto unsorted = Make(slot_type, std::array{Make(v4, std::span(dst).first(4)),
|
||||
Make(v4, std::span(dst).subspan(4, 4))});
|
||||
const auto sorted_result = EmitValueOrDefaultIfCondition(s, sort, slot_type, unsorted, [&] {
|
||||
auto sorted_dst = dst;
|
||||
for (size_t step = 0; step < network.size(); ++step) {
|
||||
const auto [x, y] = network[step];
|
||||
const auto is_tri = [&](uint32_t index) { return Binary(s, spv::OpULessThan, b, And(index, C(7)), C(2)); };
|
||||
const auto xt = is_tri(sorted_dst[x]), yt = is_tri(sorted_dst[y]);
|
||||
auto swap = Fcmp(spv::OpFOrdGreaterThan, Float(keys[x]), Float(keys[y]));
|
||||
swap = Select(s, b, Both(tri_first, Binary(s, spv::OpLogicalNotEqual, b, xt, yt)), yt, swap);
|
||||
if (step >= 10) swap = Both(wide, swap);
|
||||
const auto key = keys[x], index = sorted_dst[x];
|
||||
const auto later = [&](uint32_t position) {
|
||||
return std::ranges::any_of(std::span(network).subspan(step + 1), [=](auto pair) {
|
||||
return pair.first == position || pair.second == position;
|
||||
});
|
||||
};
|
||||
if (later(x)) keys[x] = Choose(swap, keys[y], key);
|
||||
if (later(y)) keys[y] = Choose(swap, key, keys[y]);
|
||||
sorted_dst[x] = Choose(swap, sorted_dst[y], index);
|
||||
sorted_dst[y] = Choose(swap, index, sorted_dst[y]);
|
||||
}
|
||||
return Make(slot_type, std::array{Make(v4, std::span(sorted_dst).first(4)),
|
||||
Make(v4, std::span(sorted_dst).subspan(4, 4))});
|
||||
});
|
||||
for (uint32_t slot = 0; slot < 2; ++slot) {
|
||||
const auto indices = Extract(v4, sorted_result, slot);
|
||||
for (uint32_t i = 0; i < 4; ++i) dst[slot * 4 + i] = Extract(u, indices, i);
|
||||
}
|
||||
return Result(dst, org, dir, C(0));
|
||||
});
|
||||
});
|
||||
End(result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DefineTrinityBvhIntersect(EmitterState& s) {
|
||||
if (!s.program.info.bvh8) return;
|
||||
Bvh8Emitter e {s};
|
||||
e.DefineDecompress();
|
||||
e.DefineTriangle();
|
||||
e.DefineBoxes();
|
||||
e.DefineIntersect();
|
||||
}
|
||||
|
||||
uint32_t EmitBvh8Extract(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto index = inst.Arg(1).U32();
|
||||
if (index > 16) ctx.Fail(inst, "invalid BVH8 result component");
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
if (index == 16)
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), result,
|
||||
ctx.Arg(inst, 0), 4u);
|
||||
else
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), result,
|
||||
ctx.Arg(inst, 0), index / 4, index % 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitBvh8Intersect(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& s = ctx.state;
|
||||
const auto* ray = ctx.ImageAddress(inst.Arg(1));
|
||||
if (ray == nullptr || ray->NumArgs() < 11 || s.bvh8_intersect_function == 0)
|
||||
ctx.Fail(inst, "BVH8 intersection requires the complete ray and shared function");
|
||||
Bvh8Emitter e {s};
|
||||
const auto vector = [&](uint32_t first) {
|
||||
return e.Make(e.v3, Vec3{e.Float(ctx.Arg(*ray, first)), e.Float(ctx.Arg(*ray, first + 1)),
|
||||
e.Float(ctx.Arg(*ray, first + 2))});
|
||||
};
|
||||
const std::array args {ctx.Arg(inst, 0), PackU64(s, ctx.Arg(*ray, 0), ctx.Arg(*ray, 1)),
|
||||
ctx.Arg(*ray, 10), e.Float(ctx.Arg(*ray, 2)), ctx.Arg(*ray, 3), vector(4), vector(7)};
|
||||
const auto type = TypeBvh8Result(s);
|
||||
return EmitValueOrDefaultIfCondition(s, ctx.Arg(inst, 2), type,
|
||||
s.builder.Constant(spv::OpConstantNull, type), [&] {
|
||||
return e.Call(type, s.bvh8_intersect_function, args);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -281,6 +281,7 @@ Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const Mim
|
||||
case 0x09u: return Opcode::IMAGE_STORE_MIP;
|
||||
case 0x0eu: return Opcode::IMAGE_GET_RESINFO;
|
||||
case 0x60u: return Opcode::IMAGE_GET_LOD;
|
||||
case 0xe5u: return Opcode::IMAGE_BVH8_INTERSECT_RAY;
|
||||
case 0xe6u:
|
||||
case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
|
||||
default: return Opcode::UNSUPPORTED;
|
||||
@@ -297,6 +298,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case 0xe5u:
|
||||
case 0xe6u: return 11u;
|
||||
case 0xe7u: return 12u;
|
||||
case 0x0eu: return 1u;
|
||||
@@ -427,6 +429,36 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
"BVH intersection requires full-float ray DWORDs and R128/dmask:0xf");
|
||||
}
|
||||
|
||||
if (opcode == 0xe5u) {
|
||||
inst.data_components = inst.data_dwords = 10;
|
||||
if (a16 || d16 || !r128 || inst.dmask != 0xfu ||
|
||||
(nsa_dwords != 0u && nsa_dwords != 2u) || vdata > 246u ||
|
||||
(nsa_dwords == 0u && vaddr > 245u)) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "invalid BVH8 ray or destination encoding");
|
||||
}
|
||||
if (nsa_dwords == 2u) {
|
||||
const auto origin = inst.image_nsa_addr[3], direction = inst.image_nsa_addr[4];
|
||||
const auto node = inst.image_nsa_addr[5];
|
||||
if (origin > 253u || direction > 253u)
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "BVH8 ray registers exceed VGPR range");
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
inst.image_nsa_addr[3 + i] = origin + i;
|
||||
inst.image_nsa_addr[6 + i] = direction + i;
|
||||
}
|
||||
inst.image_nsa_addr[9] = node;
|
||||
}
|
||||
const auto origin = nsa_dwords != 0 ? inst.image_nsa_addr[3] : vaddr + 4;
|
||||
const auto direction = nsa_dwords != 0 ? inst.image_nsa_addr[6] : vaddr + 7;
|
||||
bool overlapping = origin < direction + 3 && direction < origin + 3;
|
||||
for (const auto output : {2u, 3u, 6u, 7u}) {
|
||||
const auto reg = vdata + output;
|
||||
overlapping |= (reg >= origin && reg < origin + 3) ||
|
||||
(reg >= direction && reg < direction + 3);
|
||||
}
|
||||
if (overlapping)
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "overlapping BVH8 instance outputs are unsupported");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
|
||||
@@ -142,6 +142,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
{0xb0u, Opcode::DS_WRITE_ADDTID_B32, 1, 32}, {0xb1u, Opcode::DS_READ_ADDTID_B32, 1, 32},
|
||||
{0xb2u, Opcode::DS_PERMUTE_B32, 1, 32}, {0xb3u, Opcode::DS_BPERMUTE_B32, 1, 32},
|
||||
{0xdeu, Opcode::DS_WRITE_B96, 3, 32}, {0xdfu, Opcode::DS_WRITE_B128, 4, 32},
|
||||
{0xe1u, Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32, 1, 32},
|
||||
{0xfeu, Opcode::DS_READ_B96, 3, 32}, {0xffu, Opcode::DS_READ_B128, 4, 32},
|
||||
};
|
||||
|
||||
@@ -232,6 +233,7 @@ bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
|
||||
uint32_t DsSourceCount(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return 3u;
|
||||
case Opcode::DS_WRITE2_B32:
|
||||
case Opcode::DS_WRITE2ST64_B32:
|
||||
case Opcode::DS_WRITE2_B64:
|
||||
@@ -473,6 +475,13 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
||||
}
|
||||
if (inst.opcode == Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 &&
|
||||
(inst.gds || inst.offset < 8u || inst.offset > 31u || data1 > 248u)) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "invalid BVH stack operands or capacity");
|
||||
}
|
||||
if (inst.opcode == Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 && vdst == addr) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "overlapping BVH stack outputs are not implemented");
|
||||
}
|
||||
if (inst.gds && inst.opcode == Opcode::DS_CMPST_RTN_B32) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS compare-store return B32 GDS variant is not implemented");
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
|
||||
{0x21u, Opcode::S_SWAPPC_B64},
|
||||
{0x24u, Opcode::S_AND_SAVEEXEC_B64},
|
||||
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
|
||||
{0x2cu, Opcode::S_QUADMASK_B32},
|
||||
{0x2du, Opcode::S_QUADMASK_B64},
|
||||
{0x34u, Opcode::S_ABS_I32},
|
||||
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
|
||||
|
||||
@@ -517,6 +517,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::S_NOT_B64:
|
||||
case Opcode::S_WQM_B32:
|
||||
case Opcode::S_WQM_B64:
|
||||
case Opcode::S_QUADMASK_B32:
|
||||
case Opcode::S_QUADMASK_B64:
|
||||
case Opcode::S_AND_SAVEEXEC_B32:
|
||||
case Opcode::S_ORN2_SAVEEXEC_B32:
|
||||
@@ -739,6 +740,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_READ2ST64_B64:
|
||||
case Opcode::DS_READ_B96:
|
||||
case Opcode::DS_READ_B128:
|
||||
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32:
|
||||
case Opcode::DS_WRITE_B8:
|
||||
case Opcode::DS_WRITE_B16:
|
||||
case Opcode::DS_WRITE_B8_D16_HI:
|
||||
|
||||
@@ -63,6 +63,7 @@ enum class Opcode {
|
||||
S_FLBIT_I32,
|
||||
S_FLBIT_I32_I64,
|
||||
S_BITREPLICATE_B64_B32,
|
||||
S_QUADMASK_B32,
|
||||
S_QUADMASK_B64,
|
||||
S_GETPC_B64,
|
||||
S_SETPC_B64,
|
||||
@@ -248,7 +249,9 @@ enum class Opcode {
|
||||
V_MIN_I32,
|
||||
V_MAX_I32,
|
||||
V_MUL_I32_I24,
|
||||
V_MUL_HI_I32_I24,
|
||||
V_MUL_U32_U24,
|
||||
V_MUL_HI_U32_U24,
|
||||
V_MUL_LO_U32,
|
||||
V_MUL_HI_U32,
|
||||
V_MUL_LO_I32,
|
||||
@@ -257,6 +260,7 @@ enum class Opcode {
|
||||
V_CVT_PK_U8_F32,
|
||||
V_MAD_F32,
|
||||
V_MAD_I16,
|
||||
V_MAD_U16,
|
||||
V_MAD_I32_I24,
|
||||
V_MAD_U32_U24,
|
||||
V_MAD_U64_U32,
|
||||
@@ -277,16 +281,20 @@ enum class Opcode {
|
||||
V_MIN3_I32,
|
||||
V_MIN3_U32,
|
||||
V_MIN3_F16,
|
||||
V_MIN3_I16,
|
||||
V_MIN3_U16,
|
||||
V_MAX3_F32,
|
||||
V_MAX3_I32,
|
||||
V_MAX3_U32,
|
||||
V_MAX3_F16,
|
||||
V_MAX3_I16,
|
||||
V_MAX3_U16,
|
||||
V_MED3_F32,
|
||||
V_MED3_I32,
|
||||
V_MED3_U32,
|
||||
V_MED3_F16,
|
||||
V_MED3_I16,
|
||||
V_MED3_U16,
|
||||
V_SAD_U32,
|
||||
V_ADD3_U32,
|
||||
V_LSHL_ADD_U32,
|
||||
@@ -354,6 +362,7 @@ enum class Opcode {
|
||||
V_ASHRREV_I32,
|
||||
V_LSHLREV_B64,
|
||||
V_LSHRREV_B64,
|
||||
V_ASHRREV_I64,
|
||||
V_LSHLREV_B16,
|
||||
V_LSHRREV_B16,
|
||||
V_ASHRREV_I16,
|
||||
@@ -676,6 +685,7 @@ enum class Opcode {
|
||||
DS_READ2ST64_B64,
|
||||
DS_READ_B96,
|
||||
DS_READ_B128,
|
||||
DS_BVH_STACK_PUSH8_POP1_RTN_B32,
|
||||
DS_WRITE_B8,
|
||||
DS_WRITE_B16,
|
||||
DS_WRITE_B8_D16_HI,
|
||||
@@ -691,6 +701,7 @@ enum class Opcode {
|
||||
DS_WRITE_ADDTID_B32,
|
||||
DS_READ_ADDTID_B32,
|
||||
IMAGE_BVH_INTERSECT_RAY,
|
||||
IMAGE_BVH8_INTERSECT_RAY,
|
||||
IMAGE_GET_RESINFO,
|
||||
IMAGE_GET_LOD,
|
||||
IMAGE_LOAD,
|
||||
|
||||
@@ -35,7 +35,9 @@ 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},
|
||||
{0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0fu, Opcode::V_MIN_F32},
|
||||
{0x10u, Opcode::V_MAX_F32},
|
||||
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
|
||||
@@ -157,6 +159,10 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
|
||||
{0x04u, Opcode::V_CVT_F64_I32},
|
||||
{0x0fu, Opcode::V_CVT_F32_F64},
|
||||
{0x10u, Opcode::V_CVT_F64_F32},
|
||||
{0x11u, Opcode::V_CVT_F32_UBYTE0},
|
||||
{0x12u, Opcode::V_CVT_F32_UBYTE1},
|
||||
{0x13u, Opcode::V_CVT_F32_UBYTE2},
|
||||
{0x14u, Opcode::V_CVT_F32_UBYTE3},
|
||||
{0x16u, Opcode::V_CVT_F64_U32},
|
||||
{0x17u, Opcode::V_TRUNC_F64},
|
||||
{0x18u, Opcode::V_CEIL_F64},
|
||||
@@ -167,6 +173,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
|
||||
{0x07u, Opcode::V_CVT_U32_F32},
|
||||
{0x08u, Opcode::V_CVT_I32_F32},
|
||||
{0x0au, Opcode::V_CVT_F16_F32},
|
||||
{0x0bu, Opcode::V_CVT_F32_F16},
|
||||
{0x0cu, Opcode::V_CVT_RPI_I32_F32},
|
||||
{0x0du, Opcode::V_CVT_FLR_I32_F32},
|
||||
{0x0eu, Opcode::V_CVT_OFF_F32_I4},
|
||||
@@ -334,16 +341,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x152u, Opcode::V_MIN3_I32},
|
||||
{0x153u, Opcode::V_MIN3_U32},
|
||||
{0x351u, Opcode::V_MIN3_F16},
|
||||
{0x352u, Opcode::V_MIN3_I16},
|
||||
{0x353u, Opcode::V_MIN3_U16},
|
||||
{0x154u, Opcode::V_MAX3_F32},
|
||||
{0x155u, Opcode::V_MAX3_I32},
|
||||
{0x156u, Opcode::V_MAX3_U32},
|
||||
{0x354u, Opcode::V_MAX3_F16},
|
||||
{0x355u, Opcode::V_MAX3_I16},
|
||||
{0x356u, Opcode::V_MAX3_U16},
|
||||
{0x157u, Opcode::V_MED3_F32},
|
||||
{0x158u, Opcode::V_MED3_I32},
|
||||
{0x159u, Opcode::V_MED3_U32},
|
||||
{0x357u, Opcode::V_MED3_F16},
|
||||
{0x358u, Opcode::V_MED3_I16},
|
||||
{0x359u, Opcode::V_MED3_U16},
|
||||
{0x15du, Opcode::V_SAD_U32},
|
||||
{0x15eu, Opcode::V_CVT_PK_U8_F32},
|
||||
{0x178u, Opcode::V_XOR3_B32},
|
||||
@@ -354,6 +365,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x16cu, Opcode::V_MUL_HI_I32},
|
||||
{0x2ffu, Opcode::V_LSHLREV_B64},
|
||||
{0x300u, Opcode::V_LSHRREV_B64},
|
||||
{0x301u, Opcode::V_ASHRREV_I64},
|
||||
{0x303u, Opcode::V_ADD_NC_U16},
|
||||
{0x304u, Opcode::V_SUB_NC_U16},
|
||||
{0x305u, Opcode::V_MUL_LO_U16},
|
||||
@@ -370,6 +382,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x311u, Opcode::V_PACK_B32_F16},
|
||||
{0x314u, Opcode::V_LSHLREV_B16},
|
||||
{0x319u, Opcode::V_SUBREV_I32},
|
||||
{0x340u, Opcode::V_MAD_U16},
|
||||
{0x345u, Opcode::V_XAD_U32},
|
||||
{0x346u, Opcode::V_LSHL_ADD_U32},
|
||||
{0x347u, Opcode::V_ADD_LSHL_U32},
|
||||
@@ -488,8 +501,10 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
|
||||
opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
|
||||
opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 ||
|
||||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||
@@ -1335,6 +1350,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
|
||||
case Opcode::V_SUB_NC_I16:
|
||||
case Opcode::V_LSHLREV_B64:
|
||||
case Opcode::V_LSHRREV_B64:
|
||||
case Opcode::V_ASHRREV_I64:
|
||||
case Opcode::V_LSHLREV_B16:
|
||||
case Opcode::V_LSHRREV_B16:
|
||||
case Opcode::V_ASHRREV_I16:
|
||||
@@ -1523,7 +1539,10 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
|
||||
|
||||
bool SupportsNativeVop3Clamp(Opcode opcode) {
|
||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 ||
|
||||
opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 ||
|
||||
opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 ||
|
||||
opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24;
|
||||
}
|
||||
|
||||
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
|
||||
@@ -1554,7 +1573,17 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
return clamp != 0u || omod != 0u || neg != 0u;
|
||||
}
|
||||
switch (opcode) {
|
||||
case Opcode::V_MUL_HI_I32_I24:
|
||||
case Opcode::V_MUL_HI_U32_U24:
|
||||
// The high product fits in 32 bits, so clamp has no effect.
|
||||
return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u;
|
||||
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
|
||||
case Opcode::V_CVT_F32_UBYTE0:
|
||||
case Opcode::V_CVT_F32_UBYTE1:
|
||||
case Opcode::V_CVT_F32_UBYTE2:
|
||||
case Opcode::V_CVT_F32_UBYTE3:
|
||||
// Byte conversions ignore input modifiers and support all output modifiers.
|
||||
return op_sel != 0u;
|
||||
case Opcode::V_INTERP_P1LL_F16:
|
||||
case Opcode::V_INTERP_P2_F16:
|
||||
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
|
||||
@@ -20,12 +20,12 @@ void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
|
||||
Write16Bits(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, true);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, true);
|
||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
|
||||
void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, sign);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, sign);
|
||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign));
|
||||
if (inst.dst.clamp) {
|
||||
result = Saturate16(result, true);
|
||||
result = Saturate16(result, sign);
|
||||
}
|
||||
Write16Bits(DestinationOperand(inst), result);
|
||||
}
|
||||
@@ -147,6 +147,12 @@ void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
const auto arg_type = IR::ArgTypeOf(opcode, index);
|
||||
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
|
||||
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
|
||||
if (opcode == IR::ValueOpcode::ShiftRightArithmetic64 && index == 0u &&
|
||||
operand.kind == Decoder::OperandKind::LiteralConstant) {
|
||||
args[index] = ir.ConstructU64(
|
||||
ReadU32(operand),
|
||||
IR::U32(IR::Value((operand.value & 0x80000000u) ? 0xffffffffu : 0u)));
|
||||
}
|
||||
if (mask_shift_count && index == 1u) {
|
||||
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
|
||||
}
|
||||
@@ -263,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
|
||||
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) {
|
||||
const auto extract24 = [&](IR::U32 value) {
|
||||
return IR::U32(
|
||||
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
|
||||
@@ -271,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
|
||||
};
|
||||
const auto lhs = extract24(ReadU32(inst.src0));
|
||||
const auto rhs = extract24(ReadU32(inst.src1));
|
||||
auto result = ir.IMul(lhs, rhs);
|
||||
const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi)
|
||||
: IR::ValueOpcode::IMul32;
|
||||
auto result = IR::U32(ir.Emit(multiply, {lhs, rhs}));
|
||||
if (addend) {
|
||||
result = ir.IAdd(result, ReadU32(inst.src2));
|
||||
}
|
||||
@@ -369,7 +377,7 @@ void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
|
||||
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
|
||||
}
|
||||
|
||||
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_QUADMASK(const Decoder::Instruction& inst, bool is64) {
|
||||
const auto compact = [&](IR::U32 value) {
|
||||
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
|
||||
bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u))));
|
||||
@@ -382,12 +390,17 @@ void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
|
||||
ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))),
|
||||
IR::U32(IR::Value(0xffu)));
|
||||
};
|
||||
IR::U32 quads;
|
||||
if (is64) {
|
||||
const auto source = ReadU32Pair(inst.src0);
|
||||
const auto quads = ir.BitwiseOr(
|
||||
quads = ir.BitwiseOr(
|
||||
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
|
||||
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u)));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
|
||||
WriteOperand(DestinationOperand(inst), ir.ConstructU64(quads, IR::U32(IR::Value(0u))));
|
||||
} else {
|
||||
quads = compact(ReadU32(inst.src0));
|
||||
WriteOperand(DestinationOperand(inst), quads);
|
||||
}
|
||||
ir.SetScc(ir.INotEqual(quads, IR::U32(IR::Value(0u))));
|
||||
}
|
||||
|
||||
void Translator::BFM_B32(const Decoder::Instruction& inst) {
|
||||
|
||||
@@ -326,7 +326,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
|
||||
inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
|
||||
EXIT_IF(count > components.size());
|
||||
const auto nsa_components =
|
||||
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||
inst.opcode == Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY && inst.image_nsa_dwords != 0u
|
||||
? 10u : std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
if (index != 0u && index - 1u < nsa_components) {
|
||||
components[index] =
|
||||
@@ -781,6 +782,15 @@ void Translator::WriteSharedU32(uint32_t width, IR::U32 address,
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::DS_BVH_STACK(const Decoder::Instruction& inst) {
|
||||
const auto result = ir.Emit(IR::ValueOpcode::BvhStack,
|
||||
{ReadU32(inst.src0), ReadU32(inst.src1), ConstructU32x4(inst.src2, 4),
|
||||
ConstructU32x4(OffsetOperand(inst.src2, 4), 4), ir.GetExec()},
|
||||
AddMemoryInfo(MemoryInfoFromDecoded(inst), inst.pc));
|
||||
WriteOperand(inst.src0, ir.Emit(IR::ValueOpcode::CompositeExtractU32x2, {result, IR::Value(0u)}));
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::CompositeExtractU32x2, {result, IR::Value(1u)}));
|
||||
}
|
||||
|
||||
void Translator::DS_READ(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 0));
|
||||
@@ -939,6 +949,31 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
|
||||
{ReadU32(inst.src1), address, ir.GetExec()}));
|
||||
}
|
||||
|
||||
void Translator::IMAGE_BVH8(const Decoder::Instruction& inst) {
|
||||
std::array<IR::U32, 10> previous;
|
||||
for (uint32_t i = 0; i < previous.size(); ++i)
|
||||
previous[i] = ReadU32(OffsetOperand(inst.dst, i));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::Bvh8Intersect,
|
||||
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()});
|
||||
const auto get = [&](uint32_t i) {
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::Bvh8Extract, {result, IR::Value(i)}));
|
||||
};
|
||||
const auto instance = ir.INotEqual(get(16), IR::U32(IR::Value(0u)));
|
||||
for (uint32_t i = 0; i < 10; ++i) {
|
||||
auto value = get(i);
|
||||
if (i == 0 || i == 1 || i == 4 || i == 5 || i == 8 || i == 9)
|
||||
value = ir.Select(instance, previous[i], value);
|
||||
WriteOperand(OffsetOperand(inst.dst, i), value);
|
||||
}
|
||||
for (uint32_t i = 0; i < 6; ++i) {
|
||||
auto reg = inst.image_nsa_dwords != 0 ? inst.image_nsa_addr[3 + i] : inst.src0.reg + 4 + i;
|
||||
auto target = inst.src0;
|
||||
target.reg = reg;
|
||||
// Non-instance results may alias the ray inputs.
|
||||
WriteOperand(target, ir.Select(instance, get(10 + i), ReadU32(target)));
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
// Translated scalar loads have no cache of their own to invalidate.
|
||||
@@ -947,6 +982,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_GL1_INV:
|
||||
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
||||
return;
|
||||
case Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY: return IMAGE_BVH8(inst);
|
||||
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
|
||||
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
|
||||
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
|
||||
@@ -1186,6 +1222,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataAppend);
|
||||
case Decoder::Opcode::DS_WRITE_ADDTID_B32: return DS_ADDTID(inst, true);
|
||||
case Decoder::Opcode::DS_READ_ADDTID_B32: return DS_ADDTID(inst, false);
|
||||
case Decoder::Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return DS_BVH_STACK(inst);
|
||||
case Decoder::Opcode::DS_READ2_B32:
|
||||
case Decoder::Opcode::DS_READ2ST64_B32:
|
||||
case Decoder::Opcode::DS_READ2_B64:
|
||||
|
||||
@@ -248,7 +248,8 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_BITSET0_B64: return S_BITSET_B64(inst, false);
|
||||
case O::S_BITSET1_B64: return S_BITSET_B64(inst, true);
|
||||
case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst);
|
||||
case O::S_QUADMASK_B64: return S_QUADMASK_B64(inst);
|
||||
case O::S_QUADMASK_B32: return S_QUADMASK(inst, false);
|
||||
case O::S_QUADMASK_B64: return S_QUADMASK(inst, true);
|
||||
case O::S_BFM_B32: return BFM_B32(inst);
|
||||
case O::S_BFM_B64: return S_BFM_B64(inst);
|
||||
case O::S_BFE_U32: return S_BFE_U32(inst, false);
|
||||
|
||||
@@ -1165,6 +1165,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
: IR::U32(IR::Value(0u));
|
||||
entry_ir.SetExecLo(initial_lo);
|
||||
entry_ir.SetExecHi(initial_hi);
|
||||
const auto active_lane_count = [&] {
|
||||
auto count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
|
||||
if (options.wave_size == 64u) {
|
||||
count = entry_ir.IAdd(count, IR::U32(entry_ir.Emit(
|
||||
IR::ValueOpcode::BitCount32, {initial_hi})));
|
||||
}
|
||||
return count;
|
||||
};
|
||||
if (options.stage == ShaderType::Compute) {
|
||||
const auto* cs = options.input_info.compute;
|
||||
const auto thread_ids =
|
||||
@@ -1302,7 +1310,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||
}
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), active_lane_count());
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
|
||||
@@ -1380,21 +1388,17 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
} else if (options.stage == ShaderType::Vertex) {
|
||||
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
|
||||
// ESVertCount describes input vertices, independently of lane ordinals.
|
||||
auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
|
||||
if (options.wave_size == 64u) {
|
||||
vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit(
|
||||
IR::ValueOpcode::BitCount32, {initial_hi})));
|
||||
}
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
|
||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
||||
entry_ir.BitwiseOr(active_lane_count(), IR::U32(IR::Value(1u << 28u))));
|
||||
// Vulkan adds an indirect record's bases to the indices; the hardware passes them
|
||||
// only in the user SGPRs the packet patches.
|
||||
// only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
|
||||
// it does not make the record's bases zero.
|
||||
const auto& offsets = options.input_info.vertex->indirect_offsets;
|
||||
const auto index = [&](uint8_t slot, IR::StageInputKind kind,
|
||||
IR::StageInputKind base_kind) {
|
||||
if (offsets == ShaderIndirectOffsets {}) {
|
||||
if (!options.input_info.vertex->indirect_draw) {
|
||||
return builtin(kind);
|
||||
}
|
||||
const auto base = builtin(base_kind);
|
||||
|
||||
@@ -105,6 +105,8 @@ private:
|
||||
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
|
||||
const IR::MemoryInfo& memory, uint32_t pc);
|
||||
void DS_READ(const Decoder::Instruction& inst);
|
||||
void IMAGE_BVH8(const Decoder::Instruction& inst);
|
||||
void DS_BVH_STACK(const Decoder::Instruction& inst);
|
||||
void DS_READ2(const Decoder::Instruction& inst);
|
||||
void DS_WRITE(const Decoder::Instruction& inst);
|
||||
void DS_WRITE2(const Decoder::Instruction& inst);
|
||||
@@ -167,7 +169,7 @@ private:
|
||||
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void V_MAD_I16(const Decoder::Instruction& inst);
|
||||
void Integer16Mad(const Decoder::Instruction& inst, bool sign);
|
||||
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
@@ -187,7 +189,7 @@ private:
|
||||
void V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
|
||||
void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
|
||||
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false);
|
||||
void V_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||
void V_SAD_U32(const Decoder::Instruction& inst);
|
||||
void V_ADD3_U32(const Decoder::Instruction& inst);
|
||||
@@ -196,7 +198,7 @@ private:
|
||||
void V_BCNT_U32_B32(const Decoder::Instruction& inst);
|
||||
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
||||
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
|
||||
void S_QUADMASK_B64(const Decoder::Instruction& inst);
|
||||
void S_QUADMASK(const Decoder::Instruction& inst, bool is64);
|
||||
void BFM_B32(const Decoder::Instruction& inst);
|
||||
IR::U32 RightMask32(IR::U32 count);
|
||||
void S_BFM_B64(const Decoder::Instruction& inst);
|
||||
|
||||
@@ -375,9 +375,14 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_SUB_NC_U16:
|
||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
||||
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
|
||||
case O::V_MAD_I16: return V_MAD_I16(inst);
|
||||
case O::V_MAD_I16: return Integer16Mad(inst, true);
|
||||
case O::V_MAD_U16: return Integer16Mad(inst, false);
|
||||
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
|
||||
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
|
||||
case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true);
|
||||
case O::V_MAX3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMaxTri32, true);
|
||||
case O::V_MAX3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMaxTri32, false);
|
||||
case O::V_MED3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMedTri32, false);
|
||||
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
|
||||
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
|
||||
@@ -565,6 +570,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_LSHRREV_B64:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
|
||||
false, false);
|
||||
case O::V_ASHRREV_I64:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightArithmetic64, IR::Type::U64, true,
|
||||
false, false);
|
||||
|
||||
case O::V_XNOR_B32:
|
||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false);
|
||||
@@ -578,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_MAD_U32_U24: return Integer24(inst, false, true);
|
||||
case O::V_MUL_I32_I24: return Integer24(inst, true, false);
|
||||
case O::V_MUL_U32_U24: return Integer24(inst, false, false);
|
||||
case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true);
|
||||
case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true);
|
||||
case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst);
|
||||
case O::V_SAD_U32: return V_SAD_U32(inst);
|
||||
case O::V_ADD3_U32: return V_ADD3_U32(inst);
|
||||
|
||||
@@ -148,14 +148,14 @@ template <class S, class T> void Io(S& io, T& value) {
|
||||
IoAll(io, __VA_ARGS__); \
|
||||
}
|
||||
KYTY_IO_FIELDS(MemoryInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y)
|
||||
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
|
||||
KYTY_IO_FIELDS(ImageResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
|
||||
KYTY_IO_FIELDS(SamplerResource, a, b, c, d, e, f, g, h, i, j, k)
|
||||
KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
|
||||
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
||||
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||
A, B, C, D, E, F, G, H, I, J, K)
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
|
||||
|
||||
@@ -122,7 +122,6 @@ struct BufferResource {
|
||||
uint32_t descriptor_index = UINT32_MAX;
|
||||
uint32_t first_use_pc = 0;
|
||||
uint32_t max_byte_extent = 0;
|
||||
uint32_t write_extent = 0; // UINT32_MAX once a write is runtime-addressed or formatted
|
||||
uint32_t packed_stride = 0;
|
||||
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
|
||||
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
|
||||
@@ -543,6 +542,8 @@ struct ShaderInfo {
|
||||
bool uses_image_heap = false;
|
||||
bool uses_dispatch_threads = false;
|
||||
bool bvh = false;
|
||||
bool bvh8 = false;
|
||||
bool bvh_stack = false;
|
||||
bool subgroup_ballot = false;
|
||||
bool subgroup_vote = false;
|
||||
bool subgroup_barrier = false;
|
||||
|
||||
@@ -31,6 +31,7 @@ std::string TypeName(Type type) {
|
||||
"SamplerResource",
|
||||
"ImageAddress",
|
||||
"F64",
|
||||
"Bvh8Result",
|
||||
};
|
||||
const auto bits = static_cast<uint32_t>(type);
|
||||
if (bits == 0) {
|
||||
|
||||
@@ -28,6 +28,7 @@ enum class Type : uint32_t {
|
||||
SamplerResource = 1u << 18u,
|
||||
ImageAddress = 1u << 19u,
|
||||
F64 = 1u << 20u,
|
||||
Bvh8Result = 1u << 21u,
|
||||
};
|
||||
|
||||
constexpr Type operator|(Type lhs, Type rhs) {
|
||||
|
||||
@@ -44,6 +44,7 @@ constexpr Type AddressResource = Type::AddressResource;
|
||||
constexpr Type ImageResource = Type::ImageResource;
|
||||
constexpr Type SamplerResource = Type::SamplerResource;
|
||||
constexpr Type ImageAddress = Type::ImageAddress;
|
||||
constexpr Type Bvh8Result = Type::Bvh8Result;
|
||||
|
||||
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
|
||||
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
|
||||
@@ -76,7 +77,8 @@ bool HasSideEffects(ValueOpcode opcode) {
|
||||
return true;
|
||||
}
|
||||
const auto shared_access = SharedAccessOf(opcode);
|
||||
if (shared_access == SharedAccess::Write || shared_access == SharedAccess::Atomic ||
|
||||
if (shared_access == SharedAccess::Write || shared_access == SharedAccess::ReadWrite ||
|
||||
shared_access == SharedAccess::Atomic ||
|
||||
shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||
return true;
|
||||
}
|
||||
@@ -156,6 +158,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
|
||||
|
||||
SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case ValueOpcode::BvhStack: return SharedAccess::ReadWrite;
|
||||
case ValueOpcode::LoadSharedU8:
|
||||
case ValueOpcode::LoadSharedU16:
|
||||
case ValueOpcode::LoadSharedU32:
|
||||
|
||||
@@ -17,7 +17,7 @@ enum class ValueOpcode : uint16_t {
|
||||
};
|
||||
|
||||
enum class BufferAccess { None, Read, Write, Atomic };
|
||||
enum class SharedAccess { None, Read, Write, Atomic, Append, Consume };
|
||||
enum class SharedAccess { None, Read, Write, ReadWrite, Atomic, Append, Consume };
|
||||
enum class AddressAccess { None, Read, Write };
|
||||
enum class ImageAccess { None, Read, Write, Atomic };
|
||||
enum class ImageResourceClass { None, Sampled, Storage };
|
||||
|
||||
@@ -224,6 +224,7 @@ VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupOrU32, U32, U32)
|
||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
||||
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
||||
@@ -318,6 +319,9 @@ VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||
|
||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||
VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1)
|
||||
VALUE_OPCODE(Bvh8Intersect, Bvh8Result, U32x4, ImageAddress, U1)
|
||||
VALUE_OPCODE(Bvh8Extract, U32, Bvh8Result, U32)
|
||||
VALUE_OPCODE(BvhStack, U32x2, U32, U32, U32x4, U32x4, U1)
|
||||
VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
|
||||
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
|
||||
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
#include <unordered_map>
|
||||
@@ -186,6 +186,16 @@ private:
|
||||
ValueOpcode operation;
|
||||
};
|
||||
|
||||
// The subgroup reduction that replaces a proven whole-wave scan of operation.
|
||||
std::optional<ValueOpcode> WaveReduction(ValueOpcode operation) {
|
||||
switch (operation) {
|
||||
case ValueOpcode::UMin32: return ValueOpcode::SubgroupUMinU32;
|
||||
case ValueOpcode::UMax32: return ValueOpcode::SubgroupUMaxU32;
|
||||
case ValueOpcode::BitwiseOr32: return ValueOpcode::SubgroupOrU32;
|
||||
default: return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
||||
@@ -197,15 +207,12 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
const auto opcode = inst.GetOpcode();
|
||||
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
||||
if (const auto reduce = WaveReduction(opcode)) {
|
||||
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
||||
if (!seed.IsEmpty()) {
|
||||
const auto position = std::find_if(block->begin(), block->end(),
|
||||
[&](const Inst& candidate) { return &candidate == &inst; });
|
||||
const auto reduce = opcode == ValueOpcode::UMin32
|
||||
? ValueOpcode::SubgroupUMinU32
|
||||
: ValueOpcode::SubgroupUMaxU32;
|
||||
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
|
||||
auto& reduction = *block->PrependNewInst(position, *reduce, {seed});
|
||||
inst.ReplaceUsesWith(Value(&reduction));
|
||||
stats.rewritten_reductions++;
|
||||
}
|
||||
|
||||
@@ -1167,7 +1167,7 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
|
||||
|
||||
template <typename Predicate>
|
||||
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) {
|
||||
// The renderer checks captured scalar reads against final buffer and image write ranges.
|
||||
// Address writes can change the values control flow reads.
|
||||
if (program.has_address_writes) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -4022,10 +4022,10 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, const Inst& inst, uint32_t pc) {
|
||||
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
||||
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
|
||||
if (m_info.buffers[i].source == source) {
|
||||
Merge(m_info.buffers[i], memory, inst, pc);
|
||||
Merge(m_info.buffers[i], memory, op, pc);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -4035,14 +4035,13 @@ private:
|
||||
BufferResource resource;
|
||||
resource.source = source;
|
||||
resource.first_use_pc = pc;
|
||||
Merge(resource, memory, inst, pc);
|
||||
Merge(resource, memory, op, pc);
|
||||
m_info.buffers.push_back(resource);
|
||||
return static_cast<uint32_t>(m_info.buffers.size() - 1);
|
||||
}
|
||||
|
||||
static void Merge(BufferResource& resource, const MemoryInfo& memory, const Inst& inst,
|
||||
static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op,
|
||||
uint32_t pc) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto access = BufferAccessOf(op);
|
||||
const bool atomic = access == BufferAccess::Atomic;
|
||||
const bool write = access == BufferAccess::Write || atomic;
|
||||
@@ -4050,13 +4049,6 @@ private:
|
||||
resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory));
|
||||
resource.read = resource.read || !write || atomic;
|
||||
resource.written = resource.written || write;
|
||||
if (write) {
|
||||
// Formatted stores write the whole format; otherwise zero index, VGPR and SGPR offsets
|
||||
// leave only the immediate offset.
|
||||
const bool fixed = !memory.formatted && inst.Arg(1).Resolve() == Value(0u) &&
|
||||
inst.Arg(2).Resolve() == Value(0u) && inst.Arg(3).Resolve() == Value(0u);
|
||||
resource.write_extent = std::max(resource.write_extent, fixed ? ByteExtent(memory) : UINT32_MAX);
|
||||
}
|
||||
resource.atomic = resource.atomic || atomic;
|
||||
resource.formatted = resource.formatted || memory.formatted;
|
||||
resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer ||
|
||||
@@ -4182,7 +4174,7 @@ private:
|
||||
|
||||
void Collect(Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op == ValueOpcode::BvhIntersect) {
|
||||
if (op == ValueOpcode::BvhIntersect || op == ValueOpcode::Bvh8Intersect) {
|
||||
m_info.uses_dma = true;
|
||||
return;
|
||||
}
|
||||
@@ -4226,7 +4218,7 @@ private:
|
||||
m_info.uses_dma = true;
|
||||
return;
|
||||
}
|
||||
resource = AddBuffer(source, memory, inst, flags.pc);
|
||||
resource = AddBuffer(source, memory, op, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
Fail(flags.pc, "buffer resource limit exceeded");
|
||||
}
|
||||
|
||||
@@ -515,6 +515,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
|
||||
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
|
||||
case ValueOpcode::BvhIntersect: info.bvh = true; break;
|
||||
case ValueOpcode::Bvh8Intersect: info.bvh8 = true; break;
|
||||
case ValueOpcode::BvhStack: info.bvh_stack = true; break;
|
||||
case ValueOpcode::ConditionRef:
|
||||
info.subgroup_vote |=
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
@@ -522,7 +524,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
||||
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
|
||||
case ValueOpcode::SubgroupUMinU32:
|
||||
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::SubgroupUMaxU32:
|
||||
case ValueOpcode::SubgroupOrU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::DppMoveU32:
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
case ValueOpcode::ReadLane: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
@@ -59,15 +61,33 @@ void ShaderInit() {
|
||||
g_shader_map = std::make_unique<std::unordered_map<uint64_t, ShaderMapEntry>>();
|
||||
}
|
||||
|
||||
// The program's code and, at align16(code size), its literal buffer, as sized by the 0x30-byte
|
||||
// footer that ends a binary; the whole binary when the footer is unknown.
|
||||
static uint32_t ShaderProgramSize(const uint32_t* binary, uint32_t size) {
|
||||
constexpr uint32_t FooterSize = 0x30;
|
||||
if (size < FooterSize) return size;
|
||||
// Magic, then version, code size and literal buffer size at +0x10.
|
||||
const auto* footer = binary + (size - FooterSize) / 4;
|
||||
const auto end = Common::AlignUp(uint64_t {footer[5]}, 16) + footer[6];
|
||||
if (std::memcmp(footer, "barefoot", 8) != 0 || footer[4] != 1 || footer[5] == 0 ||
|
||||
end % 4 != 0 || end > size - FooterSize) {
|
||||
return size;
|
||||
}
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data) {
|
||||
EXIT_IF(g_shader_map == nullptr);
|
||||
|
||||
if (addr == 0 || data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
|
||||
EXIT("ShaderMapUserData shader=0x%016" PRIx64 " has invalid AGC shader_size=0x%08" PRIx32 "\n",
|
||||
EXIT("ShaderMapUserData shader=0x%016" PRIx64 " has invalid shader_size=0x%08" PRIx32 "\n",
|
||||
addr, data.code_size_bytes);
|
||||
}
|
||||
const auto hash = XXH3_64bits(reinterpret_cast<const void*>(addr), data.code_size_bytes);
|
||||
const auto* binary = reinterpret_cast<const uint32_t*>(addr);
|
||||
ShaderMapData mapped {data};
|
||||
// Translation reads the bytes the hash covers.
|
||||
mapped.code_size_bytes = ShaderProgramSize(binary, data.code_size_bytes);
|
||||
const auto hash = XXH3_64bits(binary, mapped.code_size_bytes);
|
||||
mapped.vertex_error =
|
||||
ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, mapped.vertex);
|
||||
std::scoped_lock lock(g_shader_map_mutex);
|
||||
@@ -291,8 +311,7 @@ static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserS
|
||||
const auto& metadata = data.vertex;
|
||||
|
||||
if (metadata.vertex_attrib_reg >= 0) {
|
||||
info.fetch_external = false;
|
||||
info.fetch_embedded = true;
|
||||
info.has_dynamic_vertex_fetch = true;
|
||||
info.fetch_attrib_reg = metadata.vertex_attrib_reg;
|
||||
info.fetch_buffer_reg = metadata.vertex_buffer_reg;
|
||||
|
||||
@@ -413,7 +432,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
||||
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
|
||||
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.clear();
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_embedded));
|
||||
key.push_back(static_cast<uint32_t>(info.has_dynamic_vertex_fetch));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
||||
key.push_back(info.resources_num);
|
||||
@@ -422,7 +441,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(info.lds_size_dwords);
|
||||
key.push_back(info.pa_cl_vs_out_cntl);
|
||||
key.push_back(info.user_clip_planes);
|
||||
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u));
|
||||
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u) |
|
||||
(uint32_t {info.indirect_draw} << 16u));
|
||||
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||
if (info.clip_space.enabled) {
|
||||
for (const float value: info.clip_space.scale) {
|
||||
@@ -693,9 +713,7 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
||||
|
||||
LOGF("ShaderDbgDumpInputInfo()\n");
|
||||
|
||||
LOGF("\t fetch_external = %s\n"
|
||||
"\t fetch_embedded = %s\n",
|
||||
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
|
||||
LOGF("\t has_dynamic_vertex_fetch = %s\n", info.has_dynamic_vertex_fetch ? "true" : "false");
|
||||
|
||||
for (int i = 0; i < info.resources_num; i++) {
|
||||
LOGF("\t input %d\n", i);
|
||||
|
||||
@@ -125,8 +125,7 @@ struct ShaderTessellationInputInfo {
|
||||
};
|
||||
|
||||
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||
// to. Like the hardware index VGPRs, the vertex shader's indices exclude both bases once either
|
||||
// slot is set.
|
||||
// to. A packet can leave both slots unused even when its record contains nonzero offsets.
|
||||
struct ShaderIndirectOffsets {
|
||||
static constexpr uint8_t None = UINT8_MAX;
|
||||
|
||||
@@ -159,8 +158,10 @@ struct ShaderVertexInputInfo {
|
||||
ShaderMeshInputInfo mesh;
|
||||
ShaderTessellationInputInfo tess;
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
bool fetch_external = false;
|
||||
bool fetch_embedded = false;
|
||||
// GPU-read records add Vulkan bases even when the packet patches no user SGPRs.
|
||||
bool indirect_draw = false;
|
||||
// Attribute-table metadata enables dynamic fetch detection; loads are matched later.
|
||||
bool has_dynamic_vertex_fetch = false;
|
||||
};
|
||||
|
||||
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
|
||||
|
||||
@@ -225,6 +225,11 @@ bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
|
||||
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
||||
bool TestGuestBackingOutsideAddressSpace();
|
||||
uint64_t TestGuestBackingSize();
|
||||
uint64_t TestFlexibleBackingHighWater();
|
||||
uint64_t TestGuestFreeRangeVisits();
|
||||
uint64_t TestDirectMappingCount();
|
||||
uint64_t TestFlexibleMappingCount();
|
||||
uint64_t TestBackingBytesZeroed();
|
||||
bool TestGuestFreeRangeBounds();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -203,6 +203,9 @@ public:
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::memset(m_backing_base + backing_offset, 0, size);
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
g_test_backing_bytes_zeroed += size;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -287,6 +290,18 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProtectViews(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||
std::shared_lock lock(m_mutex);
|
||||
bool ok = true;
|
||||
ForEachSparseRange(
|
||||
m_maps, vaddr, size,
|
||||
[&](const auto&, uint64_t start, uint64_t bytes) {
|
||||
ok = ok && VirtualMemory::Protect(start, bytes, mode);
|
||||
},
|
||||
[&](uint64_t, uint64_t) { ok = false; });
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
||||
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
||||
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
||||
@@ -324,15 +339,11 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Unmap(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
||||
bool Unmap(uint64_t vaddr, uint64_t size) {
|
||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = false;
|
||||
}
|
||||
|
||||
const auto end = vaddr + size;
|
||||
std::vector<MapEntry> targets;
|
||||
{
|
||||
@@ -350,42 +361,36 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MapEntry> removed;
|
||||
removed.reserve(targets.size());
|
||||
size_t removed = 0;
|
||||
auto rollback = [&] {
|
||||
while (removed != 0) {
|
||||
const auto& previous = targets[--removed];
|
||||
EXIT_IF(!MapFixed(previous.vaddr, previous.size, previous.backing_offset,
|
||||
previous.mode));
|
||||
}
|
||||
};
|
||||
for (const auto& entry: targets) {
|
||||
bool part_placeholder_preserved = false;
|
||||
if (!UnmapOne(entry.vaddr, entry.size, &part_placeholder_preserved)) {
|
||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
||||
}
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = false;
|
||||
}
|
||||
if (!UnmapOne(entry.vaddr, entry.size)) {
|
||||
rollback();
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(!part_placeholder_preserved);
|
||||
removed.push_back(entry);
|
||||
++removed;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
||||
// records this contiguous guest span as one free range, so normalize fragmented backing
|
||||
// views into one placeholder before publishing it there.
|
||||
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
||||
}
|
||||
if (removed > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||
rollback();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnmapOne(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
||||
bool UnmapOne(uint64_t vaddr, uint64_t size) {
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
if (g_test_backing_store_unmaps_before_failure == 0) {
|
||||
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
||||
@@ -428,9 +433,7 @@ public:
|
||||
m_maps.emplace(old.vaddr, old);
|
||||
return false;
|
||||
}
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -451,9 +454,6 @@ public:
|
||||
ok;
|
||||
}
|
||||
if (ok) {
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -481,9 +481,6 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
RemoveMappingsUnlocked(vaddr, size);
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
@@ -634,8 +631,6 @@ private:
|
||||
|
||||
HANDLE m_handle = nullptr;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
[[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; }
|
||||
|
||||
static int GetProtect(VirtualMemory::Mode mode) {
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: return PROT_READ;
|
||||
@@ -760,7 +755,7 @@ public:
|
||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
||||
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
||||
});
|
||||
@@ -864,11 +859,7 @@ public:
|
||||
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
||||
return false;
|
||||
}
|
||||
bool placeholder_preserved = false;
|
||||
if (!m_backing->Unmap(vaddr, size, &placeholder_preserved)) {
|
||||
return false;
|
||||
}
|
||||
if (!placeholder_preserved) {
|
||||
if (!m_backing->Unmap(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
||||
@@ -876,12 +867,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reservations live in VirtualRanges; these free-range checks only read the host placeholders.
|
||||
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
if (search_addr == 0) {
|
||||
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
||||
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
||||
@@ -898,7 +890,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
alignment = std::max<uint64_t>(alignment, PageSize());
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
||||
}
|
||||
|
||||
@@ -906,19 +898,19 @@ public:
|
||||
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
|
||||
@@ -1041,10 +1033,6 @@ private:
|
||||
return (value & (PageSize() - 1u)) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGranularityAligned(uint64_t value) const {
|
||||
return (value & (m_granularity - 1u)) == 0;
|
||||
}
|
||||
|
||||
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||
const auto end = vaddr + size;
|
||||
auto it = m_mapped.upper_bound(vaddr);
|
||||
@@ -1055,8 +1043,20 @@ private:
|
||||
const auto& region = it->second;
|
||||
const auto range_start = std::max(vaddr, region.vaddr);
|
||||
const auto range_end = std::min(end, region.vaddr + region.size);
|
||||
if (range_start < range_end &&
|
||||
!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
||||
if (range_start >= range_end) {
|
||||
continue;
|
||||
}
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
// Rollback can split a backing view without changing the owner interval.
|
||||
// Windows protection calls must stay within each actual view.
|
||||
if (region.type == MappingType::Backing) {
|
||||
if (!m_backing->ProtectViews(range_start, range_end - range_start, mode)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1312,11 +1312,20 @@ private:
|
||||
}
|
||||
|
||||
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
||||
uint64_t alignment) {
|
||||
uint64_t alignment) const {
|
||||
if (search_start >= search_end || size > search_end - search_start) {
|
||||
return 0;
|
||||
}
|
||||
for (const auto& [base, range_size]: m_free) {
|
||||
// Include the predecessor because search_start can lie inside a free range.
|
||||
auto it = m_free.upper_bound(search_start);
|
||||
if (it != m_free.begin()) {
|
||||
--it;
|
||||
}
|
||||
for (; it != m_free.end() && it->first < search_end; ++it) {
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
g_test_free_range_visits.fetch_add(1, std::memory_order_relaxed);
|
||||
#endif
|
||||
const auto& [base, range_size] = *it;
|
||||
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
||||
if (candidate < search_end && size <= search_end - candidate &&
|
||||
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
||||
@@ -1332,8 +1341,7 @@ private:
|
||||
return m_free.end();
|
||||
}
|
||||
auto it = std::prev(next);
|
||||
auto end = it->first + it->second;
|
||||
return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end());
|
||||
return GuestFreeRangeContains(it->first, it->second, vaddr, size) ? it : m_free.end();
|
||||
}
|
||||
|
||||
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
||||
|
||||
@@ -2277,7 +2277,10 @@ int KYTY_SYSV_ABI PthreadRwlockDestroy(PthreadRwlock* rwlock) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(*rwlock == nullptr);
|
||||
// SCE_PTHREAD_RWLOCK_INITIALIZER has no allocated resources until first use.
|
||||
if (*rwlock == nullptr) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock((*rwlock)->m);
|
||||
|
||||
@@ -115,7 +115,17 @@
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip"><string>Window resolution</string></property>
|
||||
<property name="toolTip"><string>Window resolution as WIDTHxHEIGHT; pick a preset or type a custom size. Non-16:9 sizes such as 21:9 show the game with black bars unless Stretch to window is on.</string></property>
|
||||
<property name="editable"><bool>true</bool></property>
|
||||
<property name="insertPolicy"><enum>QComboBox::NoInsert</enum></property>
|
||||
<item><property name="text"><string notr="true">1280x720</string></property></item>
|
||||
<item><property name="text"><string notr="true">1920x1080</string></property></item>
|
||||
<item><property name="text"><string notr="true">2560x1440</string></property></item>
|
||||
<item><property name="text"><string notr="true">3840x2160</string></property></item>
|
||||
<item><property name="text"><string notr="true">2560x1080</string></property></item>
|
||||
<item><property name="text"><string notr="true">3440x1440</string></property></item>
|
||||
<item><property name="text"><string notr="true">3840x1600</string></property></item>
|
||||
<item><property name="text"><string notr="true">5120x2160</string></property></item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@@ -244,6 +254,12 @@
|
||||
<widget class="QGroupBox" name="general_group">
|
||||
<property name="title"><string>General</string></property>
|
||||
<layout class="QVBoxLayout" name="generalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_pro">
|
||||
<property name="toolTip"><string>Enable experimental Pro mode for supported titles. PSSR uses linear scaling. Disabled by default.</string></property>
|
||||
<property name="text"><string>Pro mode (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
||||
@@ -256,6 +272,12 @@
|
||||
<property name="text"><string>Auto-hide cursor</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_stretch">
|
||||
<property name="toolTip"><string>Stretch the game image to fill the window instead of keeping its aspect ratio with black bars</string></property>
|
||||
<property name="text"><string>Stretch to window</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_perf_overlay">
|
||||
<property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property>
|
||||
@@ -390,6 +412,11 @@
|
||||
<property name="text"><string>Enable network connection</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_psn_emulation">
|
||||
<property name="text"><string>Emulate PSN (compatibility)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="networkSpacer">
|
||||
<property name="orientation"><enum>Qt::Vertical</enum></property>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
|
||||
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
|
||||
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
|
||||
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
|
||||
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
|
||||
<g transform="translate(256 268) scale(0.92) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<g transform="translate(0 12)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
|
||||
<g fill="url(#body)" stroke="#0b0d12" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M116 181 L137 121 L190 157 Z" fill="#0b0d12"/><path d="M396 181 L375 121 L322 157 Z" fill="#0b0d12"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="20"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="20"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="14"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M72 316 L92 336 M70 336 L86 352 M440 316 L420 336 M442 336 L426 352" fill="none" stroke="#0b0d12" stroke-width="10"/>
|
||||
<path d="M179 230 Q194 210 209 230" fill="none" stroke="#0b0d12" stroke-width="16"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="20"/>
|
||||
<g fill="none" stroke="#0b0d12"><g stroke-width="14"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M72 316 L92 336 M70 336 L86 352 M440 316 L420 336 M442 336 L426 352" stroke-width="10"/>
|
||||
<path d="M179 230 Q194 210 209 230" stroke-width="16"/></g>
|
||||
<circle cx="318" cy="222" r="21" fill="#0b0d12"/>
|
||||
<path d="M240 256 L272 256 L256 278 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,15 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs>
|
||||
<linearGradient id="body" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="tile" x2="0" y2="1"><stop stop-color="#3d7fe6"/><stop offset="1" stop-color="#1c47b0"/></linearGradient>
|
||||
<linearGradient id="body" x2="0" y2="1"><stop stop-color="#fff"/><stop offset="1" stop-color="#e3e9f2"/></linearGradient>
|
||||
<linearGradient id="gloss" x2="0" y2="1"><stop stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(256 268) scale(1.16) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z" fill="url(#body)"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z" fill="url(#body)"/>
|
||||
<rect x="16" y="16" width="480" height="480" rx="112" fill="url(#tile)"/>
|
||||
<path d="M16 128 A112 112 0 0 1 128 16 L384 16 A112 112 0 0 1 496 128 L496 200 Q256 150 16 236 Z" fill="url(#gloss)"/>
|
||||
<g transform="translate(256 268) scale(0.86) translate(-256 -268)" stroke-linejoin="round" stroke-linecap="round">
|
||||
<g transform="translate(0 18)" fill-opacity="0.22" stroke="#000" stroke-opacity="0.22" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/><path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z"/></g>
|
||||
<g fill="url(#body)" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M128 164 L137 128 L174 156 Z" fill="#0b0d12"/><path d="M384 164 L375 128 L338 156 Z" fill="#0b0d12"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="16"><path d="M100 196 L120 104 Q124 92 134 99 L214 160 Z"/><path d="M412 196 L392 104 Q388 92 378 99 L298 160 Z"/></g>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="none" stroke="#0b0d12" stroke-width="16"/>
|
||||
<g fill="none" stroke="#0b0d12" stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" fill="none" stroke="#0b0d12" stroke-width="7"/>
|
||||
<path d="M168 230 Q194 202 220 230" fill="none" stroke="#0b0d12" stroke-width="15"/>
|
||||
<path d="M256 154 C330 154 380 160 406 178 C432 196 447 244 459 302 C471 358 466 398 430 408 C400 416 380 398 360 368 L326 318 C316 304 306 300 290 300 L222 300 C206 300 196 304 186 318 L152 368 C132 398 112 416 82 408 C46 398 41 358 53 302 C65 244 80 196 106 178 C132 160 182 154 256 154 Z" fill="url(#body)" stroke="#0b0d12" stroke-width="16"/>
|
||||
<g fill="none" stroke="#0b0d12"><g stroke-width="9"><path d="M82 260 C100 274 118 288 128 304 C134 316 121 350 108 388"/><path d="M430 260 C412 274 394 288 384 304 C378 316 391 350 404 388"/></g>
|
||||
<path d="M64 322 L82 340 M62 340 L76 354 M448 322 L430 340 M450 340 L436 354" stroke-width="7"/>
|
||||
<path d="M168 230 Q194 202 220 230" stroke-width="15"/></g>
|
||||
<circle cx="322" cy="220" r="22" fill="#0b0d12"/>
|
||||
<path d="M238 252 L274 252 L256 276 Z" fill="#0b0d12" stroke="#0b0d12" stroke-width="8"/>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -21,10 +21,7 @@ inline QStringList EnumToList() {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
int count = me.keyCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto key = QString(me.key(i));
|
||||
ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit()
|
||||
? key.remove('R').toLower()
|
||||
: key);
|
||||
ret << QString(me.key(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -32,16 +29,12 @@ inline QStringList EnumToList() {
|
||||
template <class T>
|
||||
T TextToEnum(const QString& text) {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
return static_cast<T>(me.keyToValue(
|
||||
((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data()));
|
||||
return static_cast<T>(me.keyToValue(text.toUtf8().constData()));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
QString EnumToText(T value) {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
auto key = QString(me.valueToKey(static_cast<int>(value)));
|
||||
return (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() ? key.remove('R').toLower()
|
||||
: key);
|
||||
return QString(QMetaEnum::fromType<T>().valueToKey(static_cast<int>(value)));
|
||||
}
|
||||
|
||||
struct ControllerSettings {
|
||||
@@ -80,13 +73,10 @@ public:
|
||||
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
||||
|
||||
enum class Resolution {
|
||||
R1280X720,
|
||||
R1920X1080,
|
||||
R2560X1440,
|
||||
R3840X2160,
|
||||
};
|
||||
Q_ENUM(Resolution)
|
||||
static constexpr int MIN_SCREEN_SIZE = 320;
|
||||
static constexpr int MAX_SCREEN_SIZE = 16384;
|
||||
|
||||
static QString NormalizeResolution(const QString& text);
|
||||
|
||||
enum class ShaderOptimizationType { None, Size, Performance };
|
||||
Q_ENUM(ShaderOptimizationType)
|
||||
@@ -117,7 +107,7 @@ public:
|
||||
// Controller preferences always come from the global configuration.
|
||||
ControllerSettings controller;
|
||||
|
||||
Resolution screen_resolution = Resolution::R1280X720;
|
||||
QString screen_resolution = QStringLiteral("1280x720");
|
||||
QString user_name = "Kyty";
|
||||
int user_id = Config::DEFAULT_USER_ID;
|
||||
QString audio_input_device;
|
||||
@@ -126,6 +116,7 @@ public:
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool stretch_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool readback_tiled_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
@@ -137,6 +128,7 @@ public:
|
||||
bool perf_overlay_enabled = false;
|
||||
bool skip_notice_screen = false;
|
||||
bool network_connected = false;
|
||||
bool psn_emulation_enabled = false;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -150,6 +142,7 @@ public:
|
||||
QString printf_output_file = "_kyty.txt";
|
||||
bool profiler_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool pro_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
#if defined(_WIN32)
|
||||
bool red_zone_protection_enabled = false;
|
||||
@@ -168,6 +161,7 @@ public:
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
hdr_enabled = other.hdr_enabled;
|
||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||
stretch_enabled = other.stretch_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
readback_tiled_images = other.readback_tiled_images;
|
||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||
@@ -179,6 +173,7 @@ public:
|
||||
perf_overlay_enabled = other.perf_overlay_enabled;
|
||||
skip_notice_screen = other.skip_notice_screen;
|
||||
network_connected = other.network_connected;
|
||||
psn_emulation_enabled = other.psn_emulation_enabled;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
@@ -192,6 +187,7 @@ public:
|
||||
printf_output_file = other.printf_output_file;
|
||||
profiler_enabled = other.profiler_enabled;
|
||||
renderdoc_enabled = other.renderdoc_enabled;
|
||||
pro_enabled = other.pro_enabled;
|
||||
amd_cpu_enabled = other.amd_cpu_enabled;
|
||||
#if defined(_WIN32)
|
||||
red_zone_protection_enabled = other.red_zone_protection_enabled;
|
||||
|
||||
@@ -46,11 +46,15 @@ private:
|
||||
QLineEdit* m_addons_dir_edit = nullptr;
|
||||
QToolButton* m_remove_game_dir_button = nullptr;
|
||||
bool m_global_settings = false;
|
||||
bool m_geometry_restored = false;
|
||||
|
||||
protected:
|
||||
void Init(const Configuration& info);
|
||||
void InitGameDirectories();
|
||||
void AddGameDirectoryItem(const QString& dir);
|
||||
void MakeTabsScrollable();
|
||||
void GrowToFitContents();
|
||||
void ShowTab(QWidget* page);
|
||||
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
QString Configuration::NormalizeResolution(const QString& text) {
|
||||
// Older settings stored the enum key, e.g. "R1920X1080".
|
||||
static const QRegularExpression re(QStringLiteral("^\\s*R?(\\d+)\\s*[xX]\\s*(\\d+)\\s*$"));
|
||||
const auto match = re.match(text);
|
||||
if (!match.hasMatch()) {
|
||||
return {};
|
||||
}
|
||||
const int width = match.captured(1).toInt();
|
||||
const int height = match.captured(2).toInt();
|
||||
if (width < MIN_SCREEN_SIZE || height < MIN_SCREEN_SIZE || width > MAX_SCREEN_SIZE ||
|
||||
height > MAX_SCREEN_SIZE) {
|
||||
return {};
|
||||
}
|
||||
return QStringLiteral("%1x%2").arg(width).arg(height);
|
||||
}
|
||||
|
||||
QVariantMap Configuration::GameSettings() const {
|
||||
QVariantMap values;
|
||||
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
|
||||
@@ -12,6 +30,7 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(hdr_enabled);
|
||||
KYTY_CFG_SET(hide_cursor_enabled);
|
||||
KYTY_CFG_SET(stretch_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(readback_tiled_images);
|
||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||
@@ -23,6 +42,7 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(perf_overlay_enabled);
|
||||
KYTY_CFG_SET(skip_notice_screen);
|
||||
KYTY_CFG_SET(network_connected);
|
||||
KYTY_CFG_SET(psn_emulation_enabled);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
@@ -36,6 +56,7 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(printf_output_file);
|
||||
KYTY_CFG_SET(profiler_enabled);
|
||||
KYTY_CFG_SET(renderdoc_enabled);
|
||||
KYTY_CFG_SET(pro_enabled);
|
||||
KYTY_CFG_SET(amd_cpu_enabled);
|
||||
#if defined(_WIN32)
|
||||
KYTY_CFG_SET(red_zone_protection_enabled);
|
||||
@@ -48,7 +69,8 @@ QVariantMap Configuration::GameSettings() const {
|
||||
template <class Settings>
|
||||
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
|
||||
KYTY_CFG_GET(screen_resolution);
|
||||
const auto resolution = NormalizeResolution(s.value("screen_resolution").toString());
|
||||
screen_resolution = resolution.isEmpty() ? QStringLiteral("1280x720") : resolution;
|
||||
user_name = s.value("user_name", user_name).toString();
|
||||
bool user_id_ok = false;
|
||||
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
|
||||
@@ -64,6 +86,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(hdr_enabled);
|
||||
KYTY_CFG_GET(hide_cursor_enabled);
|
||||
KYTY_CFG_GET(stretch_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(readback_tiled_images);
|
||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||
@@ -75,6 +98,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(perf_overlay_enabled);
|
||||
KYTY_CFG_GET(skip_notice_screen);
|
||||
KYTY_CFG_GET(network_connected);
|
||||
KYTY_CFG_GET(psn_emulation_enabled);
|
||||
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s.value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
@@ -91,6 +115,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(printf_output_file);
|
||||
KYTY_CFG_GET(profiler_enabled);
|
||||
KYTY_CFG_GET(renderdoc_enabled);
|
||||
pro_enabled = s.value("pro_enabled", false).toBool();
|
||||
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
|
||||
#if defined(_WIN32)
|
||||
red_zone_protection_enabled =
|
||||
@@ -134,7 +159,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
|
||||
if (!it.value().isString()) {
|
||||
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
|
||||
}
|
||||
values.insert(it.key(), it.value().toString());
|
||||
const auto value = it.value().toString();
|
||||
values.insert(it.key(), it.key() == "screen_resolution" ? NormalizeResolution(value)
|
||||
: value);
|
||||
}
|
||||
|
||||
ReadGameSettingsValues(values);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QScreen>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
@@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) {
|
||||
}
|
||||
|
||||
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog),
|
||||
m_info(info) {
|
||||
: QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
|
||||
m_ui(new Ui::ConfigurationEditDialog), m_info(info) {
|
||||
m_ui->setupUi(this);
|
||||
setMinimumWidth(width());
|
||||
MakeTabsScrollable();
|
||||
InitGameDirectories();
|
||||
m_ui->controller_group->setVisible(false);
|
||||
m_ui->settings_button->setVisible(false);
|
||||
@@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
restoreGeometry(g_last_geometry);
|
||||
|
||||
Init(info);
|
||||
m_geometry_restored = restoreGeometry(g_last_geometry);
|
||||
if (!m_geometry_restored) {
|
||||
GrowToFitContents();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ConfigurationEditDialog::g_last_geometry;
|
||||
@@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
info.audio_input_device);
|
||||
}
|
||||
microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
|
||||
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
|
||||
m_ui->comboBox_screen_resolution->setCurrentText(info.screen_resolution);
|
||||
ListInit(m_ui->comboBox_present_mode, info.present_mode);
|
||||
m_ui->comboBox_gpu->clear();
|
||||
m_ui->comboBox_gpu->addItem(tr("Auto"));
|
||||
@@ -285,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||
m_ui->checkBox_stretch->setChecked(info.stretch_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||
@@ -293,6 +298,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled);
|
||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||
m_ui->checkBox_network_connected->setChecked(info.network_connected);
|
||||
m_ui->checkBox_psn_emulation->setChecked(info.psn_emulation_enabled);
|
||||
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
||||
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
|
||||
m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled);
|
||||
@@ -306,6 +312,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled);
|
||||
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
||||
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
|
||||
m_ui->checkBox_pro->setChecked(info.pro_enabled);
|
||||
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
|
||||
#if defined(__APPLE__)
|
||||
m_ui->checkBox_amd_cpu->setVisible(false);
|
||||
@@ -413,8 +420,46 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const Q
|
||||
m_game_dirs_group->setVisible(true);
|
||||
m_ui->controller_group->setVisible(true);
|
||||
update_game_directory_buttons();
|
||||
if (!m_geometry_restored) {
|
||||
GrowToFitContents();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::MakeTabsScrollable() {
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
auto* page = tabs->widget(i);
|
||||
auto title = tabs->tabText(i);
|
||||
auto* scroll = new QScrollArea;
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
tabs->removeTab(i);
|
||||
scroll->setWidget(page);
|
||||
tabs->insertTab(i, scroll, title);
|
||||
}
|
||||
tabs->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::GrowToFitContents() {
|
||||
layout()->activate();
|
||||
resize(size().expandedTo(minimumSizeHint()));
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
QSize content;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
content = content.expandedTo(
|
||||
static_cast<QScrollArea*>(tabs->widget(i))->widget()->minimumSizeHint());
|
||||
}
|
||||
const QSize chrome = size() - tabs->currentWidget()->size();
|
||||
resize(size().expandedTo(content + chrome).boundedTo(screen()->availableGeometry().size()));
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::ShowTab(QWidget* page) {
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
if (tabs->widget(i)->isAncestorOf(page)) {
|
||||
tabs->setCurrentIndex(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ConfigurationEditDialog::GetAddonsDirectory() const {
|
||||
@@ -474,13 +519,14 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
|
||||
}
|
||||
info.screen_resolution =
|
||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||
Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText());
|
||||
info.present_mode =
|
||||
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||
info.stretch_enabled = ui.checkBox_stretch->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||
@@ -489,6 +535,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked();
|
||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||
info.network_connected = ui.checkBox_network_connected->isChecked();
|
||||
info.psn_emulation_enabled = ui.checkBox_psn_emulation->isChecked();
|
||||
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
|
||||
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
|
||||
info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked();
|
||||
@@ -497,6 +544,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
||||
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
|
||||
info.pro_enabled = ui.checkBox_pro->isChecked();
|
||||
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
|
||||
#if defined(_WIN32)
|
||||
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
|
||||
@@ -518,7 +566,7 @@ void ConfigurationEditDialog::save() {
|
||||
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
|
||||
m_ui->lineEdit_cmd_dump_folder}) {
|
||||
if (field->isEnabled() && field->text().isEmpty()) {
|
||||
m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab);
|
||||
ShowTab(m_ui->debug_tab);
|
||||
field->setFocus();
|
||||
break;
|
||||
}
|
||||
@@ -540,6 +588,16 @@ void ConfigurationEditDialog::save() {
|
||||
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
||||
return;
|
||||
}
|
||||
if (Configuration::NormalizeResolution(m_ui->comboBox_screen_resolution->currentText())
|
||||
.isEmpty()) {
|
||||
ShowTab(m_ui->settings_tab);
|
||||
m_ui->comboBox_screen_resolution->setFocus();
|
||||
QMessageBox::critical(this, tr("Save failed"),
|
||||
tr("Screen resolution must be WIDTHxHEIGHT, each between %1 and %2")
|
||||
.arg(Configuration::MIN_SCREEN_SIZE)
|
||||
.arg(Configuration::MAX_SCREEN_SIZE));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateInfo(m_info, *m_ui, m_global_settings);
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
|
||||
#endif
|
||||
|
||||
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
|
||||
main_dialog->setWindowFlags(Qt::Dialog | Qt::WindowMinMaxButtonsHint |
|
||||
Qt::WindowCloseButtonHint);
|
||||
|
||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||
Qt::QueuedConnection);
|
||||
@@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) {
|
||||
|
||||
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
QStringList args;
|
||||
auto r = EnumToText(info.screen_resolution).split('x');
|
||||
auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x');
|
||||
|
||||
if (r.size() != 2) {
|
||||
return {};
|
||||
@@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
||||
AddFlag(args, info.hdr_enabled, "--hdr");
|
||||
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
|
||||
AddFlag(args, info.stretch_enabled, "--stretch");
|
||||
AddFlag(args, info.readback_linear_images, "--readback-linear-images");
|
||||
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
||||
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
|
||||
@@ -274,6 +276,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
AddFlag(args, info.perf_overlay_enabled, "--perf-overlay");
|
||||
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
|
||||
AddFlag(args, info.network_connected, "--network-connected");
|
||||
AddFlag(args, info.psn_emulation_enabled, "--emulate-psn");
|
||||
AddFlag(args, info.tessellation_enabled, "--tessellation");
|
||||
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
|
||||
AddFlag(args, info.shader_cache_enabled, "--shader-cache");
|
||||
@@ -289,6 +292,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--printf-direction" << EnumToText(info.printf_direction);
|
||||
args << "--printf-output-file" << info.printf_output_file;
|
||||
AddFlag(args, info.profiler_enabled, "--profile");
|
||||
AddFlag(args, info.pro_enabled, "--trinity");
|
||||
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
|
||||
#if defined(_WIN32)
|
||||
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -839,8 +840,8 @@ static bool is_native_reg_indirect_packet(const uint32_t* cmd, RegIndirectPacket
|
||||
return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type);
|
||||
}
|
||||
|
||||
static constexpr bool is_trinity_mode() {
|
||||
return false;
|
||||
static bool is_trinity_mode() {
|
||||
return Loader::SystemContentIsTrinityMode();
|
||||
}
|
||||
|
||||
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "libs/ajm/decoder.h"
|
||||
#include "libs/ajm/mp3_decoder.h"
|
||||
#include "libs/ajm/opus_decoder.h"
|
||||
#include "libs/ajm/resampler.h"
|
||||
#include "libs/audio.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -63,12 +64,6 @@ struct AjmSidebandMFrame {
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
struct AjmSidebandResampleInfo {
|
||||
float ratio;
|
||||
int32_t num_samples;
|
||||
uint32_t reserved[8];
|
||||
};
|
||||
|
||||
struct AjmGetStatisticsResult {
|
||||
AjmSidebandResult result;
|
||||
float engine_usage_batch;
|
||||
@@ -104,6 +99,8 @@ constexpr uint64_t AJM_FLAG_RUN_GET_CODEC_INFO = 1ull << 11u;
|
||||
constexpr uint64_t AJM_FLAG_RUN_MULTIPLE_FRAMES = 1ull << 12u;
|
||||
constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u;
|
||||
constexpr uint64_t AJM_FLAG_CONTROL_INITIALIZE = 1ull << 14u;
|
||||
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE = 1ull << 15u;
|
||||
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE_EX = 1ull << 16u;
|
||||
constexpr uint64_t AJM_FLAG_SIDEBAND_RESAMPLE_INFO = 1ull << 44u;
|
||||
constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u;
|
||||
constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u;
|
||||
@@ -177,6 +174,7 @@ struct AjmInstanceState {
|
||||
uint64_t flags = 0;
|
||||
AjmGaplessState gapless;
|
||||
std::unique_ptr<AjmDecoder> decoder;
|
||||
AjmResampler resampler;
|
||||
};
|
||||
|
||||
static std::mutex g_ajm_instances_mutex;
|
||||
@@ -358,7 +356,7 @@ static void AjmWriteSideband(uint64_t flags, void* sideband_output, size_t sideb
|
||||
offset + AJM_SIDEBAND_RESAMPLE_INFO_SIZE <= sideband_output_size) {
|
||||
auto* info = reinterpret_cast<AjmSidebandResampleInfo*>(
|
||||
static_cast<uint8_t*>(sideband_output) + offset);
|
||||
info->ratio = 1.0f;
|
||||
*info = decode_result.resample_info;
|
||||
offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE;
|
||||
}
|
||||
|
||||
@@ -425,6 +423,12 @@ static AjmDecodeResult AjmDecodeInstance(uint32_t instance, const void* input, s
|
||||
return result;
|
||||
}
|
||||
|
||||
// The resampler requires one complete decoded packet per decoder call.
|
||||
if (state->codec == static_cast<uint32_t>(AjmCodec::DecOpus) &&
|
||||
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) != 0) {
|
||||
return state->resampler.Decode(*state->decoder, state->flags, input, input_size, output,
|
||||
output_size, multiple_frames, &state->gapless);
|
||||
}
|
||||
return state->decoder->Decode(input, input_size, output, output_size, multiple_frames,
|
||||
&state->gapless);
|
||||
}
|
||||
@@ -514,6 +518,32 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
|
||||
sideband_input = static_cast<const uint8_t*>(sideband_input) + AJM_SIDEBAND_GAPLESS_SIZE;
|
||||
sideband_input_size -= AJM_SIDEBAND_GAPLESS_SIZE;
|
||||
}
|
||||
for (const auto flag : {AJM_FLAG_CONTROL_RESAMPLE, AJM_FLAG_CONTROL_RESAMPLE_EX}) {
|
||||
if ((flags & flag) == 0) {
|
||||
continue;
|
||||
}
|
||||
const bool extended = flag == AJM_FLAG_CONTROL_RESAMPLE_EX;
|
||||
const size_t size = extended ? 12 : 8;
|
||||
if (sideband_input == nullptr || sideband_input_size < size ||
|
||||
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) == 0) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
float ratio, change = 0;
|
||||
uint32_t resample_flags;
|
||||
const auto* data = static_cast<const uint8_t*>(sideband_input);
|
||||
std::memcpy(&ratio, data, sizeof(ratio));
|
||||
if (extended) {
|
||||
std::memcpy(&change, data + sizeof(ratio), sizeof(change));
|
||||
}
|
||||
std::memcpy(&resample_flags, data + size - sizeof(resample_flags), sizeof(resample_flags));
|
||||
result.result = state->resampler.SetParameters(ratio, change, resample_flags);
|
||||
if (result.result != OK) {
|
||||
return result;
|
||||
}
|
||||
sideband_input = data + size;
|
||||
sideband_input_size -= size;
|
||||
}
|
||||
if ((flags & AJM_FLAG_CONTROL_RESET) != 0) {
|
||||
state->gapless.Reset();
|
||||
decoder->Reset();
|
||||
@@ -524,6 +554,7 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
|
||||
result = decoder->Initialize(sideband_input, sideband_input_size);
|
||||
}
|
||||
|
||||
result.resample_info = state->resampler.GetInfo();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -572,6 +603,7 @@ static AjmSidebandFormat AjmGetInstanceFormat(uint32_t instance,
|
||||
if (out_result != nullptr) {
|
||||
*out_result = state->decoder->MakeResult();
|
||||
out_result->format = state->decoder->GetFormat();
|
||||
out_result->resample_info = state->resampler.GetInfo();
|
||||
}
|
||||
return state->decoder->GetFormat();
|
||||
}
|
||||
@@ -921,7 +953,13 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t
|
||||
float ratio, uint32_t flags, void* result) {
|
||||
PRINT_NAME();
|
||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
||||
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE);
|
||||
const struct {
|
||||
float ratio;
|
||||
uint32_t flags;
|
||||
} params {ratio, flags};
|
||||
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE, ¶ms,
|
||||
sizeof(params), nullptr, nullptr);
|
||||
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
|
||||
LOGF("\t instance = 0x%08" PRIx32 ", ratio = %f, flags = 0x%08" PRIx32 "\n", instance,
|
||||
static_cast<double>(ratio), flags);
|
||||
return AjmAppendJob(info, AJM_JOB_SET_RESAMPLE_PARAMETERS_SIZE, "set-resample");
|
||||
@@ -933,7 +971,14 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParametersEx(AjmBatchInfo* info, uint32_
|
||||
void* result) {
|
||||
PRINT_NAME();
|
||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
||||
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE);
|
||||
const struct {
|
||||
float ratio;
|
||||
float change;
|
||||
uint32_t flags;
|
||||
} params {ratio_start, ratio_change_per_sample, flags};
|
||||
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE_EX, ¶ms,
|
||||
sizeof(params), nullptr, nullptr);
|
||||
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
|
||||
LOGF("\t instance = 0x%08" PRIx32 ", ratio_start = %f, ratio_change = %f, flags = 0x%08" PRIx32
|
||||
"\n",
|
||||
instance, static_cast<double>(ratio_start), static_cast<double>(ratio_change_per_sample),
|
||||
@@ -952,7 +997,7 @@ int KYTY_SYSV_ABI AjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instan
|
||||
if (result != nullptr) {
|
||||
auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) +
|
||||
AJM_SIDEBAND_RESULT_SIZE);
|
||||
resample->ratio = 1.0f;
|
||||
*resample = decode_result.resample_info;
|
||||
}
|
||||
LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
|
||||
return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info");
|
||||
|
||||
@@ -56,6 +56,12 @@ enum class AjmSampleEncoding : uint32_t {
|
||||
Float = 2,
|
||||
};
|
||||
|
||||
struct AjmSidebandResampleInfo {
|
||||
float ratio = 1.0f;
|
||||
int32_t num_samples = 0;
|
||||
uint32_t reserved[8] {};
|
||||
};
|
||||
|
||||
struct AjmDecodeResult {
|
||||
int32_t result = OK;
|
||||
int32_t internal_result = OK;
|
||||
@@ -65,6 +71,7 @@ struct AjmDecodeResult {
|
||||
uint32_t frames = 0;
|
||||
uint32_t frames_per_packet = 0;
|
||||
AjmSidebandFormat format {};
|
||||
AjmSidebandResampleInfo resample_info {};
|
||||
};
|
||||
|
||||
constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
|
||||
#include "libs/ajm/decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_GAPLESS_LOOP = 1ull << 10u;
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_RESAMPLE = 1ull << 12u;
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_SEAMLESS_LOOP = 1ull << 13u;
|
||||
constexpr uint32_t AJM_RESAMPLE_FLAG_RESET = 1u;
|
||||
constexpr uint32_t AJM_RESAMPLE_FLAG_END = 2u;
|
||||
constexpr uint32_t AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER = 8u;
|
||||
|
||||
class AjmResampler {
|
||||
public:
|
||||
int32_t SetParameters(float ratio, float change, uint32_t flags) {
|
||||
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0 &&
|
||||
(!std::isfinite(ratio) || ratio < 0 || ratio > 4 || !std::isfinite(change))) {
|
||||
return AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
if ((flags & AJM_RESAMPLE_FLAG_RESET) != 0) {
|
||||
m_pcm.clear();
|
||||
m_position = 0;
|
||||
m_end = false;
|
||||
}
|
||||
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0) {
|
||||
m_ratio = ratio;
|
||||
m_change = std::abs(change) < 0.000001f ? 0 : change;
|
||||
}
|
||||
m_end |= (flags & AJM_RESAMPLE_FLAG_END) != 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] AjmSidebandResampleInfo GetInfo() const {
|
||||
AjmSidebandResampleInfo info;
|
||||
info.ratio = static_cast<float>(m_ratio);
|
||||
info.num_samples = m_end && m_position >= Samples()
|
||||
? -1
|
||||
: static_cast<int32_t>(std::ceil(Samples() - m_position));
|
||||
return info;
|
||||
}
|
||||
|
||||
AjmDecodeResult Decode(AjmDecoder& decoder, uint64_t flags, const void* input,
|
||||
size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) {
|
||||
auto result = decoder.MakeResult();
|
||||
if (result.format.channel_num == 0) {
|
||||
return decoder.Decode(input, input_size, output, output_size, multiple_frames, gapless);
|
||||
}
|
||||
bool stop_decoding = false;
|
||||
for (;;) {
|
||||
const auto format = decoder.GetFormat();
|
||||
const auto encoding = static_cast<AjmSampleEncoding>(format.sample_encoding);
|
||||
const auto sample_bytes = format.channel_num * AjmBytesPerSample(encoding);
|
||||
if (!m_pcm.empty() && sample_bytes != m_sample_bytes) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
if (output == nullptr || output_size - result.output_written < sample_bytes) {
|
||||
if (m_position < Samples() || result.input_consumed < input_size) {
|
||||
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const auto index = static_cast<size_t>(m_position);
|
||||
const auto fraction = m_position - static_cast<double>(index);
|
||||
const bool need_next = fraction != 0 && index + 1 >= Samples();
|
||||
if (index >= Samples() || need_next) {
|
||||
Compact();
|
||||
if (!stop_decoding && input != nullptr && result.input_consumed < input_size &&
|
||||
(gapless == nullptr || !gapless->IsEnd())) {
|
||||
// A decode job can produce up to six 20 ms Opus frames in one packet.
|
||||
const auto channels = std::max<uint32_t>(format.channel_num, flags & 0x7fu);
|
||||
m_frame.resize(5760 * channels * AjmBytesPerSample(encoding));
|
||||
auto decoded =
|
||||
decoder.Decode(static_cast<const uint8_t*>(input) + result.input_consumed,
|
||||
input_size - result.input_consumed, m_frame.data(),
|
||||
m_frame.size(), false, gapless);
|
||||
result.input_consumed += decoded.input_consumed;
|
||||
result.frames += decoded.frames;
|
||||
result.frames_per_packet = decoded.frames_per_packet;
|
||||
result.total_decoded_samples = decoded.total_decoded_samples;
|
||||
result.format = decoded.format;
|
||||
result.result |= decoded.result;
|
||||
result.internal_result = decoded.internal_result;
|
||||
m_sample_bytes = decoded.format.channel_num * AjmBytesPerSample(encoding);
|
||||
m_pcm.insert(m_pcm.end(), m_frame.begin(),
|
||||
m_frame.begin() + decoded.output_written);
|
||||
stop_decoding =
|
||||
!multiple_frames || decoded.result != OK || decoded.input_consumed == 0;
|
||||
if (gapless != nullptr && gapless->IsEnd() &&
|
||||
(flags & AJM_INSTANCE_FLAG_GAPLESS_LOOP) != 0) {
|
||||
gapless->Reset();
|
||||
stop_decoding |= (flags & AJM_INSTANCE_FLAG_SEAMLESS_LOOP) == 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!m_end || m_position >= Samples()) {
|
||||
if (!m_end && !stop_decoding && (gapless == nullptr || !gapless->IsEnd())) {
|
||||
result.result |= AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto current = static_cast<size_t>(m_position);
|
||||
const auto next = std::min(current + 1, Samples() - 1);
|
||||
auto* dst = static_cast<uint8_t*>(output) + result.output_written;
|
||||
const auto* a = m_pcm.data() + current * sample_bytes;
|
||||
const auto* b = m_pcm.data() + next * sample_bytes;
|
||||
const auto weight = m_position - static_cast<double>(current);
|
||||
if (weight == 0) {
|
||||
std::memcpy(dst, a, sample_bytes);
|
||||
} else {
|
||||
switch (encoding) {
|
||||
case AjmSampleEncoding::S16:
|
||||
Interpolate<int16_t>(dst, a, b, format.channel_num, weight);
|
||||
break;
|
||||
case AjmSampleEncoding::S32:
|
||||
Interpolate<int32_t>(dst, a, b, format.channel_num, weight);
|
||||
break;
|
||||
case AjmSampleEncoding::Float:
|
||||
Interpolate<float>(dst, a, b, format.channel_num, weight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.output_written += sample_bytes;
|
||||
m_position += m_ratio;
|
||||
m_ratio = std::clamp(m_ratio + m_change, 0.0, 4.0);
|
||||
if (m_ratio == 0 || m_ratio == 4) {
|
||||
m_change = 0;
|
||||
}
|
||||
}
|
||||
Compact();
|
||||
result.resample_info = GetInfo();
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static void Interpolate(uint8_t* dst, const uint8_t* a, const uint8_t* b, uint32_t channels,
|
||||
double weight) {
|
||||
for (uint32_t channel = 0; channel < channels; ++channel) {
|
||||
T first, second;
|
||||
std::memcpy(&first, a + channel * sizeof(T), sizeof(T));
|
||||
std::memcpy(&second, b + channel * sizeof(T), sizeof(T));
|
||||
const auto value =
|
||||
static_cast<T>(first + (static_cast<double>(second) - first) * weight);
|
||||
std::memcpy(dst + channel * sizeof(T), &value, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t Samples() const {
|
||||
return m_sample_bytes == 0 ? 0 : m_pcm.size() / m_sample_bytes;
|
||||
}
|
||||
|
||||
void Compact() {
|
||||
const auto consumed = std::min(static_cast<size_t>(m_position), Samples());
|
||||
const auto bytes = consumed * m_sample_bytes;
|
||||
if (bytes != 0) {
|
||||
std::memmove(m_pcm.data(), m_pcm.data() + bytes, m_pcm.size() - bytes);
|
||||
m_pcm.resize(m_pcm.size() - bytes);
|
||||
m_position -= static_cast<double>(consumed);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> m_pcm;
|
||||
std::vector<uint8_t> m_frame;
|
||||
size_t m_sample_bytes = 0;
|
||||
double m_position = 0;
|
||||
double m_ratio = 1;
|
||||
double m_change = 0;
|
||||
bool m_end = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
@@ -519,7 +519,20 @@ void GameController::SetDevices(int active_id) {
|
||||
m_trigger_state = {};
|
||||
}
|
||||
|
||||
// Input that changes no button or touch contact updates the newest unread sample, so a burst of
|
||||
// stick or sensor events between two reads cannot push unread button or touch changes out.
|
||||
void GameController::AddState() {
|
||||
if (m_states_num != 0) {
|
||||
const auto newest = (m_first_state + m_states_num - 1) % STATES_MAX;
|
||||
auto& last = m_states[newest];
|
||||
if (!m_obtained[newest] && last.buttons == m_state.buttons &&
|
||||
last.touch[0].down == m_state.touch[0].down &&
|
||||
last.touch[1].down == m_state.touch[1].down) {
|
||||
last = m_state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_states_num >= STATES_MAX) {
|
||||
m_states_num = STATES_MAX - 1;
|
||||
m_first_state = (m_first_state + 1) % STATES_MAX;
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace AppContent {
|
||||
|
||||
constexpr uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3;
|
||||
constexpr int APP_CONTENT_ERROR_PARAMETER = -2133262334; /* 0x80D90002 */
|
||||
constexpr int APP_CONTENT_ERROR_BUSY = -2133262333; /* 0x80D90003 */
|
||||
constexpr int APP_CONTENT_ERROR_NOT_MOUNTED = -2133262332; /* 0x80D90004 */
|
||||
constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */
|
||||
constexpr int APP_CONTENT_ERROR_MOUNT_FULL = -2133262330; /* 0x80D90006 */
|
||||
@@ -210,10 +211,8 @@ int KYTY_SYSV_ABI AppContentAddcontMount(uint32_t servic
|
||||
|
||||
for (const auto& m: g_addcont_mounts) {
|
||||
if (m.label == entry->entitlement_label) {
|
||||
const auto guest_point = m.point.substr(0, m.point.size() - 1);
|
||||
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1);
|
||||
LOGF("\t already mounted %s\n", guest_point.c_str());
|
||||
return OK;
|
||||
LOGF("\t already mounted %s\n", m.point.c_str());
|
||||
return APP_CONTENT_ERROR_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1162,6 +1162,8 @@ LIB_DEFINE(InitAudio_1_Ngs2) {
|
||||
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
|
||||
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
|
||||
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
|
||||
LIB_FUNC("9eic4AmjGVI", Ngs2::Ngs2VoiceQueryInfo);
|
||||
LIB_FUNC("WCayTgob7-o", Ngs2::Ngs2VoiceGetPortInfo);
|
||||
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
|
||||
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
|
||||
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Audio {
|
||||
@@ -170,15 +171,10 @@ static std::atomic_uint64_t g_audioout2_next_context {1};
|
||||
static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
int object_audio_handle = 0;
|
||||
};
|
||||
static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32;
|
||||
static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512;
|
||||
static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10;
|
||||
static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4;
|
||||
|
||||
struct AudioOut2PortStateEntry {
|
||||
bool used = false;
|
||||
@@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry {
|
||||
bool attributes_locked = false;
|
||||
std::vector<uint8_t> pcm_data;
|
||||
};
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
int object_audio_handle = 0;
|
||||
uint32_t max_ports = 0;
|
||||
// Channel slots precede object slots; each partition has its own creation limit.
|
||||
std::vector<AudioOut2PortStateEntry> ports;
|
||||
};
|
||||
struct AudioOut2SpeakerArrayState {
|
||||
bool used = false;
|
||||
uint32_t num_speakers = 0;
|
||||
@@ -223,13 +232,16 @@ struct AudioOut2Submission {
|
||||
static Common::Mutex g_audioout2_state_mutex;
|
||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||
static Common::Mutex g_audioout2_latency_mutex;
|
||||
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
||||
|
||||
static_assert(AUDIOOUT2_CHANNEL_PORTS_MAX + AUDIOOUT2_OBJECT_PORTS_MAX <=
|
||||
(1u << AUDIOOUT2_PORT_SLOT_BITS));
|
||||
static_assert(g_audioout2_contexts.size() == (1u << AUDIOOUT2_CONTEXT_SLOT_BITS));
|
||||
|
||||
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
||||
@@ -262,6 +274,11 @@ static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& co
|
||||
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
||||
}
|
||||
|
||||
// Ticks of a sixth of a microsecond: one 48 kHz sample lasts exactly 125 of them.
|
||||
static uint64_t audioout2_clock() {
|
||||
return LibKernel::KernelGetProcessTime() * 6;
|
||||
}
|
||||
|
||||
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
||||
const auto sample_count = (grains == 0 ? 512u : grains);
|
||||
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
||||
@@ -331,49 +348,51 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = LibKernel::KernelGetProcessTime();
|
||||
const auto now = audioout2_clock();
|
||||
if (state->last_update == 0 || state->queued == 0) {
|
||||
state->last_update = now;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
||||
if (now <= state->last_update || grain_micros == 0) {
|
||||
if (now <= state->last_update) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto elapsed = now - state->last_update;
|
||||
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
||||
const auto grain = uint64_t {state->num_grains} * 125;
|
||||
const auto drained = std::min<uint64_t>(state->queued, (now - state->last_update) / grain);
|
||||
if (drained == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->queued -= static_cast<uint32_t>(drained);
|
||||
state->last_update += drained * grain_micros;
|
||||
state->last_update += drained * grain;
|
||||
if (state->queued == 0) {
|
||||
state->last_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
||||
for (auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.handle == port) {
|
||||
return &state;
|
||||
}
|
||||
const auto context_slot =
|
||||
(port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u);
|
||||
const auto port_slot = port & ((1u << AUDIOOUT2_PORT_SLOT_BITS) - 1u);
|
||||
auto& context = g_audioout2_contexts[context_slot];
|
||||
if (context.used && port_slot < context.ports.size()) {
|
||||
auto& state = context.ports[port_slot];
|
||||
if (state.used && state.handle == port) return &state;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||
const auto bytes_per_sample =
|
||||
(state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||
return static_cast<size_t>(state.samples_num) *
|
||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||
}
|
||||
|
||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
||||
for (const auto& state: context.ports) {
|
||||
if (state.used && !state.pcm_data.empty()) {
|
||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||
? context.object_audio_handle
|
||||
: state.audio_handle;
|
||||
@@ -427,18 +446,21 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S
|
||||
out.object_mix.clear();
|
||||
std::vector<AudioInternal::OutputParam> params;
|
||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
||||
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
||||
// falls silent.
|
||||
for (auto& state: context->ports) {
|
||||
if (!state.used || state.pcm_data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (audioout2_port_type_is_object(state.port_type)) {
|
||||
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
||||
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
const auto i = params.size();
|
||||
out.pcm[i] = state.pcm_data;
|
||||
out.pcm[i].swap(state.pcm_data);
|
||||
out.gains[i] = state.gains;
|
||||
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
|
||||
}
|
||||
state.pcm_data.clear();
|
||||
}
|
||||
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back({context->object_audio_handle, out.object_mix.data()});
|
||||
@@ -467,22 +489,28 @@ int KYTY_SYSV_ABI AudioOut2ContextResetParam(AudioOut2ContextParam* params) {
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
|
||||
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
||||
params->max_ports = 256;
|
||||
params->max_object_ports = 256;
|
||||
params->guarantee_object_ports = 0;
|
||||
params->queue_depth = 4;
|
||||
params->num_grains = 512;
|
||||
params->flags = 1;
|
||||
params->max_ports = 8;
|
||||
params->queue_depth = 1;
|
||||
params->num_grains = 256;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static bool audioout2_valid_context_capacity(const AudioOut2ContextParam& params) {
|
||||
return params.max_ports <= AUDIOOUT2_CHANNEL_PORTS_MAX &&
|
||||
params.max_object_ports <= AUDIOOUT2_OBJECT_PORTS_MAX &&
|
||||
params.guarantee_object_ports <= params.max_object_ports;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
||||
size_t* memory_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
||||
if (!audioout2_valid_context_capacity(*params)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
||||
@@ -498,6 +526,9 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
||||
if (!audioout2_valid_context_capacity(*params)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||
const int object_audio_handle =
|
||||
@@ -524,8 +555,10 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
||||
state->handle = *ctx;
|
||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||
state->num_grains = num_grains;
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
state->last_update = audioout2_clock();
|
||||
state->object_audio_handle = object_audio_handle;
|
||||
state->max_ports = params->max_ports;
|
||||
state->ports.resize(params->max_ports + params->max_object_ports);
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||
@@ -541,7 +574,7 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||
|
||||
std::array<int, 257> audio_handles {};
|
||||
std::array<int, AUDIOOUT2_CHANNEL_PORTS_MAX + 1> audio_handles {};
|
||||
size_t audio_handles_num = 0;
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
@@ -550,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
if (state->object_audio_handle > 0) {
|
||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||
}
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
for (auto& port_state: g_audioout2_ports) {
|
||||
if (port_state.used && port_state.context == ctx) {
|
||||
if (port_state.audio_handle > 0 && audio_handles_num < audio_handles.size()) {
|
||||
for (const auto& port_state: state->ports) {
|
||||
if (port_state.used && port_state.audio_handle > 0) {
|
||||
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
||||
}
|
||||
port_state = AudioOut2PortStateEntry {};
|
||||
}
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
@@ -586,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
audioout2_update_context_locked(state);
|
||||
}
|
||||
for (auto& port: g_audioout2_ports) {
|
||||
if (port.used && port.context == ctx) {
|
||||
for (auto& port: state->ports) {
|
||||
port.attributes_locked = false;
|
||||
}
|
||||
}
|
||||
@@ -612,7 +640,7 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
||||
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||
if (state->queued < state->queue_depth || use_device_clock) {
|
||||
if (state->queued == 0) {
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
state->last_update = audioout2_clock();
|
||||
}
|
||||
if (state->queued < state->queue_depth) {
|
||||
state->queued++;
|
||||
@@ -663,19 +691,17 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
||||
|
||||
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||
const bool is_object = audioout2_port_type_is_object(params->port_type);
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
||||
auto* context_state = audioout2_find_context_locked(ctx);
|
||||
if (context_state == nullptr) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (audioout2_port_type_is_object(params->port_type) &&
|
||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
||||
params->sampling_freq != 48000 ||
|
||||
if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 ||
|
||||
(audio_format != AudioInternal::Format::FloatMono &&
|
||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
@@ -688,14 +714,21 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
AudioOut2PortStateEntry* port_state = nullptr;
|
||||
for (auto& candidate: g_audioout2_ports) {
|
||||
if (!candidate.used) {
|
||||
port_state = &candidate;
|
||||
break;
|
||||
auto slots = std::span(context_state->ports);
|
||||
slots =
|
||||
is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports);
|
||||
const auto available = std::find_if(slots.begin(), slots.end(),
|
||||
[](const auto& candidate) { return !candidate.used; });
|
||||
if (available == slots.end()) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
}
|
||||
}
|
||||
if (port_state != nullptr) {
|
||||
const auto serial = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto context_slot = static_cast<uint64_t>(context_state - g_audioout2_contexts.data());
|
||||
const auto port_slot = static_cast<uint64_t>(&*available - context_state->ports.data());
|
||||
const auto next_port = (serial << (AUDIOOUT2_PORT_SLOT_BITS + AUDIOOUT2_CONTEXT_SLOT_BITS)) |
|
||||
(context_slot << AUDIOOUT2_PORT_SLOT_BITS) | port_slot;
|
||||
auto* port_state = &*available;
|
||||
*port_state = AudioOut2PortStateEntry {};
|
||||
port_state->used = true;
|
||||
port_state->handle = next_port;
|
||||
@@ -707,23 +740,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
port_state->samples_num = samples_num;
|
||||
port_state->audio_format = audio_format;
|
||||
port_state->gains.fill(1.0f);
|
||||
}
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
if (port_state == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
int audio_handle = 0;
|
||||
|
||||
if (audio_format != AudioInternal::Format::Unknown &&
|
||||
!audioout2_port_type_is_object(params->port_type)) {
|
||||
if (audio_format != AudioInternal::Format::Unknown && !is_object) {
|
||||
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
||||
audio_format);
|
||||
}
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
||||
// Context destruction may have freed or replaced the slots while the backend opened.
|
||||
port_state = audioout2_find_port_locked(next_port);
|
||||
const bool reserved = port_state != nullptr;
|
||||
if (reserved) {
|
||||
port_state->audio_handle = audio_handle;
|
||||
}
|
||||
@@ -735,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
|
||||
*port = next_port;
|
||||
|
||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
||||
if (serial <= 16 || (serial % 600) == 0) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
||||
"\t port = 0x%016" PRIx64 "\n"
|
||||
@@ -905,10 +934,12 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
||||
if (it == g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(),
|
||||
for (const auto& context: g_audioout2_contexts) {
|
||||
if (std::any_of(context.ports.begin(), context.ports.end(),
|
||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||
return AUDIO_OUT2_ERROR_BUSY;
|
||||
}
|
||||
}
|
||||
g_audioout2_users.erase(it);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ struct Global {
|
||||
|
||||
struct Shadow {
|
||||
uintptr_t start = 0;
|
||||
int count = 0;
|
||||
uint64_t covered_bytes = 0;
|
||||
};
|
||||
|
||||
struct AsanContext {
|
||||
@@ -50,6 +50,14 @@ struct AsanContext {
|
||||
static uint32_t g_fake_stack = 0;
|
||||
static AsanContext* g_ctx = nullptr;
|
||||
|
||||
// Allocation and release callbacks can partition the same guest range differently.
|
||||
static uint64_t ShadowCoverage(uintptr_t page, uintptr_t min_addr, uintptr_t max_addr) {
|
||||
const auto guest_start = (page - 0x10000000000ull) << 3u;
|
||||
const auto guest_end = guest_start + 0x80000ull - 1;
|
||||
return std::min<uintptr_t>(max_addr, guest_end) - std::max<uintptr_t>(min_addr, guest_start) +
|
||||
1;
|
||||
}
|
||||
|
||||
static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
EXIT_IF(g_ctx == nullptr);
|
||||
|
||||
@@ -67,10 +75,11 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
static_cast<uint64_t>(min_shadow), static_cast<uint64_t>(size));
|
||||
|
||||
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
||||
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||
[page](const Shadow& s) { return s.start == page; });
|
||||
if (it != g_ctx->shadows.end()) {
|
||||
it->count++;
|
||||
it->covered_bytes += bytes;
|
||||
} else {
|
||||
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
||||
Common::VirtualMemory::Mode::ReadWrite)) {
|
||||
@@ -79,7 +88,7 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
|
||||
Shadow s;
|
||||
s.start = page;
|
||||
s.count = 1;
|
||||
s.covered_bytes = bytes;
|
||||
g_ctx->shadows.push_back(s);
|
||||
}
|
||||
}
|
||||
@@ -105,14 +114,15 @@ static void FreeShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||
[page](const Shadow& s) { return s.start == page; });
|
||||
if (it != g_ctx->shadows.end()) {
|
||||
it->count--;
|
||||
|
||||
if (it->count <= 0) {
|
||||
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||
if (bytes >= it->covered_bytes) {
|
||||
if (!Common::VirtualMemory::Free(page)) {
|
||||
EXIT("can't free shadow memory\n");
|
||||
}
|
||||
|
||||
g_ctx->shadows.erase(it);
|
||||
} else {
|
||||
it->covered_bytes -= bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +136,14 @@ static void KernelFree(uintptr_t addr, size_t size) {
|
||||
FreeShadow(addr, addr + size - 1);
|
||||
}
|
||||
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
void TestRegisterMemoryCallbacks() {
|
||||
EXIT_IF(g_ctx != nullptr);
|
||||
g_ctx = new AsanContext;
|
||||
LibKernel::Memory::RegisterCallbacks(KernelAlloc, KernelFree);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void KYTY_SYSV_ABI asan_init() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "libs/network.h"
|
||||
#include "loader/elf.h"
|
||||
#include "loader/runtimeLinker.h"
|
||||
#include "loader/systemContent.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -1321,23 +1322,12 @@ static void* KYTY_SYSV_ABI KernelGetProcParam() {
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
||||
static std::atomic_uint32_t call_count {0};
|
||||
|
||||
auto count = call_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (count < 8 || (count % 1024) == 0) {
|
||||
PRINT_NAME();
|
||||
LOGF("\tcall = %u, return = 0 (base PS5 mode)\n", count + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Loader::SystemContentIsTrinityMode() ? 1 : 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
||||
PRINT_NAME();
|
||||
|
||||
*mode = 2; // PS5 Base
|
||||
*mode = Loader::SystemContentIsTrinityMode() ? 3 : 2;
|
||||
*submode = 0; // None
|
||||
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1685,12 +1685,34 @@ LIB_VERSION("NpAuth", 1, "NpAuth", 1, 1);
|
||||
constexpr int NP_AUTH_MAX_REQUEST_NUM = 16;
|
||||
constexpr int NP_AUTH_REQUEST_ID_OFFSET = 0x10000000;
|
||||
constexpr int NP_AUTH_ERROR_INVALID_ARGUMENT = -2141912319; /* 0x80550301 */
|
||||
constexpr int NP_AUTH_ERROR_INVALID_SIZE = -2141912318; /* 0x80550302 */
|
||||
constexpr int NP_AUTH_ERROR_ABORTED = -2141912316; /* 0x80550304 */
|
||||
constexpr int NP_AUTH_ERROR_REQUEST_MAX = -2141912315; /* 0x80550305 */
|
||||
constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */
|
||||
constexpr int NP_AUTH_ERROR_INVALID_ID = -2141912313; /* 0x80550307 */
|
||||
constexpr int NP_AUTH_ERROR_SERVICE_DOWN = -2141912063; /* 0x80550401 */
|
||||
constexpr int NP_ERROR_SIGNED_OUT = -2141913082; /* 0x80550006 */
|
||||
|
||||
struct NpAuthGetAuthorizationCodeParameterV3 {
|
||||
size_t size;
|
||||
int user_id;
|
||||
uint8_t padding[4];
|
||||
const void* client_id;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
struct NpAuthGetIdTokenParameterV3 {
|
||||
size_t size;
|
||||
int user_id;
|
||||
uint8_t padding[4];
|
||||
const void* client_id;
|
||||
const void* client_secret;
|
||||
const char* scope;
|
||||
};
|
||||
|
||||
static_assert(sizeof(NpAuthGetAuthorizationCodeParameterV3) == 32);
|
||||
static_assert(sizeof(NpAuthGetIdTokenParameterV3) == 40);
|
||||
|
||||
enum class NpAuthRequestState {
|
||||
None = 0,
|
||||
Ready,
|
||||
@@ -1822,7 +1844,7 @@ static int np_auth_wait_or_poll_async(int req_id, int* result) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int np_auth_complete_signed_out(NpAuthRequest* request) {
|
||||
static int np_auth_complete_unavailable(NpAuthRequest* request, int user_id) {
|
||||
if (request->state == NpAuthRequestState::Complete) {
|
||||
request->result = NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
return NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
@@ -1833,9 +1855,11 @@ static int np_auth_complete_signed_out(NpAuthRequest* request) {
|
||||
}
|
||||
|
||||
request->state = NpAuthRequestState::Complete;
|
||||
request->result = NP_ERROR_SIGNED_OUT;
|
||||
// Emulating sign-in cannot provide a real authorization code or ID token.
|
||||
request->result = Network::NpManager::IsSignedIn(user_id) ? NP_AUTH_ERROR_SERVICE_DOWN
|
||||
: NP_ERROR_SIGNED_OUT;
|
||||
|
||||
return request->async ? 0 : NP_ERROR_SIGNED_OUT;
|
||||
return request->async ? 0 : request->result;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpAuthWaitAsync(int req_id, int* result) {
|
||||
@@ -1856,8 +1880,9 @@ static int KYTY_SYSV_ABI NpAuthPollAsync(int req_id, int* result) {
|
||||
return np_auth_wait_or_poll_async(req_id, result);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* param,
|
||||
void* auth_code, int* issuer_id) {
|
||||
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(
|
||||
int req_id, const NpAuthGetAuthorizationCodeParameterV3* param, void* auth_code,
|
||||
int* issuer_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t req_id = %d\n", req_id);
|
||||
@@ -1865,9 +1890,17 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
|
||||
LOGF("\t auth_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(auth_code));
|
||||
LOGF("\t issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id));
|
||||
|
||||
if (auth_code != nullptr) {
|
||||
memset(auth_code, 0, 136);
|
||||
if (req_id <= 0 || param == nullptr || auth_code == nullptr) {
|
||||
return NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size < sizeof(*param)) {
|
||||
return NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->client_id == nullptr || param->scope == nullptr) {
|
||||
return NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
memset(auth_code, 0, 136);
|
||||
if (issuer_id != nullptr) {
|
||||
*issuer_id = 0;
|
||||
}
|
||||
@@ -1877,46 +1910,35 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
|
||||
if (request == nullptr) {
|
||||
return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
if (request->state == NpAuthRequestState::Aborted) {
|
||||
// request->result = 0;
|
||||
// return 0;
|
||||
request->result = NP_AUTH_ERROR_ABORTED;
|
||||
return NP_AUTH_ERROR_ABORTED;
|
||||
}
|
||||
// request->state = NpAuthRequestState::Complete;
|
||||
// request->result = 0;
|
||||
|
||||
// return 0;
|
||||
return np_auth_complete_signed_out(request);
|
||||
return np_auth_complete_unavailable(request, param->user_id);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const void* param, void* id_token) {
|
||||
static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const NpAuthGetIdTokenParameterV3* param,
|
||||
void* id_token) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t req_id = %d\n", req_id);
|
||||
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
|
||||
LOGF("\t id_token = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(id_token));
|
||||
|
||||
if (id_token != nullptr) {
|
||||
memset(id_token, 0, 4104);
|
||||
if (req_id <= 0 || param == nullptr || id_token == nullptr) {
|
||||
return NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (param->size < sizeof(*param)) {
|
||||
return NP_AUTH_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (param->client_id == nullptr || param->client_secret == nullptr || param->scope == nullptr) {
|
||||
return NP_AUTH_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
memset(id_token, 0, 4104);
|
||||
|
||||
std::scoped_lock lock(g_np_auth_request_mutex);
|
||||
auto* request = np_auth_find_request(req_id);
|
||||
if (request == nullptr) {
|
||||
return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
|
||||
}
|
||||
if (request->state == NpAuthRequestState::Aborted) {
|
||||
// request->result = 0;
|
||||
// return 0;
|
||||
request->result = NP_AUTH_ERROR_ABORTED;
|
||||
return NP_AUTH_ERROR_ABORTED;
|
||||
}
|
||||
// request->state = NpAuthRequestState::Complete;
|
||||
// request->result = 0;
|
||||
|
||||
// return 0;
|
||||
return np_auth_complete_signed_out(request);
|
||||
return np_auth_complete_unavailable(request, param->user_id);
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitNet_1_NpAuth) {
|
||||
|
||||
@@ -1,349 +1,376 @@
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace LibPsml {
|
||||
namespace Libs::LibPsml {
|
||||
|
||||
LIB_VERSION("Psml", 1, "Psml", 1, 1);
|
||||
|
||||
namespace Psml {
|
||||
namespace {
|
||||
|
||||
constexpr int PSML_ERROR_NOT_INITIALIZED = static_cast<int>(0x8a810001u);
|
||||
constexpr int PSML_ERROR_INVALID_OBJECT = static_cast<int>(0x8a810005u);
|
||||
constexpr int PSML_ERROR_INVALID_POINTER = static_cast<int>(0x8a810009u);
|
||||
constexpr int PSML_ERROR_INVALID_VALUE = static_cast<int>(0x8a81000du);
|
||||
constexpr int PSML_ERROR_NULL_OBJECT = static_cast<int>(0x8a810014u);
|
||||
constexpr int NOT_INITIALIZED = static_cast<int>(0x8a810001u);
|
||||
constexpr int ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
|
||||
constexpr int TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
|
||||
constexpr int INVALID_CONTEXT = static_cast<int>(0x8a810005u);
|
||||
constexpr int COMMAND_BUFFER_FULL = static_cast<int>(0x8a810006u);
|
||||
constexpr int COMMAND_BUFFER_BAD = static_cast<int>(0x8a810007u);
|
||||
constexpr int INSUFFICIENT_BUFFER = static_cast<int>(0x8a810008u);
|
||||
constexpr int ARGUMENT_NULL = static_cast<int>(0x8a810009u);
|
||||
constexpr int TEXTURE_NULL = static_cast<int>(0x8a81000au);
|
||||
constexpr int INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
|
||||
constexpr int INVALID_PARAMETER = static_cast<int>(0x8a81000du);
|
||||
constexpr int UNSUPPORTED_RENDER_SIZE = static_cast<int>(0x8a81000eu);
|
||||
constexpr int INVALID_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
|
||||
constexpr int NOT_TRINITY_MODE = static_cast<int>(0x8a810012u);
|
||||
constexpr int UNSUPPORTED_OUTPUT_SIZE = static_cast<int>(0x8a810013u);
|
||||
constexpr int INVALID_SHARED_RESOURCES = static_cast<int>(0x8a810014u);
|
||||
constexpr int TOO_MANY_SHARED_RESOURCES = static_cast<int>(0x8a810015u);
|
||||
constexpr uint64_t MEMORY_BLOCK_SIZE = 0x200000;
|
||||
constexpr uint32_t PACKET_DWORDS = 1 + Graphics::Pssr::RequestDwords;
|
||||
|
||||
constexpr uint32_t SHARED_RESOURCES_MAGIC = 0xa9c4;
|
||||
constexpr uint32_t CONTEXT_MAGIC = 0x9231;
|
||||
|
||||
constexpr uint64_t MAIN_MEMORY_BLOCK_SIZE = 0x200000;
|
||||
constexpr uint64_t MAIN_MEMORY_ALIGNMENT = 0x200000;
|
||||
constexpr uint64_t EXTRA_VA_BYTES = 0x600000;
|
||||
|
||||
struct MainMemoryRequirements {
|
||||
struct MemoryRequirements {
|
||||
uint64_t block_size;
|
||||
uint64_t alignment;
|
||||
uint64_t block_count;
|
||||
};
|
||||
|
||||
struct MainMemoryParameters {
|
||||
uint32_t type;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
struct DirectMemoryBlock {
|
||||
uint64_t address;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct SharedResourcesInitParameters {
|
||||
uint32_t type;
|
||||
uint32_t reserved;
|
||||
struct MemoryParameters {
|
||||
uint32_t profile;
|
||||
uint32_t flags;
|
||||
const DirectMemoryBlock* blocks;
|
||||
uint64_t block_count;
|
||||
uint64_t virtual_address_start;
|
||||
};
|
||||
|
||||
static_assert(sizeof(MainMemoryRequirements) == 24);
|
||||
static_assert(sizeof(DirectMemoryBlock) == 16);
|
||||
struct SharedParameters {
|
||||
MemoryParameters memory;
|
||||
uint64_t virtual_address;
|
||||
};
|
||||
|
||||
static bool g_initialized = false;
|
||||
static uint32_t g_supported_modes = 0x3;
|
||||
static std::atomic<int32_t> g_shared_ref_count = 0;
|
||||
struct ContextParameters {
|
||||
MemoryParameters memory;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint32_t reserved;
|
||||
const void* depth_copy;
|
||||
const void* motion_copy;
|
||||
const char* name;
|
||||
void* shared;
|
||||
};
|
||||
static_assert(offsetof(ContextParameters, width) == 0x18);
|
||||
static_assert(offsetof(ContextParameters, shared) == 0x38);
|
||||
|
||||
static uint64_t BaseVaBytes(uint32_t type) {
|
||||
// The native implementation indexes this table before adding one 6 MiB range.
|
||||
switch (type) {
|
||||
case 0: return 0x06200000;
|
||||
case 1: return 0x18200000;
|
||||
case 2: return 0x12200000;
|
||||
default: return 0;
|
||||
}
|
||||
struct SharedResources {
|
||||
uint32_t contexts = 0;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
SharedResources* shared;
|
||||
};
|
||||
|
||||
struct CommandSpace {
|
||||
uint32_t* cmd;
|
||||
uint32_t dwords;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
bool g_initialized = false;
|
||||
std::unique_ptr<SharedResources> g_shared;
|
||||
std::unordered_map<const void*, std::unique_ptr<Context>> g_contexts;
|
||||
|
||||
uint64_t SharedBlockCount(uint32_t profile) {
|
||||
constexpr uint64_t bytes[] = {0x06200000, 0x18200000, 0x12200000};
|
||||
return profile < 3 ? (bytes[profile] + 0x600000) / MEMORY_BLOCK_SIZE : 0;
|
||||
}
|
||||
|
||||
static bool IsSupportedType(uint32_t type) {
|
||||
switch (type) {
|
||||
case 0: return true;
|
||||
case 1: return (g_supported_modes & 0x1u) != 0;
|
||||
case 2: return (g_supported_modes & 0x2u) != 0;
|
||||
default: return false;
|
||||
int CheckContext(const void* context) {
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
static uint64_t RequiredBlockCount(uint32_t type) {
|
||||
const auto base = BaseVaBytes(type);
|
||||
if (base == 0) {
|
||||
return 0;
|
||||
int KYTY_SYSV_ABI Initialize() {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!Loader::SystemContentIsTrinityMode()) {
|
||||
return NOT_TRINITY_MODE;
|
||||
}
|
||||
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
|
||||
if (g_initialized) {
|
||||
return ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
static int CheckInitialized() {
|
||||
return (g_initialized ? OK : PSML_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
|
||||
static void MarkSharedResources(void* resources, const SharedResourcesInitParameters& params) {
|
||||
auto* bytes = static_cast<uint8_t*>(resources);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(bytes) = SHARED_RESOURCES_MAGIC;
|
||||
*reinterpret_cast<const DirectMemoryBlock**>(bytes + 0x08) = params.blocks;
|
||||
*reinterpret_cast<uint64_t*>(bytes + 0x18) = params.block_count;
|
||||
*reinterpret_cast<uint32_t*>(bytes + 0x20) = params.type;
|
||||
*reinterpret_cast<uint64_t*>(bytes + 0x28) = params.virtual_address_start;
|
||||
}
|
||||
|
||||
static bool HasKnownObjectMagic(const void* object) {
|
||||
if (object == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto magic = *static_cast<const uint32_t*>(object);
|
||||
return magic == SHARED_RESOURCES_MAGIC || magic == CONTEXT_MAGIC;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlInitialize() {
|
||||
PRINT_NAME();
|
||||
|
||||
g_initialized = true;
|
||||
g_supported_modes = 0x3;
|
||||
g_shared_ref_count = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlGetMainMemoryRequirements(MainMemoryRequirements* out,
|
||||
const MainMemoryParameters* params) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI GetSharedRequirements(MemoryRequirements* out, const MemoryParameters* params) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
if (out == nullptr || params == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
if (!IsSupportedType(params->type)) {
|
||||
return PSML_ERROR_INVALID_VALUE;
|
||||
const auto blocks = SharedBlockCount(params->profile);
|
||||
if (blocks == 0) {
|
||||
return INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
|
||||
out->alignment = MAIN_MEMORY_ALIGNMENT;
|
||||
out->block_count = RequiredBlockCount(params->type);
|
||||
|
||||
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, blocks};
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI
|
||||
PsmlSharedResourcesInitialize(void* resources, const SharedResourcesInitParameters* params) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI CreateSharedResources(void** out, const SharedParameters* params) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
if (resources == nullptr || params == nullptr || params->blocks == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (out == nullptr || params == nullptr || params->memory.blocks == nullptr) {
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
if (params->reserved != 0 || !IsSupportedType(params->type) ||
|
||||
params->block_count < RequiredBlockCount(params->type)) {
|
||||
return PSML_ERROR_INVALID_VALUE;
|
||||
const auto required = SharedBlockCount(params->memory.profile);
|
||||
if (required == 0 || params->memory.flags != 0) {
|
||||
return INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
MarkSharedResources(resources, *params);
|
||||
g_shared_ref_count.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
if (params->memory.block_count < required) {
|
||||
return INSUFFICIENT_BUFFER;
|
||||
}
|
||||
if (g_shared) {
|
||||
return TOO_MANY_SHARED_RESOURCES;
|
||||
}
|
||||
g_shared = std::make_unique<SharedResources>();
|
||||
*out = g_shared.get();
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlSharedResourcesFinalize(void* resources) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI ReleaseSharedResources(void* resources) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
if (resources == nullptr) {
|
||||
return PSML_ERROR_NULL_OBJECT;
|
||||
if (!g_shared || resources != g_shared.get()) {
|
||||
return INVALID_SHARED_RESOURCES;
|
||||
}
|
||||
|
||||
g_shared_ref_count.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
if (g_shared->contexts != 0) {
|
||||
return INVALID_CONTEXT;
|
||||
}
|
||||
g_shared.reset();
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlGetContextMemoryRequirements(MainMemoryRequirements* out,
|
||||
uint64_t /*params*/) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI GetContextRequirements(MemoryRequirements* out, const void* params) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
if (out == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (out == nullptr || params == nullptr) {
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
|
||||
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
|
||||
out->alignment = MAIN_MEMORY_ALIGNMENT;
|
||||
out->block_count = 1;
|
||||
|
||||
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, 1};
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlContextInitialize(void* context, const void* params) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int CreateContext(void** out, const MemoryParameters* memory, uint32_t width, uint32_t height,
|
||||
void* shared, bool use_shared) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
if (context == nullptr || params == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (out == nullptr || memory == nullptr || memory->blocks == nullptr) {
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
|
||||
auto* bytes = static_cast<uint8_t*>(context);
|
||||
auto* params_bytes = static_cast<const uint8_t*>(params);
|
||||
auto* shared_resources = *reinterpret_cast<void* const*>(params_bytes + 0x08);
|
||||
if (shared_resources == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (memory->profile > 2) {
|
||||
return INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*reinterpret_cast<uint32_t*>(bytes) = CONTEXT_MAGIC;
|
||||
*reinterpret_cast<void**>(bytes + 0x360) = shared_resources;
|
||||
*reinterpret_cast<uint8_t*>(bytes + 0x368) = 0;
|
||||
|
||||
if (memory->block_count == 0) {
|
||||
return INSUFFICIENT_BUFFER;
|
||||
}
|
||||
if (width == 0 || height == 0 || width > 16384 || height > 16384) {
|
||||
return UNSUPPORTED_OUTPUT_SIZE;
|
||||
}
|
||||
if (use_shared && (!g_shared || shared != g_shared.get())) {
|
||||
return INVALID_SHARED_RESOURCES;
|
||||
}
|
||||
if (g_contexts.size() == 4) {
|
||||
return TOO_MANY_CONTEXTS;
|
||||
}
|
||||
auto record =
|
||||
std::make_unique<Context>(Context {width, height, use_shared ? g_shared.get() : nullptr});
|
||||
auto* handle = record.get();
|
||||
g_contexts.emplace(handle, std::move(record));
|
||||
if (handle->shared != nullptr) {
|
||||
++handle->shared->contexts;
|
||||
}
|
||||
*out = handle;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlContextFinalize(void* context) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(context)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
int KYTY_SYSV_ABI CreateFixedContext(void** out, const MemoryParameters* params) {
|
||||
const bool large = params != nullptr && params->profile == 1;
|
||||
return CreateContext(out, params, large ? 7680 : 3840, large ? 4320 : 2160, nullptr, false);
|
||||
}
|
||||
|
||||
*static_cast<uint32_t*>(context) = 0;
|
||||
int KYTY_SYSV_ABI CreatePrivateContext(void** out, const ContextParameters* params) {
|
||||
return CreateContext(out, params == nullptr ? nullptr : ¶ms->memory,
|
||||
params == nullptr ? 0 : params->width,
|
||||
params == nullptr ? 0 : params->height, nullptr, false);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI CreateSharedContext(void** out, const ContextParameters* params) {
|
||||
return CreateContext(
|
||||
out, params == nullptr ? nullptr : ¶ms->memory, params == nullptr ? 0 : params->width,
|
||||
params == nullptr ? 0 : params->height, params == nullptr ? nullptr : params->shared, true);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ReleaseContext(void* context) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (const int error = CheckContext(context); error != OK) {
|
||||
return error;
|
||||
}
|
||||
auto& record = g_contexts.at(context);
|
||||
if (record->shared != nullptr) {
|
||||
--record->shared->contexts;
|
||||
}
|
||||
g_contexts.erase(context);
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlGetWorkAreaSize(const void* object, uint32_t* out_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(object)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
int KYTY_SYSV_ABI GetPacketSize(const void* context, uint32_t* out_size) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (const int error = CheckContext(context); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (out_size == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
|
||||
*out_size = 0x600;
|
||||
|
||||
*out_size = PACKET_DWORDS;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlDispatch(void* context, uint64_t command, uint64_t params) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI Dispatch(const void* context, const CommandSpace* space, const void* params) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (const int error = CheckContext(context); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(context)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
if (space == nullptr || params == nullptr || space->cmd == nullptr) {
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
if (command == 0 || params == 0) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (space->dwords < PACKET_DWORDS) {
|
||||
return COMMAND_BUFFER_FULL;
|
||||
}
|
||||
|
||||
if (space->dwords > 0x4001) {
|
||||
return COMMAND_BUFFER_BAD;
|
||||
}
|
||||
const uint32_t* descriptors[2];
|
||||
std::memcpy(descriptors, params, sizeof(descriptors));
|
||||
if (descriptors[0] == nullptr || descriptors[1] == nullptr) {
|
||||
return TEXTURE_NULL;
|
||||
}
|
||||
Graphics::Pssr::Request request {};
|
||||
std::memcpy(request.output, descriptors[0], sizeof(request.output));
|
||||
std::memcpy(request.input, descriptors[1], sizeof(request.input));
|
||||
std::memcpy(&request.width, static_cast<const uint8_t*>(params) + 0xf0, sizeof(uint32_t) * 2);
|
||||
Graphics::ShaderTextureResource input, output;
|
||||
std::memcpy(input.fields, request.input, sizeof(input.fields));
|
||||
std::memcpy(output.fields, request.output, sizeof(output.fields));
|
||||
namespace Gpu = Graphics::Prospero;
|
||||
const auto is_2d = [](const Graphics::ShaderTextureResource& descriptor) {
|
||||
return !descriptor.IsNull() &&
|
||||
(descriptor.Type() == Gpu::ImageType::kColor2D ||
|
||||
descriptor.Type() == Gpu::ImageType::kColor2DArray) &&
|
||||
descriptor.TileMode() != Gpu::TileMode::kDepth &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.LastLevel() <= descriptor.MaxMip() &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth();
|
||||
};
|
||||
if (!is_2d(input) || (input.Format() != Gpu::BufferFormat::k11_11_10Float &&
|
||||
input.Format() != Gpu::BufferFormat::k10_11_11Float &&
|
||||
input.Format() != Gpu::BufferFormat::k16_16_16_16Float)) {
|
||||
return INVALID_TEXTURE;
|
||||
}
|
||||
if (!is_2d(output) || output.Format() != Gpu::BufferFormat::k11_11_10Float ||
|
||||
output.TileMode() != Gpu::TileMode::kStandard256B || output.BaseLevel() != 0 ||
|
||||
output.LastLevel() != 0 || output.MaxMip() != 0 || output.BaseArray5() != 0 ||
|
||||
output.Depth() != 0 || input.Base40() == output.Base40()) {
|
||||
return INVALID_OUTPUT_TEXTURE;
|
||||
}
|
||||
const uint32_t input_width = std::max(1u, (input.Width5() + 1u) >> input.BaseLevel());
|
||||
const uint32_t input_height = std::max(1u, (input.Height5() + 1u) >> input.BaseLevel());
|
||||
if (request.width == 0 || request.height == 0 || request.width > input_width ||
|
||||
request.height > input_height) {
|
||||
return UNSUPPORTED_RENDER_SIZE;
|
||||
}
|
||||
const auto& record = *g_contexts.at(context);
|
||||
if (uint32_t(output.Width5()) + 1 != record.width ||
|
||||
uint32_t(output.Height5()) + 1 != record.height) {
|
||||
return INVALID_OUTPUT_TEXTURE;
|
||||
}
|
||||
namespace Pm4 = Graphics::Pm4;
|
||||
space->cmd[0] = KYTY_PM4(space->dwords, Pm4::IT_NOP, Pm4::R_PSSR);
|
||||
std::memcpy(space->cmd + 1, &request, sizeof(request));
|
||||
std::memset(space->cmd + PACKET_DWORDS, 0, (space->dwords - PACKET_DWORDS) * sizeof(uint32_t));
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlGetProgress(const void* object, float* out_progress,
|
||||
uint32_t /*frame_index*/) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
int KYTY_SYSV_ABI GetMipmapBias(const void* context, float* bias, uint32_t width, uint32_t height) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (const int error = CheckContext(context); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(object)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
if (bias == nullptr) {
|
||||
return ARGUMENT_NULL;
|
||||
}
|
||||
if (out_progress == nullptr) {
|
||||
return PSML_ERROR_INVALID_POINTER;
|
||||
if (width == 0 || height == 0) {
|
||||
return UNSUPPORTED_RENDER_SIZE;
|
||||
}
|
||||
|
||||
*out_progress = 0.0f;
|
||||
|
||||
*bias = std::log2(float(width) / float(g_contexts.at(context)->width)) - 1.0f;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlRequestCapture(const void* object) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(object)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
int KYTY_SYSV_ABI ValidateContext(const void* context) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
return CheckContext(context);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlValidateObject(const void* object) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(object)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace Psml
|
||||
} // namespace
|
||||
|
||||
LIB_DEFINE(InitPsml_1) {
|
||||
LIB_FUNC("3WVD91e12ZQ", Psml::PsmlInitialize);
|
||||
LIB_FUNC("+2KpvixvL6E", Psml::PsmlGetMainMemoryRequirements);
|
||||
LIB_FUNC("eWoKNeB6V-k", Psml::PsmlSharedResourcesInitialize);
|
||||
LIB_FUNC("jEevBXmagOQ", Psml::PsmlSharedResourcesFinalize);
|
||||
LIB_FUNC("EO9YQXmJEN8", Psml::PsmlGetContextMemoryRequirements);
|
||||
LIB_FUNC("IMlj247LdTo", Psml::PsmlGetContextMemoryRequirements);
|
||||
LIB_FUNC("sjTyYheKTrU", Psml::PsmlGetContextMemoryRequirements);
|
||||
LIB_FUNC("ArakEpzsZo0", Psml::PsmlGetContextMemoryRequirements);
|
||||
LIB_FUNC("VGjrQa-WqdU", Psml::PsmlGetContextMemoryRequirements);
|
||||
LIB_FUNC("2ecEbQaf9VU", Psml::PsmlContextInitialize);
|
||||
LIB_FUNC("vUk2pWMx3KQ", Psml::PsmlContextInitialize);
|
||||
LIB_FUNC("gxv3i+MTEzU", Psml::PsmlContextInitialize);
|
||||
LIB_FUNC("JaLBe0P3jSU", Psml::PsmlContextFinalize);
|
||||
LIB_FUNC("AHalTX9wFZY", Psml::PsmlGetWorkAreaSize);
|
||||
LIB_FUNC("RUNLFro+qok", Psml::PsmlDispatch);
|
||||
LIB_FUNC("GHna9-DvnUk", Psml::PsmlGetProgress);
|
||||
LIB_FUNC("GJY0MvuTcs8", Psml::PsmlRequestCapture);
|
||||
LIB_FUNC("LXq+6mIxpCw", Psml::PsmlValidateObject);
|
||||
LIB_FUNC("FSGaTQze0UY", Psml::PsmlValidateObject);
|
||||
LIB_FUNC("3WVD91e12ZQ", Initialize);
|
||||
LIB_FUNC("+2KpvixvL6E", GetSharedRequirements);
|
||||
LIB_FUNC("eWoKNeB6V-k", CreateSharedResources);
|
||||
LIB_FUNC("jEevBXmagOQ", ReleaseSharedResources);
|
||||
LIB_FUNC("EO9YQXmJEN8", GetContextRequirements);
|
||||
LIB_FUNC("IMlj247LdTo", GetContextRequirements);
|
||||
LIB_FUNC("sjTyYheKTrU", GetContextRequirements);
|
||||
LIB_FUNC("ArakEpzsZo0", GetContextRequirements);
|
||||
LIB_FUNC("VGjrQa-WqdU", GetContextRequirements);
|
||||
LIB_FUNC("2ecEbQaf9VU", CreateFixedContext);
|
||||
LIB_FUNC("vUk2pWMx3KQ", CreatePrivateContext);
|
||||
LIB_FUNC("gxv3i+MTEzU", CreateSharedContext);
|
||||
LIB_FUNC("JaLBe0P3jSU", ReleaseContext);
|
||||
LIB_FUNC("AHalTX9wFZY", GetPacketSize);
|
||||
LIB_FUNC("RUNLFro+qok", Dispatch);
|
||||
LIB_FUNC("GHna9-DvnUk", GetMipmapBias);
|
||||
LIB_FUNC("GJY0MvuTcs8", ValidateContext);
|
||||
LIB_FUNC("LXq+6mIxpCw", ValidateContext);
|
||||
LIB_FUNC("FSGaTQze0UY", ValidateContext);
|
||||
}
|
||||
|
||||
} // namespace LibPsml
|
||||
|
||||
} // namespace Libs
|
||||
} // namespace Libs::LibPsml
|
||||
@@ -615,7 +615,12 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id();
|
||||
const auto root = save_user_directory(title_id, cond->user_id);
|
||||
std::vector<std::string> dir_list;
|
||||
struct Found {
|
||||
uint64_t key = 0;
|
||||
std::string name;
|
||||
auto operator<=>(const Found&) const = default;
|
||||
};
|
||||
std::vector<Found> dir_list;
|
||||
|
||||
if (Common::File::IsDirectoryExisting(root)) {
|
||||
for (const auto& entry: Common::File::GetDirEntries(root)) {
|
||||
@@ -625,22 +630,34 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
|
||||
dir_name_match(Common::ToLower(entry.name).c_str(),
|
||||
Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
|
||||
dir_list.push_back(entry.name);
|
||||
dir_list.push_back({.name = entry.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(dir_list.begin(), dir_list.end(), [](const std::string& a, const std::string& b) {
|
||||
return std::strcmp(a.c_str(), b.c_str()) < 0;
|
||||
});
|
||||
|
||||
// A save whose metadata cannot be read sorts as 0.
|
||||
for (auto& found: dir_list) {
|
||||
const auto directory = root / found.name;
|
||||
switch (cond->key) {
|
||||
case SaveDataSortKey::UserParam: {
|
||||
SaveDataParam param {};
|
||||
read_save_param(save_param_path(directory), ¶m);
|
||||
found.key = param.user_param;
|
||||
break;
|
||||
}
|
||||
case SaveDataSortKey::Blocks:
|
||||
case SaveDataSortKey::FreeBlocks: read_save_blocks(directory, &found.key); break;
|
||||
case SaveDataSortKey::Mtime:
|
||||
found.key = static_cast<uint64_t>(newest_save_time(directory));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
// Directory names break ties; descending order reverses both.
|
||||
std::sort(dir_list.begin(), dir_list.end());
|
||||
if (cond->order == SaveDataSortOrder::Descent) {
|
||||
std::vector<std::string> reversed;
|
||||
for (size_t i = dir_list.size(); i > 0; i--) {
|
||||
reversed.push_back(dir_list[i - 1]);
|
||||
}
|
||||
dir_list = reversed;
|
||||
std::reverse(dir_list.begin(), dir_list.end());
|
||||
}
|
||||
|
||||
auto max_count =
|
||||
@@ -651,9 +668,9 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
|
||||
for (size_t i = 0; i < max_count; i++) {
|
||||
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s",
|
||||
dir_list[i].c_str());
|
||||
dir_list[i].name.c_str());
|
||||
if (result->params != nullptr) {
|
||||
const int status = load_save_param(root / dir_list[i], &result->params[i]);
|
||||
const int status = load_save_param(root / dir_list[i].name, &result->params[i]);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
@@ -661,7 +678,7 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
if (result->infos != nullptr) {
|
||||
auto& info = result->infos[i];
|
||||
info = {};
|
||||
const int status = read_save_blocks(root / dir_list[i], &info.blocks);
|
||||
const int status = read_save_blocks(root / dir_list[i].name, &info.blocks);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||