mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 01:01:21 +00:00
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
a5b3673a39 | ||
|
|
09753bf8ff | ||
|
|
9d8c458535 | ||
|
|
358333125d | ||
|
|
fd6b28c4b2 | ||
|
|
6cee2dc0bd | ||
|
|
a526a8b9c9 | ||
|
|
1ec69f8e7d | ||
|
|
2463e3aaf0 | ||
|
|
556538bed5 |
No files matched your search
@@ -113,7 +113,7 @@ jobs:
|
|||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
cmake --build _Build/windows --target launcher ^
|
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 ^
|
shader_recompiler_compute_tests ^
|
||||||
resource_tracking_tests scalar_provenance_tests ^
|
resource_tracking_tests scalar_provenance_tests ^
|
||||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
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
|
- name: Install
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake --build _Build/macos \
|
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 \
|
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
|
||||||
resource_tracking_tests scalar_provenance_tests \
|
resource_tracking_tests scalar_provenance_tests \
|
||||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||||
@@ -212,7 +212,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir _Build/macos --output-on-failure \
|
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
|
- name: Install
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -388,7 +388,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --build _Build/linux \
|
cmake --build _Build/linux \
|
||||||
--target launcher page_manager_tests memory_tracker_tests \
|
--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_tracking_tests scalar_provenance_tests \
|
||||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||||
--parallel
|
--parallel
|
||||||
@@ -397,7 +397,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir _Build/linux --output-on-failure \
|
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
|
- name: Install
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
Vendored
+22
@@ -42,6 +42,28 @@ if(NOT KYTY_CLANG_CL)
|
|||||||
endif()
|
endif()
|
||||||
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
||||||
|
|
||||||
|
# Noto Sans v2.015 (SIL Open Font License 1.1) is libFont's fallback font.
|
||||||
|
set(KYTY_NOTO_SANS_REPOSITORY "https://raw.githubusercontent.com/notofonts/notofonts.github.io")
|
||||||
|
set(KYTY_NOTO_SANS_COMMIT 42af67ccd12bc0e31ed85384bf73e39248265374)
|
||||||
|
set(KYTY_NOTO_SANS_BASE_URL "${KYTY_NOTO_SANS_REPOSITORY}/${KYTY_NOTO_SANS_COMMIT}/fonts")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${FETCHCONTENT_BASE_DIR}/noto-sans")
|
||||||
|
function(kyty_fetch_noto_sans path sha256)
|
||||||
|
get_filename_component(name "${path}" NAME)
|
||||||
|
file(DOWNLOAD "${KYTY_NOTO_SANS_BASE_URL}/${path}" "${KYTY_NOTO_SANS_DIR}/${name}"
|
||||||
|
EXPECTED_HASH SHA256=${sha256})
|
||||||
|
endfunction()
|
||||||
|
kyty_fetch_noto_sans(LICENSE f2095b08bed08b23a6fe26112fcd679a2bee3f002eef077eb05d215ed1051bd8)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Regular.ttf
|
||||||
|
478c558ea716033cd60c03438f628dfa75694dcf6b5f6d505a2f05fd2b4f3823)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Bold.ttf
|
||||||
|
1df075a380fc7cb898acf64c1f7b3b4dd780de3caa860178bf929de35817a913)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Italic.ttf
|
||||||
|
467e3f89eeca4108bb8710a2b9e0cf2281ac56d5b0609211a83776d0505eecb5)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-BoldItalic.ttf
|
||||||
|
1b602a9d6353be42c91df097a4857b69fa2696f26703d7a33b54a15d87c2622c)
|
||||||
|
install(FILES "${KYTY_NOTO_SANS_DIR}/LICENSE" DESTINATION "licenses/noto-sans")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${KYTY_NOTO_SANS_DIR}" PARENT_SCOPE)
|
||||||
|
|
||||||
# The guest instruction patcher needs a decoder/encoder and a small
|
# The guest instruction patcher needs a decoder/encoder and a small
|
||||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||||
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
|||||||
+54
-2
@@ -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_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_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_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 ${gpu_blit_shader_headers})
|
||||||
|
|
||||||
list(APPEND kyty_emulator_src
|
list(APPEND kyty_emulator_src
|
||||||
@@ -407,6 +408,8 @@ add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
|||||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(build_flags_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/BuildFlagsTests.cpp")
|
||||||
|
|
||||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||||
@@ -520,6 +523,13 @@ add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
|||||||
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||||
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
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
|
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||||
)
|
)
|
||||||
@@ -622,6 +632,19 @@ target_sources(shader_recompiler_compute_tests PRIVATE
|
|||||||
"${gpu_test_ms_depth_header}")
|
"${gpu_test_ms_depth_header}")
|
||||||
|
|
||||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
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
|
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||||
|
|
||||||
@@ -650,6 +673,7 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||||
|
add_test(NAME build_flags COMMAND $<TARGET_FILE:build_flags_tests>)
|
||||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||||
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||||
@@ -665,6 +689,7 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
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 audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
|
||||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_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 pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||||
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
|
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
|
||||||
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||||
@@ -681,6 +706,16 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
|
||||||
add_test(NAME shader_recompiler_float_min_max
|
add_test(NAME shader_recompiler_float_min_max
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
|
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
|
add_test(NAME shader_recompiler_alignbyte
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||||
add_test(NAME graphics_pipeline_rasterization
|
add_test(NAME graphics_pipeline_rasterization
|
||||||
@@ -689,6 +724,10 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
||||||
add_test(NAME virtual_memory_allocation
|
add_test(NAME virtual_memory_allocation
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
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
|
add_test(NAME guest_red_zone_patcher
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
||||||
add_test(NAME command_scheduler_timeline
|
add_test(NAME command_scheduler_timeline
|
||||||
@@ -697,8 +736,6 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||||
add_test(NAME gpu_command_lane
|
add_test(NAME gpu_command_lane
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
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
|
add_test(NAME pm4_flip
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||||
add_test(NAME depth_bits
|
add_test(NAME depth_bits
|
||||||
@@ -743,6 +780,7 @@ if(BUILD_TESTING)
|
|||||||
image_page_table_tests
|
image_page_table_tests
|
||||||
memory_tracker_tests
|
memory_tracker_tests
|
||||||
page_manager_tests
|
page_manager_tests
|
||||||
|
build_flags_tests
|
||||||
archive_file_tests
|
archive_file_tests
|
||||||
trophy_system_tests
|
trophy_system_tests
|
||||||
avplayer_file_tests
|
avplayer_file_tests
|
||||||
@@ -760,6 +798,7 @@ if(BUILD_TESTING)
|
|||||||
audio_out2_port_tests
|
audio_out2_port_tests
|
||||||
audio_out_timing_tests
|
audio_out_timing_tests
|
||||||
ngs2_sampler_tests
|
ngs2_sampler_tests
|
||||||
|
ajm_resample_tests
|
||||||
pad_haptics_tests
|
pad_haptics_tests
|
||||||
controller_settings_tests
|
controller_settings_tests
|
||||||
host_input_tests
|
host_input_tests
|
||||||
@@ -811,6 +850,19 @@ add_custom_target(kyty_touch_cursors
|
|||||||
add_dependencies(kyty_emulator kyty_touch_cursors)
|
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
||||||
|
|
||||||
|
set(KYTY_NOTO_SANS_FONTS
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Regular.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Bold.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Italic.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-BoldItalic.ttf")
|
||||||
|
add_custom_target(kyty_noto_sans_fonts
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${KYTY_NOTO_SANS_FONTS}
|
||||||
|
"$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
VERBATIM)
|
||||||
|
add_dependencies(kyty_emulator kyty_noto_sans_fonts)
|
||||||
|
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION assets/fonts)
|
||||||
|
|
||||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(kyty_emulator opengl32)
|
target_link_libraries(kyty_emulator opengl32)
|
||||||
|
|||||||
@@ -105,10 +105,18 @@ bool HideCursorEnabled() {
|
|||||||
return g_config->hide_cursor_enabled;
|
return g_config->hide_cursor_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StretchEnabled() {
|
||||||
|
return g_config->stretch_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool VrEnabled() {
|
bool VrEnabled() {
|
||||||
return g_config->vr_enabled;
|
return g_config->vr_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Ps5ProEnabled() {
|
||||||
|
return g_config->pro_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool AmdCpuEnabled() {
|
bool AmdCpuEnabled() {
|
||||||
return g_config->amd_cpu_enabled;
|
return g_config->amd_cpu_enabled;
|
||||||
}
|
}
|
||||||
@@ -129,6 +137,10 @@ bool IsNetworkConnected() {
|
|||||||
return g_config->network_connected;
|
return g_config->network_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PsnEmulationEnabled() {
|
||||||
|
return g_config->psn_emulation_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool ShaderValidationEnabled() {
|
bool ShaderValidationEnabled() {
|
||||||
return g_config->shader_validation_enabled;
|
return g_config->shader_validation_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,13 @@ struct ConfigOptions {
|
|||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
bool hdr_enabled = false;
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
|
bool stretch_enabled = false;
|
||||||
bool splash_enabled = true;
|
bool splash_enabled = true;
|
||||||
bool vr_enabled = false;
|
bool vr_enabled = false;
|
||||||
|
bool pro_enabled = false;
|
||||||
bool amd_cpu_enabled = false;
|
bool amd_cpu_enabled = false;
|
||||||
bool network_connected = false;
|
bool network_connected = false;
|
||||||
|
bool psn_emulation_enabled = false;
|
||||||
uint32_t vblank_frequency = 60;
|
uint32_t vblank_frequency = 60;
|
||||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||||
bool vulkan_validation_enabled = false;
|
bool vulkan_validation_enabled = false;
|
||||||
@@ -125,9 +128,12 @@ int32_t GetGpuIndex();
|
|||||||
bool FullscreenEnabled();
|
bool FullscreenEnabled();
|
||||||
bool HdrEnabled();
|
bool HdrEnabled();
|
||||||
bool HideCursorEnabled();
|
bool HideCursorEnabled();
|
||||||
|
bool StretchEnabled();
|
||||||
bool VrEnabled();
|
bool VrEnabled();
|
||||||
|
bool Ps5ProEnabled();
|
||||||
bool AmdCpuEnabled();
|
bool AmdCpuEnabled();
|
||||||
bool IsNetworkConnected();
|
bool IsNetworkConnected();
|
||||||
|
bool PsnEmulationEnabled();
|
||||||
uint32_t GetVblankFrequency();
|
uint32_t GetVblankFrequency();
|
||||||
uint32_t GetConsoleLanguage();
|
uint32_t GetConsoleLanguage();
|
||||||
bool VulkanValidationEnabled();
|
bool VulkanValidationEnabled();
|
||||||
|
|||||||
@@ -99,10 +99,6 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
|||||||
|
|
||||||
if (Common::File::IsFileExisting(param_json)) {
|
if (Common::File::IsFileExisting(param_json)) {
|
||||||
Loader::SystemContentLoadParamSfo(param_json);
|
Loader::SystemContentLoadParamSfo(param_json);
|
||||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
|
||||||
flexible_memory_size != 0) {
|
|
||||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cfg.splash_enabled) {
|
if (cfg.splash_enabled) {
|
||||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "graphics/guest_gpu/hardwareContext.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/render.h"
|
||||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ public:
|
|||||||
uint32_t mode);
|
uint32_t mode);
|
||||||
void DispatchIndirect(uint64_t args_addr, 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 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 TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||||
|
|
||||||
void WaitForRewind(bool valid);
|
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_POP_MARKER] = CpOpPopMarker;
|
||||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
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_CONTEXT_STATE] = CpOpContextState;
|
||||||
|
g_cp_op_custom_func[Pm4::R_PSSR] = CpOpPssr;
|
||||||
|
|
||||||
return g_cp_op_custom_func;
|
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 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 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 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
|
} // namespace Libs::Graphics
|
||||||
|
|
||||||
|
|||||||
@@ -1706,6 +1706,17 @@ KYTY_CP_OP_PARSER(CpOpFlip) {
|
|||||||
return 5;
|
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_CP_OP_PARSER(CpOpIndexType) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
|||||||
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
|
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
|
||||||
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
|
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
|
||||||
{BufferFormat::k10_11_11Float, 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_2UNorm, 4, 0, 4, true, false},
|
||||||
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
|
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
|
||||||
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
|
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -92,12 +91,6 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
|||||||
m_work_available.Signal();
|
m_work_available.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::Wake() {
|
|
||||||
Common::LockGuard lock(m_queue_mutex);
|
|
||||||
m_wake_pending = true;
|
|
||||||
m_work_available.Signal();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GuestGpu::ProcessCommands() {
|
void GuestGpu::ProcessCommands() {
|
||||||
EXIT_IF(!IsGpuThread());
|
EXIT_IF(!IsGpuThread());
|
||||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||||
@@ -443,10 +436,7 @@ void GuestGpu::ThreadRun(void* data) {
|
|||||||
}
|
}
|
||||||
if (selected_queue < 0) {
|
if (selected_queue < 0) {
|
||||||
gpu->m_processing = false;
|
gpu->m_processing = false;
|
||||||
// A wake that came before this wait must not be lost.
|
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
|
||||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
|
||||||
}
|
|
||||||
for (auto& queue: gpu->m_queues) {
|
for (auto& queue: gpu->m_queues) {
|
||||||
if (!queue.empty()) {
|
if (!queue.empty()) {
|
||||||
queue.front().blocked = false;
|
queue.front().blocked = false;
|
||||||
@@ -814,6 +804,27 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
|||||||
write(registers.draw_index, draw_index);
|
write(registers.draw_index, draw_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Writes the GS user-data slots the packet patches; false when the shaders cannot read the
|
||||||
|
// offsets from GPU records: a first-index or subdraw-index destination, or another stage's
|
||||||
|
// user data.
|
||||||
|
static bool IndirectDrawOffsets(IndirectDrawRegisters registers, ShaderIndirectOffsets& offsets) {
|
||||||
|
if (registers.index_offset != Pm4::SH_NOP || registers.draw_index != Pm4::SH_NOP) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto [location, slot]: {std::pair {registers.vertex_offset, &offsets.vertex},
|
||||||
|
std::pair {registers.instance_offset, &offsets.instance}}) {
|
||||||
|
if (location == Pm4::SH_NOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint32_t index = location - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||||
|
if (index >= HW::UserSgprInfo::SGPRS_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*slot = static_cast<uint8_t>(index);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||||
uint32_t draw_initiator, bool indexed) {
|
uint32_t draw_initiator, bool indexed) {
|
||||||
const auto args_size =
|
const auto args_size =
|
||||||
@@ -845,13 +856,10 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
|||||||
// NUM_INSTANCES keeps the last draw's instance count.
|
// 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 +
|
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
// The GPU reads the records if the shaders allow; the VS prolog removes Vulkan's bases
|
||||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
// from the index VGPRs and copies them only to the user SGPRs the packet patches.
|
||||||
// while they are zero.
|
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
|
||||||
registers.index_offset == Pm4::SH_NOP && registers.draw_index == Pm4::SH_NOP &&
|
|
||||||
(!indexed || m_index_buffer_size != 0) &&
|
|
||||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||||
{.index_count = m_index_buffer_size,
|
{.index_count = m_index_buffer_size,
|
||||||
.index_addr = IndexAddress(0),
|
.index_addr = IndexAddress(0),
|
||||||
@@ -921,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) {
|
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||||
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
||||||
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
||||||
@@ -941,11 +954,10 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
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.
|
BufferFlush();
|
||||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
|
||||||
static_cast<int>(display_buffer_index))) {
|
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||||
SuspendPm4();
|
static_cast<int>(display_buffer_index));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||||
@@ -976,16 +988,28 @@ void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector
|
|||||||
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||||
m_renderer.GetOcclusionQueries().Publish();
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
|
|
||||||
|
// A label beside GPU-written bytes goes to the backing store, so they are not downloaded.
|
||||||
|
// Other labels go through the guest mapping: its fault keeps a clean cached copy current and
|
||||||
|
// downloads GPU-written bytes under the label before the store.
|
||||||
|
const auto write_label = [this, address](const void* value, uint64_t size) {
|
||||||
|
const auto vaddr = reinterpret_cast<uint64_t>(address);
|
||||||
|
auto& buffers = m_renderer.GetBufferCache();
|
||||||
|
if (!GuestRange {vaddr, size}.Valid() || !buffers.IsRegionGpuModified(vaddr, size) ||
|
||||||
|
buffers.HasGpuDirtyBytes(vaddr, size) ||
|
||||||
|
!LibKernel::Memory::TryWriteBacking(vaddr, value, size)) {
|
||||||
|
std::memcpy(address, value, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
||||||
switch (data_sel) {
|
switch (data_sel) {
|
||||||
case 0: break;
|
case 0: break;
|
||||||
case 1: {
|
case 1: {
|
||||||
const auto value = static_cast<uint32_t>(data);
|
const auto value = static_cast<uint32_t>(data);
|
||||||
std::memcpy(address, &value, sizeof(value));
|
write_label(&value, sizeof(value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
||||||
case 2: std::memcpy(address, &data, sizeof(data)); break;
|
case 2: write_label(&data, sizeof(data)); break;
|
||||||
case 5: {
|
case 5: {
|
||||||
GetScheduler().Finish();
|
GetScheduler().Finish();
|
||||||
const auto gds = static_cast<uint32_t>(data);
|
const auto gds = static_cast<uint32_t>(data);
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public:
|
|||||||
[[nodiscard]] bool IsStopping();
|
[[nodiscard]] bool IsStopping();
|
||||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||||
void SendCommandSync(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.
|
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||||
void Submit(std::span<const uint32_t> commands);
|
void Submit(std::span<const uint32_t> commands);
|
||||||
@@ -84,7 +82,6 @@ private:
|
|||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_accepting = true;
|
bool m_accepting = true;
|
||||||
bool m_stopping = false;
|
bool m_stopping = false;
|
||||||
bool m_wake_pending = false;
|
|
||||||
bool m_shutdown_complete = false;
|
bool m_shutdown_complete = false;
|
||||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
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_FLIP = 0x17;
|
||||||
constexpr uint32_t R_DMA_DATA = 0x19;
|
constexpr uint32_t R_DMA_DATA = 0x19;
|
||||||
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
||||||
|
constexpr uint32_t R_PSSR = 0x1B;
|
||||||
|
|
||||||
constexpr uint32_t R_NUM = 0x3F + 1;
|
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_
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -17,14 +18,19 @@ public:
|
|||||||
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
||||||
it = std::prev(it);
|
it = std::prev(it);
|
||||||
}
|
}
|
||||||
uint64_t begin = address;
|
if (it == m_ranges.end() || it->first > address) {
|
||||||
uint64_t last = end;
|
it = m_ranges.emplace_hint(it, address, end);
|
||||||
while (it != m_ranges.end() && it->first <= last) {
|
} else {
|
||||||
begin = std::min(begin, it->first);
|
if (it->second >= end) {
|
||||||
last = std::max(last, it->second);
|
return;
|
||||||
it = m_ranges.erase(it);
|
}
|
||||||
|
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) {
|
void Subtract(uint64_t address, uint64_t size) {
|
||||||
|
|||||||
+30
-30
@@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||||
const bool source_depth = source.info.IsDepth();
|
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||||
const bool destination_depth = destination.info.IsDepth();
|
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||||
if (source_depth == destination_depth) {
|
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto& depth = source_depth ? source : destination;
|
auto& depth = source_wide ? source : destination;
|
||||||
auto& color = source_depth ? destination : source;
|
auto& narrow = source_wide ? destination : source;
|
||||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
|
||||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||||
@@ -507,42 +506,42 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
|||||||
|
|
||||||
const auto layers = depth.info.resources.layers;
|
const auto layers = depth.info.resources.layers;
|
||||||
const uint64_t depth_slice =
|
const uint64_t depth_slice =
|
||||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||||
const uint64_t color_slice =
|
const uint64_t narrow_slice =
|
||||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||||
const auto depth_size = depth_slice * layers;
|
const std::array depth_copies {
|
||||||
const auto color_size = color_slice * layers;
|
|
||||||
const std::array depth_copies {
|
|
||||||
vk::BufferImageCopy {0,
|
vk::BufferImageCopy {0,
|
||||||
depth.info.pitch,
|
depth.info.pitch,
|
||||||
depth.info.extent.height,
|
depth.info.extent.height,
|
||||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||||
{},
|
{},
|
||||||
depth.info.extent}};
|
depth.info.extent}};
|
||||||
const std::array color_copies {
|
const std::array narrow_copies {
|
||||||
vk::BufferImageCopy {0,
|
vk::BufferImageCopy {0,
|
||||||
color.info.pitch,
|
narrow.info.pitch,
|
||||||
color.info.extent.height,
|
narrow.info.extent.height,
|
||||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
{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 depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||||
const TileManager::D16Layout promote_layout {
|
const TileManager::D16Layout promote_layout {
|
||||||
.width = depth.info.extent.width,
|
.width = depth.info.extent.width,
|
||||||
.height = depth.info.extent.height,
|
.height = depth.info.extent.height,
|
||||||
.layers = layers,
|
.layers = layers,
|
||||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||||
.source_slice_stride = color_slice,
|
.source_slice_stride = narrow_slice,
|
||||||
.target_slice_stride = depth_slice,
|
.target_slice_stride = depth_slice,
|
||||||
};
|
};
|
||||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
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);
|
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,
|
{.width = promote_layout.width,
|
||||||
.height = promote_layout.height,
|
.height = promote_layout.height,
|
||||||
.layers = promote_layout.layers,
|
.layers = promote_layout.layers,
|
||||||
@@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
|||||||
.target_row_stride = promote_layout.source_row_stride,
|
.target_row_stride = promote_layout.source_row_stride,
|
||||||
.source_slice_stride = promote_layout.target_slice_stride,
|
.source_slice_stride = promote_layout.target_slice_stride,
|
||||||
.target_slice_stride = promote_layout.source_slice_stride});
|
.target_slice_stride = promote_layout.source_slice_stride});
|
||||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||||
color_buffer.size);
|
narrow_buffer.size);
|
||||||
} else {
|
} else {
|
||||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
narrow_buffer.size);
|
||||||
|
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||||
promote_layout);
|
promote_layout);
|
||||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||||
depth_buffer.size);
|
depth_buffer.size);
|
||||||
|
|||||||
@@ -125,6 +125,14 @@ void DynamicState::Commit(const GraphicContext& graphics, vk::CommandBuffer cmdb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (dirty_state.cull_mode) {
|
||||||
|
dirty_state.cull_mode = false;
|
||||||
|
cmdbuf.setCullMode(cull_mode);
|
||||||
|
}
|
||||||
|
if (dirty_state.front_face) {
|
||||||
|
dirty_state.front_face = false;
|
||||||
|
cmdbuf.setFrontFace(front_face);
|
||||||
|
}
|
||||||
if (dirty_state.blend_constants) {
|
if (dirty_state.blend_constants) {
|
||||||
dirty_state.blend_constants = false;
|
dirty_state.blend_constants = false;
|
||||||
cmdbuf.setBlendConstants(blend_constants.data());
|
cmdbuf.setBlendConstants(blend_constants.data());
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ struct DynamicState {
|
|||||||
bool stencil_back_write_mask : 1;
|
bool stencil_back_write_mask : 1;
|
||||||
bool stencil_back_compare_mask : 1;
|
bool stencil_back_compare_mask : 1;
|
||||||
|
|
||||||
|
bool cull_mode : 1;
|
||||||
|
bool front_face : 1;
|
||||||
|
|
||||||
bool blend_constants : 1;
|
bool blend_constants : 1;
|
||||||
bool color_write_enables : 1;
|
bool color_write_enables : 1;
|
||||||
bool line_width : 1;
|
bool line_width : 1;
|
||||||
@@ -86,6 +89,9 @@ struct DynamicState {
|
|||||||
uint32_t stencil_back_write_mask {};
|
uint32_t stencil_back_write_mask {};
|
||||||
uint32_t stencil_back_compare_mask {};
|
uint32_t stencil_back_compare_mask {};
|
||||||
|
|
||||||
|
vk::CullModeFlags cull_mode {};
|
||||||
|
vk::FrontFace front_face {};
|
||||||
|
|
||||||
std::array<float, 4> blend_constants {};
|
std::array<float, 4> blend_constants {};
|
||||||
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
||||||
uint32_t num_color_write_enables {};
|
uint32_t num_color_write_enables {};
|
||||||
@@ -221,6 +227,20 @@ struct DynamicState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetCullMode(const vk::CullModeFlags cull_mode_) {
|
||||||
|
if (cull_mode != cull_mode_) {
|
||||||
|
cull_mode = cull_mode_;
|
||||||
|
dirty_state.cull_mode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFrontFace(const vk::FrontFace front_face_) {
|
||||||
|
if (front_face != front_face_) {
|
||||||
|
front_face = front_face_;
|
||||||
|
dirty_state.front_face = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
||||||
if (blend_constants != blend_constants_) {
|
if (blend_constants != blend_constants_) {
|
||||||
blend_constants = blend_constants_;
|
blend_constants = blend_constants_;
|
||||||
|
|||||||
@@ -17,9 +17,15 @@
|
|||||||
|
|
||||||
namespace Libs::Graphics {
|
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 };
|
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask };
|
||||||
|
|
||||||
@@ -99,6 +105,8 @@ struct ImageInfo {
|
|||||||
switch (guest_format) {
|
switch (guest_format) {
|
||||||
case Prospero::BufferFormat::k10_11_11Float:
|
case Prospero::BufferFormat::k10_11_11Float:
|
||||||
return ColorTransform::Reverse10_11_11;
|
return ColorTransform::Reverse10_11_11;
|
||||||
|
case Prospero::BufferFormat::k2_10_10_10UNorm:
|
||||||
|
return ColorTransform::Rotate2_10_10_10;
|
||||||
default: return ColorTransform::None;
|
default: return ColorTransform::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,8 +295,9 @@ inline bool ImageInfo::IsDepth() const noexcept {
|
|||||||
[[nodiscard]] inline VideoOutCompression
|
[[nodiscard]] inline VideoOutCompression
|
||||||
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
|
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
|
||||||
uint64_t dcc_clear_color) noexcept {
|
uint64_t dcc_clear_color) noexcept {
|
||||||
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
|
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
|
||||||
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
|
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
|
||||||
|
constexpr uint32_t DCC_128_128_128 = 0x00100024u;
|
||||||
if (!compressed) {
|
if (!compressed) {
|
||||||
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
|
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
|
||||||
? VideoOutCompression::Uncompressed
|
? VideoOutCompression::Uncompressed
|
||||||
@@ -300,6 +309,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
|
|||||||
switch (dcc_control) {
|
switch (dcc_control) {
|
||||||
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
|
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
|
||||||
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
|
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
|
||||||
|
case DCC_128_128_128: return VideoOutCompression::Dcc128_128_128;
|
||||||
default: return VideoOutCompression::Unsupported;
|
default: return VideoOutCompression::Unsupported;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ struct HostFormatInfo {
|
|||||||
|
|
||||||
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
|
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
|
||||||
Prospero::ChannelOrder order) {
|
Prospero::ChannelOrder order) {
|
||||||
if (guest_format == Prospero::BufferFormat::k10_11_11Float) {
|
switch (guest_format) {
|
||||||
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
|
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) {
|
if (order == Prospero::ChannelOrder::kAlt) {
|
||||||
switch (guest_format) {
|
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 ||
|
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
|
||||||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
|
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
|
||||||
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
|
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) {
|
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||||
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
|
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
|
||||||
};
|
};
|
||||||
@@ -484,7 +485,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
|||||||
vk::AccessFlagBits::eHostWrite |
|
vk::AccessFlagBits::eHostWrite |
|
||||||
vk::AccessFlagBits::eTransferWrite |
|
vk::AccessFlagBits::eTransferWrite |
|
||||||
vk::AccessFlagBits::eShaderWrite;
|
vk::AccessFlagBits::eShaderWrite;
|
||||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||||
command.pipelineBarrier(
|
command.pipelineBarrier(
|
||||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||||
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
|||||||
uint32_t element_bytes = 0;
|
uint32_t element_bytes = 0;
|
||||||
switch (transform) {
|
switch (transform) {
|
||||||
case ColorTransform::SwapBgra16: element_bytes = 8; break;
|
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");
|
default: EXIT("invalid color transform\n");
|
||||||
}
|
}
|
||||||
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
|
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace {
|
|||||||
|
|
||||||
namespace IR = ShaderRecompiler::IR;
|
namespace IR = ShaderRecompiler::IR;
|
||||||
|
|
||||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||||
|
|
||||||
@@ -133,12 +133,16 @@ ImageHeap::~ImageHeap() {
|
|||||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
const auto size = request.size & ~uint64_t {3};
|
const auto size = request.size & ~uint64_t {3};
|
||||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
if (request.count == 0u || request.step == 0u ||
|
||||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto memory = std::ranges::find_if(
|
auto memory = std::ranges::find_if(
|
||||||
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||||
|
if (memory == m_memories.end() && size < MinTableBytes &&
|
||||||
|
m_sightings.try_emplace(request.base, m_draw).first->second == m_draw) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||||
if (!m_context.IsMapped(request.base, size)) return false;
|
if (!m_context.IsMapped(request.base, size)) return false;
|
||||||
@@ -593,26 +597,42 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
|||||||
} else {
|
} else {
|
||||||
m_unmapped.push_back({address, size});
|
m_unmapped.push_back({address, size});
|
||||||
}
|
}
|
||||||
auto& scheduler = m_context.GetCommandScheduler();
|
|
||||||
std::scoped_lock lock {m_mutex};
|
std::scoped_lock lock {m_mutex};
|
||||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Unwatch(*memory);
|
Drop(*memory);
|
||||||
for (auto& view: memory->views) {
|
|
||||||
std::erase(m_active, view.get());
|
|
||||||
for (const auto& [value, index]: view->elements) {
|
|
||||||
Unreference(m_elements[index]);
|
|
||||||
view->released.push_back(index);
|
|
||||||
}
|
|
||||||
Retire(*view);
|
|
||||||
if (scheduler.Active()) {
|
|
||||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageHeap::RunGarbageCollector() {
|
||||||
|
// A smaller table rebuilt each frame moves to a new base. Larger ones keep their dead words.
|
||||||
|
std::scoped_lock lock {m_mutex};
|
||||||
|
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||||
|
if (memory->size >= MinTableBytes || memory->draw > m_suspend_draw) return false;
|
||||||
|
Drop(*memory);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
m_suspend_draw = m_draw;
|
||||||
|
m_sightings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageHeap::Drop(Memory& memory) {
|
||||||
|
auto& scheduler = m_context.GetCommandScheduler();
|
||||||
|
Unwatch(memory);
|
||||||
|
for (auto& view: memory.views) {
|
||||||
|
std::erase(m_active, view.get());
|
||||||
|
for (const auto& [value, index]: view->elements) {
|
||||||
|
Unreference(m_elements[index]);
|
||||||
|
view->released.push_back(index);
|
||||||
|
}
|
||||||
|
Retire(*view);
|
||||||
|
if (scheduler.Active()) {
|
||||||
|
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics
|
} // namespace Libs::Graphics
|
||||||
@@ -26,7 +26,7 @@ struct ImageHeapTestAccess;
|
|||||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||||
// Image::heap_epoch.
|
// Image::heap_epoch. A smaller table leaves at a suspend point if the frame did not acquire it.
|
||||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||||
public:
|
public:
|
||||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||||
@@ -47,6 +47,8 @@ public:
|
|||||||
// CPU write fault, on any thread.
|
// CPU write fault, on any thread.
|
||||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||||
void UnmapMemory(uint64_t address, uint64_t size);
|
void UnmapMemory(uint64_t address, uint64_t size);
|
||||||
|
// At a suspend point.
|
||||||
|
void RunGarbageCollector();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||||
@@ -107,6 +109,7 @@ private:
|
|||||||
template <bool track>
|
template <bool track>
|
||||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||||
void Unwatch(const Memory& memory);
|
void Unwatch(const Memory& memory);
|
||||||
|
void Drop(Memory& memory);
|
||||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||||
void UpdateProbe(View& view, uint64_t probe);
|
void UpdateProbe(View& view, uint64_t probe);
|
||||||
void Assign(View& view, size_t slot, uint32_t entry);
|
void Assign(View& view, size_t slot, uint32_t entry);
|
||||||
@@ -125,9 +128,11 @@ private:
|
|||||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||||
std::vector<View*> m_active; // Acquired since the last commit.
|
std::vector<View*> m_active; // Acquired since the last commit.
|
||||||
uint64_t m_draw = 1;
|
uint64_t m_draw = 1;
|
||||||
|
uint64_t m_suspend_draw = 0; // m_draw at the last suspend point.
|
||||||
std::vector<Element> m_elements;
|
std::vector<Element> m_elements;
|
||||||
std::vector<uint32_t> m_free;
|
std::vector<uint32_t> m_free;
|
||||||
|
std::unordered_map<uint64_t, uint64_t> m_sightings; // Smaller tables: base, first draw.
|
||||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||||
std::vector<uint32_t> m_scratch;
|
std::vector<uint32_t> m_scratch;
|
||||||
|
|||||||
@@ -687,6 +687,10 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||||
}
|
}
|
||||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
const bool 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) {
|
if (object_primitive_id) {
|
||||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||||
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
||||||
@@ -1003,15 +1007,11 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(const CommandBuffer&
|
|||||||
}
|
}
|
||||||
static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask();
|
static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask();
|
||||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||||
static_params.cull_back = !rect_list && mc.cull_back;
|
|
||||||
static_params.cull_front = !rect_list && mc.cull_front;
|
|
||||||
static_params.face = mc.face;
|
|
||||||
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
||||||
static_params.polygon_mode =
|
static_params.polygon_mode =
|
||||||
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
|
ResolvePolygonMode(mc, !rect_list && mc.cull_front, !rect_list && mc.cull_back);
|
||||||
if (tessellation) {
|
if (tessellation) {
|
||||||
static_params.patch_control_points =
|
static_params.patch_control_points = vs_input_info.tess.input_control_points;
|
||||||
static_cast<uint8_t>(vs_input_info.tess.input_control_points);
|
|
||||||
} else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) {
|
} else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) {
|
||||||
// Rectangle lists expand through generated tessellation stages.
|
// Rectangle lists expand through generated tessellation stages.
|
||||||
static_params.patch_control_points = 3;
|
static_params.patch_control_points = 3;
|
||||||
|
|||||||
@@ -54,15 +54,12 @@ struct PipelineStaticParameters {
|
|||||||
bool depth_clip_enable = true;
|
bool depth_clip_enable = true;
|
||||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||||
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
||||||
uint8_t patch_control_points = 0;
|
uint32_t patch_control_points = 0;
|
||||||
bool primitive_restart_enable = false;
|
bool primitive_restart_enable = false;
|
||||||
uint32_t samples = 1;
|
uint32_t samples = 1;
|
||||||
bool sample_shading_enable = false;
|
bool sample_shading_enable = false;
|
||||||
bool alpha_to_coverage_enable = false;
|
bool alpha_to_coverage_enable = false;
|
||||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||||
bool cull_front = false;
|
|
||||||
bool cull_back = false;
|
|
||||||
bool face = false;
|
|
||||||
bool provoking_vtx_last = false;
|
bool provoking_vtx_last = false;
|
||||||
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
||||||
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||||
@@ -140,6 +137,9 @@ struct DrawRenderState {
|
|||||||
bool ps_active = false;
|
bool ps_active = false;
|
||||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||||
ShaderPixelInputInfo ps_input_info;
|
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 {
|
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||||
|
|||||||
@@ -219,17 +219,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
vk::PipelineViewportStateCreateInfo viewport_state {};
|
vk::PipelineViewportStateCreateInfo viewport_state {};
|
||||||
viewport_state.pNext = &depth_clip_control;
|
viewport_state.pNext = &depth_clip_control;
|
||||||
|
|
||||||
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
|
|
||||||
if (static_params.cull_back) {
|
|
||||||
cull_mode |= vk::CullModeFlagBits::eBack;
|
|
||||||
}
|
|
||||||
if (static_params.cull_front) {
|
|
||||||
cull_mode |= vk::CullModeFlagBits::eFront;
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::FrontFace front_face =
|
|
||||||
(static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);
|
|
||||||
|
|
||||||
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
||||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||||
|
|
||||||
@@ -250,8 +239,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
provoking_vertex.pNext = rasterizer.pNext;
|
provoking_vertex.pNext = rasterizer.pNext;
|
||||||
rasterizer.pNext = &provoking_vertex;
|
rasterizer.pNext = &provoking_vertex;
|
||||||
}
|
}
|
||||||
rasterizer.cullMode = cull_mode;
|
|
||||||
rasterizer.frontFace = front_face;
|
|
||||||
rasterizer.polygonMode = static_params.polygon_mode;
|
rasterizer.polygonMode = static_params.polygon_mode;
|
||||||
rasterizer.lineWidth = 1.0f;
|
rasterizer.lineWidth = 1.0f;
|
||||||
|
|
||||||
@@ -347,6 +334,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
vk::DynamicState::eViewportWithCount,
|
vk::DynamicState::eViewportWithCount,
|
||||||
vk::DynamicState::eScissorWithCount,
|
vk::DynamicState::eScissorWithCount,
|
||||||
vk::DynamicState::eLineWidth,
|
vk::DynamicState::eLineWidth,
|
||||||
|
vk::DynamicState::eCullMode,
|
||||||
|
vk::DynamicState::eFrontFace,
|
||||||
vk::DynamicState::eDepthTestEnable,
|
vk::DynamicState::eDepthTestEnable,
|
||||||
vk::DynamicState::eDepthWriteEnable,
|
vk::DynamicState::eDepthWriteEnable,
|
||||||
vk::DynamicState::eDepthCompareOp,
|
vk::DynamicState::eDepthCompareOp,
|
||||||
|
|||||||
@@ -45,9 +45,10 @@ enum class DrawOffsetSource : uint8_t {
|
|||||||
|
|
||||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||||
struct DrawIndirectArgs {
|
struct DrawIndirectArgs {
|
||||||
uint64_t address = 0;
|
uint64_t address = 0;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
uint32_t stride = 0;
|
uint32_t stride = 0;
|
||||||
|
ShaderIndirectOffsets offsets;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawIndexArgs {
|
struct DrawIndexArgs {
|
||||||
@@ -149,15 +150,21 @@ private:
|
|||||||
friend class CommandScheduler;
|
friend class CommandScheduler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace Pssr {
|
||||||
|
struct Request;
|
||||||
|
}
|
||||||
|
|
||||||
class RenderExecutor {
|
class RenderExecutor {
|
||||||
public:
|
public:
|
||||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||||
|
~RenderExecutor();
|
||||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||||
|
|
||||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
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);
|
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,
|
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||||
uint32_t mode, bool async_compute);
|
uint32_t mode, bool async_compute);
|
||||||
|
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
|
||||||
|
|
||||||
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
||||||
void FindBuffers(std::span<PreparedBindings* const> stages);
|
void FindBuffers(std::span<PreparedBindings* const> stages);
|
||||||
@@ -218,6 +225,9 @@ private:
|
|||||||
std::vector<ImageId> m_bound_images;
|
std::vector<ImageId> m_bound_images;
|
||||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
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 class CommandProcessor;
|
||||||
friend struct RenderExecutorTestAccess;
|
friend struct RenderExecutorTestAccess;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ void RenderContext::RunGarbageCollector() {
|
|||||||
m_fault_process_pending = false;
|
m_fault_process_pending = false;
|
||||||
m_buffer_cache.ProcessFaultBuffer();
|
m_buffer_cache.ProcessFaultBuffer();
|
||||||
}
|
}
|
||||||
|
m_image_heap.RunGarbageCollector();
|
||||||
m_texture_cache.ProcessDownloadImages();
|
m_texture_cache.ProcessDownloadImages();
|
||||||
m_texture_cache.RunGarbageCollector();
|
m_texture_cache.RunGarbageCollector();
|
||||||
m_buffer_cache.RunGarbageCollector();
|
m_buffer_cache.RunGarbageCollector();
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
|||||||
const ShaderVertexInputInfo& vs_input_info) {
|
const ShaderVertexInputInfo& vs_input_info) {
|
||||||
auto vertex_offset = static_cast<int32_t>(index_offset);
|
auto vertex_offset = static_cast<int32_t>(index_offset);
|
||||||
uint32_t instance_offset = 0;
|
uint32_t instance_offset = 0;
|
||||||
if (!vs_input_info.fetch_embedded) {
|
if (!vs_input_info.has_dynamic_vertex_fetch) {
|
||||||
return {vertex_offset, instance_offset};
|
return {vertex_offset, instance_offset};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +403,20 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
|||||||
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rect lists are drawn without culling.
|
||||||
|
vk::CullModeFlags cull_mode {};
|
||||||
|
if (!Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||||
|
if (mode.cull_front) {
|
||||||
|
cull_mode |= vk::CullModeFlagBits::eFront;
|
||||||
|
}
|
||||||
|
if (mode.cull_back) {
|
||||||
|
cull_mode |= vk::CullModeFlagBits::eBack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic_state.SetCullMode(cull_mode);
|
||||||
|
dynamic_state.SetFrontFace(mode.face ? vk::FrontFace::eClockwise
|
||||||
|
: vk::FrontFace::eCounterClockwise);
|
||||||
|
|
||||||
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
||||||
if (depth.stencil_test_enable) {
|
if (depth.stencil_test_enable) {
|
||||||
const auto& front = depth.stencil_front;
|
const auto& front = depth.stencil_front;
|
||||||
@@ -1236,24 +1250,36 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||||
// mesh and quad-list draws do. 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,
|
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||||
const ShaderVertexInputInfo& vs,
|
const ShaderVertexInputInfo& vs,
|
||||||
DrawOffsetSource source,
|
DrawOffsetSource source,
|
||||||
int32_t first_vertex,
|
int32_t first_vertex,
|
||||||
uint32_t first_instance,
|
uint32_t first_instance,
|
||||||
const DrawIndirectArgs& indirect) {
|
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 (indirect.address != 0) {
|
||||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
if (!vs.indirect_draw ||
|
||||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||||
|
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||||
|
vs.indirect_offsets != indirect.offsets) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return DrawEmitInfo {.indirect = indirect};
|
return DrawEmitInfo {.indirect = indirect};
|
||||||
}
|
}
|
||||||
if (source == DrawOffsetSource::IndirectArgs) {
|
if (source == DrawOffsetSource::IndirectArgs) {
|
||||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
// 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);
|
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||||
@@ -1310,7 +1336,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
|||||||
|
|
||||||
hw_check(buffer);
|
hw_check(buffer);
|
||||||
|
|
||||||
DrawRenderState state {};
|
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||||
|
.indirect_draw = args.indirect.address != 0};
|
||||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1415,7 +1442,8 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
|||||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||||
args.vertex_count, args.instance_count, args.first_instance};
|
args.vertex_count, args.instance_count, args.first_instance};
|
||||||
|
|
||||||
DrawRenderState state {};
|
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||||
|
.indirect_draw = args.indirect.address != 0};
|
||||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||||
ResetBindings();
|
ResetBindings();
|
||||||
return true;
|
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) {
|
uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
|
||||||
switch (transform) {
|
switch (transform) {
|
||||||
case 1u:
|
case 1u:
|
||||||
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
|
|||||||
}
|
}
|
||||||
break;
|
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;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
#extension GL_EXT_shader_16bit_storage : require
|
||||||
|
|
||||||
layout(constant_id = 0) const uint D32 = 0u;
|
layout(constant_id = 0) const uint D32 = 0u;
|
||||||
layout(local_size_x = 64) in;
|
layout(local_size_x = 64) in;
|
||||||
|
|
||||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
layout(set = 0, binding = 1, std430) buffer Output { uint16_t data[]; } output_buffer;
|
||||||
|
|
||||||
layout(push_constant, std430) uniform Conversion {
|
layout(push_constant, std430) uniform Conversion {
|
||||||
uint src_base;
|
uint src_base;
|
||||||
@@ -32,9 +33,5 @@ void main() {
|
|||||||
}
|
}
|
||||||
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
||||||
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
||||||
uint shift = (dst & 2u) * 8u;
|
output_buffer.data[dst >> 1u] = uint16_t(decode(input_buffer.data[src >> 2u]));
|
||||||
uint mask = 0xffffu << shift;
|
|
||||||
uint word = dst >> 2u;
|
|
||||||
atomicAnd(output_buffer.data[word], ~mask);
|
|
||||||
atomicOr(output_buffer.data[word], decode(input_buffer.data[src >> 2u]) << shift);
|
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,8 @@ bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanIm
|
|||||||
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
|
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
|
||||||
|
|
||||||
VmaAllocationCreateInfo alloc_info {};
|
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;
|
vk::Image::CType native_image = VK_NULL_HANDLE;
|
||||||
const auto result = static_cast<vk::Result>(
|
const auto result = static_cast<vk::Result>(
|
||||||
|
|||||||
@@ -533,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
|||||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||||
Row("Extended", Bytes(guest.automatic_allocated));
|
Row("Extended", Bytes(guest.automatic_allocated));
|
||||||
// Past the pool size the guest sees no page-table entries available.
|
// Past its total a side sees no page-table entries available.
|
||||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||||
|
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||||
Row("Page table (2 MB)",
|
Row("Page table (2 MB)",
|
||||||
Fmt("CPU %llu GPU %llu / %llu",
|
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||||
|
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||||
entries > guest.page_entries_total ? BAD
|
LevelColor(static_cast<double>(percent), 90, 100));
|
||||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
|
||||||
: GOOD);
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
if (BeginSection("Emulator process")) {
|
if (BeginSection("Emulator process")) {
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public:
|
|||||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||||
bool Flip(uint32_t micros);
|
bool Flip(uint32_t micros);
|
||||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
void Wait(VideoOutConfig& cfg, int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||||
@@ -240,7 +240,6 @@ private:
|
|||||||
Graphics::Presenter::Frame* frame;
|
Graphics::Presenter::Frame* frame;
|
||||||
bool premultiplied_alpha;
|
bool premultiplied_alpha;
|
||||||
};
|
};
|
||||||
void SignalDoneLocked();
|
|
||||||
|
|
||||||
Graphics::Presenter& m_presenter;
|
Graphics::Presenter& m_presenter;
|
||||||
Common::Mutex m_mutex;
|
Common::Mutex m_mutex;
|
||||||
@@ -251,7 +250,6 @@ private:
|
|||||||
std::list<Request> m_cpu_requests;
|
std::list<Request> m_cpu_requests;
|
||||||
std::list<Request> m_cancelled_requests;
|
std::list<Request> m_cancelled_requests;
|
||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_gpu_waiting = false;
|
|
||||||
uint64_t m_next_request_id = 1;
|
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) {
|
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||||
@@ -1035,7 +1033,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
|||||||
it = queue->erase(it);
|
it = queue->erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalDoneLocked();
|
m_done_cond_var.SignalAll();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_submit_cond_var.SignalAll();
|
m_submit_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
@@ -1207,24 +1205,17 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlipQueue::SignalDoneLocked() {
|
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||||
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) {
|
|
||||||
Common::LockGuard lock(m_mutex);
|
Common::LockGuard lock(m_mutex);
|
||||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
|
||||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
auto has_request = [this, &cfg, index] {
|
||||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||||
m_gpu_waiting = !safe;
|
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||||
return safe;
|
};
|
||||||
|
while (has_request()) {
|
||||||
|
m_done_cond_var.Wait(&m_mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlipQueue::Flip(uint32_t micros) {
|
bool FlipQueue::Flip(uint32_t micros) {
|
||||||
@@ -1320,7 +1311,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
|||||||
it = m_requests.erase(it);
|
it = m_requests.erase(it);
|
||||||
}
|
}
|
||||||
m_processing = false;
|
m_processing = false;
|
||||||
SignalDoneLocked();
|
m_done_cond_var.SignalAll();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
for (size_t i = count; i != 0; i--) {
|
for (size_t i = count; i != 0; i--) {
|
||||||
@@ -1703,12 +1694,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
|||||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
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);
|
auto* ctx = m_impl->Get(handle);
|
||||||
EXIT_IF(ctx == nullptr);
|
EXIT_IF(ctx == nullptr);
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
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) {
|
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public:
|
|||||||
void CompleteFlip(uint64_t request_id);
|
void CompleteFlip(uint64_t request_id);
|
||||||
void SubmitFlipPreparation(uint64_t request_id);
|
void SubmitFlipPreparation(uint64_t request_id);
|
||||||
void WaitForSubmitSlot(int handle);
|
void WaitForSubmitSlot(int handle);
|
||||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
void WaitFlipDone(int handle, int index);
|
||||||
|
|
||||||
[[nodiscard]] Impl& State() noexcept;
|
[[nodiscard]] Impl& State() noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
vk::SurfaceFormatKHR SelectFormat();
|
vk::SurfaceFormatKHR SelectFormat();
|
||||||
void Destroy();
|
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;
|
WindowContext& m_window;
|
||||||
vk::SwapchainKHR m_handle = nullptr;
|
vk::SwapchainKHR m_handle = nullptr;
|
||||||
@@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() {
|
|||||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
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;
|
auto device = m_window.graphic_ctx.device;
|
||||||
if (m_overlay_pipeline == nullptr) {
|
if (m_overlay_pipeline == nullptr) {
|
||||||
vk::SamplerCreateInfo sampler {};
|
vk::SamplerCreateInfo sampler {};
|
||||||
@@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
|||||||
rendering.colorAttachmentCount = 1;
|
rendering.colorAttachmentCount = 1;
|
||||||
rendering.pColorAttachments = &attachment;
|
rendering.pColorAttachments = &attachment;
|
||||||
command.beginRendering(&rendering);
|
command.beginRendering(&rendering);
|
||||||
const vk::Viewport viewport {
|
const vk::Viewport viewport {static_cast<float>(rect.offset.x),
|
||||||
0.0f, 0.0f, static_cast<float>(m_extent.width), static_cast<float>(m_extent.height),
|
static_cast<float>(rect.offset.y),
|
||||||
0.0f, 1.0f};
|
static_cast<float>(rect.extent.width),
|
||||||
const vk::Rect2D scissor {{0, 0}, m_extent};
|
static_cast<float>(rect.extent.height),
|
||||||
|
0.0f,
|
||||||
|
1.0f};
|
||||||
command.setViewport(0, 1, &viewport);
|
command.setViewport(0, 1, &viewport);
|
||||||
command.setScissor(0, 1, &scissor);
|
command.setScissor(0, 1, &rect);
|
||||||
command.draw(3, 1, 0, 0);
|
command.draw(3, 1, 0, 0);
|
||||||
command.endRendering();
|
command.endRendering();
|
||||||
}
|
}
|
||||||
@@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||||
nullptr, 0, nullptr, 1, &to_transfer);
|
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) {
|
if (source != nullptr) {
|
||||||
vk::ImageBlit region {};
|
vk::ImageBlit region {};
|
||||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
@@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
region.dstSubresource.mipLevel = 0;
|
region.dstSubresource.mipLevel = 0;
|
||||||
region.dstSubresource.baseArrayLayer = 0;
|
region.dstSubresource.baseArrayLayer = 0;
|
||||||
region.dstSubresource.layerCount = 1;
|
region.dstSubresource.layerCount = 1;
|
||||||
region.dstOffsets[1].x = static_cast<int>(m_extent.width);
|
region.dstOffsets[0].x = game_rect.offset.x;
|
||||||
region.dstOffsets[1].y = static_cast<int>(m_extent.height);
|
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;
|
region.dstOffsets[1].z = 1;
|
||||||
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||||
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
||||||
®ion, vk::Filter::eLinear);
|
®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 {};
|
vk::ImageMemoryBarrier to_present {};
|
||||||
@@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
&to_present);
|
&to_present);
|
||||||
if (draw_attachment) {
|
if (draw_attachment) {
|
||||||
if (draw_overlay) {
|
if (draw_overlay) {
|
||||||
DrawOverlay(vk_command, overlay);
|
DrawOverlay(vk_command, overlay, game_rect);
|
||||||
if (draw_system_overlay) {
|
if (draw_system_overlay) {
|
||||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ struct VulkanExtensions {
|
|||||||
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
||||||
vk::PhysicalDeviceVulkan11Features features {};
|
vk::PhysicalDeviceVulkan11Features features {};
|
||||||
features.storageBuffer16BitAccess = VK_TRUE;
|
features.storageBuffer16BitAccess = VK_TRUE;
|
||||||
|
features.shaderDrawParameters = VK_TRUE;
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +279,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
|||||||
required_features12.samplerMirrorClampToEdge);
|
required_features12.samplerMirrorClampToEdge);
|
||||||
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
||||||
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
||||||
|
check_feature(features11.shaderDrawParameters, "shaderDrawParameters",
|
||||||
|
WindowContext::RequiredVulkan11Features().shaderDrawParameters);
|
||||||
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
||||||
required_features12.storageBuffer8BitAccess);
|
required_features12.storageBuffer8BitAccess);
|
||||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||||
|
|||||||
@@ -814,7 +814,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
|||||||
const auto& cfg = *selected_cfg;
|
const auto& cfg = *selected_cfg;
|
||||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
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(
|
embedded_fetch = DetectEmbeddedVertexFetch(
|
||||||
decoded, *options.input_info.vertex, options.user_data_base,
|
decoded, *options.input_info.vertex, options.user_data_base,
|
||||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
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);
|
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Opcode::V_AND_B32:
|
case Opcode::V_AND_B32: {
|
||||||
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) ||
|
const auto mask = lhs.kind == Kind::PackedControlPoint ? rhs
|
||||||
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
|
: rhs.kind == Kind::PackedControlPoint ? lhs
|
||||||
value = constant(0u);
|
: Address {};
|
||||||
|
if (mask == constant(0xffu)) value = constant(0u);
|
||||||
|
if (mask == constant(0x1f00u)) value = {Kind::Affine, 256u, 0u};
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); 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_LO_U32: value = multiply(lhs, rhs); break;
|
||||||
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(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,
|
uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
|
||||||
Vec3 origin, Vec3 direction) {
|
Vec3 origin, Vec3 direction) {
|
||||||
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
|
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 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)); };
|
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).
|
// The four triangles are (0,1,2), (1,3,2), (2,3,4), (2,4,0).
|
||||||
const std::array vertices {
|
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))));
|
NodeWord(s, block, Binary(s, spv::OpIAdd, u, first, ConstantU32(s, axis))));
|
||||||
}
|
}
|
||||||
const auto flag = NodeWord(s, block, ConstantU32(s, 15));
|
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 y_largest = cmp(spv::OpFOrdLessThan, abs(direction[0]), abs(direction[1]));
|
||||||
const auto z_largest = cmp(spv::OpFOrdLessThan,
|
const auto z_largest = cmp(spv::OpFOrdLessThan,
|
||||||
Select(s, f, y_largest, abs(direction[1]), abs(direction[0])), abs(direction[2]));
|
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);
|
cmp(spv::OpFOrdGreaterThan, projected[i][1], zero), right);
|
||||||
missed = either(missed, both(cmp(spv::OpFOrdEqual, edge[i], zero), excluded));
|
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 coordinate = [&](uint32_t shift) {
|
||||||
const auto index = Binary(s, spv::OpBitwiseAnd, u,
|
const auto index = Binary(s, spv::OpBitwiseAnd, u,
|
||||||
Binary(s, spv::OpShiftRightLogical, u, remap, ConstantU32(s, shift)), ConstantU32(s, 3));
|
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 {
|
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, ConstantF32(s, 0x7f800000), numerator)),
|
||||||
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32Value(s, 1.0f), denominator)),
|
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))) };
|
Select(s, u, bary, coordinate(2), Select(s, u, missed, ConstantU32(s, 0), ConstantU32(s, 1))) };
|
||||||
return Vector(s, TypeU32Vector(s, 4), result);
|
return Vector(s, TypeU32Vector(s, 4), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
|
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
|
||||||
uint32_t half, uint32_t child) {
|
uint32_t half, uint32_t child) {
|
||||||
const auto f = TypeF32(s);
|
const auto f = TypeF32(s);
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
|||||||
}
|
}
|
||||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId ||
|
||||||
|
kind == IR::StageInputKind::BaseVertex || kind == IR::StageInputKind::BaseInstance) {
|
||||||
const auto value = state.builder.AllocateId();
|
const auto value = state.builder.AllocateId();
|
||||||
const auto bits = state.builder.AllocateId();
|
const auto bits = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||||
|
|||||||
@@ -338,6 +338,9 @@ inline constexpr auto EmitDataConsume = EmitAppendConsume;
|
|||||||
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
void EmitImage(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 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 EmitImageQueryDimensions = EmitImage;
|
||||||
inline constexpr auto EmitImageQueryLod = EmitImage;
|
inline constexpr auto EmitImageQueryLod = EmitImage;
|
||||||
inline constexpr auto EmitImageRead = EmitImage;
|
inline constexpr auto EmitImageRead = EmitImage;
|
||||||
|
|||||||
@@ -118,8 +118,11 @@ struct EmitterState {
|
|||||||
uint32_t bda_pointer_function = 0;
|
uint32_t bda_pointer_function = 0;
|
||||||
uint32_t indirect_format_function = 0;
|
uint32_t indirect_format_function = 0;
|
||||||
uint32_t indirect_format_geometry_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 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_variable = 0;
|
||||||
uint32_t gds_length = 0;
|
uint32_t gds_length = 0;
|
||||||
uint32_t push_constant_variable = 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 TypeU32(EmitterState& state);
|
||||||
uint32_t TypeU64(EmitterState& state);
|
uint32_t TypeU64(EmitterState& state);
|
||||||
uint32_t TypeU32Pair(EmitterState& state);
|
uint32_t TypeU32Pair(EmitterState& state);
|
||||||
|
uint32_t TypeBvh8Result(EmitterState& state);
|
||||||
uint32_t TypeI32(EmitterState& state);
|
uint32_t TypeI32(EmitterState& state);
|
||||||
uint32_t TypeI32Pair(EmitterState& state);
|
uint32_t TypeI32Pair(EmitterState& state);
|
||||||
uint32_t TypeF32(EmitterState& state);
|
uint32_t TypeF32(EmitterState& state);
|
||||||
@@ -540,9 +544,13 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
|
|||||||
void EmitProgram(EmitterState& state);
|
void EmitProgram(EmitterState& state);
|
||||||
|
|
||||||
void DefineGetBdaPointer(EmitterState& state);
|
void DefineGetBdaPointer(EmitterState& state);
|
||||||
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
|
|
||||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
|
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
|
||||||
void DefineBvhIntersect(EmitterState& state);
|
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);
|
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||||
|
|
||||||
// These templates accept local lambdas from several emitter translation units.
|
// These templates accept local lambdas from several emitter translation units.
|
||||||
|
|||||||
@@ -1026,53 +1026,50 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
|
|||||||
return ConstructU32Composite(state, components, values);
|
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;
|
auto& state = ctx.state;
|
||||||
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
const auto geometry = state.builder.AllocateId();
|
||||||
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
|
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
|
||||||
const auto geometry = state.builder.AllocateId();
|
state.indirect_format_geometry_function, buffer.format);
|
||||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
|
const auto field = [&](uint32_t index) {
|
||||||
state.indirect_format_geometry_function, buffer.format);
|
const auto value = state.builder.AllocateId();
|
||||||
const auto field = [&](uint32_t index) {
|
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
|
||||||
const auto value = state.builder.AllocateId();
|
return value;
|
||||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
|
};
|
||||||
return value;
|
const auto bytes = field(0);
|
||||||
};
|
const auto component = Binary(
|
||||||
const auto bytes = field(0);
|
state, spv::OpUMod, TypeU32(state),
|
||||||
const auto component = Binary(
|
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
|
||||||
state, spv::OpUMod, TypeU32(state),
|
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
|
||||||
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
|
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||||
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
|
Unary(state, spv::OpNot, TypeU64(state), alignment));
|
||||||
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
const auto address = Binary(
|
||||||
Unary(state, spv::OpNot, TypeU64(state), alignment));
|
state, spv::OpIAdd, TypeU64(state), base,
|
||||||
const auto address = Binary(
|
Unary(state, spv::OpUConvert, TypeU64(state),
|
||||||
state, spv::OpIAdd, TypeU64(state), base,
|
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
|
||||||
Unary(state, spv::OpUConvert, TypeU64(state),
|
const auto memory = AndCondition(
|
||||||
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
|
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
|
||||||
const auto memory = AndCondition(
|
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
|
||||||
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
|
ConstantU32(state, 4)));
|
||||||
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
|
const auto raw = EmitValueOrZeroIfCondition(
|
||||||
ConstantU32(state, 4)));
|
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
|
||||||
const auto raw = EmitValueOrZeroIfCondition(
|
[&]() {
|
||||||
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
|
// Format alignment and component spacing keep every selected component within one DWORD.
|
||||||
[&]() {
|
const auto word = LoadBdaDword(
|
||||||
// Format alignment and component spacing keep every selected component within one DWORD.
|
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
|
||||||
const auto word = LoadBdaDword(
|
ConstantU64(state, ~uint64_t {3})));
|
||||||
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
|
const auto byte = Binary(
|
||||||
ConstantU64(state, ~uint64_t {3})));
|
state, spv::OpBitwiseAnd, TypeU32(state),
|
||||||
const auto byte = Binary(
|
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
|
||||||
state, spv::OpBitwiseAnd, TypeU32(state),
|
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
|
||||||
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
|
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
|
||||||
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
|
ConstantU32(state, 3)));
|
||||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
|
});
|
||||||
ConstantU32(state, 3)));
|
const auto result = state.builder.AllocateId();
|
||||||
});
|
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
|
||||||
const auto result = state.builder.AllocateId();
|
state.indirect_format_function, buffer.format, buffer.selector,
|
||||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
|
component, raw);
|
||||||
state.indirect_format_function, buffer.format, buffer.selector,
|
return result;
|
||||||
component, raw);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
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) {
|
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
const auto mem = ctx.Memory(inst);
|
const auto mem = ctx.Memory(inst);
|
||||||
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
|
|
||||||
return LoadIndirectFormattedX(ctx, inst);
|
|
||||||
}
|
|
||||||
return EmitValueOrDefaultIfCondition(
|
return EmitValueOrDefaultIfCondition(
|
||||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
||||||
ConstantU32CompositeZero(state, components), [&]() {
|
ConstantU32CompositeZero(state, components), [&]() {
|
||||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||||
const auto function = state.indirect_buffer_load_functions.at(
|
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 args = IndirectBufferArgs(ctx, inst);
|
||||||
const auto result = state.builder.AllocateId();
|
const auto result = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
|
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) {
|
void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
||||||
auto& state = ctx.state;
|
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);
|
const auto type = TypeU32(state);
|
||||||
constexpr auto format_count =
|
constexpr auto format_count =
|
||||||
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
|
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
|
||||||
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
|||||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
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) {
|
void DefineGetBdaPointer(EmitterState& state) {
|
||||||
if (!state.program.info.uses_dma) {
|
if (!state.program.info.uses_dma) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -476,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
|||||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||||
|
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||||
|
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||||
default: return UINT32_MAX;
|
default: return UINT32_MAX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
|||||||
case IR::StageInputKind::PrimitiveId:
|
case IR::StageInputKind::PrimitiveId:
|
||||||
case IR::StageInputKind::InstanceIndex:
|
case IR::StageInputKind::InstanceIndex:
|
||||||
case IR::StageInputKind::Layer:
|
case IR::StageInputKind::Layer:
|
||||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
case IR::StageInputKind::SampleId:
|
||||||
|
case IR::StageInputKind::BaseVertex:
|
||||||
|
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||||
case IR::StageInputKind::WorkgroupId:
|
case IR::StageInputKind::WorkgroupId:
|
||||||
case IR::StageInputKind::NumWorkgroups:
|
case IR::StageInputKind::NumWorkgroups:
|
||||||
case IR::StageInputKind::LocalInvocationId:
|
case IR::StageInputKind::LocalInvocationId:
|
||||||
@@ -802,6 +806,10 @@ void DefineModule(EmitterState& state) {
|
|||||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||||
}
|
}
|
||||||
|
if (InputVariableForKind(state, IR::StageInputKind::BaseVertex) != 0 ||
|
||||||
|
InputVariableForKind(state, IR::StageInputKind::BaseInstance) != 0) {
|
||||||
|
state.builder.RequireCapability(spv::CapabilityDrawParameters);
|
||||||
|
}
|
||||||
if (state.program.info.image_gather_extended) {
|
if (state.program.info.image_gather_extended) {
|
||||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,6 +414,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
|
|||||||
case IR::Type::U32:
|
case IR::Type::U32:
|
||||||
case IR::Type::F16: return TypeU32(state);
|
case IR::Type::F16: return TypeU32(state);
|
||||||
case IR::Type::U64: return TypeU64(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::U32x2: return TypeU32Pair(state);
|
||||||
case IR::Type::F32: return TypeF32(state);
|
case IR::Type::F32: return TypeF32(state);
|
||||||
case IR::Type::F64: return TypeF64(state);
|
case IR::Type::F64: return TypeF64(state);
|
||||||
@@ -675,8 +676,8 @@ void EmitProgram(EmitterState& state) {
|
|||||||
}
|
}
|
||||||
DefineGetBdaPointer(state);
|
DefineGetBdaPointer(state);
|
||||||
DefineIndirectBufferLoads(ctx);
|
DefineIndirectBufferLoads(ctx);
|
||||||
DefineIndirectBufferFormats(ctx);
|
|
||||||
DefineBvhIntersect(state);
|
DefineBvhIntersect(state);
|
||||||
|
DefineTrinityFunctions(state);
|
||||||
if (program.info.uses_swizzle) {
|
if (program.info.uses_swizzle) {
|
||||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||||
if (state.lane_count == 2) {
|
if (state.lane_count == 2) {
|
||||||
|
|||||||
@@ -29,12 +29,14 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
const auto pointer = state.builder.AllocateId();
|
const auto pointer = state.builder.AllocateId();
|
||||||
if (kind == Attribute::Factor) {
|
if (kind == Attribute::Factor) {
|
||||||
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
||||||
const auto index = inst.Arg(1).U32() / 4u;
|
const auto index = inst.Arg(1).U32() / 4u;
|
||||||
const auto outer = index < 3u;
|
const bool quad = tess.domain == 2u;
|
||||||
EXIT_NOT_IMPLEMENTED(index >= 4u);
|
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)),
|
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
|
||||||
pointer, outer ? variable : state.tess_inner_variable,
|
pointer, outer ? variable : state.tess_inner_variable,
|
||||||
ConstantU32(state, outer ? index : index - 3u));
|
ConstantU32(state, outer ? index : index - outer_count));
|
||||||
return pointer;
|
return pointer;
|
||||||
}
|
}
|
||||||
auto address = ctx.Arg(inst, 1);
|
auto address = ctx.Arg(inst, 1);
|
||||||
@@ -128,20 +130,25 @@ void DefineTessellationInterfaces(EmitterState& state) {
|
|||||||
|
|
||||||
void DefineTessellationExecutionModes(EmitterState& state) {
|
void DefineTessellationExecutionModes(EmitterState& state) {
|
||||||
const auto& tess = state.input_info.vertex->tess;
|
const auto& tess = state.input_info.vertex->tess;
|
||||||
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output.
|
// Triangle or quad domain, integer or fractional partitioning, triangle output.
|
||||||
EXIT_NOT_IMPLEMENTED(tess.domain != 1u ||
|
EXIT_NOT_IMPLEMENTED((tess.domain != 1u && tess.domain != 2u) ||
|
||||||
(tess.partitioning != 0u && tess.partitioning != 2u) ||
|
(tess.partitioning != 0u && tess.partitioning != 2u && tess.partitioning != 3u) ||
|
||||||
tess.output_topology != 2u);
|
(tess.output_topology != 2u && tess.output_topology != 3u));
|
||||||
state.builder.RequireCapability(spv::CapabilityTessellation);
|
state.builder.RequireCapability(spv::CapabilityTessellation);
|
||||||
if (state.program.stage == ShaderType::TessellationControl) {
|
if (state.program.stage == ShaderType::TessellationControl) {
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
|
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
|
||||||
tess.output_control_points);
|
tess.output_control_points);
|
||||||
} else {
|
} 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.builder.AddExecutionMode(
|
||||||
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
|
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
|
||||||
: spv::ExecutionModeSpacingFractionalOdd);
|
: tess.partitioning == 2u ? spv::ExecutionModeSpacingFractionalOdd
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
|
: 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 0x09u: return Opcode::IMAGE_STORE_MIP;
|
||||||
case 0x0eu: return Opcode::IMAGE_GET_RESINFO;
|
case 0x0eu: return Opcode::IMAGE_GET_RESINFO;
|
||||||
case 0x60u: return Opcode::IMAGE_GET_LOD;
|
case 0x60u: return Opcode::IMAGE_GET_LOD;
|
||||||
|
case 0xe5u: return Opcode::IMAGE_BVH8_INTERSECT_RAY;
|
||||||
case 0xe6u:
|
case 0xe6u:
|
||||||
case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
|
case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
|
||||||
default: return Opcode::UNSUPPORTED;
|
default: return Opcode::UNSUPPORTED;
|
||||||
@@ -297,6 +298,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
case 0xe5u:
|
||||||
case 0xe6u: return 11u;
|
case 0xe6u: return 11u;
|
||||||
case 0xe7u: return 12u;
|
case 0xe7u: return 12u;
|
||||||
case 0x0eu: return 1u;
|
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");
|
"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(vdata, inst.dst);
|
||||||
DecodeVectorGpr(vaddr, inst.src0);
|
DecodeVectorGpr(vaddr, inst.src0);
|
||||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
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},
|
{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},
|
{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},
|
{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},
|
{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) {
|
uint32_t DsSourceCount(Opcode opcode) {
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return 3u;
|
||||||
case Opcode::DS_WRITE2_B32:
|
case Opcode::DS_WRITE2_B32:
|
||||||
case Opcode::DS_WRITE2ST64_B32:
|
case Opcode::DS_WRITE2ST64_B32:
|
||||||
case Opcode::DS_WRITE2_B64:
|
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) {
|
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
|
||||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
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) {
|
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");
|
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},
|
{0x21u, Opcode::S_SWAPPC_B64},
|
||||||
{0x24u, Opcode::S_AND_SAVEEXEC_B64},
|
{0x24u, Opcode::S_AND_SAVEEXEC_B64},
|
||||||
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
|
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
|
||||||
|
{0x2cu, Opcode::S_QUADMASK_B32},
|
||||||
{0x2du, Opcode::S_QUADMASK_B64},
|
{0x2du, Opcode::S_QUADMASK_B64},
|
||||||
{0x34u, Opcode::S_ABS_I32},
|
{0x34u, Opcode::S_ABS_I32},
|
||||||
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
|
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ std::string InstructionToString(const Instruction& inst) {
|
|||||||
case Opcode::S_NOT_B64:
|
case Opcode::S_NOT_B64:
|
||||||
case Opcode::S_WQM_B32:
|
case Opcode::S_WQM_B32:
|
||||||
case Opcode::S_WQM_B64:
|
case Opcode::S_WQM_B64:
|
||||||
|
case Opcode::S_QUADMASK_B32:
|
||||||
case Opcode::S_QUADMASK_B64:
|
case Opcode::S_QUADMASK_B64:
|
||||||
case Opcode::S_AND_SAVEEXEC_B32:
|
case Opcode::S_AND_SAVEEXEC_B32:
|
||||||
case Opcode::S_ORN2_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_READ2ST64_B64:
|
||||||
case Opcode::DS_READ_B96:
|
case Opcode::DS_READ_B96:
|
||||||
case Opcode::DS_READ_B128:
|
case Opcode::DS_READ_B128:
|
||||||
|
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32:
|
||||||
case Opcode::DS_WRITE_B8:
|
case Opcode::DS_WRITE_B8:
|
||||||
case Opcode::DS_WRITE_B16:
|
case Opcode::DS_WRITE_B16:
|
||||||
case Opcode::DS_WRITE_B8_D16_HI:
|
case Opcode::DS_WRITE_B8_D16_HI:
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ enum class Opcode {
|
|||||||
S_FLBIT_I32,
|
S_FLBIT_I32,
|
||||||
S_FLBIT_I32_I64,
|
S_FLBIT_I32_I64,
|
||||||
S_BITREPLICATE_B64_B32,
|
S_BITREPLICATE_B64_B32,
|
||||||
|
S_QUADMASK_B32,
|
||||||
S_QUADMASK_B64,
|
S_QUADMASK_B64,
|
||||||
S_GETPC_B64,
|
S_GETPC_B64,
|
||||||
S_SETPC_B64,
|
S_SETPC_B64,
|
||||||
@@ -248,7 +249,9 @@ enum class Opcode {
|
|||||||
V_MIN_I32,
|
V_MIN_I32,
|
||||||
V_MAX_I32,
|
V_MAX_I32,
|
||||||
V_MUL_I32_I24,
|
V_MUL_I32_I24,
|
||||||
|
V_MUL_HI_I32_I24,
|
||||||
V_MUL_U32_U24,
|
V_MUL_U32_U24,
|
||||||
|
V_MUL_HI_U32_U24,
|
||||||
V_MUL_LO_U32,
|
V_MUL_LO_U32,
|
||||||
V_MUL_HI_U32,
|
V_MUL_HI_U32,
|
||||||
V_MUL_LO_I32,
|
V_MUL_LO_I32,
|
||||||
@@ -257,6 +260,7 @@ enum class Opcode {
|
|||||||
V_CVT_PK_U8_F32,
|
V_CVT_PK_U8_F32,
|
||||||
V_MAD_F32,
|
V_MAD_F32,
|
||||||
V_MAD_I16,
|
V_MAD_I16,
|
||||||
|
V_MAD_U16,
|
||||||
V_MAD_I32_I24,
|
V_MAD_I32_I24,
|
||||||
V_MAD_U32_U24,
|
V_MAD_U32_U24,
|
||||||
V_MAD_U64_U32,
|
V_MAD_U64_U32,
|
||||||
@@ -277,16 +281,20 @@ enum class Opcode {
|
|||||||
V_MIN3_I32,
|
V_MIN3_I32,
|
||||||
V_MIN3_U32,
|
V_MIN3_U32,
|
||||||
V_MIN3_F16,
|
V_MIN3_F16,
|
||||||
|
V_MIN3_I16,
|
||||||
V_MIN3_U16,
|
V_MIN3_U16,
|
||||||
V_MAX3_F32,
|
V_MAX3_F32,
|
||||||
V_MAX3_I32,
|
V_MAX3_I32,
|
||||||
V_MAX3_U32,
|
V_MAX3_U32,
|
||||||
V_MAX3_F16,
|
V_MAX3_F16,
|
||||||
|
V_MAX3_I16,
|
||||||
|
V_MAX3_U16,
|
||||||
V_MED3_F32,
|
V_MED3_F32,
|
||||||
V_MED3_I32,
|
V_MED3_I32,
|
||||||
V_MED3_U32,
|
V_MED3_U32,
|
||||||
V_MED3_F16,
|
V_MED3_F16,
|
||||||
V_MED3_I16,
|
V_MED3_I16,
|
||||||
|
V_MED3_U16,
|
||||||
V_SAD_U32,
|
V_SAD_U32,
|
||||||
V_ADD3_U32,
|
V_ADD3_U32,
|
||||||
V_LSHL_ADD_U32,
|
V_LSHL_ADD_U32,
|
||||||
@@ -354,6 +362,7 @@ enum class Opcode {
|
|||||||
V_ASHRREV_I32,
|
V_ASHRREV_I32,
|
||||||
V_LSHLREV_B64,
|
V_LSHLREV_B64,
|
||||||
V_LSHRREV_B64,
|
V_LSHRREV_B64,
|
||||||
|
V_ASHRREV_I64,
|
||||||
V_LSHLREV_B16,
|
V_LSHLREV_B16,
|
||||||
V_LSHRREV_B16,
|
V_LSHRREV_B16,
|
||||||
V_ASHRREV_I16,
|
V_ASHRREV_I16,
|
||||||
@@ -676,6 +685,7 @@ enum class Opcode {
|
|||||||
DS_READ2ST64_B64,
|
DS_READ2ST64_B64,
|
||||||
DS_READ_B96,
|
DS_READ_B96,
|
||||||
DS_READ_B128,
|
DS_READ_B128,
|
||||||
|
DS_BVH_STACK_PUSH8_POP1_RTN_B32,
|
||||||
DS_WRITE_B8,
|
DS_WRITE_B8,
|
||||||
DS_WRITE_B16,
|
DS_WRITE_B16,
|
||||||
DS_WRITE_B8_D16_HI,
|
DS_WRITE_B8_D16_HI,
|
||||||
@@ -691,6 +701,7 @@ enum class Opcode {
|
|||||||
DS_WRITE_ADDTID_B32,
|
DS_WRITE_ADDTID_B32,
|
||||||
DS_READ_ADDTID_B32,
|
DS_READ_ADDTID_B32,
|
||||||
IMAGE_BVH_INTERSECT_RAY,
|
IMAGE_BVH_INTERSECT_RAY,
|
||||||
|
IMAGE_BVH8_INTERSECT_RAY,
|
||||||
IMAGE_GET_RESINFO,
|
IMAGE_GET_RESINFO,
|
||||||
IMAGE_GET_LOD,
|
IMAGE_GET_LOD,
|
||||||
IMAGE_LOAD,
|
IMAGE_LOAD,
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
|
|||||||
{0x05u, Opcode::V_SUBREV_F32},
|
{0x05u, Opcode::V_SUBREV_F32},
|
||||||
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
|
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
|
||||||
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
|
{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},
|
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||||
|
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||||
{0x0fu, Opcode::V_MIN_F32},
|
{0x0fu, Opcode::V_MIN_F32},
|
||||||
{0x10u, Opcode::V_MAX_F32},
|
{0x10u, Opcode::V_MAX_F32},
|
||||||
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
|
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
|
||||||
@@ -157,6 +159,10 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
|
|||||||
{0x04u, Opcode::V_CVT_F64_I32},
|
{0x04u, Opcode::V_CVT_F64_I32},
|
||||||
{0x0fu, Opcode::V_CVT_F32_F64},
|
{0x0fu, Opcode::V_CVT_F32_F64},
|
||||||
{0x10u, Opcode::V_CVT_F64_F32},
|
{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},
|
{0x16u, Opcode::V_CVT_F64_U32},
|
||||||
{0x17u, Opcode::V_TRUNC_F64},
|
{0x17u, Opcode::V_TRUNC_F64},
|
||||||
{0x18u, Opcode::V_CEIL_F64},
|
{0x18u, Opcode::V_CEIL_F64},
|
||||||
@@ -167,6 +173,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
|
|||||||
{0x07u, Opcode::V_CVT_U32_F32},
|
{0x07u, Opcode::V_CVT_U32_F32},
|
||||||
{0x08u, Opcode::V_CVT_I32_F32},
|
{0x08u, Opcode::V_CVT_I32_F32},
|
||||||
{0x0au, Opcode::V_CVT_F16_F32},
|
{0x0au, Opcode::V_CVT_F16_F32},
|
||||||
|
{0x0bu, Opcode::V_CVT_F32_F16},
|
||||||
{0x0cu, Opcode::V_CVT_RPI_I32_F32},
|
{0x0cu, Opcode::V_CVT_RPI_I32_F32},
|
||||||
{0x0du, Opcode::V_CVT_FLR_I32_F32},
|
{0x0du, Opcode::V_CVT_FLR_I32_F32},
|
||||||
{0x0eu, Opcode::V_CVT_OFF_F32_I4},
|
{0x0eu, Opcode::V_CVT_OFF_F32_I4},
|
||||||
@@ -334,16 +341,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
|||||||
{0x152u, Opcode::V_MIN3_I32},
|
{0x152u, Opcode::V_MIN3_I32},
|
||||||
{0x153u, Opcode::V_MIN3_U32},
|
{0x153u, Opcode::V_MIN3_U32},
|
||||||
{0x351u, Opcode::V_MIN3_F16},
|
{0x351u, Opcode::V_MIN3_F16},
|
||||||
|
{0x352u, Opcode::V_MIN3_I16},
|
||||||
{0x353u, Opcode::V_MIN3_U16},
|
{0x353u, Opcode::V_MIN3_U16},
|
||||||
{0x154u, Opcode::V_MAX3_F32},
|
{0x154u, Opcode::V_MAX3_F32},
|
||||||
{0x155u, Opcode::V_MAX3_I32},
|
{0x155u, Opcode::V_MAX3_I32},
|
||||||
{0x156u, Opcode::V_MAX3_U32},
|
{0x156u, Opcode::V_MAX3_U32},
|
||||||
{0x354u, Opcode::V_MAX3_F16},
|
{0x354u, Opcode::V_MAX3_F16},
|
||||||
|
{0x355u, Opcode::V_MAX3_I16},
|
||||||
|
{0x356u, Opcode::V_MAX3_U16},
|
||||||
{0x157u, Opcode::V_MED3_F32},
|
{0x157u, Opcode::V_MED3_F32},
|
||||||
{0x158u, Opcode::V_MED3_I32},
|
{0x158u, Opcode::V_MED3_I32},
|
||||||
{0x159u, Opcode::V_MED3_U32},
|
{0x159u, Opcode::V_MED3_U32},
|
||||||
{0x357u, Opcode::V_MED3_F16},
|
{0x357u, Opcode::V_MED3_F16},
|
||||||
{0x358u, Opcode::V_MED3_I16},
|
{0x358u, Opcode::V_MED3_I16},
|
||||||
|
{0x359u, Opcode::V_MED3_U16},
|
||||||
{0x15du, Opcode::V_SAD_U32},
|
{0x15du, Opcode::V_SAD_U32},
|
||||||
{0x15eu, Opcode::V_CVT_PK_U8_F32},
|
{0x15eu, Opcode::V_CVT_PK_U8_F32},
|
||||||
{0x178u, Opcode::V_XOR3_B32},
|
{0x178u, Opcode::V_XOR3_B32},
|
||||||
@@ -354,6 +365,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
|||||||
{0x16cu, Opcode::V_MUL_HI_I32},
|
{0x16cu, Opcode::V_MUL_HI_I32},
|
||||||
{0x2ffu, Opcode::V_LSHLREV_B64},
|
{0x2ffu, Opcode::V_LSHLREV_B64},
|
||||||
{0x300u, Opcode::V_LSHRREV_B64},
|
{0x300u, Opcode::V_LSHRREV_B64},
|
||||||
|
{0x301u, Opcode::V_ASHRREV_I64},
|
||||||
{0x303u, Opcode::V_ADD_NC_U16},
|
{0x303u, Opcode::V_ADD_NC_U16},
|
||||||
{0x304u, Opcode::V_SUB_NC_U16},
|
{0x304u, Opcode::V_SUB_NC_U16},
|
||||||
{0x305u, Opcode::V_MUL_LO_U16},
|
{0x305u, Opcode::V_MUL_LO_U16},
|
||||||
@@ -370,6 +382,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
|||||||
{0x311u, Opcode::V_PACK_B32_F16},
|
{0x311u, Opcode::V_PACK_B32_F16},
|
||||||
{0x314u, Opcode::V_LSHLREV_B16},
|
{0x314u, Opcode::V_LSHLREV_B16},
|
||||||
{0x319u, Opcode::V_SUBREV_I32},
|
{0x319u, Opcode::V_SUBREV_I32},
|
||||||
|
{0x340u, Opcode::V_MAD_U16},
|
||||||
{0x345u, Opcode::V_XAD_U32},
|
{0x345u, Opcode::V_XAD_U32},
|
||||||
{0x346u, Opcode::V_LSHL_ADD_U32},
|
{0x346u, Opcode::V_LSHL_ADD_U32},
|
||||||
{0x347u, Opcode::V_ADD_LSHL_U32},
|
{0x347u, Opcode::V_ADD_LSHL_U32},
|
||||||
@@ -488,8 +501,10 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
|
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
|
||||||
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
|
return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
|
||||||
opcode == Opcode::V_MAD_I16;
|
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) {
|
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||||
@@ -1335,6 +1350,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
|
|||||||
case Opcode::V_SUB_NC_I16:
|
case Opcode::V_SUB_NC_I16:
|
||||||
case Opcode::V_LSHLREV_B64:
|
case Opcode::V_LSHLREV_B64:
|
||||||
case Opcode::V_LSHRREV_B64:
|
case Opcode::V_LSHRREV_B64:
|
||||||
|
case Opcode::V_ASHRREV_I64:
|
||||||
case Opcode::V_LSHLREV_B16:
|
case Opcode::V_LSHLREV_B16:
|
||||||
case Opcode::V_LSHRREV_B16:
|
case Opcode::V_LSHRREV_B16:
|
||||||
case Opcode::V_ASHRREV_I16:
|
case Opcode::V_ASHRREV_I16:
|
||||||
@@ -1523,7 +1539,10 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
|
|||||||
|
|
||||||
bool SupportsNativeVop3Clamp(Opcode opcode) {
|
bool SupportsNativeVop3Clamp(Opcode opcode) {
|
||||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(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,
|
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;
|
return clamp != 0u || omod != 0u || neg != 0u;
|
||||||
}
|
}
|
||||||
switch (opcode) {
|
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_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_P1LL_F16:
|
||||||
case Opcode::V_INTERP_P2_F16:
|
case Opcode::V_INTERP_P2_F16:
|
||||||
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
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);
|
Write16Bits(DestinationOperand(inst), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) {
|
||||||
const auto lhs = ReadU16AsU32(inst.src0, true);
|
const auto lhs = ReadU16AsU32(inst.src0, sign);
|
||||||
const auto rhs = ReadU16AsU32(inst.src1, true);
|
const auto rhs = ReadU16AsU32(inst.src1, sign);
|
||||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
|
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign));
|
||||||
if (inst.dst.clamp) {
|
if (inst.dst.clamp) {
|
||||||
result = Saturate16(result, true);
|
result = Saturate16(result, sign);
|
||||||
}
|
}
|
||||||
Write16Bits(DestinationOperand(inst), result);
|
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 arg_type = IR::ArgTypeOf(opcode, index);
|
||||||
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
|
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
|
||||||
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
|
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) {
|
if (mask_shift_count && index == 1u) {
|
||||||
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
|
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);
|
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) {
|
const auto extract24 = [&](IR::U32 value) {
|
||||||
return IR::U32(
|
return IR::U32(
|
||||||
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
|
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 lhs = extract24(ReadU32(inst.src0));
|
||||||
const auto rhs = extract24(ReadU32(inst.src1));
|
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) {
|
if (addend) {
|
||||||
result = ir.IAdd(result, ReadU32(inst.src2));
|
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)));
|
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) {
|
const auto compact = [&](IR::U32 value) {
|
||||||
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
|
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))));
|
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.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))),
|
||||||
IR::U32(IR::Value(0xffu)));
|
IR::U32(IR::Value(0xffu)));
|
||||||
};
|
};
|
||||||
const auto source = ReadU32Pair(inst.src0);
|
IR::U32 quads;
|
||||||
const auto quads = ir.BitwiseOr(
|
if (is64) {
|
||||||
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
|
const auto source = ReadU32Pair(inst.src0);
|
||||||
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u)));
|
quads = ir.BitwiseOr(
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
|
||||||
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) {
|
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);
|
inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
|
||||||
EXIT_IF(count > components.size());
|
EXIT_IF(count > components.size());
|
||||||
const auto nsa_components =
|
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++) {
|
for (uint32_t index = 0; index < count; index++) {
|
||||||
if (index != 0u && index - 1u < nsa_components) {
|
if (index != 0u && index - 1u < nsa_components) {
|
||||||
components[index] =
|
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) {
|
void Translator::DS_READ(const Decoder::Instruction& inst) {
|
||||||
const auto memory = MemoryInfoFromDecoded(inst);
|
const auto memory = MemoryInfoFromDecoded(inst);
|
||||||
const auto address = ReadU32(MemorySourceAt(inst, 0));
|
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()}));
|
{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) {
|
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||||
switch (inst.opcode) {
|
switch (inst.opcode) {
|
||||||
// Translated scalar loads have no cache of their own to invalidate.
|
// 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:
|
case Decoder::Opcode::BUFFER_GL1_INV:
|
||||||
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
||||||
return;
|
return;
|
||||||
|
case Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY: return IMAGE_BVH8(inst);
|
||||||
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
|
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
|
||||||
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
|
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
|
||||||
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
|
{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);
|
return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataAppend);
|
||||||
case Decoder::Opcode::DS_WRITE_ADDTID_B32: return DS_ADDTID(inst, true);
|
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_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_READ2_B32:
|
||||||
case Decoder::Opcode::DS_READ2ST64_B32:
|
case Decoder::Opcode::DS_READ2ST64_B32:
|
||||||
case Decoder::Opcode::DS_READ2_B64:
|
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_BITSET0_B64: return S_BITSET_B64(inst, false);
|
||||||
case O::S_BITSET1_B64: return S_BITSET_B64(inst, true);
|
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_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_B32: return BFM_B32(inst);
|
||||||
case O::S_BFM_B64: return S_BFM_B64(inst);
|
case O::S_BFM_B64: return S_BFM_B64(inst);
|
||||||
case O::S_BFE_U32: return S_BFE_U32(inst, false);
|
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));
|
: IR::U32(IR::Value(0u));
|
||||||
entry_ir.SetExecLo(initial_lo);
|
entry_ir.SetExecLo(initial_lo);
|
||||||
entry_ir.SetExecHi(initial_hi);
|
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) {
|
if (options.stage == ShaderType::Compute) {
|
||||||
const auto* cs = options.input_info.compute;
|
const auto* cs = options.input_info.compute;
|
||||||
const auto thread_ids =
|
const auto thread_ids =
|
||||||
@@ -1302,7 +1310,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||||
}
|
}
|
||||||
} else if (options.stage == ShaderType::Local) {
|
} 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),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
|
||||||
builtin(IR::StageInputKind::VertexIndex));
|
builtin(IR::StageInputKind::VertexIndex));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
|
||||||
@@ -1380,19 +1388,32 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
} else if (options.stage == ShaderType::Vertex) {
|
} else if (options.stage == ShaderType::Vertex) {
|
||||||
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
|
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
|
||||||
// ESVertCount describes input vertices, independently of lane ordinals.
|
// 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),
|
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
|
||||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
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. 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 (!options.input_info.vertex->indirect_draw) {
|
||||||
|
return builtin(kind);
|
||||||
|
}
|
||||||
|
const auto base = builtin(base_kind);
|
||||||
|
if (slot < options.user_data_count) {
|
||||||
|
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(options.user_data_base + slot),
|
||||||
|
base);
|
||||||
|
}
|
||||||
|
return entry_ir.ISub(builtin(kind), base);
|
||||||
|
};
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||||
builtin(IR::StageInputKind::VertexIndex));
|
index(offsets.vertex, IR::StageInputKind::VertexIndex,
|
||||||
|
IR::StageInputKind::BaseVertex));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||||
builtin(IR::StageInputKind::InstanceIndex));
|
index(offsets.instance, IR::StageInputKind::InstanceIndex,
|
||||||
|
IR::StageInputKind::BaseInstance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ private:
|
|||||||
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
|
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
|
||||||
const IR::MemoryInfo& memory, uint32_t pc);
|
const IR::MemoryInfo& memory, uint32_t pc);
|
||||||
void DS_READ(const Decoder::Instruction& inst);
|
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_READ2(const Decoder::Instruction& inst);
|
||||||
void DS_WRITE(const Decoder::Instruction& inst);
|
void DS_WRITE(const Decoder::Instruction& inst);
|
||||||
void DS_WRITE2(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 Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||||
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||||
void Integer16Ternary(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,
|
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool arithmetic);
|
bool arithmetic);
|
||||||
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||||
@@ -187,7 +189,7 @@ private:
|
|||||||
void V_XOR3_B32(const Decoder::Instruction& inst);
|
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 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 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_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||||
void V_SAD_U32(const Decoder::Instruction& inst);
|
void V_SAD_U32(const Decoder::Instruction& inst);
|
||||||
void V_ADD3_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_BCNT_U32_B32(const Decoder::Instruction& inst);
|
||||||
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
||||||
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
|
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);
|
void BFM_B32(const Decoder::Instruction& inst);
|
||||||
IR::U32 RightMask32(IR::U32 count);
|
IR::U32 RightMask32(IR::U32 count);
|
||||||
void S_BFM_B64(const Decoder::Instruction& inst);
|
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_U16:
|
||||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
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_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_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_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_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, 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);
|
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:
|
case O::V_LSHRREV_B64:
|
||||||
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
|
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
|
||||||
false, false);
|
false, false);
|
||||||
|
case O::V_ASHRREV_I64:
|
||||||
|
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightArithmetic64, IR::Type::U64, true,
|
||||||
|
false, false);
|
||||||
|
|
||||||
case O::V_XNOR_B32:
|
case O::V_XNOR_B32:
|
||||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false);
|
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_MAD_U32_U24: return Integer24(inst, false, true);
|
||||||
case O::V_MUL_I32_I24: return Integer24(inst, true, false);
|
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_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_MAD_U64_U32: return V_MAD_U64_U32(inst);
|
||||||
case O::V_SAD_U32: return V_SAD_U32(inst);
|
case O::V_SAD_U32: return V_SAD_U32(inst);
|
||||||
case O::V_ADD3_U32: return V_ADD3_U32(inst);
|
case O::V_ADD3_U32: return V_ADD3_U32(inst);
|
||||||
|
|||||||
@@ -118,15 +118,14 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Program::~Program() {
|
Program::~Program() {
|
||||||
// Planning expressions can refer to block values but outlive block storage in the base class.
|
// Planning expressions can refer to block values but outlive block storage in the base class,
|
||||||
|
// and values cross block boundaries: detach everything before any storage is destroyed.
|
||||||
for (auto& inst: value_storage) {
|
for (auto& inst: value_storage) {
|
||||||
inst.Invalidate();
|
inst.DropArgs();
|
||||||
}
|
}
|
||||||
// Values may cross block boundaries. Detach all arguments before any block starts destroying
|
|
||||||
// its instruction storage so reverse-use links always point to live definitions.
|
|
||||||
for (auto* block: blocks) {
|
for (auto* block: blocks) {
|
||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
inst.Invalidate();
|
inst.DropArgs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
|
|||||||
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
||||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
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,
|
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::CountedRows, a, b)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
|
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,
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ enum class StageInputKind {
|
|||||||
LocalInvocationId,
|
LocalInvocationId,
|
||||||
LocalInvocationIndex,
|
LocalInvocationIndex,
|
||||||
GlobalInvocationId,
|
GlobalInvocationId,
|
||||||
|
BaseVertex,
|
||||||
|
BaseInstance,
|
||||||
Parameter,
|
Parameter,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -541,6 +543,8 @@ struct ShaderInfo {
|
|||||||
bool uses_image_heap = false;
|
bool uses_image_heap = false;
|
||||||
bool uses_dispatch_threads = false;
|
bool uses_dispatch_threads = false;
|
||||||
bool bvh = false;
|
bool bvh = false;
|
||||||
|
bool bvh8 = false;
|
||||||
|
bool bvh_stack = false;
|
||||||
bool subgroup_ballot = false;
|
bool subgroup_ballot = false;
|
||||||
bool subgroup_vote = false;
|
bool subgroup_vote = false;
|
||||||
bool subgroup_barrier = false;
|
bool subgroup_barrier = false;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ std::string TypeName(Type type) {
|
|||||||
"SamplerResource",
|
"SamplerResource",
|
||||||
"ImageAddress",
|
"ImageAddress",
|
||||||
"F64",
|
"F64",
|
||||||
|
"Bvh8Result",
|
||||||
};
|
};
|
||||||
const auto bits = static_cast<uint32_t>(type);
|
const auto bits = static_cast<uint32_t>(type);
|
||||||
if (bits == 0) {
|
if (bits == 0) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ enum class Type : uint32_t {
|
|||||||
SamplerResource = 1u << 18u,
|
SamplerResource = 1u << 18u,
|
||||||
ImageAddress = 1u << 19u,
|
ImageAddress = 1u << 19u,
|
||||||
F64 = 1u << 20u,
|
F64 = 1u << 20u,
|
||||||
|
Bvh8Result = 1u << 21u,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr Type operator|(Type lhs, Type rhs) {
|
constexpr Type operator|(Type lhs, Type rhs) {
|
||||||
|
|||||||
@@ -25,22 +25,6 @@ Value Value::F32(float value) {
|
|||||||
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Value::IsEmpty() const {
|
|
||||||
return type == Type::Void;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsImmediate() const {
|
|
||||||
return type != Type::Opaque;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsIdentity() const {
|
|
||||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsPhi() const {
|
|
||||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type Value::GetType() const {
|
Type Value::GetType() const {
|
||||||
if (IsPhi()) {
|
if (IsPhi()) {
|
||||||
return inst->Flags<Type>();
|
return inst->Flags<Type>();
|
||||||
@@ -56,19 +40,6 @@ Inst* Value::Instruction() const {
|
|||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst* Value::TryInstruction() const {
|
|
||||||
return type == Type::Opaque ? inst : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Inst* Value::ResolveInstruction() const {
|
|
||||||
EXIT_IF(type != Type::Opaque);
|
|
||||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Value::Resolve() const {
|
|
||||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScalarReg Value::ScalarRegister() const {
|
ScalarReg Value::ScalarRegister() const {
|
||||||
EXIT_IF(type != Type::ScalarReg);
|
EXIT_IF(type != Type::ScalarReg);
|
||||||
return scalar_reg;
|
return scalar_reg;
|
||||||
@@ -94,11 +65,6 @@ uint16_t Value::U16() const {
|
|||||||
return imm_u16;
|
return imm_u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Value::U32() const {
|
|
||||||
EXIT_IF(type != Type::U32);
|
|
||||||
return imm_u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Value::U64() const {
|
uint64_t Value::U64() const {
|
||||||
EXIT_IF(type != Type::U64);
|
EXIT_IF(type != Type::U64);
|
||||||
return imm_u64;
|
return imm_u64;
|
||||||
@@ -152,10 +118,6 @@ Inst::~Inst() {
|
|||||||
ClearArgs();
|
ClearArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueOpcode Inst::GetOpcode() const {
|
|
||||||
return opcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type Inst::GetType() const {
|
Type Inst::GetType() const {
|
||||||
if (opcode == ValueOpcode::Phi) {
|
if (opcode == ValueOpcode::Phi) {
|
||||||
return static_cast<Type>(flags);
|
return static_cast<Type>(flags);
|
||||||
@@ -178,51 +140,25 @@ size_t Inst::UseCount() const {
|
|||||||
return uses.size();
|
return uses.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Inst::NumArgs() const {
|
|
||||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Inst::NumPhiBlocks() const {
|
size_t Inst::NumPhiBlocks() const {
|
||||||
return num_args == PhiArity ? phi_args.size() : 0;
|
return num_args == PhiArity ? phi_args.size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Inst::Arg(size_t index) const {
|
|
||||||
EXIT_IF(index >= NumArgs());
|
|
||||||
if (num_args <= InlineArity) {
|
|
||||||
return fixed_args[index];
|
|
||||||
}
|
|
||||||
return num_args == PhiArity ? phi_args[index].second : large_args[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Block* Inst::PhiBlock(size_t index) const {
|
Block* Inst::PhiBlock(size_t index) const {
|
||||||
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
||||||
return phi_args[index].first;
|
return phi_args[index].first;
|
||||||
}
|
}
|
||||||
|
|
||||||
Block* Inst::Parent() const {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Use>& Inst::Uses() const {
|
|
||||||
return uses;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Inst::SetParent(Block* block) {
|
void Inst::SetParent(Block* block) {
|
||||||
parent = block;
|
parent = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inst::SetArg(size_t index, Value value) {
|
void Inst::SetArg(size_t index, Value value) {
|
||||||
const auto old = Arg(index);
|
auto& arg = ArgSlot(*this, index);
|
||||||
if (auto* old_inst = old.TryInstruction(); old_inst != nullptr) {
|
if (auto* old_inst = arg.TryInstruction(); old_inst != nullptr) {
|
||||||
RemoveUse(old_inst, index);
|
RemoveUse(old_inst, index);
|
||||||
}
|
}
|
||||||
if (num_args <= InlineArity) {
|
arg = value;
|
||||||
fixed_args[index] = value;
|
|
||||||
} else if (num_args == PhiArity) {
|
|
||||||
phi_args[index].second = value;
|
|
||||||
} else {
|
|
||||||
large_args[index] = value;
|
|
||||||
}
|
|
||||||
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
||||||
AddUse(new_inst, index);
|
AddUse(new_inst, index);
|
||||||
}
|
}
|
||||||
@@ -238,11 +174,15 @@ void Inst::AddPhiOperand(Block* predecessor, Value value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
||||||
const auto old_uses = uses;
|
// Detach the arguments first (a phi can use itself), then move every use over in order.
|
||||||
for (const auto& use: old_uses) {
|
|
||||||
use.user->SetArg(use.operand, replacement);
|
|
||||||
}
|
|
||||||
Invalidate();
|
Invalidate();
|
||||||
|
const auto old_uses = std::exchange(uses, {});
|
||||||
|
for (const auto& use: old_uses) {
|
||||||
|
ArgSlot(*use.user, use.operand) = replacement;
|
||||||
|
}
|
||||||
|
if (auto* target = replacement.TryInstruction(); target != nullptr) {
|
||||||
|
target->uses.insert(target->uses.end(), old_uses.begin(), old_uses.end());
|
||||||
|
}
|
||||||
if (preserve) {
|
if (preserve) {
|
||||||
opcode = ValueOpcode::Identity;
|
opcode = ValueOpcode::Identity;
|
||||||
num_args = 1;
|
num_args = 1;
|
||||||
@@ -272,6 +212,10 @@ void Inst::ClearArgs() {
|
|||||||
RemoveUse(value_inst, index);
|
RemoveUse(value_inst, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DropArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::DropArgs() {
|
||||||
if (num_args == PhiArity) {
|
if (num_args == PhiArity) {
|
||||||
std::destroy_at(&phi_args);
|
std::destroy_at(&phi_args);
|
||||||
std::construct_at(&fixed_args);
|
std::construct_at(&fixed_args);
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ public:
|
|||||||
static Value F16(uint16_t bits);
|
static Value F16(uint16_t bits);
|
||||||
static Value F32(float value);
|
static Value F32(float value);
|
||||||
|
|
||||||
[[nodiscard]] bool IsEmpty() const;
|
[[nodiscard]] bool IsEmpty() const { return type == Type::Void; }
|
||||||
[[nodiscard]] bool IsImmediate() const;
|
[[nodiscard]] bool IsImmediate() const { return type != Type::Opaque; }
|
||||||
[[nodiscard]] bool IsIdentity() const;
|
[[nodiscard]] bool IsIdentity() const;
|
||||||
[[nodiscard]] bool IsPhi() const;
|
[[nodiscard]] bool IsPhi() const;
|
||||||
[[nodiscard]] Type GetType() const;
|
[[nodiscard]] Type GetType() const;
|
||||||
|
|
||||||
[[nodiscard]] Inst* Instruction() const;
|
[[nodiscard]] Inst* Instruction() const;
|
||||||
[[nodiscard]] Inst* TryInstruction() const;
|
[[nodiscard]] Inst* TryInstruction() const { return type == Type::Opaque ? inst : nullptr; }
|
||||||
[[nodiscard]] Inst* ResolveInstruction() const;
|
[[nodiscard]] Inst* ResolveInstruction() const;
|
||||||
[[nodiscard]] Value Resolve() const;
|
[[nodiscard]] Value Resolve() const;
|
||||||
[[nodiscard]] ScalarReg ScalarRegister() const;
|
[[nodiscard]] ScalarReg ScalarRegister() const;
|
||||||
@@ -112,7 +112,7 @@ public:
|
|||||||
Inst(Inst&&) = delete;
|
Inst(Inst&&) = delete;
|
||||||
Inst& operator=(Inst&&) = delete;
|
Inst& operator=(Inst&&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] ValueOpcode GetOpcode() const;
|
[[nodiscard]] ValueOpcode GetOpcode() const { return opcode; }
|
||||||
[[nodiscard]] Type GetType() const;
|
[[nodiscard]] Type GetType() const;
|
||||||
[[nodiscard]] bool MayHaveSideEffects() const;
|
[[nodiscard]] bool MayHaveSideEffects() const;
|
||||||
[[nodiscard]] bool HasUses() const;
|
[[nodiscard]] bool HasUses() const;
|
||||||
@@ -121,8 +121,8 @@ public:
|
|||||||
[[nodiscard]] size_t NumPhiBlocks() const;
|
[[nodiscard]] size_t NumPhiBlocks() const;
|
||||||
[[nodiscard]] Value Arg(size_t index) const;
|
[[nodiscard]] Value Arg(size_t index) const;
|
||||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||||
[[nodiscard]] Block* Parent() const;
|
[[nodiscard]] Block* Parent() const { return parent; }
|
||||||
[[nodiscard]] const std::vector<Use>& Uses() const;
|
[[nodiscard]] const std::vector<Use>& Uses() const { return uses; }
|
||||||
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
||||||
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
||||||
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
||||||
@@ -138,6 +138,8 @@ public:
|
|||||||
void AddPhiOperand(Block* predecessor, Value value);
|
void AddPhiOperand(Block* predecessor, Value value);
|
||||||
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
||||||
void Invalidate();
|
void Invalidate();
|
||||||
|
// Drops the arguments without updating their use lists, for teardown that destroys both sides.
|
||||||
|
void DropArgs();
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
||||||
@@ -157,6 +159,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
||||||
|
|
||||||
|
template <typename Self>
|
||||||
|
static auto& ArgSlot(Self& self, size_t index);
|
||||||
void AddUse(Inst* used, size_t operand);
|
void AddUse(Inst* used, size_t operand);
|
||||||
void RemoveUse(Inst* used, size_t operand);
|
void RemoveUse(Inst* used, size_t operand);
|
||||||
void ClearArgs();
|
void ClearArgs();
|
||||||
@@ -181,4 +185,44 @@ private:
|
|||||||
|
|
||||||
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
||||||
|
|
||||||
|
// Called per operand by the IR passes and the resource walker; inline, as the build has no LTO.
|
||||||
|
inline bool Value::IsIdentity() const {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::IsPhi() const {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Inst* Value::ResolveInstruction() const {
|
||||||
|
EXIT_IF(type != Type::Opaque);
|
||||||
|
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Value Value::Resolve() const {
|
||||||
|
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t Value::U32() const {
|
||||||
|
EXIT_IF(type != Type::U32);
|
||||||
|
return imm_u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t Inst::NumArgs() const {
|
||||||
|
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Self>
|
||||||
|
auto& Inst::ArgSlot(Self& self, size_t index) {
|
||||||
|
EXIT_IF(index >= self.NumArgs());
|
||||||
|
if (self.num_args <= InlineArity) {
|
||||||
|
return self.fixed_args[index];
|
||||||
|
}
|
||||||
|
return self.num_args == PhiArity ? self.phi_args[index].second : self.large_args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Value Inst::Arg(size_t index) const {
|
||||||
|
return ArgSlot(*this, index);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||||
@@ -44,6 +44,7 @@ constexpr Type AddressResource = Type::AddressResource;
|
|||||||
constexpr Type ImageResource = Type::ImageResource;
|
constexpr Type ImageResource = Type::ImageResource;
|
||||||
constexpr Type SamplerResource = Type::SamplerResource;
|
constexpr Type SamplerResource = Type::SamplerResource;
|
||||||
constexpr Type ImageAddress = Type::ImageAddress;
|
constexpr Type ImageAddress = Type::ImageAddress;
|
||||||
|
constexpr Type Bvh8Result = Type::Bvh8Result;
|
||||||
|
|
||||||
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
|
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
|
||||||
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
|
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
|
||||||
@@ -76,7 +77,8 @@ bool HasSideEffects(ValueOpcode opcode) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const auto shared_access = SharedAccessOf(opcode);
|
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) {
|
shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -156,6 +158,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
|
|||||||
|
|
||||||
SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
case ValueOpcode::BvhStack: return SharedAccess::ReadWrite;
|
||||||
case ValueOpcode::LoadSharedU8:
|
case ValueOpcode::LoadSharedU8:
|
||||||
case ValueOpcode::LoadSharedU16:
|
case ValueOpcode::LoadSharedU16:
|
||||||
case ValueOpcode::LoadSharedU32:
|
case ValueOpcode::LoadSharedU32:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ enum class ValueOpcode : uint16_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum class BufferAccess { None, Read, Write, Atomic };
|
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 AddressAccess { None, Read, Write };
|
||||||
enum class ImageAccess { None, Read, Write, Atomic };
|
enum class ImageAccess { None, Read, Write, Atomic };
|
||||||
enum class ImageResourceClass { None, Sampled, Storage };
|
enum class ImageResourceClass { None, Sampled, Storage };
|
||||||
|
|||||||
@@ -318,6 +318,9 @@ VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
|||||||
|
|
||||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||||
VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1)
|
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(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
|
||||||
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
|
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
|
||||||
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
|
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
|
||||||
|
|||||||
@@ -4182,7 +4182,7 @@ private:
|
|||||||
|
|
||||||
void Collect(Inst& inst) {
|
void Collect(Inst& inst) {
|
||||||
const auto op = inst.GetOpcode();
|
const auto op = inst.GetOpcode();
|
||||||
if (op == ValueOpcode::BvhIntersect) {
|
if (op == ValueOpcode::BvhIntersect || op == ValueOpcode::Bvh8Intersect) {
|
||||||
m_info.uses_dma = true;
|
m_info.uses_dma = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
|||||||
case StageInputKind::GlobalInvocationId:
|
case StageInputKind::GlobalInvocationId:
|
||||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||||
break;
|
break;
|
||||||
|
case StageInputKind::BaseVertex: AddInput(info, kind, 0, 1, "gl_BaseVertex"); break;
|
||||||
|
case StageInputKind::BaseInstance: AddInput(info, kind, 0, 1, "gl_BaseInstance"); break;
|
||||||
case StageInputKind::PackedAncillary:
|
case StageInputKind::PackedAncillary:
|
||||||
case StageInputKind::Parameter: break;
|
case StageInputKind::Parameter: break;
|
||||||
}
|
}
|
||||||
@@ -444,6 +446,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
|||||||
case StageInputKind::InstanceIndex:
|
case StageInputKind::InstanceIndex:
|
||||||
case StageInputKind::FrontFacing:
|
case StageInputKind::FrontFacing:
|
||||||
case StageInputKind::LocalInvocationIndex:
|
case StageInputKind::LocalInvocationIndex:
|
||||||
|
case StageInputKind::BaseVertex:
|
||||||
|
case StageInputKind::BaseInstance:
|
||||||
if (component != 0u) {
|
if (component != 0u) {
|
||||||
return Fail("typed scalar builtin component is out of range");
|
return Fail("typed scalar builtin component is out of range");
|
||||||
}
|
}
|
||||||
@@ -511,6 +515,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
|||||||
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
|
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
|
||||||
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
|
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
|
||||||
case ValueOpcode::BvhIntersect: info.bvh = 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:
|
case ValueOpcode::ConditionRef:
|
||||||
info.subgroup_vote |=
|
info.subgroup_vote |=
|
||||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||||
|
|||||||
@@ -291,10 +291,9 @@ static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserS
|
|||||||
const auto& metadata = data.vertex;
|
const auto& metadata = data.vertex;
|
||||||
|
|
||||||
if (metadata.vertex_attrib_reg >= 0) {
|
if (metadata.vertex_attrib_reg >= 0) {
|
||||||
info.fetch_external = false;
|
info.has_dynamic_vertex_fetch = true;
|
||||||
info.fetch_embedded = true;
|
info.fetch_attrib_reg = metadata.vertex_attrib_reg;
|
||||||
info.fetch_attrib_reg = metadata.vertex_attrib_reg;
|
info.fetch_buffer_reg = metadata.vertex_buffer_reg;
|
||||||
info.fetch_buffer_reg = metadata.vertex_buffer_reg;
|
|
||||||
|
|
||||||
const auto* attrib = reinterpret_cast<const uint32_t*>(
|
const auto* attrib = reinterpret_cast<const uint32_t*>(
|
||||||
static_cast<uint64_t>(user_sgpr.value[metadata.vertex_attrib_reg]) |
|
static_cast<uint64_t>(user_sgpr.value[metadata.vertex_attrib_reg]) |
|
||||||
@@ -413,7 +412,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
|||||||
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
|
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
|
||||||
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||||
key.clear();
|
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_attrib_reg));
|
||||||
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
||||||
key.push_back(info.resources_num);
|
key.push_back(info.resources_num);
|
||||||
@@ -422,6 +421,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
|||||||
key.push_back(info.lds_size_dwords);
|
key.push_back(info.lds_size_dwords);
|
||||||
key.push_back(info.pa_cl_vs_out_cntl);
|
key.push_back(info.pa_cl_vs_out_cntl);
|
||||||
key.push_back(info.user_clip_planes);
|
key.push_back(info.user_clip_planes);
|
||||||
|
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));
|
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||||
if (info.clip_space.enabled) {
|
if (info.clip_space.enabled) {
|
||||||
for (const float value: info.clip_space.scale) {
|
for (const float value: info.clip_space.scale) {
|
||||||
@@ -692,9 +693,7 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
|||||||
|
|
||||||
LOGF("ShaderDbgDumpInputInfo()\n");
|
LOGF("ShaderDbgDumpInputInfo()\n");
|
||||||
|
|
||||||
LOGF("\t fetch_external = %s\n"
|
LOGF("\t has_dynamic_vertex_fetch = %s\n", info.has_dynamic_vertex_fetch ? "true" : "false");
|
||||||
"\t fetch_embedded = %s\n",
|
|
||||||
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
|
|
||||||
|
|
||||||
for (int i = 0; i < info.resources_num; i++) {
|
for (int i = 0; i < info.resources_num; i++) {
|
||||||
LOGF("\t input %d\n", i);
|
LOGF("\t input %d\n", i);
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ struct ShaderTessellationInputInfo {
|
|||||||
uint32_t output_topology = 0;
|
uint32_t output_topology = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||||
|
// to. A packet can leave both slots unused even when its record contains nonzero offsets.
|
||||||
|
struct ShaderIndirectOffsets {
|
||||||
|
static constexpr uint8_t None = UINT8_MAX;
|
||||||
|
|
||||||
|
uint8_t vertex = None;
|
||||||
|
uint8_t instance = None;
|
||||||
|
|
||||||
|
bool operator==(const ShaderIndirectOffsets&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct ShaderVertexInputInfo {
|
struct ShaderVertexInputInfo {
|
||||||
static constexpr int RES_MAX = 32;
|
static constexpr int RES_MAX = 32;
|
||||||
|
|
||||||
@@ -146,8 +157,11 @@ struct ShaderVertexInputInfo {
|
|||||||
ShaderClipSpaceTransform clip_space;
|
ShaderClipSpaceTransform clip_space;
|
||||||
ShaderMeshInputInfo mesh;
|
ShaderMeshInputInfo mesh;
|
||||||
ShaderTessellationInputInfo tess;
|
ShaderTessellationInputInfo tess;
|
||||||
bool fetch_external = false;
|
ShaderIndirectOffsets indirect_offsets;
|
||||||
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 {
|
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
|
||||||
|
|||||||
+487
-1106
File diff suppressed because it is too large.
Load diff
+9
-4
@@ -26,9 +26,10 @@ struct DebugStats {
|
|||||||
uint64_t flexible_total = 0;
|
uint64_t flexible_total = 0;
|
||||||
uint64_t flexible_used = 0;
|
uint64_t flexible_used = 0;
|
||||||
uint64_t pool_committed = 0;
|
uint64_t pool_committed = 0;
|
||||||
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
||||||
uint64_t gpu_page_entries = 0;
|
uint64_t gpu_page_entries = 0;
|
||||||
uint64_t page_entries_total = 0; // Per side.
|
uint64_t cpu_page_entries_total = 0;
|
||||||
|
uint64_t gpu_page_entries_total = 0;
|
||||||
};
|
};
|
||||||
[[nodiscard]] DebugStats GetDebugStats();
|
[[nodiscard]] DebugStats GetDebugStats();
|
||||||
|
|
||||||
@@ -125,7 +126,6 @@ static_assert(sizeof(KernelMemoryPoolBlockStats) == 16,
|
|||||||
"KernelMemoryPoolBlockStats struct size is incorrect");
|
"KernelMemoryPoolBlockStats struct size is incorrect");
|
||||||
|
|
||||||
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
||||||
void SetFlexibleMemorySize(uint64_t size);
|
|
||||||
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
||||||
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
||||||
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
||||||
@@ -225,6 +225,11 @@ bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
|
|||||||
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
||||||
bool TestGuestBackingOutsideAddressSpace();
|
bool TestGuestBackingOutsideAddressSpace();
|
||||||
uint64_t TestGuestBackingSize();
|
uint64_t TestGuestBackingSize();
|
||||||
|
uint64_t TestFlexibleBackingHighWater();
|
||||||
|
uint64_t TestGuestFreeRangeVisits();
|
||||||
|
uint64_t TestDirectMappingCount();
|
||||||
|
uint64_t TestFlexibleMappingCount();
|
||||||
|
uint64_t TestBackingBytesZeroed();
|
||||||
bool TestGuestFreeRangeBounds();
|
bool TestGuestFreeRangeBounds();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,9 @@ public:
|
|||||||
}
|
}
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
std::memset(m_backing_base + backing_offset, 0, size);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +290,18 @@ public:
|
|||||||
return true;
|
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,
|
bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
||||||
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
||||||
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
||||||
@@ -324,15 +339,11 @@ public:
|
|||||||
#endif
|
#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) {
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto end = vaddr + size;
|
const auto end = vaddr + size;
|
||||||
std::vector<MapEntry> targets;
|
std::vector<MapEntry> targets;
|
||||||
{
|
{
|
||||||
@@ -350,42 +361,36 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<MapEntry> removed;
|
size_t removed = 0;
|
||||||
removed.reserve(targets.size());
|
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) {
|
for (const auto& entry: targets) {
|
||||||
bool part_placeholder_preserved = false;
|
if (!UnmapOne(entry.vaddr, entry.size)) {
|
||||||
if (!UnmapOne(entry.vaddr, entry.size, &part_placeholder_preserved)) {
|
rollback();
|
||||||
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;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(!part_placeholder_preserved);
|
++removed;
|
||||||
removed.push_back(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
// 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
|
// records this contiguous guest span as one free range, so normalize fragmented backing
|
||||||
// views into one placeholder before publishing it there.
|
// views into one placeholder before publishing it there.
|
||||||
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
|
if (removed > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
rollback();
|
||||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return 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 defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
if (g_test_backing_store_unmaps_before_failure == 0) {
|
if (g_test_backing_store_unmaps_before_failure == 0) {
|
||||||
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
||||||
@@ -428,9 +433,7 @@ public:
|
|||||||
m_maps.emplace(old.vaddr, old);
|
m_maps.emplace(old.vaddr, old);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,9 +454,6 @@ public:
|
|||||||
ok;
|
ok;
|
||||||
}
|
}
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,9 +481,6 @@ public:
|
|||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
RemoveMappingsUnlocked(vaddr, size);
|
RemoveMappingsUnlocked(vaddr, size);
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -634,8 +631,6 @@ private:
|
|||||||
|
|
||||||
HANDLE m_handle = nullptr;
|
HANDLE m_handle = nullptr;
|
||||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
[[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; }
|
|
||||||
|
|
||||||
static int GetProtect(VirtualMemory::Mode mode) {
|
static int GetProtect(VirtualMemory::Mode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VirtualMemory::Mode::Read: return PROT_READ;
|
case VirtualMemory::Mode::Read: return PROT_READ;
|
||||||
@@ -760,7 +755,7 @@ public:
|
|||||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||||
return false;
|
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 std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
||||||
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
||||||
});
|
});
|
||||||
@@ -864,11 +859,7 @@ public:
|
|||||||
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool placeholder_preserved = false;
|
if (!m_backing->Unmap(vaddr, size)) {
|
||||||
if (!m_backing->Unmap(vaddr, size, &placeholder_preserved)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!placeholder_preserved) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
||||||
@@ -876,12 +867,13 @@ public:
|
|||||||
return true;
|
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) {
|
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
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) {
|
if (search_addr == 0) {
|
||||||
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
||||||
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
||||||
@@ -898,7 +890,7 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
alignment = std::max<uint64_t>(alignment, PageSize());
|
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);
|
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,19 +898,19 @@ public:
|
|||||||
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return FindContaining(vaddr, size) != m_free.end();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
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();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
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();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,10 +1033,6 @@ private:
|
|||||||
return (value & (PageSize() - 1u)) == 0;
|
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) {
|
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||||
const auto end = vaddr + size;
|
const auto end = vaddr + size;
|
||||||
auto it = m_mapped.upper_bound(vaddr);
|
auto it = m_mapped.upper_bound(vaddr);
|
||||||
@@ -1055,8 +1043,20 @@ private:
|
|||||||
const auto& region = it->second;
|
const auto& region = it->second;
|
||||||
const auto range_start = std::max(vaddr, region.vaddr);
|
const auto range_start = std::max(vaddr, region.vaddr);
|
||||||
const auto range_end = std::min(end, region.vaddr + region.size);
|
const auto range_end = std::min(end, region.vaddr + region.size);
|
||||||
if (range_start < range_end &&
|
if (range_start >= range_end) {
|
||||||
!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1312,12 +1312,21 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
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) {
|
if (search_start >= search_end || size > search_end - search_start) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for (const auto& [base, range_size]: m_free) {
|
// Include the predecessor because search_start can lie inside a free range.
|
||||||
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
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 &&
|
if (candidate < search_end && size <= search_end - candidate &&
|
||||||
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
@@ -1331,9 +1340,8 @@ private:
|
|||||||
if (next == m_free.begin()) {
|
if (next == m_free.begin()) {
|
||||||
return m_free.end();
|
return m_free.end();
|
||||||
}
|
}
|
||||||
auto it = std::prev(next);
|
auto it = std::prev(next);
|
||||||
auto end = it->first + it->second;
|
return GuestFreeRangeContains(it->first, it->second, vaddr, size) ? it : m_free.end();
|
||||||
return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
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;
|
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);
|
std::lock_guard lock((*rwlock)->m);
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ if(APPLE)
|
|||||||
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
|
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||||
install(TARGETS kyty_updater DESTINATION "KytyPS5.app/Contents/MacOS")
|
install(TARGETS kyty_updater DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||||
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds")
|
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds")
|
||||||
|
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION "KytyPS5.app/Contents/Resources/assets/fonts")
|
||||||
install(DIRECTORY "${CMAKE_SOURCE_DIR}/assets/cursors" DESTINATION "KytyPS5.app/Contents/Resources/assets")
|
install(DIRECTORY "${CMAKE_SOURCE_DIR}/assets/cursors" DESTINATION "KytyPS5.app/Contents/Resources/assets")
|
||||||
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
|
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
|
||||||
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
|
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
|
||||||
|
|||||||
@@ -115,7 +115,17 @@
|
|||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
<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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
@@ -244,6 +254,12 @@
|
|||||||
<widget class="QGroupBox" name="general_group">
|
<widget class="QGroupBox" name="general_group">
|
||||||
<property name="title"><string>General</string></property>
|
<property name="title"><string>General</string></property>
|
||||||
<layout class="QVBoxLayout" name="generalLayout">
|
<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>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||||
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
||||||
@@ -256,6 +272,12 @@
|
|||||||
<property name="text"><string>Auto-hide cursor</string></property>
|
<property name="text"><string>Auto-hide cursor</string></property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_perf_overlay">
|
<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>
|
<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>
|
<property name="text"><string>Enable network connection</string></property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_psn_emulation">
|
||||||
|
<property name="text"><string>Emulate PSN (compatibility)</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="networkSpacer">
|
<spacer name="networkSpacer">
|
||||||
<property name="orientation"><enum>Qt::Vertical</enum></property>
|
<property name="orientation"><enum>Qt::Vertical</enum></property>
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ inline QStringList EnumToList() {
|
|||||||
auto me = QMetaEnum::fromType<T>();
|
auto me = QMetaEnum::fromType<T>();
|
||||||
int count = me.keyCount();
|
int count = me.keyCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
auto key = QString(me.key(i));
|
ret << QString(me.key(i));
|
||||||
ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit()
|
|
||||||
? key.remove('R').toLower()
|
|
||||||
: key);
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -32,16 +29,12 @@ inline QStringList EnumToList() {
|
|||||||
template <class T>
|
template <class T>
|
||||||
T TextToEnum(const QString& text) {
|
T TextToEnum(const QString& text) {
|
||||||
auto me = QMetaEnum::fromType<T>();
|
auto me = QMetaEnum::fromType<T>();
|
||||||
return static_cast<T>(me.keyToValue(
|
return static_cast<T>(me.keyToValue(text.toUtf8().constData()));
|
||||||
((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
QString EnumToText(T value) {
|
QString EnumToText(T value) {
|
||||||
auto me = QMetaEnum::fromType<T>();
|
return QString(QMetaEnum::fromType<T>().valueToKey(static_cast<int>(value)));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControllerSettings {
|
struct ControllerSettings {
|
||||||
@@ -80,13 +73,10 @@ public:
|
|||||||
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||||
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
||||||
|
|
||||||
enum class Resolution {
|
static constexpr int MIN_SCREEN_SIZE = 320;
|
||||||
R1280X720,
|
static constexpr int MAX_SCREEN_SIZE = 16384;
|
||||||
R1920X1080,
|
|
||||||
R2560X1440,
|
static QString NormalizeResolution(const QString& text);
|
||||||
R3840X2160,
|
|
||||||
};
|
|
||||||
Q_ENUM(Resolution)
|
|
||||||
|
|
||||||
enum class ShaderOptimizationType { None, Size, Performance };
|
enum class ShaderOptimizationType { None, Size, Performance };
|
||||||
Q_ENUM(ShaderOptimizationType)
|
Q_ENUM(ShaderOptimizationType)
|
||||||
@@ -117,7 +107,7 @@ public:
|
|||||||
// Controller preferences always come from the global configuration.
|
// Controller preferences always come from the global configuration.
|
||||||
ControllerSettings controller;
|
ControllerSettings controller;
|
||||||
|
|
||||||
Resolution screen_resolution = Resolution::R1280X720;
|
QString screen_resolution = QStringLiteral("1280x720");
|
||||||
QString user_name = "Kyty";
|
QString user_name = "Kyty";
|
||||||
int user_id = Config::DEFAULT_USER_ID;
|
int user_id = Config::DEFAULT_USER_ID;
|
||||||
QString audio_input_device;
|
QString audio_input_device;
|
||||||
@@ -126,6 +116,7 @@ public:
|
|||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
bool hdr_enabled = false;
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
|
bool stretch_enabled = false;
|
||||||
bool readback_linear_images = false;
|
bool readback_linear_images = false;
|
||||||
bool readback_tiled_images = false;
|
bool readback_tiled_images = false;
|
||||||
bool sync_raw_image_buffers = false;
|
bool sync_raw_image_buffers = false;
|
||||||
@@ -137,6 +128,7 @@ public:
|
|||||||
bool perf_overlay_enabled = false;
|
bool perf_overlay_enabled = false;
|
||||||
bool skip_notice_screen = false;
|
bool skip_notice_screen = false;
|
||||||
bool network_connected = false;
|
bool network_connected = false;
|
||||||
|
bool psn_emulation_enabled = false;
|
||||||
int vblank_frequency = 60;
|
int vblank_frequency = 60;
|
||||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||||
bool vulkan_validation_enabled = false;
|
bool vulkan_validation_enabled = false;
|
||||||
@@ -150,6 +142,7 @@ public:
|
|||||||
QString printf_output_file = "_kyty.txt";
|
QString printf_output_file = "_kyty.txt";
|
||||||
bool profiler_enabled = false;
|
bool profiler_enabled = false;
|
||||||
bool renderdoc_enabled = false;
|
bool renderdoc_enabled = false;
|
||||||
|
bool pro_enabled = false;
|
||||||
bool amd_cpu_enabled = false;
|
bool amd_cpu_enabled = false;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
bool red_zone_protection_enabled = false;
|
bool red_zone_protection_enabled = false;
|
||||||
@@ -168,6 +161,7 @@ public:
|
|||||||
fullscreen_enabled = other.fullscreen_enabled;
|
fullscreen_enabled = other.fullscreen_enabled;
|
||||||
hdr_enabled = other.hdr_enabled;
|
hdr_enabled = other.hdr_enabled;
|
||||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||||
|
stretch_enabled = other.stretch_enabled;
|
||||||
readback_linear_images = other.readback_linear_images;
|
readback_linear_images = other.readback_linear_images;
|
||||||
readback_tiled_images = other.readback_tiled_images;
|
readback_tiled_images = other.readback_tiled_images;
|
||||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||||
@@ -179,6 +173,7 @@ public:
|
|||||||
perf_overlay_enabled = other.perf_overlay_enabled;
|
perf_overlay_enabled = other.perf_overlay_enabled;
|
||||||
skip_notice_screen = other.skip_notice_screen;
|
skip_notice_screen = other.skip_notice_screen;
|
||||||
network_connected = other.network_connected;
|
network_connected = other.network_connected;
|
||||||
|
psn_emulation_enabled = other.psn_emulation_enabled;
|
||||||
vblank_frequency = other.vblank_frequency;
|
vblank_frequency = other.vblank_frequency;
|
||||||
console_language = other.console_language;
|
console_language = other.console_language;
|
||||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||||
@@ -192,6 +187,7 @@ public:
|
|||||||
printf_output_file = other.printf_output_file;
|
printf_output_file = other.printf_output_file;
|
||||||
profiler_enabled = other.profiler_enabled;
|
profiler_enabled = other.profiler_enabled;
|
||||||
renderdoc_enabled = other.renderdoc_enabled;
|
renderdoc_enabled = other.renderdoc_enabled;
|
||||||
|
pro_enabled = other.pro_enabled;
|
||||||
amd_cpu_enabled = other.amd_cpu_enabled;
|
amd_cpu_enabled = other.amd_cpu_enabled;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
red_zone_protection_enabled = other.red_zone_protection_enabled;
|
red_zone_protection_enabled = other.red_zone_protection_enabled;
|
||||||
|
|||||||
@@ -46,11 +46,15 @@ private:
|
|||||||
QLineEdit* m_addons_dir_edit = nullptr;
|
QLineEdit* m_addons_dir_edit = nullptr;
|
||||||
QToolButton* m_remove_game_dir_button = nullptr;
|
QToolButton* m_remove_game_dir_button = nullptr;
|
||||||
bool m_global_settings = false;
|
bool m_global_settings = false;
|
||||||
|
bool m_geometry_restored = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void Init(const Configuration& info);
|
void Init(const Configuration& info);
|
||||||
void InitGameDirectories();
|
void InitGameDirectories();
|
||||||
void AddGameDirectoryItem(const QString& dir);
|
void AddGameDirectoryItem(const QString& dir);
|
||||||
|
void MakeTabsScrollable();
|
||||||
|
void GrowToFitContents();
|
||||||
|
void ShowTab(QWidget* page);
|
||||||
|
|
||||||
void moveEvent(QMoveEvent* event) override;
|
void moveEvent(QMoveEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
#include "configuration.h"
|
#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 Configuration::GameSettings() const {
|
||||||
QVariantMap values;
|
QVariantMap values;
|
||||||
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
|
#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(fullscreen_enabled);
|
||||||
KYTY_CFG_SET(hdr_enabled);
|
KYTY_CFG_SET(hdr_enabled);
|
||||||
KYTY_CFG_SET(hide_cursor_enabled);
|
KYTY_CFG_SET(hide_cursor_enabled);
|
||||||
|
KYTY_CFG_SET(stretch_enabled);
|
||||||
KYTY_CFG_SET(readback_linear_images);
|
KYTY_CFG_SET(readback_linear_images);
|
||||||
KYTY_CFG_SET(readback_tiled_images);
|
KYTY_CFG_SET(readback_tiled_images);
|
||||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||||
@@ -23,6 +42,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(perf_overlay_enabled);
|
KYTY_CFG_SET(perf_overlay_enabled);
|
||||||
KYTY_CFG_SET(skip_notice_screen);
|
KYTY_CFG_SET(skip_notice_screen);
|
||||||
KYTY_CFG_SET(network_connected);
|
KYTY_CFG_SET(network_connected);
|
||||||
|
KYTY_CFG_SET(psn_emulation_enabled);
|
||||||
KYTY_CFG_SET(vblank_frequency);
|
KYTY_CFG_SET(vblank_frequency);
|
||||||
KYTY_CFG_SET(console_language);
|
KYTY_CFG_SET(console_language);
|
||||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||||
@@ -36,6 +56,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(printf_output_file);
|
KYTY_CFG_SET(printf_output_file);
|
||||||
KYTY_CFG_SET(profiler_enabled);
|
KYTY_CFG_SET(profiler_enabled);
|
||||||
KYTY_CFG_SET(renderdoc_enabled);
|
KYTY_CFG_SET(renderdoc_enabled);
|
||||||
|
KYTY_CFG_SET(pro_enabled);
|
||||||
KYTY_CFG_SET(amd_cpu_enabled);
|
KYTY_CFG_SET(amd_cpu_enabled);
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
KYTY_CFG_SET(red_zone_protection_enabled);
|
KYTY_CFG_SET(red_zone_protection_enabled);
|
||||||
@@ -48,7 +69,8 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
template <class Settings>
|
template <class Settings>
|
||||||
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||||
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
|
#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();
|
user_name = s.value("user_name", user_name).toString();
|
||||||
bool user_id_ok = false;
|
bool user_id_ok = false;
|
||||||
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
|
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(fullscreen_enabled);
|
||||||
KYTY_CFG_GET(hdr_enabled);
|
KYTY_CFG_GET(hdr_enabled);
|
||||||
KYTY_CFG_GET(hide_cursor_enabled);
|
KYTY_CFG_GET(hide_cursor_enabled);
|
||||||
|
KYTY_CFG_GET(stretch_enabled);
|
||||||
KYTY_CFG_GET(readback_linear_images);
|
KYTY_CFG_GET(readback_linear_images);
|
||||||
KYTY_CFG_GET(readback_tiled_images);
|
KYTY_CFG_GET(readback_tiled_images);
|
||||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
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(perf_overlay_enabled);
|
||||||
KYTY_CFG_GET(skip_notice_screen);
|
KYTY_CFG_GET(skip_notice_screen);
|
||||||
KYTY_CFG_GET(network_connected);
|
KYTY_CFG_GET(network_connected);
|
||||||
|
KYTY_CFG_GET(psn_emulation_enabled);
|
||||||
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
|
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
|
||||||
console_language = s.value("console_language", console_language).toInt();
|
console_language = s.value("console_language", console_language).toInt();
|
||||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
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(printf_output_file);
|
||||||
KYTY_CFG_GET(profiler_enabled);
|
KYTY_CFG_GET(profiler_enabled);
|
||||||
KYTY_CFG_GET(renderdoc_enabled);
|
KYTY_CFG_GET(renderdoc_enabled);
|
||||||
|
pro_enabled = s.value("pro_enabled", false).toBool();
|
||||||
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
|
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
red_zone_protection_enabled =
|
red_zone_protection_enabled =
|
||||||
@@ -134,7 +159,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
|
|||||||
if (!it.value().isString()) {
|
if (!it.value().isString()) {
|
||||||
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
|
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);
|
ReadGameSettingsValues(values);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QScrollArea>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
@@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
||||||
: QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog),
|
: QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
|
||||||
m_info(info) {
|
m_ui(new Ui::ConfigurationEditDialog), m_info(info) {
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
setMinimumWidth(width());
|
MakeTabsScrollable();
|
||||||
InitGameDirectories();
|
InitGameDirectories();
|
||||||
m_ui->controller_group->setVisible(false);
|
m_ui->controller_group->setVisible(false);
|
||||||
m_ui->settings_button->setVisible(false);
|
m_ui->settings_button->setVisible(false);
|
||||||
@@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
|||||||
|
|
||||||
setSizeGripEnabled(true);
|
setSizeGripEnabled(true);
|
||||||
|
|
||||||
restoreGeometry(g_last_geometry);
|
|
||||||
|
|
||||||
Init(info);
|
Init(info);
|
||||||
|
m_geometry_restored = restoreGeometry(g_last_geometry);
|
||||||
|
if (!m_geometry_restored) {
|
||||||
|
GrowToFitContents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ConfigurationEditDialog::g_last_geometry;
|
QByteArray ConfigurationEditDialog::g_last_geometry;
|
||||||
@@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
info.audio_input_device);
|
info.audio_input_device);
|
||||||
}
|
}
|
||||||
microphone->setCurrentIndex(microphone->findData(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);
|
ListInit(m_ui->comboBox_present_mode, info.present_mode);
|
||||||
m_ui->comboBox_gpu->clear();
|
m_ui->comboBox_gpu->clear();
|
||||||
m_ui->comboBox_gpu->addItem(tr("Auto"));
|
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_fullscreen->setChecked(info.fullscreen_enabled);
|
||||||
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
||||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_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->setChecked(info.readback_linear_images);
|
||||||
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
||||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
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_shader_cache->setChecked(info.shader_cache_enabled);
|
||||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||||
m_ui->checkBox_network_connected->setChecked(info.network_connected);
|
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->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
||||||
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
|
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
|
||||||
m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled);
|
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_shader_validation->setChecked(info.shader_validation_enabled);
|
||||||
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
||||||
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_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);
|
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
m_ui->checkBox_amd_cpu->setVisible(false);
|
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_game_dirs_group->setVisible(true);
|
||||||
m_ui->controller_group->setVisible(true);
|
m_ui->controller_group->setVisible(true);
|
||||||
update_game_directory_buttons();
|
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();
|
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 {
|
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.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
|
||||||
}
|
}
|
||||||
info.screen_resolution =
|
info.screen_resolution =
|
||||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText());
|
||||||
info.present_mode =
|
info.present_mode =
|
||||||
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
||||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||||
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
||||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->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_linear_images = ui.checkBox_readback->isChecked();
|
||||||
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
||||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->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.shader_cache_enabled = ui.checkBox_shader_cache->isChecked();
|
||||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||||
info.network_connected = ui.checkBox_network_connected->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_enabled = ui.checkBox_trophy_notifications->isChecked();
|
||||||
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
|
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
|
||||||
info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked();
|
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.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||||
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
||||||
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->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();
|
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
|
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,
|
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
|
||||||
m_ui->lineEdit_cmd_dump_folder}) {
|
m_ui->lineEdit_cmd_dump_folder}) {
|
||||||
if (field->isEnabled() && field->text().isEmpty()) {
|
if (field->isEnabled() && field->text().isEmpty()) {
|
||||||
m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab);
|
ShowTab(m_ui->debug_tab);
|
||||||
field->setFocus();
|
field->setFocus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -540,6 +588,16 @@ void ConfigurationEditDialog::save() {
|
|||||||
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
||||||
return;
|
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);
|
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.
|
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
|
||||||
#endif
|
#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,
|
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
@@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) {
|
|||||||
|
|
||||||
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||||
QStringList args;
|
QStringList args;
|
||||||
auto r = EnumToText(info.screen_resolution).split('x');
|
auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x');
|
||||||
|
|
||||||
if (r.size() != 2) {
|
if (r.size() != 2) {
|
||||||
return {};
|
return {};
|
||||||
@@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
||||||
AddFlag(args, info.hdr_enabled, "--hdr");
|
AddFlag(args, info.hdr_enabled, "--hdr");
|
||||||
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
|
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_linear_images, "--readback-linear-images");
|
||||||
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
||||||
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
|
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.perf_overlay_enabled, "--perf-overlay");
|
||||||
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
|
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
|
||||||
AddFlag(args, info.network_connected, "--network-connected");
|
AddFlag(args, info.network_connected, "--network-connected");
|
||||||
|
AddFlag(args, info.psn_emulation_enabled, "--emulate-psn");
|
||||||
AddFlag(args, info.tessellation_enabled, "--tessellation");
|
AddFlag(args, info.tessellation_enabled, "--tessellation");
|
||||||
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
|
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
|
||||||
AddFlag(args, info.shader_cache_enabled, "--shader-cache");
|
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-direction" << EnumToText(info.printf_direction);
|
||||||
args << "--printf-output-file" << info.printf_output_file;
|
args << "--printf-output-file" << info.printf_output_file;
|
||||||
AddFlag(args, info.profiler_enabled, "--profile");
|
AddFlag(args, info.profiler_enabled, "--profile");
|
||||||
|
AddFlag(args, info.pro_enabled, "--trinity");
|
||||||
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
|
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
|
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
|
||||||
|
|||||||
+3
-2
@@ -24,6 +24,7 @@
|
|||||||
#include "kernel/pthread.h"
|
#include "kernel/pthread.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/libs.h"
|
#include "libs/libs.h"
|
||||||
|
#include "loader/systemContent.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#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);
|
return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr bool is_trinity_mode() {
|
static bool is_trinity_mode() {
|
||||||
return false;
|
return Loader::SystemContentIsTrinityMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
|
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
|
||||||
|
|||||||
+55
-10
@@ -6,6 +6,7 @@
|
|||||||
#include "libs/ajm/decoder.h"
|
#include "libs/ajm/decoder.h"
|
||||||
#include "libs/ajm/mp3_decoder.h"
|
#include "libs/ajm/mp3_decoder.h"
|
||||||
#include "libs/ajm/opus_decoder.h"
|
#include "libs/ajm/opus_decoder.h"
|
||||||
|
#include "libs/ajm/resampler.h"
|
||||||
#include "libs/audio.h"
|
#include "libs/audio.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/libs.h"
|
#include "libs/libs.h"
|
||||||
@@ -63,12 +64,6 @@ struct AjmSidebandMFrame {
|
|||||||
uint32_t reserved;
|
uint32_t reserved;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AjmSidebandResampleInfo {
|
|
||||||
float ratio;
|
|
||||||
int32_t num_samples;
|
|
||||||
uint32_t reserved[8];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmGetStatisticsResult {
|
struct AjmGetStatisticsResult {
|
||||||
AjmSidebandResult result;
|
AjmSidebandResult result;
|
||||||
float engine_usage_batch;
|
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_RUN_MULTIPLE_FRAMES = 1ull << 12u;
|
||||||
constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u;
|
constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u;
|
||||||
constexpr uint64_t AJM_FLAG_CONTROL_INITIALIZE = 1ull << 14u;
|
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_RESAMPLE_INFO = 1ull << 44u;
|
||||||
constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u;
|
constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u;
|
||||||
constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u;
|
constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u;
|
||||||
@@ -177,6 +174,7 @@ struct AjmInstanceState {
|
|||||||
uint64_t flags = 0;
|
uint64_t flags = 0;
|
||||||
AjmGaplessState gapless;
|
AjmGaplessState gapless;
|
||||||
std::unique_ptr<AjmDecoder> decoder;
|
std::unique_ptr<AjmDecoder> decoder;
|
||||||
|
AjmResampler resampler;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::mutex g_ajm_instances_mutex;
|
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) {
|
offset + AJM_SIDEBAND_RESAMPLE_INFO_SIZE <= sideband_output_size) {
|
||||||
auto* info = reinterpret_cast<AjmSidebandResampleInfo*>(
|
auto* info = reinterpret_cast<AjmSidebandResampleInfo*>(
|
||||||
static_cast<uint8_t*>(sideband_output) + offset);
|
static_cast<uint8_t*>(sideband_output) + offset);
|
||||||
info->ratio = 1.0f;
|
*info = decode_result.resample_info;
|
||||||
offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE;
|
offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +423,12 @@ static AjmDecodeResult AjmDecodeInstance(uint32_t instance, const void* input, s
|
|||||||
return result;
|
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,
|
return state->decoder->Decode(input, input_size, output, output_size, multiple_frames,
|
||||||
&state->gapless);
|
&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 = static_cast<const uint8_t*>(sideband_input) + AJM_SIDEBAND_GAPLESS_SIZE;
|
||||||
sideband_input_size -= 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) {
|
if ((flags & AJM_FLAG_CONTROL_RESET) != 0) {
|
||||||
state->gapless.Reset();
|
state->gapless.Reset();
|
||||||
decoder->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 = decoder->Initialize(sideband_input, sideband_input_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.resample_info = state->resampler.GetInfo();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,6 +603,7 @@ static AjmSidebandFormat AjmGetInstanceFormat(uint32_t instance,
|
|||||||
if (out_result != nullptr) {
|
if (out_result != nullptr) {
|
||||||
*out_result = state->decoder->MakeResult();
|
*out_result = state->decoder->MakeResult();
|
||||||
out_result->format = state->decoder->GetFormat();
|
out_result->format = state->decoder->GetFormat();
|
||||||
|
out_result->resample_info = state->resampler.GetInfo();
|
||||||
}
|
}
|
||||||
return state->decoder->GetFormat();
|
return state->decoder->GetFormat();
|
||||||
}
|
}
|
||||||
@@ -921,7 +953,13 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t
|
|||||||
float ratio, uint32_t flags, void* result) {
|
float ratio, uint32_t flags, void* result) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
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,
|
LOGF("\t instance = 0x%08" PRIx32 ", ratio = %f, flags = 0x%08" PRIx32 "\n", instance,
|
||||||
static_cast<double>(ratio), flags);
|
static_cast<double>(ratio), flags);
|
||||||
return AjmAppendJob(info, AJM_JOB_SET_RESAMPLE_PARAMETERS_SIZE, "set-resample");
|
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) {
|
void* result) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
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
|
LOGF("\t instance = 0x%08" PRIx32 ", ratio_start = %f, ratio_change = %f, flags = 0x%08" PRIx32
|
||||||
"\n",
|
"\n",
|
||||||
instance, static_cast<double>(ratio_start), static_cast<double>(ratio_change_per_sample),
|
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) {
|
if (result != nullptr) {
|
||||||
auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) +
|
auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) +
|
||||||
AJM_SIDEBAND_RESULT_SIZE);
|
AJM_SIDEBAND_RESULT_SIZE);
|
||||||
resample->ratio = 1.0f;
|
*resample = decode_result.resample_info;
|
||||||
}
|
}
|
||||||
LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
|
LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
|
||||||
return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info");
|
return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info");
|
||||||
|
|||||||
+15
-8
@@ -56,15 +56,22 @@ enum class AjmSampleEncoding : uint32_t {
|
|||||||
Float = 2,
|
Float = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandResampleInfo {
|
||||||
|
float ratio = 1.0f;
|
||||||
|
int32_t num_samples = 0;
|
||||||
|
uint32_t reserved[8] {};
|
||||||
|
};
|
||||||
|
|
||||||
struct AjmDecodeResult {
|
struct AjmDecodeResult {
|
||||||
int32_t result = OK;
|
int32_t result = OK;
|
||||||
int32_t internal_result = OK;
|
int32_t internal_result = OK;
|
||||||
size_t input_consumed = 0;
|
size_t input_consumed = 0;
|
||||||
size_t output_written = 0;
|
size_t output_written = 0;
|
||||||
uint64_t total_decoded_samples = 0;
|
uint64_t total_decoded_samples = 0;
|
||||||
uint32_t frames = 0;
|
uint32_t frames = 0;
|
||||||
uint32_t frames_per_packet = 0;
|
uint32_t frames_per_packet = 0;
|
||||||
AjmSidebandFormat format {};
|
AjmSidebandFormat format {};
|
||||||
|
AjmSidebandResampleInfo resample_info {};
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
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 = {};
|
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() {
|
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) {
|
if (m_states_num >= STATES_MAX) {
|
||||||
m_states_num = STATES_MAX - 1;
|
m_states_num = STATES_MAX - 1;
|
||||||
m_first_state = (m_first_state + 1) % STATES_MAX;
|
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 uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3;
|
||||||
constexpr int APP_CONTENT_ERROR_PARAMETER = -2133262334; /* 0x80D90002 */
|
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_MOUNTED = -2133262332; /* 0x80D90004 */
|
||||||
constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */
|
constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */
|
||||||
constexpr int APP_CONTENT_ERROR_MOUNT_FULL = -2133262330; /* 0x80D90006 */
|
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) {
|
for (const auto& m: g_addcont_mounts) {
|
||||||
if (m.label == entry->entitlement_label) {
|
if (m.label == entry->entitlement_label) {
|
||||||
const auto guest_point = m.point.substr(0, m.point.size() - 1);
|
LOGF("\t already mounted %s\n", m.point.c_str());
|
||||||
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1);
|
return APP_CONTENT_ERROR_BUSY;
|
||||||
LOGF("\t already mounted %s\n", guest_point.c_str());
|
|
||||||
return OK;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1162,6 +1162,8 @@ LIB_DEFINE(InitAudio_1_Ngs2) {
|
|||||||
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
|
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
|
||||||
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
|
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
|
||||||
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
|
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
|
||||||
|
LIB_FUNC("9eic4AmjGVI", Ngs2::Ngs2VoiceQueryInfo);
|
||||||
|
LIB_FUNC("WCayTgob7-o", Ngs2::Ngs2VoiceGetPortInfo);
|
||||||
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
|
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
|
||||||
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
|
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
|
||||||
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
|
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
|
||||||
|
|||||||
+119
-88
@@ -13,6 +13,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Audio {
|
namespace Libs::Audio {
|
||||||
@@ -168,17 +169,12 @@ struct AudioOut2MasteringStatesV2 {
|
|||||||
|
|
||||||
static std::atomic_uint64_t g_audioout2_next_context {1};
|
static std::atomic_uint64_t g_audioout2_next_context {1};
|
||||||
static std::atomic_uint64_t g_audioout2_next_port {1};
|
static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||||
|
|
||||||
struct AudioOut2ContextState {
|
static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32;
|
||||||
bool used = false;
|
static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512;
|
||||||
AudioOut2ContextHandle handle = 0;
|
static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10;
|
||||||
uint32_t queue_depth = 4;
|
static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4;
|
||||||
uint32_t queued = 0;
|
|
||||||
uint32_t num_grains = 512;
|
|
||||||
uint64_t last_update = 0;
|
|
||||||
int object_audio_handle = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AudioOut2PortStateEntry {
|
struct AudioOut2PortStateEntry {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
@@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry {
|
|||||||
bool attributes_locked = false;
|
bool attributes_locked = false;
|
||||||
std::vector<uint8_t> pcm_data;
|
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 {
|
struct AudioOut2SpeakerArrayState {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
uint32_t num_speakers = 0;
|
uint32_t num_speakers = 0;
|
||||||
@@ -223,13 +232,16 @@ struct AudioOut2Submission {
|
|||||||
static Common::Mutex g_audioout2_state_mutex;
|
static Common::Mutex g_audioout2_state_mutex;
|
||||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||||
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
||||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
|
||||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||||
static Common::Mutex g_audioout2_latency_mutex;
|
static Common::Mutex g_audioout2_latency_mutex;
|
||||||
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
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_NOT_READY = -2144960504; /* 0x80268008 */
|
||||||
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
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()];
|
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) {
|
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
||||||
const auto sample_count = (grains == 0 ? 512u : grains);
|
const auto sample_count = (grains == 0 ? 512u : grains);
|
||||||
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
||||||
@@ -331,49 +348,51 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto now = LibKernel::KernelGetProcessTime();
|
const auto now = audioout2_clock();
|
||||||
if (state->last_update == 0 || state->queued == 0) {
|
if (state->last_update == 0 || state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (now <= state->last_update) {
|
||||||
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
|
||||||
if (now <= state->last_update || grain_micros == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto elapsed = now - state->last_update;
|
const auto grain = uint64_t {state->num_grains} * 125;
|
||||||
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
const auto drained = std::min<uint64_t>(state->queued, (now - state->last_update) / grain);
|
||||||
if (drained == 0) {
|
if (drained == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->queued -= static_cast<uint32_t>(drained);
|
state->queued -= static_cast<uint32_t>(drained);
|
||||||
state->last_update += drained * grain_micros;
|
state->last_update += drained * grain;
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
||||||
for (auto& state: g_audioout2_ports) {
|
const auto context_slot =
|
||||||
if (state.used && state.handle == port) {
|
(port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u);
|
||||||
return &state;
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
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) *
|
return static_cast<size_t>(state.samples_num) *
|
||||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||||
for (const auto& state: g_audioout2_ports) {
|
for (const auto& state: context.ports) {
|
||||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
if (state.used && !state.pcm_data.empty()) {
|
||||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||||
? context.object_audio_handle
|
? context.object_audio_handle
|
||||||
: state.audio_handle;
|
: state.audio_handle;
|
||||||
@@ -427,18 +446,21 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S
|
|||||||
out.object_mix.clear();
|
out.object_mix.clear();
|
||||||
std::vector<AudioInternal::OutputParam> params;
|
std::vector<AudioInternal::OutputParam> params;
|
||||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||||
for (const auto& state: g_audioout2_ports) {
|
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
||||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
// falls silent.
|
||||||
|
for (auto& state: context->ports) {
|
||||||
|
if (!state.used || state.pcm_data.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(state.port_type)) {
|
if (audioout2_port_type_is_object(state.port_type)) {
|
||||||
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
||||||
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||||
const auto i = params.size();
|
const auto i = params.size();
|
||||||
out.pcm[i] = state.pcm_data;
|
out.pcm[i].swap(state.pcm_data);
|
||||||
out.gains[i] = state.gains;
|
out.gains[i] = state.gains;
|
||||||
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
|
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) {
|
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||||
params.push_back({context->object_audio_handle, out.object_mix.data()});
|
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);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
|
|
||||||
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
||||||
params->max_ports = 256;
|
params->max_ports = 8;
|
||||||
params->max_object_ports = 256;
|
params->queue_depth = 1;
|
||||||
params->guarantee_object_ports = 0;
|
params->num_grains = 256;
|
||||||
params->queue_depth = 4;
|
|
||||||
params->num_grains = 512;
|
|
||||||
params->flags = 1;
|
|
||||||
|
|
||||||
return OK;
|
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,
|
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
||||||
size_t* memory_size) {
|
size_t* memory_size) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(memory_size == 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);
|
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||||
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
*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(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(ctx == 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 auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||||
const int object_audio_handle =
|
const int object_audio_handle =
|
||||||
@@ -524,8 +555,10 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
state->handle = *ctx;
|
state->handle = *ctx;
|
||||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||||
state->num_grains = num_grains;
|
state->num_grains = num_grains;
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
state->object_audio_handle = object_audio_handle;
|
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();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||||
@@ -541,8 +574,8 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
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;
|
size_t audio_handles_num = 0;
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
auto* state = audioout2_find_context_locked(ctx);
|
auto* state = audioout2_find_context_locked(ctx);
|
||||||
@@ -550,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
if (state->object_audio_handle > 0) {
|
if (state->object_audio_handle > 0) {
|
||||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||||
}
|
}
|
||||||
*state = AudioOut2ContextState {};
|
for (const auto& port_state: state->ports) {
|
||||||
}
|
if (port_state.used && port_state.audio_handle > 0) {
|
||||||
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()) {
|
|
||||||
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
||||||
}
|
}
|
||||||
port_state = AudioOut2PortStateEntry {};
|
|
||||||
}
|
}
|
||||||
|
*state = AudioOut2ContextState {};
|
||||||
}
|
}
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
@@ -586,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
|||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||||
audioout2_update_context_locked(state);
|
audioout2_update_context_locked(state);
|
||||||
}
|
for (auto& port: state->ports) {
|
||||||
for (auto& port: g_audioout2_ports) {
|
|
||||||
if (port.used && port.context == ctx) {
|
|
||||||
port.attributes_locked = false;
|
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);
|
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||||
if (state->queued < state->queue_depth || use_device_clock) {
|
if (state->queued < state->queue_depth || use_device_clock) {
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
}
|
}
|
||||||
if (state->queued < state->queue_depth) {
|
if (state->queued < state->queue_depth) {
|
||||||
state->queued++;
|
state->queued++;
|
||||||
@@ -663,21 +691,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(port == 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_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 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();
|
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) {
|
if (context_state == nullptr) {
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(params->port_type) &&
|
if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 ||
|
||||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
(audio_format != AudioInternal::Format::FloatMono &&
|
||||||
params->sampling_freq != 48000 ||
|
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||||
(audio_format != AudioInternal::Format::FloatMono &&
|
|
||||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
@@ -688,42 +714,45 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
AudioOut2PortStateEntry* port_state = nullptr;
|
auto slots = std::span(context_state->ports);
|
||||||
for (auto& candidate: g_audioout2_ports) {
|
slots =
|
||||||
if (!candidate.used) {
|
is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports);
|
||||||
port_state = &candidate;
|
const auto available = std::find_if(slots.begin(), slots.end(),
|
||||||
break;
|
[](const auto& candidate) { return !candidate.used; });
|
||||||
}
|
if (available == slots.end()) {
|
||||||
}
|
g_audioout2_state_mutex.Unlock();
|
||||||
if (port_state != nullptr) {
|
|
||||||
*port_state = AudioOut2PortStateEntry {};
|
|
||||||
port_state->used = true;
|
|
||||||
port_state->handle = next_port;
|
|
||||||
port_state->context = ctx;
|
|
||||||
port_state->user = params->user_handle;
|
|
||||||
port_state->port_type = params->port_type;
|
|
||||||
port_state->data_format = params->data_format;
|
|
||||||
port_state->sampling_freq = params->sampling_freq;
|
|
||||||
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;
|
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
port_state->context = ctx;
|
||||||
|
port_state->user = params->user_handle;
|
||||||
|
port_state->port_type = params->port_type;
|
||||||
|
port_state->data_format = params->data_format;
|
||||||
|
port_state->sampling_freq = params->sampling_freq;
|
||||||
|
port_state->samples_num = samples_num;
|
||||||
|
port_state->audio_format = audio_format;
|
||||||
|
port_state->gains.fill(1.0f);
|
||||||
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
int audio_handle = 0;
|
int audio_handle = 0;
|
||||||
|
|
||||||
if (audio_format != AudioInternal::Format::Unknown &&
|
if (audio_format != AudioInternal::Format::Unknown && !is_object) {
|
||||||
!audioout2_port_type_is_object(params->port_type)) {
|
|
||||||
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
||||||
audio_format);
|
audio_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
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) {
|
if (reserved) {
|
||||||
port_state->audio_handle = audio_handle;
|
port_state->audio_handle = audio_handle;
|
||||||
}
|
}
|
||||||
@@ -735,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
|
|
||||||
*port = next_port;
|
*port = next_port;
|
||||||
|
|
||||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
if (serial <= 16 || (serial % 600) == 0) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
||||||
"\t port = 0x%016" PRIx64 "\n"
|
"\t port = 0x%016" PRIx64 "\n"
|
||||||
@@ -901,21 +930,23 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
||||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||||
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
||||||
if (it == g_audioout2_users.end()) {
|
if (it == g_audioout2_users.end()) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
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) {
|
||||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
if (std::any_of(context.ports.begin(), context.ports.end(),
|
||||||
return AUDIO_OUT2_ERROR_BUSY;
|
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||||
|
return AUDIO_OUT2_ERROR_BUSY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_audioout2_users.erase(it);
|
g_audioout2_users.erase(it);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
||||||
uint32_t* context_attributes,
|
uint32_t* context_attributes,
|
||||||
uint32_t* port_attributes) {
|
uint32_t* port_attributes) {
|
||||||
if (context_attributes == nullptr || port_attributes == nullptr) {
|
if (context_attributes == nullptr || port_attributes == nullptr) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-10
@@ -38,8 +38,8 @@ struct Global {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Shadow {
|
struct Shadow {
|
||||||
uintptr_t start = 0;
|
uintptr_t start = 0;
|
||||||
int count = 0;
|
uint64_t covered_bytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AsanContext {
|
struct AsanContext {
|
||||||
@@ -50,6 +50,14 @@ struct AsanContext {
|
|||||||
static uint32_t g_fake_stack = 0;
|
static uint32_t g_fake_stack = 0;
|
||||||
static AsanContext* g_ctx = nullptr;
|
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) {
|
static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||||
EXIT_IF(g_ctx == nullptr);
|
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));
|
static_cast<uint64_t>(min_shadow), static_cast<uint64_t>(size));
|
||||||
|
|
||||||
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
||||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||||
[page](const Shadow& s) { return s.start == page; });
|
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()) {
|
if (it != g_ctx->shadows.end()) {
|
||||||
it->count++;
|
it->covered_bytes += bytes;
|
||||||
} else {
|
} else {
|
||||||
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
||||||
Common::VirtualMemory::Mode::ReadWrite)) {
|
Common::VirtualMemory::Mode::ReadWrite)) {
|
||||||
@@ -78,8 +87,8 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shadow s;
|
Shadow s;
|
||||||
s.start = page;
|
s.start = page;
|
||||||
s.count = 1;
|
s.covered_bytes = bytes;
|
||||||
g_ctx->shadows.push_back(s);
|
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(),
|
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||||
[page](const Shadow& s) { return s.start == page; });
|
[page](const Shadow& s) { return s.start == page; });
|
||||||
if (it != g_ctx->shadows.end()) {
|
if (it != g_ctx->shadows.end()) {
|
||||||
it->count--;
|
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||||
|
if (bytes >= it->covered_bytes) {
|
||||||
if (it->count <= 0) {
|
|
||||||
if (!Common::VirtualMemory::Free(page)) {
|
if (!Common::VirtualMemory::Free(page)) {
|
||||||
EXIT("can't free shadow memory\n");
|
EXIT("can't free shadow memory\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
g_ctx->shadows.erase(it);
|
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);
|
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() {
|
static void KYTY_SYSV_ABI asan_init() {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
|
|||||||
+20
-46
@@ -2,10 +2,12 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/stringUtils.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/libs.h"
|
#include "libs/libs.h"
|
||||||
#include "loader/symbolDatabase.h"
|
#include "loader/symbolDatabase.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
@@ -675,19 +677,6 @@ static bool font_is_italic(const FontState* font) {
|
|||||||
font->render_effect_slant != 0.0f;
|
font->render_effect_slant != 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* fallback_font_file_name(bool bold, bool italic) {
|
|
||||||
if (bold && italic) {
|
|
||||||
return "Roboto-BoldItalic.ttf";
|
|
||||||
}
|
|
||||||
if (bold) {
|
|
||||||
return "Roboto-Bold.ttf";
|
|
||||||
}
|
|
||||||
if (italic) {
|
|
||||||
return "Roboto-Italic.ttf";
|
|
||||||
}
|
|
||||||
return "Roboto-Regular.ttf";
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::vector<uint8_t> read_file_bytes(const std::filesystem::path& path) {
|
static std::vector<uint8_t> read_file_bytes(const std::filesystem::path& path) {
|
||||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -709,49 +698,34 @@ static std::vector<uint8_t> read_file_bytes(const std::filesystem::path& path) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::filesystem::path> fallback_font_candidates(const char* file_name) {
|
static std::vector<uint8_t> load_fallback_font_data(const char* file_name) {
|
||||||
const auto rel =
|
const char* base_path = SDL_GetBasePath();
|
||||||
std::filesystem::path("3rdparty") / "tracy" / "profiler" / "src" / "font" / file_name;
|
if (base_path == nullptr) {
|
||||||
std::vector<std::filesystem::path> paths;
|
LOGF("Font: SDL_GetBasePath failed: %s\n", SDL_GetError());
|
||||||
|
return {};
|
||||||
auto source_root = std::filesystem::path(__FILE__).parent_path().parent_path().parent_path();
|
|
||||||
paths.push_back(source_root / rel);
|
|
||||||
|
|
||||||
std::error_code ec;
|
|
||||||
auto cwd = std::filesystem::current_path(ec);
|
|
||||||
if (!ec) {
|
|
||||||
paths.push_back(cwd / rel);
|
|
||||||
paths.push_back(cwd / ".." / rel);
|
|
||||||
paths.push_back(cwd / ".." / ".." / rel);
|
|
||||||
}
|
}
|
||||||
|
// SDL returns UTF-8; path(const char*) would read it in the Windows ANSI code page.
|
||||||
return paths;
|
const auto path = Common::PathFromUtf8(base_path) / "assets" / "fonts" / file_name;
|
||||||
}
|
auto data = read_file_bytes(path);
|
||||||
|
if (data.empty()) {
|
||||||
static std::vector<uint8_t> load_fallback_font_data(bool bold, bool italic) {
|
LOGF("Font: fallback font not found: %s\n", Common::PathToString(path).c_str());
|
||||||
for (const auto& path: fallback_font_candidates(fallback_font_file_name(bold, italic))) {
|
|
||||||
auto data = read_file_bytes(path);
|
|
||||||
if (!data.empty()) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::vector<uint8_t>& fallback_font_data(bool bold, bool italic) {
|
static const std::vector<uint8_t>& fallback_font_data(bool bold, bool italic) {
|
||||||
static const auto regular = load_fallback_font_data(false, false);
|
static const auto regular = load_fallback_font_data("NotoSans-Regular.ttf");
|
||||||
static const auto bold_data = load_fallback_font_data(true, false);
|
static const auto bold_data = load_fallback_font_data("NotoSans-Bold.ttf");
|
||||||
static const auto italic_data = load_fallback_font_data(false, true);
|
static const auto italic_data = load_fallback_font_data("NotoSans-Italic.ttf");
|
||||||
static const auto bold_italic = load_fallback_font_data(true, true);
|
static const auto bold_italic = load_fallback_font_data("NotoSans-BoldItalic.ttf");
|
||||||
|
|
||||||
if (bold && italic && !bold_italic.empty()) {
|
if (bold && italic) {
|
||||||
return bold_italic;
|
return bold_italic;
|
||||||
}
|
}
|
||||||
if (bold && !bold_data.empty()) {
|
if (bold) {
|
||||||
return bold_data;
|
return bold_data;
|
||||||
}
|
}
|
||||||
if (italic && !italic_data.empty()) {
|
if (italic) {
|
||||||
return italic_data;
|
return italic_data;
|
||||||
}
|
}
|
||||||
return regular;
|
return regular;
|
||||||
|
|||||||
+3
-13
@@ -20,6 +20,7 @@
|
|||||||
#include "libs/network.h"
|
#include "libs/network.h"
|
||||||
#include "loader/elf.h"
|
#include "loader/elf.h"
|
||||||
#include "loader/runtimeLinker.h"
|
#include "loader/runtimeLinker.h"
|
||||||
|
#include "loader/systemContent.h"
|
||||||
#include "loader/symbolDatabase.h"
|
#include "loader/symbolDatabase.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -1321,23 +1322,12 @@ static void* KYTY_SYSV_ABI KernelGetProcParam() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
||||||
static std::atomic_uint32_t call_count {0};
|
return Loader::SystemContentIsTrinityMode() ? 1 : 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
||||||
PRINT_NAME();
|
*mode = Loader::SystemContentIsTrinityMode() ? 3 : 2;
|
||||||
|
|
||||||
*mode = 2; // PS5 Base
|
|
||||||
*submode = 0; // None
|
*submode = 0; // None
|
||||||
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user