Compare commits

..
Author SHA1 Message Date
nmzik 5e837a9bea shader: share formatted indirect buffer load emission 2026-10-29 05:52:04 +01:00
nmzikandFrenzied a0b99a8c21 ngs2: accept larger custom voice state buffers
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
nmzikandFrenzied 8cfe1c4d34 ngs2: apply custom channel matrices before mastering output
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
Çağatay Güçlü f71da54b9b shader: support fractional-even tessellation spacing 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 63207739bc shader: initialize local vertex count from active lanes 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 143f640706 shader: preserve counterclockwise tessellation output 2026-10-29 05:52:04 +01:00
nmzik 54b937c85f shader: support high products of 24-bit integers 2026-10-28 22:49:21 +01:00
nmzik 22610cb93e shader: support unsigned 16-bit multiply-add 2026-10-28 22:41:29 +01:00
nmzik 0386f33bf3 shader: support four 16-bit integer ternary operations 2026-10-28 22:37:24 +01:00
nmzik 138d5b86c4 shader: support S_QUADMASK_B32 2026-10-28 22:31:16 +01:00
nmzik 963aaeb3b6 gpu: preserve low-alpha 2/10/10/10 render-target storage 2026-10-28 22:29:50 +01:00
Çağatay Güçlü 77458260f1 shader: reflect masked hull control-point addresses 2026-10-28 22:19:09 +01:00
nmzik 41de2fd64e shader: support five VOP3 float conversion forms 2026-10-28 22:19:02 +01:00
Çağatay Güçlü 128c938514 shader: support quad tessellation domains and factor routing 2026-10-28 22:09:39 +01:00
Chai T a8e3c29b2e shader: implement V_ASHRREV_I64 (VOP3 0x301) 2026-10-28 21:51:28 +01:00
Devran Cosmo Uenal c528324d27 audio: let the AJM Opus decoder write part of a packet (#1914)
ajm: buffer and resample Opus output
2026-10-28 21:31:16 +01:00
Mr. Big BritchesandClaude Opus 5.5 c67352d5d6 ci: build and run the NGS2 sampler tests (#1957)
ngs2_sampler_tests is registered with ctest but no job built it or listed
it in the test filter, so the NGS2 unit tests never ran in CI. Each
platform now builds the target and includes ngs2_sampler in its ctest
filter.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:58:06 +01:00
Nawid3333 6c01401862 graphics: accept video-out buffers with 128-byte independent DCC blocks (Gran Turismo 7) (#1945)
graphics: accept video-out buffers with 128-byte independent DCC blocks
2026-10-28 04:44:09 +01:00
nmzik a833e1b735 graphics: fix vertex and instance IDs for unpatched indirect draws 2026-10-28 04:33:03 +01:00
nmzik 1be1c6c4f9 graphics: revert a358b847c command-processor display waits 2026-10-27 23:15:01 +01:00
M4andnmzik 69fdb03dc2 Launcher display options (#1938)
* launcher: add minimize and maximize buttons to the main window

The main window is a QDialog, which on Windows only gets a close button.

* graphics: keep the game's aspect ratio when presenting to the window

The guest frame was blitted across the whole swapchain image, so a window
or fullscreen display whose shape differs from the frame stretched the
picture. Fit the frame inside the window instead and clear the remaining
bars to black. The video-out overlay layer is drawn into the same
rectangle so it stays aligned with the frame underneath.

* launcher: accept custom screen resolutions such as 21:9

The resolution was a fixed enum of four 16:9 sizes. Make the combo box
editable so any WIDTHxHEIGHT between 320 and 16384 can be entered, and
add ultrawide presets (2560x1080, 3440x1440, 3840x1600, 5120x2160).

Settings saved by older launchers stored the enum key ("R1920X1080");
NormalizeResolution still accepts that form, so they load unchanged.

* launcher: add a Stretch to window option

Presentation now keeps the game's aspect ratio with black bars. Add
--stretch to fill the whole window as before, exposed in the launcher as
a "Stretch to window" checkbox under General, off by default.

* launcher: make the settings dialog resizable and its tabs scrollable

The dialog had no maximize button and pinned its minimum width to its
initial 900 px, and without scroll areas it could not be made shorter
than its content. Add a maximize button (not minimize, since the dialog
is modal), drop the width pin, and wrap each tab page in a frameless
scroll area.

With the scroll areas the layout's minimum size no longer forces the
dialog open, so size it explicitly: grow it to fit the largest tab,
capped at the available screen, when there is no saved geometry and
whenever the global settings add the game folders panel.

* launcher: simplify display options and preserve saved settings

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-27 21:59:56 +01:00
nmzik 2efd31443f audio: support 512 object ports per AudioOut2 context 2026-10-27 21:41:30 +01:00
nmzikandMajed-allababedi 9eef2e1434 graphics: reuse range-set nodes when merging additions
Co-authored-by: Majed-allababedi <60163390+Majed-allababedi@users.noreply.github.com>
2026-10-27 21:41:30 +01:00
nmzik 39faa62ebd controller: merge stick and sensor events into the newest unread pad sample 2026-10-27 21:15:03 +01:00
nmzik 7ff8aff5e6 audio: drain the AudioOut2 queue at an exact 48 kHz grain rate 2026-10-27 21:15:03 +01:00
nmzik 64a554dea7 audio: a pushed AudioOut2 grain consumes its ports' PCM 2026-10-27 21:15:03 +01:00
nmzik 4b0282e343 savedata: sort directory searches by the requested key 2026-10-27 21:15:03 +01:00
nmzik ffc81377b2 memory: refactor (#1941)
* memory: refactor

* tests: avoid Windows small macro collision

* memory: retain allocation nodes on partial release
2026-10-27 21:14:11 +01:00
bigjjmoneymanandnmzik 0685bffb2f texture cache: convert D16<->D24/D32 depth copies instead of crashing (#1873)
gpu: convert guest D16 depth between native D16 and stenciled four-byte host depth

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-27 10:41:22 +01:00
nmzik a5b3673a39 graphics: draw patched-offset indirect draws from GPU-read records 2026-10-27 10:13:15 +01:00
nmzik 09753bf8ff memory: change defaults 2026-10-27 10:13:15 +01:00
nmzik 9d8c458535 gpu: keep reused smaller image tables resident and drop them a frame after their last draw 2026-10-27 10:13:15 +01:00
358333125d libFont: ship Noto Sans as the fallback font (#1917)
* cmake: download Noto Sans at configure time

Fetch the hinted Regular, Bold, Italic and Bold Italic of Noto Sans
v2.015 and their licence from the Noto project's font distribution
(notofonts.github.io) with FetchContent, like the other third-party
dependencies. The commit and each file's SHA-256 are pinned in
variables.

Only these four styles are needed, so they are fetched individually
instead of through the release archive (117 MB, every style and build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmake: ship Noto Sans beside the emulator

Copy the downloaded fonts to assets/fonts next to kyty_emulator on every
build, like the touch cursors and the trophy sound, and install them
there with the SIL Open Font License under licenses/noto-sans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* libFont: fall back to the shipped Noto Sans instead of Tracy's Roboto

Fonts stb_truetype cannot read, including every system font set opened
with sceFontOpenFontSet, used Roboto from Tracy's source folder, found
relative to the source tree or the working directory. Installed builds
have neither, so their system fonts drew every character as '?'.

Load Noto Sans Regular, Bold, Italic or Bold Italic from assets/fonts
next to the emulator instead, and log when a file is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* libFont: fetch Noto Sans with file(DOWNLOAD) and drop the style substitution

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-27 07:51:24 +01:00
nmzik fd6b28c4b2 build: target x86-64-v3 without floating-point contraction 2026-10-27 07:04:38 +01:00
Senaxx 6cee2dc0bd shader: inline the hot IR operand accessors 2026-10-27 07:04:38 +01:00
Senaxx a526a8b9c9 shader: hand IR uses over at once on replacement and skip use lists at program teardown 2026-10-27 07:04:38 +01:00
Senaxx 1ec69f8e7d gpu: make cull mode and front face dynamic state 2026-10-27 07:04:38 +01:00
nmzik 2463e3aaf0 gpu: write end-of-pipe labels beside GPU-written bytes to the backing store 2026-10-27 07:04:38 +01:00
nmzikandstrxno 556538bed5 gpu: write D16 demotion results with one 16-bit store
Co-authored-by: strxno <rokas.pettersen@gmail.com>
2026-10-27 07:04:38 +01:00
Mr. Big BritchesandClaude Opus 5.5 770438cee0 ci: fix the Linux build and the macOS SRT walker test crashes (#1922)
* graphics: give InlinePageOwnerList's const_iterator iterator traits

The Linux build failed in textureCache.cpp: std::any_of over the image
owners of a page needs std::iterator_traits, which libstdc++ only fills
from the iterator's own member types. MSVC's STL and libc++ don't
consult them there, so Windows and macOS built.

Declare the five member types. The category is input, not forward,
because operator* returns a copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tests: assert InlinePageOwnerList's const_iterator has an input category

Only libstdc++ needs the iterator traits for std::any_of, so removing
them would compile on Windows and macOS and break only the Linux build.
The assert fails the build on every platform instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tests: serve SRT walker test loads on macOS too

FakeGuestMemory only installed its fault handler on Linux and Windows,
so on macOS the walker's deliberately faulting loads crashed
scalar_provenance, resource_materialization and resource_tracking.

Share the Linux sigaction path with macOS, read the registers from the
macOS thread state, and catch SIGBUS as well as SIGSEGV there, like the
emulator's own handler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:26:08 +01:00
nmzik db12f0c5fb shader: select consecutive GPU-selected sampler table entries without a switch 2026-10-27 04:23:01 +01:00
nmzik 3f9666acbb shader: seal SSA blocks once their predecessors are filled 2026-10-27 04:23:01 +01:00
nmzik 7eb8abb279 shader: drop the duplicate-use scan from Inst::AddUse 2026-10-27 04:23:01 +01:00
Claxten 91a5ff6cb7 shader: decode s_dcache_inv as a no-op 2026-10-27 04:23:01 +01:00
Claxten 95a507a8ff shader: support 64-bit buffer atomic minimum 2026-10-27 04:23:01 +01:00
Claxten b2053c2861 shader: bound per-lane descriptor table loops by the program's uniform loop count 2026-10-27 04:23:01 +01:00
Claxten 9801fd862b shader: zero image descriptors that only unreached blocks use 2026-10-27 04:23:01 +01:00
nmzikandshadowbeat070 78adb9293d shader: give A16 1D and late 3D gradients their own address VGPRs
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-27 04:23:01 +01:00
Claxten 0a72641678 shader: evaluate saturating float-to-signed conversions in runtime descriptor values 2026-10-27 04:23:01 +01:00
Claxten 99f4eba223 shader: read bit-scan records only at ranks their mask buffer words set 2026-10-27 04:23:01 +01:00
nmzikandshadowbeat070 04bfd9070c shader: give an A16 bias its own address VGPR
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-27 04:23:01 +01:00
nmzikandshadowbeat070 ffa3992c1a shader: decode every PS5 image_gather4 encoding
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-27 04:23:00 +01:00
Claxten 1b70938052 agc: emit async memory semaphores and execute them in the CP 2026-10-27 04:23:00 +01:00
Claxten 914a3bb4b8 shader: enumerate set ranks of eight-word light bitmaps 2026-10-27 04:23:00 +01:00
Claxten ffb6ad6cab shader: bound selected-lane record indices by guarded shared append lists 2026-10-27 04:23:00 +01:00
Claxten ff5d16eefc tests: expect an inclusive child limit to bound one more record 2026-10-27 04:23:00 +01:00
Claxten d118afbeae tests: expect scalar selector records past the V# to read zero 2026-10-27 04:23:00 +01:00
Claxten a3ac6d0d32 shader: bound table keys by an inclusive guard with a constant limit 2026-10-27 04:23:00 +01:00
Claxten f297c0bfe1 shader: bound bit-scan record keys and drop the key a guard rules out 2026-10-27 04:23:00 +01:00
Claxten 20bb268a48 shader: bound raw image table keys by a signed clamp on the key or a sibling read 2026-10-27 04:23:00 +01:00
Claxten d3768c00e9 shader: accept indirect image rows whose type reads the leading sample coordinates 2026-10-27 04:23:00 +01:00
Claxten dfcf1c0b03 shader: bound waterfall image table keys converted from clamped float arithmetic 2026-10-27 04:23:00 +01:00
Claxten de3ed905c8 shader: track image table keys divided from a runtime base plus the workgroup ID 2026-10-27 04:23:00 +01:00
Claxten 0a140911ad kernel: default flexible memory to 448 MiB and charge loaded modules' segments to it 2026-10-27 04:23:00 +01:00
nmzik 2483573651 shader: mask V_MAD_U64_U32 carry-out bits of lanes outside EXEC 2026-10-27 04:23:00 +01:00
117 changed files with 6832 additions and 2396 deletions

No files matched your search

+6 -6
View File
@@ -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
+22
View File
@@ -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)
+36 -2
View File
@@ -407,6 +407,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 +522,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"
) )
@@ -650,6 +659,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 +675,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 +692,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
@@ -697,8 +718,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 +762,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 +780,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 +832,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)
+4
View File
@@ -105,6 +105,10 @@ 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;
} }
+2
View File
@@ -64,6 +64,7 @@ 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 amd_cpu_enabled = false; bool amd_cpu_enabled = false;
@@ -125,6 +126,7 @@ int32_t GetGpuIndex();
bool FullscreenEnabled(); bool FullscreenEnabled();
bool HdrEnabled(); bool HdrEnabled();
bool HideCursorEnabled(); bool HideCursorEnabled();
bool StretchEnabled();
bool VrEnabled(); bool VrEnabled();
bool AmdCpuEnabled(); bool AmdCpuEnabled();
bool IsNetworkConnected(); bool IsNetworkConnected();
-4
View File
@@ -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());
@@ -98,6 +98,7 @@ public:
template <typename T> template <typename T>
void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op); void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op);
void MemSemaphore(uint64_t* counter, bool signal, bool write);
void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control); void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control);
void WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes); void WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes);
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy, void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
@@ -202,6 +202,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData; g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem; g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_func[Pm4::IT_RELEASE_MEM] = CpOpReleaseMem; g_cp_op_func[Pm4::IT_RELEASE_MEM] = CpOpReleaseMem;
g_cp_op_func[Pm4::IT_MEM_SEMAPHORE] = CpOpMemSemaphore;
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind; g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg; g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg; g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
@@ -136,6 +136,7 @@ uint32_t CpOpEventWrite(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpMemSemaphore(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -2060,6 +2060,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
return 7; return 7;
} }
KYTY_CP_OP_PARSER(CpOpMemSemaphore) {
KYTY_PROFILER_FUNCTION();
const auto control = buffer[2];
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0023900 || (control & ~0xe0110000u) != 0 ||
(control >> 29u) < 6u);
auto* counter = reinterpret_cast<uint64_t*>(
(buffer[0] & ~0x7u) | (static_cast<uint64_t>(buffer[1] & 0x3ffffu) << 32u));
// A signal updates the counter before the next packet, so its mailbox stall never waits.
cp.MemSemaphore(counter, (control >> 29u) == 6u, ((control >> 20u) & 0x1u) != 0);
return 3;
}
KYTY_CP_OP_PARSER(CpOpSetContextReg) { KYTY_CP_OP_PARSER(CpOpSetContextReg) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
+1
View File
@@ -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},
+53 -24
View File
@@ -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) {
@@ -281,6 +274,16 @@ template void CommandProcessor::WaitRegMem<uint32_t>(uint32_t, const uint32_t*,
template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*, uint64_t, uint64_t, template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*, uint64_t, uint64_t,
uint32_t, uint32_t); uint32_t, uint32_t);
void CommandProcessor::MemSemaphore(uint64_t* counter, bool signal, bool write) {
if (signal) {
*counter = write ? 1u : *counter + 1u;
} else if (*counter == 0) {
SuspendPm4();
} else {
(*counter)--;
}
}
void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num,
uint32_t write_control) { uint32_t write_control) {
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu); const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
@@ -433,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.
if (!std::exchange(gpu->m_wake_pending, false)) {
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100); 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;
@@ -804,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 =
@@ -835,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),
@@ -931,11 +949,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,
@@ -966,16 +983,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);
-3
View File
@@ -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 =
+13 -7
View File
@@ -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) {
@@ -8,6 +8,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iterator>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@@ -118,6 +119,13 @@ public:
class const_iterator final { class const_iterator final {
public: public:
// libstdc++'s algorithms need these. Input, not forward, because operator* returns a copy.
using iterator_category = std::input_iterator_tag;
using value_type = OwnerT;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = OwnerT;
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); } [[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
const_iterator& operator++() noexcept { const_iterator& operator++() noexcept {
++m_index; ++m_index;
+29 -29
View File
@@ -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,12 +506,10 @@ 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 auto color_size = color_slice * layers;
const std::array depth_copies { const std::array depth_copies {
vk::BufferImageCopy {0, vk::BufferImageCopy {0,
depth.info.pitch, depth.info.pitch,
@@ -520,29 +517,31 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
{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;
} }
} }
@@ -289,6 +297,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
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) {
case Prospero::BufferFormat::k10_11_11Float:
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; 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,14 +597,32 @@ 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) { 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()); std::erase(m_active, view.get());
for (const auto& [value, index]: view->elements) { for (const auto& [value, index]: view->elements) {
Unreference(m_elements[index]); Unreference(m_elements[index]);
@@ -611,8 +633,6 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
scheduler.DeferOperation([buffer = std::move(view->buffer)] {}); scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
} }
} }
return true;
});
} }
} // 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);
@@ -126,8 +129,10 @@ private:
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,
+1
View File
@@ -48,6 +48,7 @@ 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 {
@@ -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();
+36 -8
View File
@@ -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;
@@ -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);
} }
@@ -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")) {
+15 -24
View File
@@ -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 has_request = [this, &cfg, index] {
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; }; auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
const bool safe = cfg.flip_status.currentBuffer != index && return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
std::none_of(m_requests.begin(), m_requests.end(), matches) && std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches); };
m_gpu_waiting = !safe; while (has_request()) {
return safe; 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) {
+1 -1
View File
@@ -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;
+49 -14
View File
@@ -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,
&region, vk::Filter::eLinear); &region, 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;
@@ -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);
@@ -38,7 +38,8 @@ bool HasFlag(const IR::MemoryInfo& mem, uint32_t flag) {
uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address, uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t component) { uint32_t component) {
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component); const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags,
mem.image_dimension, component);
const auto packed = layout.bit_offset / 32u; const auto packed = layout.bit_offset / 32u;
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0); if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
auto value = ctx.Def(address.Arg(packed)); auto value = ctx.Def(address.Arg(packed));
@@ -56,8 +57,9 @@ uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address, uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t component) { uint32_t component) {
const auto value = AddressU32(ctx, mem, address, component); const auto value = AddressU32(ctx, mem, address, component);
return Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component).bit_width == 16u const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags,
? EmitF16BitsToF32(ctx.state, value) mem.image_dimension, component);
return layout.bit_width == 16u ? EmitF16BitsToF32(ctx.state, value)
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
} }
@@ -811,6 +813,25 @@ uint32_t EmitImageResult(ValueEmitContext& ctx, const IR::Inst& inst, const Emit
return mixed ? value : result_bits(mem.resource, value); return mixed ? value : result_bits(mem.resource, value);
} }
// Ordinals outside the table select candidates[0]. Past ordinal 0, a consecutive table is an
// offset of ordinal - 1, which wraps at 0 so that one unsigned compare checks both ends.
uint32_t SamplerArrayIndex(EmitterState& state, uint32_t ordinal,
const std::vector<uint32_t>& candidates) {
const auto count = static_cast<uint32_t>(candidates.size());
const auto gap = std::adjacent_find(candidates.begin() + 1, candidates.end(),
[](uint32_t lhs, uint32_t rhs) { return rhs != lhs + 1u; });
if (count == 1u || gap != candidates.end()) {
return EmitIndexSwitch(state, ordinal, count, TypeU32(state),
[&](uint32_t item) { return ConstantU32(state, candidates[item]); });
}
const auto offset = Binary(state, spv::OpISub, TypeU32(state), ordinal, ConstantU32(state, 1u));
const auto in_table = Binary(state, spv::OpULessThan, TypeBool(state), offset,
ConstantU32(state, count - 1u));
const auto index = Binary(state, spv::OpIAdd, TypeU32(state), offset,
ConstantU32(state, candidates[1]));
return Select(state, TypeU32(state), in_table, index, ConstantU32(state, candidates[0]));
}
struct ImageSampler { struct ImageSampler {
uint32_t id = 0; uint32_t id = 0;
bool dynamic = false; bool dynamic = false;
@@ -846,9 +867,7 @@ std::array<ImageSampler, 2> LoadImageSamplers(ValueEmitContext& ctx, const IR::I
selected = EmitIndirectResourceIndex(state, selected, selected = EmitIndirectResourceIndex(state, selected,
samplers[index].indirect_mapping_offset, samplers[index].indirect_mapping_offset,
samplers[index].indirect_search_iterations, 0u); samplers[index].indirect_search_iterations, 0u);
array_index = EmitIndexSwitch( array_index = SamplerArrayIndex(state, selected, candidates);
state, selected, static_cast<uint32_t>(candidates.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, candidates[ordinal]); });
} }
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u}; sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
}; };
@@ -291,6 +291,7 @@ inline constexpr auto EmitBufferAtomicIAdd32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicISub32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicISub32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicSMin32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicSMin32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicUMin32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicUMin32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicUMin64 = EmitBufferAtomic64;
inline constexpr auto EmitBufferAtomicSMax32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicSMax32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicUMax32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicUMax32 = EmitAtomic32;
inline constexpr auto EmitBufferAtomicAnd32 = EmitAtomic32; inline constexpr auto EmitBufferAtomicAnd32 = EmitAtomic32;
@@ -118,7 +118,7 @@ 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 gds_variable = 0; uint32_t gds_variable = 0;
uint32_t gds_length = 0; uint32_t gds_length = 0;
@@ -540,7 +540,6 @@ 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);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address); uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
@@ -1026,10 +1026,8 @@ 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 buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
const auto geometry = state.builder.AllocateId(); const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format); state.indirect_format_geometry_function, buffer.format);
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
state.indirect_format_function, buffer.format, buffer.selector, state.indirect_format_function, buffer.format, buffer.selector,
component, raw); component, raw);
return result; 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;
@@ -585,6 +585,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::ImageAtomicUMin32: case IR::ValueOpcode::ImageAtomicUMin32:
case IR::ValueOpcode::ImageAtomicUMin64: case IR::ValueOpcode::ImageAtomicUMin64:
case IR::ValueOpcode::BufferAtomicUMin32: case IR::ValueOpcode::BufferAtomicUMin32:
case IR::ValueOpcode::BufferAtomicUMin64:
case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin; case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin;
case IR::ValueOpcode::ImageAtomicSMax32: case IR::ValueOpcode::ImageAtomicSMax32:
case IR::ValueOpcode::BufferAtomicSMax32: case IR::ValueOpcode::BufferAtomicSMax32:
@@ -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);
} }
@@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) {
} }
DefineGetBdaPointer(state); DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx); DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state); DefineBvhIntersect(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();
@@ -30,11 +30,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
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);
} }
} }
@@ -14,12 +14,6 @@ struct MimgSampleInfo {
uint32_t flags = 0; uint32_t flags = 0;
}; };
struct MimgGatherInfo {
uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t flags = 0;
};
struct MimgAtomicInfo { struct MimgAtomicInfo {
uint32_t encoding = 0; uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED; Opcode decoded = Opcode::UNSUPPORTED;
@@ -181,23 +175,72 @@ constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = {
ImageSampleFlagOffset | ImageSampleFlagAdjust}, ImageSampleFlagOffset | ImageSampleFlagAdjust},
}; };
constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = { constexpr MimgSampleInfo MIMG_GATHER_OPCODE_LIST[] = {
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod}, {0x40u, "image_gather4", 0},
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero}, {0x41u, "image_gather4_cl", ImageSampleFlagLodClamp},
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare}, {0x44u, "image_gather4_l", ImageSampleFlagLod},
{0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod}, {0x45u, "image_gather4_b", ImageSampleFlagBias},
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ, {0x46u, "image_gather4_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp},
ImageSampleFlagCompare | ImageSampleFlagLevelZero}, {0x47u, "image_gather4_lz", ImageSampleFlagLevelZero},
{0x57u, Opcode::IMAGE_GATHER4_LZ_O, {0x48u, "image_gather4_c", ImageSampleFlagCompare},
ImageSampleFlagLevelZero | ImageSampleFlagOffset}, {0x49u, "image_gather4_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp},
{0x58u, Opcode::IMAGE_GATHER4_C_O, {0x4cu, "image_gather4_c_l", ImageSampleFlagCompare | ImageSampleFlagLod},
ImageSampleFlagCompare | ImageSampleFlagOffset}, {0x4du, "image_gather4_c_b", ImageSampleFlagCompare | ImageSampleFlagBias},
{0x5fu, Opcode::IMAGE_GATHER4_C_LZ_O, {0x4eu, "image_gather4_c_b_cl",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp},
{0x4fu, "image_gather4_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero},
{0x50u, "image_gather4_o", ImageSampleFlagOffset},
{0x51u, "image_gather4_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x54u, "image_gather4_l_o", ImageSampleFlagLod | ImageSampleFlagOffset},
{0x55u, "image_gather4_b_o", ImageSampleFlagBias | ImageSampleFlagOffset},
{0x56u, "image_gather4_b_cl_o",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x57u, "image_gather4_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x58u, "image_gather4_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset},
{0x59u, "image_gather4_c_cl_o",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x5cu, "image_gather4_c_l_o",
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset},
{0x5du, "image_gather4_c_b_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset},
{0x5eu, "image_gather4_c_b_cl_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset},
{0x5fu, "image_gather4_c_lz_o",
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset}, ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x61u, Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal}, {0x61u, "image_gather4h", ImageSampleFlagGatherHorizontal},
{0xc8u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare | ImageSampleFlagAdjust}, {0xc0u, "image_gather4_a", ImageSampleFlagAdjust},
{0xd8u, Opcode::IMAGE_GATHER4_C_O, {0xc1u, "image_gather4_cl_a", ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xc5u, "image_gather4_b_a", ImageSampleFlagBias | ImageSampleFlagAdjust},
{0xc6u, "image_gather4_b_cl_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xc8u, "image_gather4_c_a", ImageSampleFlagCompare | ImageSampleFlagAdjust},
{0xc9u, "image_gather4_c_cl_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xcdu, "image_gather4_c_b_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagAdjust},
{0xceu, "image_gather4_c_b_cl_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagAdjust},
{0xd0u, "image_gather4_o_a", ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xd1u, "image_gather4_cl_o_a",
ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xd5u, "image_gather4_b_o_a",
ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xd6u, "image_gather4_b_cl_o_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xd8u, "image_gather4_c_o_a",
ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust}, ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xd9u, "image_gather4_c_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xddu, "image_gather4_c_b_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xdeu, "image_gather4_c_b_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset | ImageSampleFlagAdjust},
}; };
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = { constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
@@ -219,13 +262,13 @@ constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCO
constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCODE_LIST); constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCODE_LIST);
constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST); constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST);
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather, Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgSampleInfo* gather,
const MimgAtomicInfo* atomic) { const MimgAtomicInfo* atomic) {
if (sample != nullptr) { if (sample != nullptr) {
return Opcode::IMAGE_SAMPLE; return Opcode::IMAGE_SAMPLE;
} }
if (gather != nullptr) { if (gather != nullptr) {
return gather->decoded; return Opcode::IMAGE_GATHER4;
} }
if (atomic != nullptr) { if (atomic != nullptr) {
return atomic->decoded; return atomic->decoded;
@@ -244,24 +287,10 @@ Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const Mim
} }
} }
uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInfo* gather) {
if (sample != nullptr) {
return sample->flags;
}
if (gather != nullptr) {
return gather->flags;
}
return 0;
}
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension, uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
const MimgSampleInfo* sample, const MimgGatherInfo* gather, const MimgSampleInfo* sampled, const MimgAtomicInfo* atomic) {
const MimgAtomicInfo* atomic) { if (sampled != nullptr) {
if (sample != nullptr) { return ImageSampleAddressComponents(sampled->flags, dimension);
return ImageSampleAddressComponents(sample->flags, dimension);
}
if (gather != nullptr) {
return ImageSampleAddressComponents(gather->flags, dimension);
} }
if (atomic != nullptr) { if (atomic != nullptr) {
return ImageCoordComponents(dimension); return ImageCoordComponents(dimension);
@@ -290,33 +319,31 @@ uint32_t CountDmaskComponents(uint32_t dmask) {
} // namespace } // namespace
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component) { ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
const auto width = [flags](uint32_t index) { uint32_t component) {
if ((flags & ImageSampleFlagA16) == 0u) return 32u; if ((flags & ImageSampleFlagA16) == 0u) return {component * 32u, 32u};
uint32_t cursor = 0; // A16 offsets, bias and PCF reference each take a whole VGPR, the bias as a half in its low
if ((flags & ImageSampleFlagOffset) != 0u) { // 16 bits. The other components pack two per VGPR, except that each 1D derivative and the last
if (index == cursor++) return 32u; // two 3D ones take the low half of their own VGPR.
const bool has_bias = (flags & ImageSampleFlagBias) != 0u;
const uint32_t bias_index = (flags & ImageSampleFlagOffset) != 0u ? 1u : 0u;
const uint32_t whole_vgprs = std::popcount(
flags & (ImageSampleFlagOffset | ImageSampleFlagBias | ImageSampleFlagCompare));
if (component < whole_vgprs) {
return {component * 32u, has_bias && component == bias_index ? 16u : 32u};
} }
if ((flags & ImageSampleFlagBias) != 0u) { const uint32_t axes =
if (index == cursor++) return 16u; (flags & ImageSampleFlagDerivative) != 0u ? ImageGradientComponents(dimension) : 0u;
} const uint32_t unpaired = axes % 2u * 2u;
if ((flags & ImageSampleFlagCompare) != 0u && index == cursor) return 32u; const uint32_t paired = axes * 2u - unpaired;
return 16u; const uint32_t index = component - whole_vgprs;
}; const uint32_t half = index + std::clamp(index, paired, paired + unpaired) - paired;
uint32_t offset = 0; return {whole_vgprs * 32u + half * 16u, 16u};
for (uint32_t index = 0; index < component; index++) {
const auto bits = width(index);
if (bits == 32u) offset = (offset + 31u) & ~31u;
offset += bits;
}
const auto bits = width(component);
if (bits == 32u) offset = (offset + 31u) & ~31u;
return {offset, bits};
} }
uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components) { uint32_t ImageAddressDwordCount(uint32_t flags, ImageDimension dimension, uint32_t components) {
if (components == 0u) return 0u; if (components == 0u) return 0u;
const auto last = ImageAddressComponentLayout(flags, components - 1u); const auto last = ImageAddressComponentLayout(flags, dimension, components - 1u);
return (last.bit_offset + last.bit_width + 31u) / 32u; return (last.bit_offset + last.bit_width + 31u) / 32u;
} }
@@ -339,6 +366,7 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode); const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode); const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode); const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
const auto* sampled = sample != nullptr ? sample : gather;
inst.pc = pc; inst.pc = pc;
inst.word_count = word_count; inst.word_count = word_count;
@@ -351,11 +379,11 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.data_dwords = d16 ? (inst.data_components + 1u) / 2u : inst.data_components; inst.data_dwords = d16 ? (inst.data_components + 1u) / 2u : inst.data_components;
inst.glc = ((word0 >> 13u) & 1u) != 0; inst.glc = ((word0 >> 13u) & 1u) != 0;
inst.slc = ((word0 >> 25u) & 1u) != 0; inst.slc = ((word0 >> 25u) & 1u) != 0;
inst.image_sample_flags = DecodeMimgSampleFlags(sample, gather); inst.image_sample_flags = sampled != nullptr ? sampled->flags : 0u;
if (a16) { if (a16) {
inst.image_sample_flags |= ImageSampleFlagA16; inst.image_sample_flags |= ImageSampleFlagA16;
} }
if ((sample != nullptr || gather != nullptr) && (word0 & (1u << 12u)) != 0u) { if (sampled != nullptr && (word0 & (1u << 12u)) != 0u) {
inst.image_sample_flags |= ImageSampleFlagUnnormalized; inst.image_sample_flags |= ImageSampleFlagUnnormalized;
} }
inst.image_dimension = dimension; inst.image_dimension = dimension;
@@ -364,8 +392,7 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
for (uint32_t i = 0; i < nsa_dwords * 4u; i++) { for (uint32_t i = 0; i < nsa_dwords * 4u; i++) {
inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu; inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
} }
inst.image_address_components = inst.image_address_components = DecodeMimgAddressComponents(opcode, dimension, sampled, atomic);
DecodeMimgAddressComponents(opcode, dimension, sample, gather, atomic);
SetRawWords(inst, code, word_index, word_count); SetRawWords(inst, code, word_index, word_count);
if (inst.opcode == Opcode::UNSUPPORTED) { if (inst.opcode == Opcode::UNSUPPORTED) {
@@ -389,8 +416,8 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
} }
} }
} }
const bool supports_d16 = sample != nullptr || gather != nullptr || opcode == 0x00u || const bool supports_d16 = sampled != nullptr || opcode == 0x00u || opcode == 0x01u ||
opcode == 0x01u || opcode == 0x08u || opcode == 0x09u; opcode == 0x08u || opcode == 0x09u;
if (d16 && !supports_d16) { if (d16 && !supports_d16) {
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data"); SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data");
} }
@@ -407,9 +434,10 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.src_count = 3; inst.src_count = 3;
} }
const char* MimgSampleOpcodeName(uint32_t opcode) { const char* MimgSampledOpcodeName(uint32_t opcode) {
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode); const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
return sample != nullptr ? sample->name : nullptr; const auto* sampled = sample != nullptr ? sample : Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
return sampled != nullptr ? sampled->name : nullptr;
} }
} // namespace Libs::Graphics::ShaderRecompiler::Decoder } // namespace Libs::Graphics::ShaderRecompiler::Decoder
@@ -8,15 +8,16 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst); Instruction& inst);
const char* MimgSampleOpcodeName(uint32_t opcode); const char* MimgSampledOpcodeName(uint32_t opcode);
struct ImageAddressComponent { struct ImageAddressComponent {
uint32_t bit_offset; uint32_t bit_offset;
uint32_t bit_width; uint32_t bit_width;
}; };
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component); ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components); uint32_t component);
uint32_t ImageAddressDwordCount(uint32_t flags, ImageDimension dimension, uint32_t components);
} // namespace Libs::Graphics::ShaderRecompiler::Decoder } // namespace Libs::Graphics::ShaderRecompiler::Decoder
@@ -22,7 +22,7 @@ constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
{0x04u, Opcode::S_LOAD_DWORDX16, 16, 32}, {0x08u, Opcode::S_BUFFER_LOAD_DWORD, 1, 32}, {0x04u, Opcode::S_LOAD_DWORDX16, 16, 32}, {0x08u, Opcode::S_BUFFER_LOAD_DWORD, 1, 32},
{0x09u, Opcode::S_BUFFER_LOAD_DWORDX2, 2, 32}, {0x0au, Opcode::S_BUFFER_LOAD_DWORDX4, 4, 32}, {0x09u, Opcode::S_BUFFER_LOAD_DWORDX2, 2, 32}, {0x0au, Opcode::S_BUFFER_LOAD_DWORDX4, 4, 32},
{0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32}, {0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32},
{0x25u, Opcode::S_MEMREALTIME, 2, 32}, {0x20u, Opcode::S_DCACHE_INV, 0, 0}, {0x25u, Opcode::S_MEMREALTIME, 2, 32},
}; };
constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = { constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
@@ -69,6 +69,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32}, {0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32},
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32}, {0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
{0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32}, {0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32},
{0x56u, Opcode::BUFFER_ATOMIC_UMIN_X2, 2, 32},
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32}, {0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32}, {0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0}, {0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
@@ -285,6 +286,9 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
if (inst.opcode == Opcode::UNSUPPORTED) { if (inst.opcode == Opcode::UNSUPPORTED) {
SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented"); SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
} }
if (inst.opcode == Opcode::S_DCACHE_INV) {
return;
}
DecodeScalarDestination(sdst, pc, inst.dst); DecodeScalarDestination(sdst, pc, inst.dst);
if (inst.opcode == Opcode::S_MEMREALTIME) { if (inst.opcode == Opcode::S_MEMREALTIME) {
@@ -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},
@@ -123,10 +123,9 @@ std::string ImageSampleFlagsToString(uint32_t flags) {
} }
std::string FormatMimg(const Instruction& inst) { std::string FormatMimg(const Instruction& inst) {
const char* sample_name = const char* sampled_name = MimgSampledOpcodeName(inst.opcode_id);
inst.opcode == Opcode::IMAGE_SAMPLE ? MimgSampleOpcodeName(inst.opcode_id) : nullptr;
const std::string_view name = const std::string_view name =
sample_name != nullptr ? sample_name : magic_enum::enum_name(inst.opcode); sampled_name != nullptr ? sampled_name : magic_enum::enum_name(inst.opcode);
std::string text = std::string text =
fmt::format("0x{:08x}: {} {}, {}, {}, {} ; dmask=0x{:x} image_dim={}", inst.pc, name, fmt::format("0x{:08x}: {} {}, {}, {}, {} ; dmask=0x{:x} image_dim={}", inst.pc, name,
OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str(), OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str(),
@@ -138,22 +137,10 @@ std::string FormatMimg(const Instruction& inst) {
if (inst.image_r128) { if (inst.image_r128) {
text += " r128=1"; text += " r128=1";
} }
switch (inst.opcode) { if (sampled_name != nullptr) {
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_L:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_O:
case Opcode::IMAGE_GATHER4_C_LZ_O:
case Opcode::IMAGE_GATHER4H:
text += fmt::format(" sample_flags={} addr_components={}", text += fmt::format(" sample_flags={} addr_components={}",
ImageSampleFlagsToString(inst.image_sample_flags).c_str(), ImageSampleFlagsToString(inst.image_sample_flags).c_str(),
inst.image_address_components); inst.image_address_components);
break;
default: break;
} }
if (inst.image_nsa_dwords != 0) { if (inst.image_nsa_dwords != 0) {
text += fmt::format(" nsa_dwords={} nsa_addr=", inst.image_nsa_dwords); text += fmt::format(" nsa_dwords={} nsa_addr=", inst.image_nsa_dwords);
@@ -530,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:
@@ -570,6 +558,7 @@ std::string InstructionToString(const Instruction& inst) {
OperandToString(inst.src0).c_str())); OperandToString(inst.src0).c_str()));
case Opcode::S_BARRIER: case Opcode::S_BARRIER:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc)); return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
case Opcode::S_DCACHE_INV:
case Opcode::BUFFER_GL0_INV: case Opcode::BUFFER_GL0_INV:
case Opcode::BUFFER_GL1_INV: case Opcode::BUFFER_GL1_INV:
return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode)); return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode));
@@ -616,15 +605,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::IMAGE_LOAD_MIP: case Opcode::IMAGE_LOAD_MIP:
case Opcode::IMAGE_GET_RESINFO: case Opcode::IMAGE_GET_RESINFO:
case Opcode::IMAGE_GET_LOD: case Opcode::IMAGE_GET_LOD:
case Opcode::IMAGE_GATHER4_L: case Opcode::IMAGE_GATHER4: return WithUnsupportedReason(inst, FormatMimg(inst));
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_L:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_O:
case Opcode::IMAGE_GATHER4_C_LZ_O:
case Opcode::IMAGE_GATHER4H: return WithUnsupportedReason(inst, FormatMimg(inst));
case Opcode::S_LOAD_DWORD: case Opcode::S_LOAD_DWORD:
case Opcode::S_LOAD_DWORDX2: case Opcode::S_LOAD_DWORDX2:
case Opcode::S_LOAD_DWORDX4: case Opcode::S_LOAD_DWORDX4:
@@ -685,6 +666,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_ATOMIC_SUB: case Opcode::BUFFER_ATOMIC_SUB:
case Opcode::BUFFER_ATOMIC_SMIN: case Opcode::BUFFER_ATOMIC_SMIN:
case Opcode::BUFFER_ATOMIC_UMIN: case Opcode::BUFFER_ATOMIC_UMIN:
case Opcode::BUFFER_ATOMIC_UMIN_X2:
case Opcode::BUFFER_ATOMIC_SMAX: case Opcode::BUFFER_ATOMIC_SMAX:
case Opcode::BUFFER_ATOMIC_UMAX: case Opcode::BUFFER_ATOMIC_UMAX:
case Opcode::BUFFER_ATOMIC_AND: case Opcode::BUFFER_ATOMIC_AND:
@@ -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,
@@ -544,6 +553,7 @@ enum class Opcode {
S_BUFFER_LOAD_DWORDX8, S_BUFFER_LOAD_DWORDX8,
S_BUFFER_LOAD_DWORDX16, S_BUFFER_LOAD_DWORDX16,
S_MEMREALTIME, S_MEMREALTIME,
S_DCACHE_INV,
BUFFER_LOAD_FORMAT_X, BUFFER_LOAD_FORMAT_X,
BUFFER_LOAD_FORMAT_XY, BUFFER_LOAD_FORMAT_XY,
BUFFER_LOAD_FORMAT_XYZ, BUFFER_LOAD_FORMAT_XYZ,
@@ -596,6 +606,7 @@ enum class Opcode {
BUFFER_ATOMIC_SUB, BUFFER_ATOMIC_SUB,
BUFFER_ATOMIC_SMIN, BUFFER_ATOMIC_SMIN,
BUFFER_ATOMIC_UMIN, BUFFER_ATOMIC_UMIN,
BUFFER_ATOMIC_UMIN_X2,
BUFFER_ATOMIC_SMAX, BUFFER_ATOMIC_SMAX,
BUFFER_ATOMIC_UMAX, BUFFER_ATOMIC_UMAX,
BUFFER_ATOMIC_AND, BUFFER_ATOMIC_AND,
@@ -708,15 +719,7 @@ enum class Opcode {
IMAGE_ATOMIC_FMIN, IMAGE_ATOMIC_FMIN,
IMAGE_ATOMIC_FMAX, IMAGE_ATOMIC_FMAX,
IMAGE_SAMPLE, IMAGE_SAMPLE,
IMAGE_GATHER4_L, IMAGE_GATHER4,
IMAGE_GATHER4_LZ,
IMAGE_GATHER4_C,
IMAGE_GATHER4_C_L,
IMAGE_GATHER4_C_LZ,
IMAGE_GATHER4_LZ_O,
IMAGE_GATHER4_C_O,
IMAGE_GATHER4_C_LZ_O,
IMAGE_GATHER4H,
V_INTERP_P1_F32, V_INTERP_P1_F32,
V_INTERP_P2_F32, V_INTERP_P2_F32,
V_INTERP_MOV_F32, V_INTERP_MOV_F32,
@@ -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));
} }
@@ -291,7 +299,7 @@ void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
inst.dst2.kind != Decoder::OperandKind::Unknown) { inst.dst2.kind != Decoder::OperandKind::Unknown) {
// The 32x32 product fits in 64 bits, so only the addition can carry. // The 32x32 product fits in 64 bits, so only the addition can carry.
const auto carry = IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product})); const auto carry = IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product}));
WriteMask(inst.dst2, carry, VectorMask(carry)); WriteVectorMask(inst.dst2, carry);
} }
} }
@@ -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)));
}; };
IR::U32 quads;
if (is64) {
const auto source = ReadU32Pair(inst.src0); const auto source = ReadU32Pair(inst.src0);
const auto quads = ir.BitwiseOr( quads = ir.BitwiseOr(
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u)))); compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u))); WriteOperand(DestinationOperand(inst), ir.ConstructU64(quads, IR::U32(IR::Value(0u))));
WriteOperand(DestinationOperand(inst), result); } else {
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}))); 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) {
@@ -322,8 +322,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
const Decoder::Operand& base) { const Decoder::Operand& base) {
std::array<IR::Value, 13> components {}; std::array<IR::Value, 13> components {};
components.fill(IR::Value(0u)); components.fill(IR::Value(0u));
const auto count = const auto count = Decoder::ImageAddressDwordCount(
Decoder::ImageAddressDwordCount(inst.image_sample_flags, 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); std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
@@ -941,6 +941,8 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
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.
case Decoder::Opcode::S_DCACHE_INV: return;
case Decoder::Opcode::BUFFER_GL0_INV: case Decoder::Opcode::BUFFER_GL0_INV:
case Decoder::Opcode::BUFFER_GL1_INV: case Decoder::Opcode::BUFFER_GL1_INV:
ir.Emit(IR::ValueOpcode::DataCacheInvalidate); ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
@@ -1029,6 +1031,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMin32); return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMin32);
case Decoder::Opcode::BUFFER_ATOMIC_UMIN: case Decoder::Opcode::BUFFER_ATOMIC_UMIN:
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin32); return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin32);
case Decoder::Opcode::BUFFER_ATOMIC_UMIN_X2:
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin64);
case Decoder::Opcode::BUFFER_ATOMIC_SMAX: case Decoder::Opcode::BUFFER_ATOMIC_SMAX:
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMax32); return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMax32);
case Decoder::Opcode::BUFFER_ATOMIC_UMAX: case Decoder::Opcode::BUFFER_ATOMIC_UMAX:
@@ -1166,15 +1170,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::IMAGE_STORE: case Decoder::Opcode::IMAGE_STORE:
case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst); case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst);
case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst); case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst);
case Decoder::Opcode::IMAGE_GATHER4_L: case Decoder::Opcode::IMAGE_GATHER4: return IMAGE_GATHER(inst);
case Decoder::Opcode::IMAGE_GATHER4_LZ:
case Decoder::Opcode::IMAGE_GATHER4_C:
case Decoder::Opcode::IMAGE_GATHER4_C_L:
case Decoder::Opcode::IMAGE_GATHER4_C_LZ:
case Decoder::Opcode::IMAGE_GATHER4_LZ_O:
case Decoder::Opcode::IMAGE_GATHER4_C_O:
case Decoder::Opcode::IMAGE_GATHER4_C_LZ_O:
case Decoder::Opcode::IMAGE_GATHER4H: return IMAGE_GATHER(inst);
case Decoder::Opcode::DS_MIN_F32: case Decoder::Opcode::DS_MIN_F32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicFMin32, false); return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicFMin32, false);
@@ -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 &&
@@ -167,7 +167,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 +187,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 +196,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();
} }
} }
} }
@@ -169,6 +168,8 @@ bool EquivalentSelectorDomain(const ResourcePlan& program,
const DescriptorSource::IndirectDescriptor& right) { const DescriptorSource::IndirectDescriptor& right) {
return left.selector == right.selector && left.signed_count == right.signed_count && return left.selector == right.selector && left.signed_count == right.signed_count &&
left.workgroup_axis == right.workgroup_axis && left.workgroup_axis == right.workgroup_axis &&
left.workgroup_multiplier == right.workgroup_multiplier &&
left.workgroup_shift == right.workgroup_shift &&
EquivalentValue(program, left.key_count, right.key_count) && EquivalentValue(program, left.key_count, right.key_count) &&
EquivalentValue(program, left.selector_first, right.selector_first) && EquivalentValue(program, left.selector_first, right.selector_first) &&
EquivalentValue(program, left.selector_mask, right.selector_mask); EquivalentValue(program, left.selector_mask, right.selector_mask);
@@ -157,12 +157,15 @@ 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)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b) KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j) 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,
l)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d) KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p,
q, r)
KYTY_IO_FIELDS(DescriptorSource, a, b, c) KYTY_IO_FIELDS(DescriptorSource, a, b, c)
KYTY_IO_FIELDS(SrtRead, a, b) KYTY_IO_FIELDS(SrtRead, a, b)
KYTY_IO_FIELDS(ResourceBlock, a, b, c, d) KYTY_IO_FIELDS(ResourceBlock, a, b, c, d, e)
KYTY_IO_FIELDS(UniformFill, a, b, c, d, e, f) KYTY_IO_FIELDS(UniformFill, a, b, c, d, e, f)
KYTY_IO_FIELDS(UniformFillPlan, a, b, c, d) KYTY_IO_FIELDS(UniformFillPlan, a, b, c, d)
KYTY_IO_FIELDS(ResourceSpecialization::Buffer, a, b, c, d, e, f, g, h) KYTY_IO_FIELDS(ResourceSpecialization::Buffer, a, b, c, d, e, f, g, h)
+18 -2
View File
@@ -251,6 +251,8 @@ enum class StageInputKind {
LocalInvocationId, LocalInvocationId,
LocalInvocationIndex, LocalInvocationIndex,
GlobalInvocationId, GlobalInvocationId,
BaseVertex,
BaseInstance,
Parameter, Parameter,
}; };
@@ -572,6 +574,13 @@ struct DescriptorSource {
bool operator==(const CountedRows& other) const = default; bool operator==(const CountedRows& other) const = default;
}; };
struct MaskRanks {
uint32_t source = UINT32_MAX;
uint32_t stride = 0; // Byte spacing of the mask words its load can address.
uint32_t offset = 0;
bool operator==(const MaskRanks& other) const = default;
};
uint32_t source = UINT32_MAX; uint32_t source = UINT32_MAX;
uint32_t stride = 0; uint32_t stride = 0;
uint32_t offset = 0; uint32_t offset = 0;
@@ -579,10 +588,12 @@ struct DescriptorSource {
uint32_t bit_offset = 0; uint32_t bit_offset = 0;
uint32_t bit_mask = UINT32_MAX; uint32_t bit_mask = UINT32_MAX;
bool global = false; bool global = false;
// Four DWORD bitmap words per record; set-bit ranks subtract from the last key. // Four or eight DWORD bitmap words per record; set ranks subtract from the last key.
bool bitset = false; bool bitset = false;
std::optional<CountedRows> rows; std::optional<CountedRows> rows;
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges; std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
std::optional<uint32_t> excluded; // A guard keeps this key from the read.
std::optional<MaskRanks> ranks; // Records are the bits some mask word sets.
bool operator==(const SelectorRead& other) const = default; bool operator==(const SelectorRead& other) const = default;
}; };
@@ -606,8 +617,12 @@ struct DescriptorSource {
bool indexed = false; bool indexed = false;
bool signed_count = false; bool signed_count = false;
uint32_t workgroup_axis = UINT32_MAX; uint32_t workgroup_axis = UINT32_MAX;
// Nonzero shift: workgroup keys are (selector_first + ID) * multiplier >> shift.
uint32_t workgroup_multiplier = 0;
uint32_t workgroup_shift = 0;
Value key_count; Value key_count;
// First selector index, or first descriptor key for a direct or bitmap range. // First selector index, workgroup key base, or first descriptor key for a direct or bitmap
// range.
Value selector_first; Value selector_first;
Value selector_mask; Value selector_mask;
std::vector<uint32_t> sources; std::vector<uint32_t> sources;
@@ -634,6 +649,7 @@ struct ResourceBlock {
Value condition; Value condition;
std::vector<uint32_t> successors; std::vector<uint32_t> successors;
std::vector<uint32_t> sources; std::vector<uint32_t> sources;
std::vector<uint32_t> images;
std::vector<uint32_t> srt_reads; std::vector<uint32_t> srt_reads;
}; };
+15 -74
View File
@@ -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;
@@ -256,9 +196,6 @@ void Inst::Invalidate() {
} }
void Inst::AddUse(Inst* used, size_t operand) { void Inst::AddUse(Inst* used, size_t operand) {
const auto found = std::ranges::find_if(
used->uses, [&](const Use& use) { return use.user == this && use.operand == operand; });
EXIT_IF(found != used->uses.end());
used->uses.push_back({this, operand}); used->uses.push_back({this, operand});
} }
@@ -275,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);
+50 -6
View File
@@ -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
@@ -123,6 +123,7 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
case ValueOpcode::BufferAtomicISub32: case ValueOpcode::BufferAtomicISub32:
case ValueOpcode::BufferAtomicSMin32: case ValueOpcode::BufferAtomicSMin32:
case ValueOpcode::BufferAtomicUMin32: case ValueOpcode::BufferAtomicUMin32:
case ValueOpcode::BufferAtomicUMin64:
case ValueOpcode::BufferAtomicSMax32: case ValueOpcode::BufferAtomicSMax32:
case ValueOpcode::BufferAtomicUMax32: case ValueOpcode::BufferAtomicUMax32:
case ValueOpcode::BufferAtomicAnd32: case ValueOpcode::BufferAtomicAnd32:
@@ -140,6 +141,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
switch (opcode) { switch (opcode) {
case ValueOpcode::BufferAtomicSwap64: case ValueOpcode::BufferAtomicSwap64:
case ValueOpcode::BufferAtomicIAdd64: case ValueOpcode::BufferAtomicIAdd64:
case ValueOpcode::BufferAtomicUMin64:
case ValueOpcode::BufferAtomicAnd64: case ValueOpcode::BufferAtomicAnd64:
case ValueOpcode::BufferAtomicOr64: case ValueOpcode::BufferAtomicOr64:
case ValueOpcode::LoadBufferU32x2: case ValueOpcode::LoadBufferU32x2:
@@ -269,6 +269,7 @@ VALUE_OPCODE(BufferAtomicIAdd64, U64, BufferResource, U32, U32, U32, U64, U1)
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicUMin64, U64, BufferResource, U32, U32, U32, U64, U1)
VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1) VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
@@ -331,6 +331,18 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
keys.resize(count); keys.resize(count);
return true; return true;
}; };
// Reads the key of each record whose rank is set.
const auto read_ranks = [&](uint64_t base, uint64_t size, uint64_t first, uint64_t step,
uint32_t ranks) {
keys.reserve(std::popcount(ranks));
for (; ranks != 0u; ranks &= ranks - 1u) {
const auto offset = first + std::countr_zero(ranks) * step;
uint32_t key = 0;
if (offset > UINT32_MAX || !read_selector(base, size, offset, {&key, 1})) return false;
keys.push_back(key);
}
return true;
};
if (selector->rows) { if (selector->rows) {
const auto& rows = *selector->rows; const auto& rows = *selector->rows;
DescriptorValue count_value; DescriptorValue count_value;
@@ -377,19 +389,21 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
} }
} else if (selector->bitset) { } else if (selector->bitset) {
ShaderBufferResource material; ShaderBufferResource material;
std::array<uint32_t, 4> masks {}; std::array<uint32_t, 8> storage {};
const auto record_words = std::min<size_t>(selector->stride / 4u, storage.size());
const auto masks = std::span(storage).first(record_words);
uint32_t first = 0, count = 0; uint32_t first = 0, count = 0;
if (!DecodeBufferDescriptor(material_value, material) || if (!DecodeBufferDescriptor(material_value, material) ||
!WalkedValue(program, indirect.selector_first, first) || !WalkedValue(program, indirect.selector_first, first) ||
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u || !EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
selector->stride != sizeof(masks) || selector->offset != 0u || selector->stride != masks.size_bytes() || selector->offset != 0u ||
material.Type() != 0u || material.Stride() != sizeof(masks) || material.Type() != 0u || material.Stride() != selector->stride ||
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u || material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
(material.Base48() & 3u) != 0u) return false; (material.Base48() & 3u) != 0u) return false;
if (material.Format() != Prospero::BufferFormat::kInvalid) { if (material.Format() != Prospero::BufferFormat::kInvalid) {
const auto words = material.GetSize() / sizeof(uint32_t); const auto words = material.GetSize() / sizeof(uint32_t);
// Bitmap storage can grow with resolution while its candidate domain stays // Bitmap storage can grow with resolution while its candidate domain stays at 32 keys
// at 128 keys. Bound scratch memory, preserving each coherent read's capture. // per record word. Bound scratch memory, preserving each coherent read's capture.
for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) { for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) {
keys.resize(std::min<uint64_t>(words - first_word, MaxIndirectDescriptorProbes)); keys.resize(std::min<uint64_t>(words - first_word, MaxIndirectDescriptorProbes));
if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u, if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u,
@@ -445,12 +459,35 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
uint64_t {first} + count > material.NumRecords())) return false; uint64_t {first} + count > material.NumRecords())) return false;
const auto last = count != 0u ? uint64_t {first} + count - 1u : 0u; const auto last = count != 0u ? uint64_t {first} + count - 1u : 0u;
if (last > UINT32_MAX) return false; if (last > UINT32_MAX) return false;
// Out-of-bounds scalar reads return zero.
const auto end = count != 0u ? last * step + selector->offset + 4u : 0u; const auto end = count != 0u ? last * step + selector->offset + 4u : 0u;
if (end > uint64_t {UINT32_MAX} + 1u || if (end > uint64_t {UINT32_MAX} + 1u) return false;
(indirect.workgroup_axis == UINT32_MAX && end > size)) const auto start = uint64_t {first} * step + selector->offset;
if (selector->ranks) {
// A scan selects the ranks some word of its mask's address class sets.
const auto& mask = *selector->ranks;
DescriptorValue mask_value;
ShaderBufferResource masks;
if (count > 32u || !WalkedDescriptor(program, mask.source, mask_value) ||
!DecodeBufferDescriptor(mask_value, masks)) return false;
// Each chunk holds whole strides of the power-of-two class, or one of its words.
const auto words = mask.stride / 4u;
const auto chunk =
words <= MaxIndirectDescriptorProbes ? MaxIndirectDescriptorProbes : 1u;
uint32_t ranks = 0;
for (auto at = uint64_t {mask.offset}; at + 4u <= masks.GetSize();
at += uint64_t {std::max(chunk, words)} * 4u) {
keys.resize(std::min<uint64_t>((masks.GetSize() - at) / 4u, chunk));
if (!ReadScalarTable(masks.Base48(), masks.GetSize(), at, runtime, keys))
return false; return false;
if (!read_keys(base, size, uint64_t {first} * step + selector->offset, step, count)) for (size_t word = 0; word < keys.size(); word += words) ranks |= keys[word];
}
keys.clear();
if (count < 32u) ranks &= (1u << count) - 1u;
if (!read_ranks(base, size, start, step, ranks)) return false;
} else if (!read_keys(base, size, start, step, count)) {
return false; return false;
}
} else if (!indirect.selector_mask.IsEmpty()) { } else if (!indirect.selector_mask.IsEmpty()) {
uint32_t mask = 0; uint32_t mask = 0;
uint32_t count = 0; uint32_t count = 0;
@@ -462,18 +499,8 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
if (count < 32u) mask &= (1u << count) - 1u; if (count < 32u) mask &= (1u << count) - 1u;
const auto material_base = const auto material_base =
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0]; (static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
keys.reserve(std::popcount(mask)); if (!read_ranks(material_base, UINT64_MAX, selector->offset, selector->stride, mask))
while (mask != 0u) { return false;
const auto index = std::countr_zero(mask);
const auto offset = static_cast<uint64_t>(selector->offset) +
static_cast<uint64_t>(index) * selector->stride;
if (offset > UINT32_MAX) return false;
uint32_t key = 0;
if (!read_selector(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
{&key, 1})) return false;
keys.push_back(key);
mask &= mask - 1u;
}
} else if (selector->stride == 0u) { } else if (selector->stride == 0u) {
ShaderBufferResource material; ShaderBufferResource material;
if (!DecodeBufferDescriptor(material_value, material) || if (!DecodeBufferDescriptor(material_value, material) ||
@@ -503,6 +530,7 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
if (selector->bit_offset != 0u || selector->bit_mask != UINT32_MAX || selector->maximum != UINT32_MAX) if (selector->bit_offset != 0u || selector->bit_mask != UINT32_MAX || selector->maximum != UINT32_MAX)
for (auto& key: keys) for (auto& key: keys)
key = std::min((key >> selector->bit_offset) & selector->bit_mask, selector->maximum); key = std::min((key >> selector->bit_offset) & selector->bit_mask, selector->maximum);
if (selector->excluded) std::erase(keys, *selector->excluded);
std::ranges::sort(keys); std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end()); keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
} }
@@ -756,6 +784,12 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
std::iota(keys.begin(), keys.end(), first_key); std::iota(keys.begin(), keys.end(), first_key);
if (!keys.empty() && keys.back() < keys.front()) if (!keys.empty() && keys.back() < keys.front())
std::rotate(keys.begin(), keys.begin() + (UINT32_MAX - first_key + 1ull), keys.end()); std::rotate(keys.begin(), keys.begin() + (UINT32_MAX - first_key + 1ull), keys.end());
if (indirect.workgroup_shift != 0u) {
for (auto& key: keys)
key = static_cast<uint32_t>(uint64_t {key} * indirect.workgroup_multiplier >>
indirect.workgroup_shift);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
}
} else { } else {
if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false; if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false;
if (!selector_keys->resolved) { if (!selector_keys->resolved) {
@@ -1041,6 +1075,26 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
return dimension == Decoder::ImageDimension::Dim1D || return dimension == Decoder::ImageDimension::Dim1D ||
dimension == Decoder::ImageDimension::Dim2D; dimension == Decoder::ImageDimension::Dim2D;
}; };
// The descriptor's type selects the address layout. A 2D or 3D candidate reads the leading
// coordinates of an equal or larger dimension, but an array with more than one slice reads
// the next as its slice. Derivative, LOD, clamp and mip operands shift with the dimension.
const auto leading = [&](Decoder::ImageDimension dimension, const DescriptorValue& value) {
const bool sliced =
static_cast<Prospero::ImageType>((value.dwords[3] >> 28u) & 0xfu) ==
Prospero::ImageType::kColor2DArray &&
(value.dwords[4] & 0x1fffu) != ((value.dwords[4] >> 16u) & 0x1fffu);
return !sliced &&
(dimension == Decoder::ImageDimension::Dim2D || dimension == base.dimension);
};
const bool leading_coordinates = (base.dimension == Decoder::ImageDimension::Dim2D ||
base.dimension == Decoder::ImageDimension::Dim3D) &&
leading(image_class.dimension, snapshot.images[exemplar]) &&
std::ranges::none_of(program.memory_info, [&](const MemoryInfo& memory) {
return memory.kind == ResourceKind::Image && memory.resource == root_index &&
(memory.image_has_mip || (memory.image_sample_flags &
(Decoder::ImageSampleFlagLod | Decoder::ImageSampleFlagDerivative |
Decoder::ImageSampleFlagLodClamp)) != 0u);
});
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) { for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
auto& image = specialization.images[candidate]; auto& image = specialization.images[candidate];
if (image.indirect_root != root_index) { if (image.indirect_root != root_index) {
@@ -1060,8 +1114,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
base.dimension == Decoder::ImageDimension::Dim2D && base.dimension == Decoder::ImageDimension::Dim2D &&
!image.cube && !image_class.cube && is_1d_or_2d(image.dimension) && !image.cube && !image_class.cube && is_1d_or_2d(image.dimension) &&
is_1d_or_2d(image_class.dimension); is_1d_or_2d(image_class.dimension);
const bool prefix_coordinates =
leading_coordinates && leading(image.dimension, snapshot.images[candidate]);
if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension)) && if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension)) &&
!unfiltered_coordinates) || !unfiltered_coordinates && !prefix_coordinates) ||
image.conversion_format != image_class.conversion_format || image.conversion_format != image_class.conversion_format ||
(!storage_swizzle && image.shader_swizzle != image_class.shader_swizzle)) { (!storage_swizzle && image.shader_swizzle != image_class.shader_swizzle)) {
return SpecializationFail( return SpecializationFail(
@@ -1164,14 +1220,16 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
block.sources.push_back(program.info.buffers.at(memory.resource).source); block.sources.push_back(program.info.buffers.at(memory.resource).source);
} else { } else {
block.sources.push_back(program.info.images.at(memory.resource).source); block.sources.push_back(program.info.images.at(memory.resource).source);
block.images.push_back(memory.resource);
if (image.needs_sampler) { if (image.needs_sampler) {
block.sources.push_back(program.info.samplers.at(memory.sampler).source); block.sources.push_back(program.info.samplers.at(memory.sampler).source);
} }
} }
} }
std::ranges::sort(block.sources); for (auto* list: {&block.sources, &block.images}) {
block.sources.erase(std::unique(block.sources.begin(), block.sources.end()), std::ranges::sort(*list);
block.sources.end()); list->erase(std::unique(list->begin(), list->end()), list->end());
}
} }
for (auto& block: blocks) block.condition = predicate(block.condition); for (auto& block: blocks) block.condition = predicate(block.condition);
if (std::ranges::none_of( if (std::ranges::none_of(
@@ -1645,7 +1703,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
} }
if (source->indirect_descriptor.has_value()) { if (source->indirect_descriptor.has_value()) {
snapshot.images[i] = {.dword_count = 8u}; snapshot.images[i] = {.dword_count = 8u};
if (!WalkedActive(program, image.source)) { if (!WalkedActive(program, image.source) || !WalkedImageActive(program, i)) {
continue; continue;
} }
const auto& indirect = *source->indirect_descriptor; const auto& indirect = *source->indirect_descriptor;
@@ -1665,7 +1723,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
if (!evaluate(image.source, snapshot.images[i])) { if (!evaluate(image.source, snapshot.images[i])) {
return false; return false;
} }
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) { // An image listed only by unreached blocks reads zero, not its source's descriptor.
if (!WalkedImageActive(program, i) ||
!ValidImageDescriptor(snapshot.images[i], image.r128)) {
snapshot.images[i].dwords.fill(0); snapshot.images[i].dwords.fill(0);
} }
} }
@@ -1711,8 +1771,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
} }
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) { uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, and // Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, entries of
// entries that need shader-side conversion, read as null instead of failing the draw. // another dimension and entries that need shader-side conversion read as null instead of
// failing the draw.
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu); const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu); const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
const auto numeric_class = const auto numeric_class =
@@ -7,6 +7,8 @@
#include <algorithm> #include <algorithm>
#include <bit> #include <bit>
#include <cmath>
#include <cstdio>
#include <fmt/format.h> #include <fmt/format.h>
#include <map> #include <map>
#include <numeric> #include <numeric>
@@ -1014,7 +1016,9 @@ private:
(!shared_image_heap && (a.table_offset != b.table_offset || (!shared_image_heap && (a.table_offset != b.table_offset ||
a.table_immediate != b.table_immediate || a.table_immediate != b.table_immediate ||
a.table_stride != b.table_stride)) || a.indexed != b.indexed || a.table_stride != b.table_stride)) || a.indexed != b.indexed ||
a.workgroup_axis != b.workgroup_axis || a.sources != b.sources || a.workgroup_axis != b.workgroup_axis ||
a.workgroup_multiplier != b.workgroup_multiplier ||
a.workgroup_shift != b.workgroup_shift || a.sources != b.sources ||
a.signed_count != b.signed_count || a.signed_count != b.signed_count ||
!EquivalentValue(m_program, a.key_count, b.key_count) || !EquivalentValue(m_program, a.key_count, b.key_count) ||
a.selector_first.IsEmpty() != b.selector_first.IsEmpty() || a.selector_first.IsEmpty() != b.selector_first.IsEmpty() ||
@@ -1136,31 +1140,63 @@ private:
LaneQuantifier lanes = LaneQuantifier::All; LaneQuantifier lanes = LaneQuantifier::All;
}; };
bool NonzeroOnEntry(Value value, const Block* block) const { // The immediate that edge proves value differs from on every lane, if any.
if (block == nullptr) return false; std::optional<uint32_t> ExcludedBy(const EdgePredicate& edge, Value value) const {
bool witnessed = false; if (edge.lanes != LaneQuantifier::All) return {};
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
return current->ImmPredecessors().size() != 1u;
}, [&](const EdgePredicate& edge) {
if (edge.lanes != LaneQuantifier::All) return false;
const auto* test = edge.condition.TryInstruction(); const auto* test = edge.condition.TryInstruction();
if (test != nullptr && test->NumArgs() == 2u && if (test != nullptr && test->NumArgs() == 2u &&
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) || ((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
(test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) { (test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) {
for (uint32_t arg = 0; arg < 2u; ++arg) { for (uint32_t arg = 0; arg < 2u; ++arg) {
uint32_t immediate; uint32_t immediate;
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u && if (ImmediateU32(test->Arg(arg), immediate) &&
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return witnessed = true; EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return immediate;
} }
} }
return false; return {};
}
bool NonzeroOnEntry(Value value, const Block* block) const {
if (block == nullptr) return false;
bool witnessed = false;
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
return current->ImmPredecessors().size() != 1u;
}, [&](const EdgePredicate& edge) {
return ExcludedBy(edge, value) == 0u && (witnessed = true);
}); });
return guarded && witnessed; return guarded && witnessed;
} }
uint32_t WorkgroupAxis(Value key) const { // The key that every path from its definition to block rules out, if any.
std::optional<uint32_t> ExcludedKeyOnEntry(Value key, const Block* block) const {
const auto* definition = key.Resolve().TryInstruction();
if (definition == nullptr) return {};
std::optional<uint32_t> excluded;
const auto defined = [&](const Block* current) { return current == definition->Parent(); };
const bool guarded = GuardedOnEntry(block, defined, [&](const EdgePredicate& edge) {
const auto immediate = ExcludedBy(edge, key);
if (!immediate || (excluded && excluded != immediate)) return false;
excluded = immediate;
return true;
});
return guarded ? excluded : std::nullopt;
}
// The workgroup ID axis of key; with first, also of a runtime first + workgroup ID sum.
uint32_t WorkgroupAxis(Value key, Value* first = nullptr) const {
const auto* builtin = key.Resolve().TryInstruction(); const auto* builtin = key.Resolve().TryInstruction();
uint32_t axis = UINT32_MAX; uint32_t axis = UINT32_MAX;
if (first != nullptr && builtin != nullptr && builtin->GetOpcode() == ValueOpcode::IAdd32) {
for (uint32_t arm = 0; arm < 2u; ++arm) {
axis = WorkgroupAxis(builtin->Arg(arm));
const auto base = builtin->Arg(arm ^ 1u);
if (axis == UINT32_MAX ||
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
*first = base;
return axis;
}
return UINT32_MAX;
}
if (m_program.stage != ShaderType::Compute || builtin == nullptr || if (m_program.stage != ShaderType::Compute || builtin == nullptr ||
builtin->GetOpcode() != ValueOpcode::GetBuiltin || builtin->NumArgs() != 2u || builtin->GetOpcode() != ValueOpcode::GetBuiltin || builtin->NumArgs() != 2u ||
builtin->Arg(0) != Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) || builtin->Arg(0) != Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) ||
@@ -1168,6 +1204,27 @@ private:
return axis; return axis;
} }
// Matches key = (first + workgroup ID) * multiplier >> shift with shift >= 32; first and the
// division are optional.
bool MatchWorkgroupKey(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
uint32_t bits = 0, multiplier = 0;
const auto* shift = key.Resolve().TryInstruction();
if (shift != nullptr && shift->GetOpcode() == ValueOpcode::ShiftRightLogical32) {
const auto* high = shift->Arg(0).Resolve().TryInstruction();
if (high != nullptr && high->GetOpcode() == ValueOpcode::UMulHi &&
ImmediateU32(shift->Arg(1), bits) && bits < 32u &&
ImmediateU32(high->Arg(1), multiplier)) {
key = high->Arg(0);
bits += 32u;
}
}
indirect.workgroup_axis = WorkgroupAxis(key, &indirect.selector_first);
if (indirect.workgroup_axis == UINT32_MAX) return false;
indirect.workgroup_multiplier = multiplier;
indirect.workgroup_shift = bits;
return true;
}
bool MatchTableOffset(Value value, Value& key, uint32_t& offset, uint32_t& stride) const { bool MatchTableOffset(Value value, Value& key, uint32_t& offset, uint32_t& stride) const {
offset = 0; offset = 0;
for (;;) { for (;;) {
@@ -1672,6 +1729,8 @@ private:
uint32_t initial_arm; uint32_t initial_arm;
}; };
static bool ZeroBased(uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; }
std::optional<Induction> MatchInduction(Value key, const auto& accepts_step) const { std::optional<Induction> MatchInduction(Value key, const auto& accepts_step) const {
key = key.Resolve(); key = key.Resolve();
const auto* phi = key.TryInstruction(); const auto* phi = key.TryInstruction();
@@ -1733,8 +1792,7 @@ private:
} }
uint32_t PostTestLoopMaximum(Value key) const { uint32_t PostTestLoopMaximum(Value key) const {
const auto induction = MatchInduction(key, const auto induction = MatchInduction(key, ZeroBased);
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; });
if (!induction) return UINT32_MAX; if (!induction) return UINT32_MAX;
const auto [phi, step, initial_arm] = *induction; const auto [phi, step, initial_arm] = *induction;
const auto* incoming = phi->PhiBlock(initial_arm ^ 1u); const auto* incoming = phi->PhiBlock(initial_arm ^ 1u);
@@ -1767,8 +1825,7 @@ private:
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound, const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound,
LaneQuantifier lanes = LaneQuantifier::Any) const { LaneQuantifier lanes = LaneQuantifier::Any) const {
return BoundedInduction(key, use, return BoundedInduction(key, use, ZeroBased,
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; },
[&](const Inst& compare, uint32_t initial) { [&](const Inst& compare, uint32_t initial) {
return (compare.GetOpcode() == ValueOpcode::SLessThan32 || return (compare.GetOpcode() == ValueOpcode::SLessThan32 ||
compare.GetOpcode() == ValueOpcode::ULessThan32) && compare.GetOpcode() == ValueOpcode::ULessThan32) &&
@@ -1793,6 +1850,29 @@ private:
}); });
} }
// A zero-based key that only per-lane data bounds takes the count all uniform zero-based
// loops in the program share, assuming each lane's count stays within it.
bool ProgramLoopCount(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
if (!MatchInduction(key, ZeroBased) || BoundedLoop(key, nullptr) != nullptr) return false;
const Inst* count = nullptr;
for (auto* block: m_program.blocks) {
for (auto& phi: *block) {
if (phi.GetOpcode() != ValueOpcode::Phi) break;
const auto* compare = BoundedLoop(Value(&phi), nullptr);
if (compare == nullptr) continue;
if (count != nullptr && !EquivalentValue(m_program, compare->Arg(1), count->Arg(1)))
return false;
count = compare;
}
}
if (count == nullptr) return false;
std::printf("Warning: shader 0x%016llx bounds a per-lane table loop by its uniform loops' "
"count\n", static_cast<unsigned long long>(m_program.shader_hash));
indirect.key_count = count->Arg(1);
indirect.signed_count = count->GetOpcode() == ValueOpcode::SLessThan32;
return true;
}
Value ActiveValue(Value value, Value guard) const { Value ActiveValue(Value value, Value guard) const {
value = value.Resolve(); value = value.Resolve();
while (const auto* select = value.TryInstruction()) { while (const auto* select = value.TryInstruction()) {
@@ -1829,6 +1909,10 @@ private:
} }
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const { bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
// The proven local-zero invocation fails every all-lane bypass condition that excludes it,
// such as an all-inactive or an all-nonzero bypass.
const auto* negated = SimplifyGuard(witness).TryInstruction();
const bool nonzero = negated != nullptr && negated->GetOpcode() == ValueOpcode::LogicalNot;
std::vector<const Block*> pending {use}; std::vector<const Block*> pending {use};
for (size_t index = 0; index < pending.size(); ++index) { for (size_t index = 0; index < pending.size(); ++index) {
const auto* current = pending[index]; const auto* current = pending[index];
@@ -1837,9 +1921,9 @@ private:
for (const auto* previous: current->ImmPredecessors()) { for (const auto* previous: current->ImmPredecessors()) {
if (!witness.IsEmpty()) { if (!witness.IsEmpty()) {
const auto edge = ConditionalEdge(previous, current); const auto edge = ConditionalEdge(previous, current);
// The proven local-zero invocation makes an all-inactive bypass impossible. if (edge && edge->lanes == LaneQuantifier::All &&
if (edge && edge->lanes == LaneQuantifier::All && !edge->positive && (edge->positive ? nonzero && Implies(edge->condition, negated->Arg(0))
Implies(witness, edge->condition)) continue; : Implies(witness, edge->condition))) continue;
} }
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous); if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
} }
@@ -1948,7 +2032,10 @@ private:
bool LocalZeroWitness(Value guard) const { bool LocalZeroWitness(Value guard) const {
const auto* equal = SimplifyGuard(guard).TryInstruction(); const auto* equal = SimplifyGuard(guard).TryInstruction();
if (equal == nullptr || equal->GetOpcode() != ValueOpcode::IEqual32) return false; const bool negated = equal != nullptr && equal->GetOpcode() == ValueOpcode::LogicalNot;
if (negated) equal = SimplifyGuard(equal->Arg(0)).TryInstruction();
const auto expected = negated ? ValueOpcode::INotEqual32 : ValueOpcode::IEqual32;
if (equal == nullptr || equal->GetOpcode() != expected) return false;
for (uint32_t arg = 0; arg < 2u; ++arg) { for (uint32_t arg = 0; arg < 2u; ++arg) {
if (equal->Arg(arg).Resolve() != Value(0u)) continue; if (equal->Arg(arg).Resolve() != Value(0u)) continue;
Value induction; Value induction;
@@ -1982,6 +2069,10 @@ private:
EquivalentValue(m_program, test->Arg(0), value)) return test->Arg(1); EquivalentValue(m_program, test->Arg(0), value)) return test->Arg(1);
if ((positive ? op == ValueOpcode::UGreaterThan32 : op == ValueOpcode::ULessThanEqual32) && if ((positive ? op == ValueOpcode::UGreaterThan32 : op == ValueOpcode::ULessThanEqual32) &&
EquivalentValue(m_program, test->Arg(1), value)) return test->Arg(0); EquivalentValue(m_program, test->Arg(1), value)) return test->Arg(0);
uint32_t limit = 0;
if ((positive ? op == ValueOpcode::ULessThanEqual32 : op == ValueOpcode::UGreaterThan32) &&
ImmediateU32(test->Arg(1), limit) && limit != UINT32_MAX &&
EquivalentValue(m_program, test->Arg(0), value)) return Value(limit + 1u);
const auto* scan = value.Resolve().TryInstruction(); const auto* scan = value.Resolve().TryInstruction();
if (finite_bitscan && scan != nullptr && scan->GetOpcode() == ValueOpcode::FindILsb32 && if (finite_bitscan && scan != nullptr && scan->GetOpcode() == ValueOpcode::FindILsb32 &&
(positive ? op == ValueOpcode::INotEqual32 : op == ValueOpcode::IEqual32)) { (positive ? op == ValueOpcode::INotEqual32 : op == ValueOpcode::IEqual32)) {
@@ -2056,7 +2147,7 @@ private:
const auto* read = ActiveValue(value, active).TryInstruction(); const auto* read = ActiveValue(value, active).TryInstruction();
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 || if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
m_program.stage != ShaderType::Compute || m_program.stage != ShaderType::Compute ||
read->Arg(1).Resolve() != Value(true)) return {}; !Implies(active, read->Arg(1))) return {};
uint64_t group_size = 1u; uint64_t group_size = 1u;
for (const auto dimension: m_input.compute->threads_num) { for (const auto dimension: m_input.compute->threads_num) {
if (dimension == 0u || dimension > m_input.compute->wave_size) return {}; if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
@@ -2087,10 +2178,16 @@ private:
const auto* limit = BoundedLoop(read_offset.index, read->Parent(), const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; }); [](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
if (limit == nullptr) return {}; if (limit == nullptr) return {};
auto counter_active = active;
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction(); const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
// Every lane reads the same counter word, so a selected lane carries it.
if (counter_read != nullptr && counter_read->GetOpcode() == ValueOpcode::ReadFirstLane) {
counter_active = counter_read->Arg(1);
counter_read = ActiveValue(counter_read->Arg(0), counter_active).TryInstruction();
}
uint32_t consumed_counter = 0; uint32_t consumed_counter = 0;
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 || if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
counter_read->Arg(1).Resolve() != Value(true) || !Implies(counter_active, counter_read->Arg(1)) ||
!constant_address(*counter_read, consumed_counter)) return {}; !constant_address(*counter_read, consumed_counter)) return {};
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; }; struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
@@ -2304,25 +2401,23 @@ private:
((offset | stride | memory->offset) & 3u) == 0u && ((offset | stride | memory->offset) & 3u) == 0u &&
uint64_t {offset} + memory->offset <= UINT32_MAX; uint64_t {offset} + memory->offset <= UINT32_MAX;
if (affine) { if (affine) {
workgroup_axis = WorkgroupAxis(index); workgroup_axis = WorkgroupAxis(index, &first);
if (workgroup_axis == UINT32_MAX) {
const auto* sum = index.Resolve().TryInstruction();
if (sum != nullptr && sum->GetOpcode() == ValueOpcode::IAdd32) {
for (uint32_t arm = 0; arm < 2u; ++arm) {
const auto axis = WorkgroupAxis(sum->Arg(arm));
const auto base = sum->Arg(arm ^ 1u);
if (axis == UINT32_MAX ||
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
workgroup_axis = axis;
first = base;
break;
}
}
}
const auto* bound = BoundedLoop(index, read->Parent()); const auto* bound = BoundedLoop(index, read->Parent());
if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) { if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) {
count = bound->Arg(1); count = bound->Arg(1);
} else if (workgroup_axis == UINT32_MAX && !require_bound) {
// Other bounded indices, such as bit scans, read the records up to their maximum;
// a bit scan of a nonzero mask load reads only the ranks its words set.
const auto maximum = SelectorMaximum(index);
if (maximum == 31u) selector.ranks = BitScanRanks(index);
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
} }
// A record selected from a shared append list is below the list's bound.
const auto* lane = index.Resolve().TryInstruction();
if (count.IsEmpty() && lane != nullptr &&
lane->GetOpcode() == ValueOpcode::ReadFirstLane &&
HasActiveLane(lane->Arg(1), lane->Parent()))
count = SharedLoadBound(lane->Arg(0), lane->Arg(1));
} }
// Raw pointers have no V# bounds for enumerating an unbounded selector domain. // Raw pointers have no V# bounds for enumerating an unbounded selector domain.
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() && if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
@@ -2498,7 +2593,8 @@ private:
// Each equation is an inductive subset obligation. Phis require every incoming // Each equation is an inductive subset obligation. Phis require every incoming
// value; AND needs either operand. Grounding rejects cycles without an initial // value; AND needs either operand. Grounding rejects cycles without an initial
// value, then the greatest fixed point checks every initial and carried edge. // value, then the greatest fixed point checks every initial and carried edge.
bool BitsetMaskSubset(Value mask, Value active, Value word, const Inst& read) const { bool BitsetMaskSubset(Value mask, Value active, Value word,
std::span<const Inst* const> reads) const {
struct State { struct State {
Value value; Value value;
Value guard; Value guard;
@@ -2510,7 +2606,10 @@ private:
bool all = true; bool all = true;
std::vector<size_t> terms; std::vector<size_t> terms;
}; };
const auto load_guard = read.Arg(4).Resolve(); // A scalar load reads one word without EXEC; four-word loads take their guard.
const bool scalar = reads[0]->GetOpcode() == ValueOpcode::ReadConstBuffer;
const auto load_guard = scalar ? Value(true) : reads[0]->Arg(4).Resolve();
const auto words = static_cast<uint32_t>(reads.size()) * (scalar ? 1u : 4u);
std::vector<State> states(2); std::vector<State> states(2);
std::vector<Equation> equations(2); std::vector<Equation> equations(2);
const auto add = [&](State state) { const auto add = [&](State state) {
@@ -2521,9 +2620,9 @@ private:
equations.emplace_back(); equations.emplace_back();
return states.size() - 1u; return states.size() - 1u;
}; };
std::array<size_t, 5> roots {add({.value = active})}; std::vector<size_t> roots {add({.value = active})};
for (uint32_t component = 0; component < 4u; ++component) for (uint32_t component = 0; component < words; ++component)
roots[component + 1u] = add({mask, active, component, component}); roots.push_back(add({mask, active, component, component}));
for (size_t index = 2; index < states.size(); ++index) { for (size_t index = 2; index < states.size(); ++index) {
const auto state = states[index]; const auto state = states[index];
const auto* inst = state.value.TryInstruction(); const auto* inst = state.value.TryInstruction();
@@ -2573,11 +2672,14 @@ private:
switch (inst->GetOpcode()) { switch (inst->GetOpcode()) {
case ValueOpcode::CompositeExtractU32x4: { case ValueOpcode::CompositeExtractU32x4: {
uint32_t component; uint32_t component;
if (inst->Arg(0).Resolve().TryInstruction() == &read && if (inst->Arg(0).Resolve().TryInstruction() == reads[state.component / 4u] &&
ImmediateU32(inst->Arg(1), component) && component == state.component) ImmediateU32(inst->Arg(1), component) && component == state.component % 4u)
equation.terms = {1u}; equation.terms = {1u};
break; break;
} }
case ValueOpcode::ReadConstBuffer:
if (scalar && inst == reads[state.component]) equation.terms = {1u};
break;
case ValueOpcode::Phi: case ValueOpcode::Phi:
// The predecessor may be an earlier outer iteration. Establish // The predecessor may be an earlier outer iteration. Establish
// this register's invariant without the current word or EXEC. // this register's invariant without the current word or EXEC.
@@ -2598,17 +2700,17 @@ private:
const auto* masked = key.TryInstruction(); const auto* masked = key.TryInstruction();
uint32_t bound = 0; uint32_t bound = 0;
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 && if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
DescendingLoop(word, masked->Parent(), bound) && bound == 3u) { DescendingLoop(word, masked->Parent(), bound) && bound == words - 1u) {
for (uint32_t side = 0; side < 2u; ++side) { for (uint32_t side = 0; side < 2u; ++side) {
uint32_t bits; uint32_t bits;
if (ImmediateU32(masked->Arg(side), bits) && (bits & 3u) == 3u) if (ImmediateU32(masked->Arg(side), bits) && (bits & bound) == bound)
key = masked->Arg(side ^ 1u).Resolve(); key = masked->Arg(side ^ 1u).Resolve();
} }
} }
return EquivalentValue(m_program, key, word); return EquivalentValue(m_program, key, word);
}); });
uint32_t literal; uint32_t literal;
if (ImmediateU32(equality, literal) && literal < 4u) selected_word = literal; if (ImmediateU32(equality, literal) && literal < words) selected_word = literal;
equation.terms.push_back(term(inst->Arg(1), inst->Arg(0), selected_word)); equation.terms.push_back(term(inst->Arg(1), inst->Arg(0), selected_word));
} }
if (!selected || !*selected) if (!selected || !*selected)
@@ -2646,7 +2748,53 @@ private:
return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; }); return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; });
} }
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds, // The mask words whose set bits are every record a bit scan of index selects: the scanned
// value keeps only bits of one scalar load, which reads a word its address can reach.
std::optional<DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks>
BitScanRanks(Value index) {
const auto* scan = index.Resolve().TryInstruction();
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindILsb32) return {};
const auto* masked = scan->Arg(0).Resolve().TryInstruction();
if (masked == nullptr) return {};
std::vector<const Inst*> values {masked};
const Inst* load = nullptr;
for (size_t i = 0; i < values.size() && load == nullptr; ++i) {
const auto* value = values[i];
if (value->GetOpcode() == ValueOpcode::ReadConstBuffer) load = value;
if (value->GetOpcode() != ValueOpcode::Phi &&
value->GetOpcode() != ValueOpcode::BitwiseAnd32) continue;
for (size_t arg = 0; arg < value->NumArgs(); ++arg) {
const auto* next = value->Arg(arg).Resolve().TryInstruction();
if (next != nullptr && std::ranges::find(values, next) == values.end())
values.push_back(next);
}
}
uint32_t memory_index = 0;
const auto* memory = load != nullptr ? ScalarReadMemory(*load, memory_index) : nullptr;
if (memory == nullptr || memory->offset > INT32_MAX ||
!MemoryIndexBelongsTo(memory_index, *load) ||
!BitsetMaskSubset(scan->Arg(0), Value(true), Value(0u), std::array {load})) return {};
auto address = load->Arg(1);
auto active = Value(true);
const auto* lane = address.Resolve().TryInstruction();
if (lane != nullptr && lane->GetOpcode() == ValueOpcode::ReadFirstLane) {
if (!HasActiveLane(lane->Arg(1), lane->Parent())) return {};
active = lane->Arg(1);
address = lane->Arg(0);
}
// A left shift keeps the address a multiple of its scale; other addresses read any word.
const auto* shift = ActiveValue(address, active).TryInstruction();
uint32_t bits = 0;
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
!ImmediateU32(shift->Arg(1), bits) || bits >= 32u) bits = 0;
DescriptorSource source;
if (!MakeRuntimeTableSource(*load, source)) return {};
return DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks {
.source = InternSource(source), .stride = std::max(1u << bits, 4u),
.offset = memory->offset & ~3u};
}
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds, uint32_t words,
DescriptorSource::IndirectDescriptor& indirect) { DescriptorSource::IndirectDescriptor& indirect) {
const auto selected_value = [&](Value value) { const auto selected_value = [&](Value value) {
for (;;) { for (;;) {
@@ -2670,7 +2818,7 @@ private:
} }
const auto* word_inst = word.TryInstruction(); const auto* word_inst = word.TryInstruction();
if (word_inst == nullptr || std::ranges::none_of(bounds, [&](const auto& bound) { if (word_inst == nullptr || std::ranges::none_of(bounds, [&](const auto& bound) {
return bound.first == word_inst && bound.second == 3u; return bound.first == word_inst && bound.second == words - 1u;
})) return false; })) return false;
// FFBH's leading-zero count is inverted again to recover the set-bit rank. // FFBH's leading-zero count is inverted again to recover the set-bit rank.
for (uint32_t inverse = 0; inverse < 2u; ++inverse) { for (uint32_t inverse = 0; inverse < 2u; ++inverse) {
@@ -2682,21 +2830,34 @@ private:
const auto* scan = bit.TryInstruction(); const auto* scan = bit.TryInstruction();
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindUMsb32 || if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindUMsb32 ||
!ImpliesNonzero(active, scan->Arg(0))) return false; !ImpliesNonzero(active, scan->Arg(0))) return false;
const auto bitmap_load = [&](const Inst& read, uint32_t offset) {
const auto index = read.Flags<MemoryFlags>().index;
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(read.Parent()) ||
index >= m_program.memory_info.size()) return false;
const auto& memory = m_program.memory_info[index];
return memory.kind == ResourceKind::Buffer && !memory.typed && !memory.formatted &&
memory.idxen && !memory.offen && memory.offset == offset &&
memory.data_bits == 32u && memory.data_dwords == 4u &&
read.Arg(2).Resolve() == Value(0u) && read.Arg(3).Resolve() == Value(0u);
};
for (const auto* block: m_program.blocks) { for (const auto* block: m_program.blocks) {
for (const auto& read: *block) { for (const auto& read: *block) {
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(block)) continue; if (!bitmap_load(read, 0u)) continue;
const auto index = read.Flags<MemoryFlags>().index; // An eight-word record continues in a second four-word load of the same buffer and EXEC.
if (index >= m_program.memory_info.size()) continue; std::array<const Inst*, 2> reads {&read};
const auto& memory = m_program.memory_info[index]; if (words == 8u)
if (memory.kind != ResourceKind::Buffer || memory.typed || memory.formatted || for (const auto* other: m_program.blocks)
!memory.idxen || memory.offen || memory.offset != 0u || memory.data_bits != 32u || for (const auto& load: *other)
memory.data_dwords != 4u || read.Arg(2).Resolve() != Value(0u) || if (reads[1] == nullptr && bitmap_load(load, 16u) &&
read.Arg(3).Resolve() != Value(0u) || EquivalentValue(m_program, load.Arg(0), read.Arg(0)) &&
!BitsetMaskSubset(scan->Arg(0), active, word, read)) continue; EquivalentValue(m_program, load.Arg(4), read.Arg(4))) reads[1] = &load;
const auto parts = std::span(reads).first(words / 4u);
DescriptorSource source; DescriptorSource source;
if (!MakeRuntimeTableSource(read, source)) continue; if (std::ranges::find(parts, nullptr) != parts.end() ||
!BitsetMaskSubset(scan->Arg(0), active, word, parts) ||
!MakeRuntimeTableSource(read, source)) continue;
indirect.selector.emplace(DescriptorSource::IndirectDescriptor::SelectorRead { indirect.selector.emplace(DescriptorSource::IndirectDescriptor::SelectorRead {
.source = InternSource(source), .stride = 16u, .offset = 0u, .bitset = true}); .source = InternSource(source), .stride = words * 4u, .offset = 0u, .bitset = true});
return true; return true;
} }
} }
@@ -2726,7 +2887,8 @@ private:
first.SetArg(1, Value(maximum)); first.SetArg(1, Value(maximum));
indirect.selector_first = Value(&first); indirect.selector_first = Value(&first);
indirect.key_count = Value(maximum + 1u); indirect.key_count = Value(maximum + 1u);
if (maximum == 127u) MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, indirect); if (maximum == 127u || maximum == 255u)
MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, (maximum + 1u) / 32u, indirect);
return true; return true;
} }
@@ -2993,6 +3155,47 @@ private:
return true; return true;
} }
// The EXEC predicate of the image access through use; empty for any other use.
Value AccessPredicate(const Use& use, bool sampler) const {
const auto& access = *use.user;
if (use.operand != (sampler ? 1u : 0u) ||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
const auto mask = access.Arg(access.NumArgs() - 1u);
return mask.GetType() == Type::U1 ? mask : Value {};
}
// A key no bound covers takes its maximum from a signed clamp to a constant, on the key or on
// the key plus a constant in an earlier-tracked sibling read of the same table. Negative keys
// and keys past that maximum read the first row.
uint32_t ClampedTableMaximum(Value key, const DescriptorSource& table,
const DescriptorSource::IndirectDescriptor& indirect) const {
uint32_t row = 0, offset = 0;
const auto clamped = [&](Value value) {
const auto* min = value.Resolve().TryInstruction();
if (min != nullptr && min->GetOpcode() == ValueOpcode::SMin32)
for (uint32_t arg = 0; arg < 2u; ++arg)
if (ImmediateU32(min->Arg(arg), row)) return min->Arg(arg ^ 1u);
return Value {};
};
if (!clamped(key).IsEmpty()) return row;
for (const auto& sibling: m_indirect_descriptors) {
const auto& read = *m_sources[sibling.source].indirect_descriptor;
const auto* add = clamped(sibling.key).Resolve().TryInstruction();
if (add == nullptr || add->GetOpcode() != ValueOpcode::IAdd32 ||
m_sources[read.table_source] != table ||
read.table_offset != indirect.table_offset ||
read.table_stride != indirect.table_stride ||
read.table_immediate != indirect.table_immediate) continue;
for (uint32_t arg = 0; arg < 2u; ++arg)
if (ImmediateU32(add->Arg(arg), offset) &&
EquivalentValue(m_program, add->Arg(arg ^ 1u), key)) return row;
}
return UINT32_MAX;
}
bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor, bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor,
IndirectDescriptorPlan& plan) { IndirectDescriptorPlan& plan) {
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource; const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
@@ -3019,6 +3222,7 @@ private:
} else if (!plan.table_indexed && } else if (!plan.table_indexed &&
MatchScalarMaterialKey(key, indirect, material_source, MatchScalarMaterialKey(key, indirect, material_source,
!sampler && table_source.dword_count == 4u, sampler)) { !sampler && table_source.dword_count == 4u, sampler)) {
indirect.selector->excluded = ExcludedKeyOnEntry(key, handle.Parent());
// Keep live descriptor payloads, but project descriptor-only SMEM onto // Keep live descriptor payloads, but project descriptor-only SMEM onto
// the GPU key using the same plan as other indirect image reads. // the GPU key using the same plan as other indirect image reads.
plan.retain_reads |= table_source.dword_count == 4u; plan.retain_reads |= table_source.dword_count == 4u;
@@ -3080,36 +3284,42 @@ private:
if (indirect.key_count.IsEmpty() && if (indirect.key_count.IsEmpty() &&
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) && !MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) &&
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) && !MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) &&
!MatchReversedImageRange(key, indirect)) { !MatchReversedImageRange(key, indirect) && !MatchWorkgroupKey(key, indirect)) {
const auto maximum = SelectorMaximum(key); auto maximum = SelectorMaximum(key);
if (maximum >= MaxIndirectDescriptorProbes) return false; if (maximum >= MaxIndirectDescriptorProbes) {
// Each image access bounds the key on the lanes it executes.
maximum = 0u;
for (const auto& use: handle.Uses()) {
const auto active = AccessPredicate(use, false);
if (active.IsEmpty()) {
maximum = UINT32_MAX;
break;
}
maximum = std::max(maximum, SelectorMaximum(key, {}, 0, active));
}
}
if (maximum >= MaxIndirectDescriptorProbes)
maximum = ClampedTableMaximum(key, table_source, indirect);
if (maximum < MaxIndirectDescriptorProbes)
indirect.key_count = Value(maximum + 1u); indirect.key_count = Value(maximum + 1u);
else if (!ProgramLoopCount(key, indirect))
return false;
} }
if ((table_offset & 3u) != 0u) return false; if ((table_offset & 3u) != 0u) return false;
} else { } else {
// Keep the GPU selector and descriptor reads, and preserve the proven scalar // Keep the GPU selector and descriptor reads, and preserve the proven scalar
// offset domain within the bounded image table. // offset domain within the bounded image table.
if (sampler || plan.split_offsets) return false; if (sampler || plan.split_offsets) return false;
const auto active = [&](const Use& use, bool sampler) -> Value {
const auto& access = *use.user;
if (use.operand != (sampler ? 1u : 0u) ||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
const auto mask = access.Arg(access.NumArgs() - 1u);
return mask.GetType() == Type::U1 ? mask : Value {};
};
bool shared_selector = false; bool shared_selector = false;
if (!plan.table_indexed) { if (!plan.table_indexed) {
for (const auto& owner: m_indirect_descriptors) { for (const auto& owner: m_indirect_descriptors) {
const auto& selected = *m_sources[owner.source].indirect_descriptor; const auto& selected = *m_sources[owner.source].indirect_descriptor;
if (!selected.selector || !EquivalentValue(m_program, key, owner.key) || if (!selected.selector || !EquivalentValue(m_program, key, owner.key) ||
!std::ranges::all_of(handle.Uses(), [&](const Use& target) { !std::ranges::all_of(handle.Uses(), [&](const Use& target) {
const auto target_active = active(target, false); const auto target_active = AccessPredicate(target, false);
return !target_active.IsEmpty() && return !target_active.IsEmpty() &&
std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) { std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) {
const auto owner_active = active(witness, const auto owner_active = AccessPredicate(witness,
owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource); owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource);
return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) && return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) &&
Implies(target_active, owner_active); Implies(target_active, owner_active);
@@ -3430,6 +3640,42 @@ private:
return result; return result;
} }
// Bounds of an F32 value on the lanes where guard holds; NaN when unknown.
std::pair<float, float> FloatRange(Value value, Value guard, uint32_t depth = 0) const {
constexpr std::pair unknown {NAN, NAN};
value = ActiveValue(value, guard);
if (value.IsImmediate() && value.GetType() == Type::F32)
return {value.F32Value(), value.F32Value()};
const auto* inst = value.TryInstruction();
// Each multiply-add evaluates three operands; keep the search shallow.
if (depth > 12u || inst == nullptr) return unknown;
const auto range = [&](uint32_t arg) {
return FloatRange(inst->Arg(arg), guard, depth + 1u);
};
switch (inst->GetOpcode()) {
case ValueOpcode::BitCastF32U32:
case ValueOpcode::BitCastU32F32: return range(0);
// Clamping maps NaN to zero.
case ValueOpcode::FPSaturate32: return {0.0f, 1.0f};
case ValueOpcode::FPFloor32: {
const auto [low, high] = range(0);
return {std::floor(low), std::floor(high)};
}
case ValueOpcode::FPMad32: {
// The rounded multiply and add are monotonic, so the bounds lie at the corners.
const auto a = range(0), b = range(1), c = range(2);
if (!std::isfinite(a.first) || !std::isfinite(a.second) ||
!std::isfinite(b.first) || !std::isfinite(b.second)) return unknown;
const auto [low, high] = std::minmax({a.first * b.first, a.first * b.second,
a.second * b.first, a.second * b.second});
const std::pair bounds {low + c.first, high + c.second};
if (!std::isfinite(bounds.first) || !std::isfinite(bounds.second)) return unknown;
return bounds;
}
default: return unknown;
}
}
uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0, uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0,
Value guard = {}) const { Value guard = {}) const {
if (depth > 32u) return UINT32_MAX; if (depth > 32u) return UINT32_MAX;
@@ -3445,9 +3691,13 @@ private:
switch (inst->GetOpcode()) { switch (inst->GetOpcode()) {
case ValueOpcode::ReadFirstLane: case ValueOpcode::ReadFirstLane:
return SelectedSelectorDomain(*inst, guard).maximum; return SelectedSelectorDomain(*inst, guard).maximum;
case ValueOpcode::ReadLane: case ValueOpcode::ReadLane: {
// ReadLane ignores EXEC; only context-free bounds on every source lane apply. // ReadLane ignores EXEC; only context-free bounds on every source lane apply,
return PossibleU32Bits(inst->Arg(0)); // unless guard proves a waterfall key equal to the executing lanes' local value.
const auto local = EqualLocalKey(guard, value);
return local.IsEmpty() ? PossibleU32Bits(inst->Arg(0))
: SelectorMaximum(local, bounds, depth + 1u, guard);
}
case ValueOpcode::Phi: { case ValueOpcode::Phi: {
const auto initial = InitialCandidateMask(value); const auto initial = InitialCandidateMask(value);
if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard); if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard);
@@ -3474,6 +3724,18 @@ private:
NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX; NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX;
case ValueOpcode::UMin32: case ValueOpcode::UMin32:
case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1)); case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1));
case ValueOpcode::SMin32: {
// Signed and unsigned minimums agree on nonnegative operands.
const auto left = maximum(0), right = maximum(1);
if (std::max(left, right) <= INT32_MAX) return std::min(left, right);
break;
}
case ValueOpcode::ConvertS32F32: {
// The conversion truncates toward zero.
const auto [low, high] = FloatRange(inst->Arg(0), guard);
if (low >= 0.0f && high < 0x1p31f) return static_cast<uint32_t>(high);
break;
}
case ValueOpcode::SelectU32: case ValueOpcode::SelectU32:
if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1); if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1);
return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard) return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard)
@@ -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");
} }
@@ -82,6 +82,7 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
case ValueOpcode::BitCastU32F32: case ValueOpcode::BitCastU32F32:
case ValueOpcode::BitCastF32U32: case ValueOpcode::BitCastF32U32:
case ValueOpcode::ConvertU32F32: case ValueOpcode::ConvertU32F32:
case ValueOpcode::ConvertS32F32:
case ValueOpcode::ConvertF32U32: case ValueOpcode::ConvertF32U32:
case ValueOpcode::CompositeConstructU64: case ValueOpcode::CompositeConstructU64:
case ValueOpcode::CompositeExtractU64: case ValueOpcode::CompositeExtractU64:
@@ -147,8 +148,8 @@ bool ImmediateBits(Value value, uint64_t& bits) {
} }
// Scratch layout in bytes: per descriptor source its eight DWORDs and a known DWORD, one // Scratch layout in bytes: per descriptor source its eight DWORDs and a known DWORD, one
// activity byte per source, then one qword per walked value (all ones when unknown). The // activity byte per source, then per image, then one qword per walked value (all ones when
// walker's value slots, captured addresses, reachability and states follow. // unknown). The walker's value slots, captured addresses, reachability and states follow.
constexpr uint32_t DescriptorStride = 9u * sizeof(uint32_t); constexpr uint32_t DescriptorStride = 9u * sizeof(uint32_t);
uint32_t ActiveOffset(const ResourcePlan& plan) { uint32_t ActiveOffset(const ResourcePlan& plan) {
@@ -156,7 +157,8 @@ uint32_t ActiveOffset(const ResourcePlan& plan) {
} }
uint32_t OutputOffset(const ResourcePlan& plan) { uint32_t OutputOffset(const ResourcePlan& plan) {
return (ActiveOffset(plan) + static_cast<uint32_t>(plan.descriptor_sources.size()) + 7u) & ~7u; const auto active = plan.descriptor_sources.size() + plan.info.images.size();
return (ActiveOffset(plan) + static_cast<uint32_t>(active) + 7u) & ~7u;
} }
const uint8_t* Scratch(const ResourcePlan& plan) { const uint8_t* Scratch(const ResourcePlan& plan) {
@@ -1038,6 +1040,23 @@ void WalkerCompiler::EmitStrict(const Node& node) {
c.L(done); c.L(done);
break; break;
} }
case ValueOpcode::ConvertS32F32: {
// The conversion saturates and maps NaN to zero. cvttss2si's out-of-range result,
// INT32_MIN, is already right for negative overflow.
Xbyak::Label done;
c.movd(c.xmm0, c.eax);
c.xor_(c.eax, c.eax);
c.ucomiss(c.xmm0, c.xmm0);
c.jp(done, Near);
c.mov(c.ecx, 0x4f000000u);
c.movd(c.xmm1, c.ecx);
c.mov(c.eax, INT32_MAX);
c.ucomiss(c.xmm0, c.xmm1);
c.jae(done, Near);
c.cvttss2si(c.eax, c.xmm0);
c.L(done);
break;
}
case ValueOpcode::FPMul32: case ValueOpcode::FPMul32:
c.movd(c.xmm0, c.eax); c.movd(c.xmm0, c.eax);
c.movd(c.xmm1, c.ecx); c.movd(c.xmm1, c.ecx);
@@ -1272,11 +1291,14 @@ void WalkerCompiler::Compile(std::span<const Value> values) {
for (const auto slot: blocks[block].srt_reads) flat(slot); for (const auto slot: blocks[block].srt_reads) flat(slot);
c.L(skip); c.L(skip);
} }
// A source no block lists is always active. // A source or image no block lists is always active.
for (uint32_t source = 0; source < sources.size(); ++source) { for (uint32_t index = 0; index < sources.size() + m_plan.info.images.size(); ++index) {
const bool image = index >= sources.size();
const uint32_t id = image ? index - static_cast<uint32_t>(sources.size()) : index;
bool listed = false; bool listed = false;
for (uint32_t block = 0; block < blocks.size(); ++block) { for (uint32_t block = 0; block < blocks.size(); ++block) {
if (std::ranges::find(blocks[block].sources, source) == blocks[block].sources.end()) continue; const auto& list = image ? blocks[block].images : blocks[block].sources;
if (std::ranges::find(list, id) == list.end()) continue;
if (listed) { if (listed) {
c.or_(c.al, c.byte[c.rdx + m_reach + block]); c.or_(c.al, c.byte[c.rdx + m_reach + block]);
} else { } else {
@@ -1284,7 +1306,7 @@ void WalkerCompiler::Compile(std::span<const Value> values) {
} }
listed = true; listed = true;
} }
const auto active = c.byte[c.rdx + ActiveOffset(m_plan) + source]; const auto active = c.byte[c.rdx + ActiveOffset(m_plan) + index];
if (listed) { if (listed) {
c.mov(active, c.al); c.mov(active, c.al);
} else { } else {
@@ -1391,6 +1413,10 @@ bool WalkedActive(const ResourcePlan& plan, uint32_t source) {
return Scratch(plan)[ActiveOffset(plan) + source] != 0u; return Scratch(plan)[ActiveOffset(plan) + source] != 0u;
} }
bool WalkedImageActive(const ResourcePlan& plan, uint32_t image) {
return Scratch(plan)[ActiveOffset(plan) + plan.descriptor_sources.size() + image] != 0u;
}
bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) { bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) {
return RuntimeValidator(program, type).Run(value); return RuntimeValidator(program, type).Run(value);
} }
@@ -78,6 +78,7 @@ bool WalkSrt(const ResourcePlan& plan, const SrtRuntime& runtime, std::vector<ui
bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result); bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result);
bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result); bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result);
bool WalkedActive(const ResourcePlan& plan, uint32_t source); bool WalkedActive(const ResourcePlan& plan, uint32_t source);
bool WalkedImageActive(const ResourcePlan& plan, uint32_t image);
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0. // Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
// The exception handler completes such a load as a zero read. // The exception handler completes such a load as a zero read.
uint32_t SrtWalkerLoadLength(uint64_t rip); uint32_t SrtWalkerLoadLength(uint64_t rip);
@@ -349,13 +349,19 @@ void VisitInstruction(Pass& pass, Block* block, Inst& inst) {
void RewriteToSsa(const BlockList& blocks) { void RewriteToSsa(const BlockList& blocks) {
Pass pass; Pass pass;
std::unordered_map<Block*, size_t> filled_predecessors;
for (auto* block: blocks) { for (auto* block: blocks) {
if (block->ImmPredecessors().empty()) {
pass.Seal(block);
}
for (auto& inst: *block) { for (auto& inst: *block) {
VisitInstruction(pass, block, inst); VisitInstruction(pass, block, inst);
} }
for (auto* successor: block->ImmSuccessors()) {
if (++filled_predecessors[successor] == successor->ImmPredecessors().size()) {
pass.Seal(successor);
}
} }
for (auto* block: blocks) {
pass.Seal(block);
} }
} }
+5 -6
View File
@@ -291,8 +291,7 @@ 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;
@@ -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);
+16 -2
View File
@@ -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 {
+461 -1059
View File
File diff suppressed because it is too large. Load diff
+10 -2
View File
@@ -28,7 +28,8 @@ struct DebugStats {
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);
@@ -197,6 +197,9 @@ int KYTY_SYSV_ABI KernelMemoryPoolGetBlockStats(KernelMemoryPoolBlockStats* outp
uint64_t AllocateProgramMemory(uint64_t search_addr, uint64_t size, uint64_t AllocateProgramMemory(uint64_t search_addr, uint64_t size,
Common::VirtualMemory::Mode mode, const char* name); Common::VirtualMemory::Mode mode, const char* name);
// Loaded modules' segments use flexible memory without taking its backing.
void ChargeFlexibleMemory(uint64_t size);
void ReleaseFlexibleMemory(uint64_t size);
void SetProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode); void SetProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode);
uint64_t AllocateRuntimeMemory(uint64_t search_addr, uint64_t size, uint64_t AllocateRuntimeMemory(uint64_t search_addr, uint64_t size,
Common::VirtualMemory::Mode mode, const char* name, Common::VirtualMemory::Mode mode, const char* name,
@@ -222,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
+65 -57
View File
@@ -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,11 +1312,20 @@ 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.
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); 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)) {
@@ -1332,8 +1341,7 @@ private:
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) {
+1
View File
@@ -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">
@@ -256,6 +266,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>
+10 -18
View File
@@ -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;
@@ -168,6 +159,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;
@@ -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;
+25 -2
View File
@@ -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);
@@ -48,7 +67,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 +84,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);
@@ -134,7 +155,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);
+63 -9
View File
@@ -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);
@@ -413,8 +418,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 +517,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();
@@ -518,7 +562,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 +584,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);
+4 -2
View File
@@ -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");
+42 -2
View File
@@ -2480,6 +2480,9 @@ static constexpr uint32_t AGC_DRAW_INDIRECT_MULTI_END_TAG = 0xc6000000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u; static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG = 0xc9010000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG = 0xc9020000u;
static constexpr uint32_t AGC_MEM_SEMAPHORE_END_TAG = 0xc9000000u;
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u;
static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u; static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u;
@@ -3077,6 +3080,42 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
poll_cycles); poll_cycles);
} }
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
uint8_t mailbox, uint8_t signal_type, uint8_t op) {
EXIT_NOT_IMPLEMENTED(op != 6u && op != 7u);
auto* cmd =
buf->AllocateDW(AGC_WAIT_USER_DATA_BEGIN_DW + AGC_INTERNAL_DATA_PACKET_SIZE_DW + 4u);
if (cmd == nullptr) {
return nullptr;
}
const auto address_value = reinterpret_cast<uint64_t>(address);
const bool signal = (op == 6u);
cmd[0] = KYTY_PM4(AGC_WAIT_USER_DATA_BEGIN_DW, Pm4::IT_SET_UCONFIG_REG, 1u);
cmd[1] = AGC_INTERNAL_DATA_REGISTER;
cmd[2] = (signal ? AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG : AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG) |
static_cast<uint32_t>((address_value >> 32u) & 0xffffu);
cmd[3] = static_cast<uint32_t>(address_value);
// A signal pads with a NOP header where a wait ends with its end marker.
auto* semaphore = cmd + AGC_WAIT_USER_DATA_BEGIN_DW;
if (signal) {
semaphore[0] = KYTY_PM4(AGC_INTERNAL_DATA_PACKET_SIZE_DW, Pm4::IT_NOP, 0u);
semaphore += AGC_INTERNAL_DATA_PACKET_SIZE_DW;
}
semaphore[0] = KYTY_PM4(4, Pm4::IT_MEM_SEMAPHORE, 0u);
semaphore[1] = static_cast<uint32_t>(address_value) & ~0x7u;
semaphore[2] = static_cast<uint32_t>(address_value >> 32u);
semaphore[3] = (static_cast<uint32_t>(op) << 29u) | ((signal_type & 0x1u) << 20u) |
((mailbox & 0x1u) << 16u);
if (!signal) {
write_agc_internal_data_packet(semaphore + 4, AGC_MEM_SEMAPHORE_END_TAG);
}
return cmd;
}
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) { uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) {
PRINT_NAME(); PRINT_NAME();
@@ -3840,11 +3879,12 @@ bool AgcIsInternalDataPacket(uint32_t cmd_id, const uint32_t* payload) {
const auto size_dw = KYTY_PM4_LEN(cmd_id); const auto size_dw = KYTY_PM4_LEN(cmd_id);
if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) { if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) {
const auto tag = payload[1] & 0xffff0000u; const auto tag = payload[1] & 0xffff0000u;
return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG; return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG ||
tag == AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG || tag == AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG;
} }
return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW && return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW &&
(payload[1] == AGC_WAIT_USER_DATA_END_TAG || (payload[1] == AGC_WAIT_USER_DATA_END_TAG || payload[1] == AGC_MEM_SEMAPHORE_END_TAG ||
payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG || payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG ||
payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG); payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG);
} }
+2
View File
@@ -216,6 +216,8 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
uint8_t cache_policy, const volatile void* address, uint8_t cache_policy, const volatile void* address,
uint64_t reference, uint64_t mask, uint32_t poll_cycles); uint64_t reference, uint64_t mask, uint32_t poll_cycles);
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size); uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size);
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
uint8_t mailbox, uint8_t signal_type, uint8_t op);
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
uint64_t dst_address_or_offset, uint8_t src, uint64_t dst_address_or_offset, uint8_t src,
uint8_t src_cache_policy, uint8_t src_cache_policy,
+55 -10
View File
@@ -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, &params,
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, &params,
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");
+7
View File
@@ -56,6 +56,12 @@ 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;
@@ -65,6 +71,7 @@ struct AjmDecodeResult {
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;
+182
View File
@@ -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
+13
View File
@@ -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;
+1
View File
@@ -78,6 +78,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize); LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind); LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem); LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
LIB_FUNC("q4VuU-QsLOE", Gen5::AgcAcbMemSemaphore);
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize); LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData); LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData); LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData);
+98 -67
View File
@@ -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 {
@@ -170,15 +171,10 @@ static std::atomic_uint64_t g_audioout2_next_context {1};
static std::atomic_uint64_t g_audioout2_next_port {1}; static 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,7 +574,7 @@ 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();
@@ -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,19 +691,17 @@ 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 ||
params->sampling_freq != 48000 ||
(audio_format != AudioInternal::Format::FloatMono && (audio_format != AudioInternal::Format::FloatMono &&
audio_format != AudioInternal::Format::Signed16bitMono))) { audio_format != AudioInternal::Format::Signed16bitMono))) {
g_audioout2_state_mutex.Unlock(); g_audioout2_state_mutex.Unlock();
@@ -688,14 +714,21 @@ 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();
return AUDIO_OUT2_ERROR_PORT_FULL;
} }
} const auto serial = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
if (port_state != nullptr) { 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 = AudioOut2PortStateEntry {};
port_state->used = true; port_state->used = true;
port_state->handle = next_port; port_state->handle = next_port;
@@ -707,23 +740,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
port_state->samples_num = samples_num; port_state->samples_num = samples_num;
port_state->audio_format = audio_format; port_state->audio_format = audio_format;
port_state->gains.fill(1.0f); port_state->gains.fill(1.0f);
}
g_audioout2_state_mutex.Unlock(); g_audioout2_state_mutex.Unlock();
if (port_state == nullptr) {
return AUDIO_OUT2_ERROR_PORT_FULL;
}
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"
@@ -905,10 +934,12 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle 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) {
if (std::any_of(context.ports.begin(), context.ports.end(),
[handle](const auto& port) { return port.used && port.user == handle; })) { [handle](const auto& port) { return port.used && port.user == handle; })) {
return AUDIO_OUT2_ERROR_BUSY; return AUDIO_OUT2_ERROR_BUSY;
} }
}
g_audioout2_users.erase(it); g_audioout2_users.erase(it);
return OK; return OK;
} }
+24 -6
View File
@@ -39,7 +39,7 @@ 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 bytes = ShadowCoverage(page, min_addr, 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++; 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)) {
@@ -79,7 +88,7 @@ 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();
+21 -47
View File
@@ -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());
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);
}
return paths;
}
static std::vector<uint8_t> load_fallback_font_data(bool bold, bool italic) {
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 {}; return {};
} }
// SDL returns UTF-8; path(const char*) would read it in the Windows ANSI code page.
const auto path = Common::PathFromUtf8(base_path) / "assets" / "fonts" / file_name;
auto data = read_file_bytes(path);
if (data.empty()) {
LOGF("Font: fallback font not found: %s\n", Common::PathToString(path).c_str());
}
return data;
}
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;
+31 -14
View File
@@ -615,7 +615,12 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
Common::LockGuard lock(g_mount_mutex); Common::LockGuard lock(g_mount_mutex);
const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id(); const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id();
const auto root = save_user_directory(title_id, cond->user_id); const auto root = save_user_directory(title_id, cond->user_id);
std::vector<std::string> dir_list; struct Found {
uint64_t key = 0;
std::string name;
auto operator<=>(const Found&) const = default;
};
std::vector<Found> dir_list;
if (Common::File::IsDirectoryExisting(root)) { if (Common::File::IsDirectoryExisting(root)) {
for (const auto& entry: Common::File::GetDirEntries(root)) { for (const auto& entry: Common::File::GetDirEntries(root)) {
@@ -625,22 +630,34 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' || if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
dir_name_match(Common::ToLower(entry.name).c_str(), dir_name_match(Common::ToLower(entry.name).c_str(),
Common::ToLower(std::string(cond->dir_name->data)).c_str())) { Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
dir_list.push_back(entry.name); dir_list.push_back({.name = entry.name});
} }
} }
} }
} }
std::sort(dir_list.begin(), dir_list.end(), [](const std::string& a, const std::string& b) { // A save whose metadata cannot be read sorts as 0.
return std::strcmp(a.c_str(), b.c_str()) < 0; for (auto& found: dir_list) {
}); const auto directory = root / found.name;
switch (cond->key) {
case SaveDataSortKey::UserParam: {
SaveDataParam param {};
read_save_param(save_param_path(directory), &param);
found.key = param.user_param;
break;
}
case SaveDataSortKey::Blocks:
case SaveDataSortKey::FreeBlocks: read_save_blocks(directory, &found.key); break;
case SaveDataSortKey::Mtime:
found.key = static_cast<uint64_t>(newest_save_time(directory));
break;
default: break;
}
}
// Directory names break ties; descending order reverses both.
std::sort(dir_list.begin(), dir_list.end());
if (cond->order == SaveDataSortOrder::Descent) { if (cond->order == SaveDataSortOrder::Descent) {
std::vector<std::string> reversed; std::reverse(dir_list.begin(), dir_list.end());
for (size_t i = dir_list.size(); i > 0; i--) {
reversed.push_back(dir_list[i - 1]);
}
dir_list = reversed;
} }
auto max_count = auto max_count =
@@ -651,9 +668,9 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
for (size_t i = 0; i < max_count; i++) { for (size_t i = 0; i < max_count; i++) {
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s", std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s",
dir_list[i].c_str()); dir_list[i].name.c_str());
if (result->params != nullptr) { if (result->params != nullptr) {
const int status = load_save_param(root / dir_list[i], &result->params[i]); const int status = load_save_param(root / dir_list[i].name, &result->params[i]);
if (status != OK) { if (status != OK) {
return status; return status;
} }
@@ -661,7 +678,7 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
if (result->infos != nullptr) { if (result->infos != nullptr) {
auto& info = result->infos[i]; auto& info = result->infos[i];
info = {}; info = {};
const int status = read_save_blocks(root / dir_list[i], &info.blocks); const int status = read_save_blocks(root / dir_list[i].name, &info.blocks);
if (status != OK) { if (status != OK) {
return status; return status;
} }
+56 -17
View File
@@ -546,6 +546,7 @@ struct Ngs2VoiceInternal {
}; };
struct Module { struct Module {
std::vector<uint8_t> param, work, state; std::vector<uint8_t> param, work, state;
std::vector<float> channel_matrix;
void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr; void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr;
std::array<uintptr_t, 3> user_data {}; std::array<uintptr_t, 3> user_data {};
uint32_t flags = 1; uint32_t flags = 1;
@@ -563,6 +564,7 @@ struct Ngs2VoiceInternal {
std::vector<std::vector<float>> matrices; std::vector<std::vector<float>> matrices;
std::vector<Module> modules; std::vector<Module> modules;
std::vector<float> samples; std::vector<float> samples;
std::vector<float> mix_samples;
std::vector<float> output_matrix; std::vector<float> output_matrix;
uint32_t channels = 0; uint32_t channels = 0;
uint32_t output_id = 0; uint32_t output_id = 0;
@@ -614,6 +616,27 @@ struct Ngs2VoiceInternal {
return samples.data() + size_t(id) * grain * channels; return samples.data() + size_t(id) * grain * channels;
} }
void MixChannels(const std::vector<float>& matrix, uint32_t buffer, uint32_t grain) {
if (matrix.empty()) {
return;
}
EXIT_NOT_IMPLEMENTED(channels > 37);
auto* input = Buffer(buffer, grain);
mix_samples.assign(size_t(channels) * grain, 0.0f);
for (uint32_t dst = 0; dst < channels; ++dst) {
for (uint32_t src = 0; src < channels; ++src) {
const float level = matrix[dst * 37 + src];
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) {
mix_samples[dst * grain + i] += input[src * grain + i] * level;
}
}
}
std::copy(mix_samples.begin(), mix_samples.end(), input);
}
void ResetSetupState() { void ResetSetupState() {
SetEvent(4); SetEvent(4);
std::ranges::fill(ports, Port {}); std::ranges::fill(ports, Port {});
@@ -1850,6 +1873,10 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
for (size_t m = 0; m < voice.modules.size(); ++m) { for (size_t m = 0; m < voice.modules.size(); ++m) {
const auto& info = custom.module[m]; const auto& info = custom.module[m];
auto& module = voice.modules[m]; auto& module = voice.modules[m];
if (info.module_id == 0x17) {
voice.MixChannels(module.channel_matrix, info.source_buffer_id, grain);
continue;
}
if (info.module_id != 0x19 && info.module_id != 0x1f) { if (info.module_id != 0x19 && info.module_id != 0x1f) {
if (!module.render_warned) { if (!module.render_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
@@ -1934,7 +1961,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info); EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
const auto& output = buffer_info[voice->output_id]; const auto& output = buffer_info[voice->output_id];
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr); EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
EXIT_NOT_IMPLEMENTED((output.waveform_type != 0x12 && output.waveform_type != 0x18) || EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x12 && output.waveform_type != 0x18);
EXIT_NOT_IMPLEMENTED(voice->rack->type == Ngs2RackType::Mastering &&
output.num_channels != voice->channels); output.num_channels != voice->channels);
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels *
(output.waveform_type == 0x12 ? sizeof(int16_t) (output.waveform_type == 0x12 ? sizeof(int16_t)
@@ -1954,6 +1982,9 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
const float level = voice->output_matrix.empty() const float level = voice->output_matrix.empty()
? (src == dst ? 1.0f : 0.0f) ? (src == dst ? 1.0f : 0.0f)
: voice->output_matrix.at(src * output.num_channels + dst); : voice->output_matrix.at(src * output.num_channels + dst);
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) { for (uint32_t i = 0; i < grain; ++i) {
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level; pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
} }
@@ -2452,7 +2483,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id; voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id;
auto& module = voice->modules[index]; auto& module = voice->modules[index];
if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) || if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) ||
(module_id != 0x19 && module_id != 0x1f)) { (module_id != 0x17 && module_id != 0x19 && module_id != 0x1f)) {
if (!module.control_warned) { if (!module.control_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
"warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n", "warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n",
@@ -2461,6 +2492,16 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
} }
break; break;
} }
if (module_id == 0x17) {
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
37 * 37 * sizeof(float));
const auto* levels = reinterpret_cast<const float*>(param + 1);
module.channel_matrix.resize(37 * 37);
std::transform(levels, levels + module.channel_matrix.size(),
module.channel_matrix.begin(),
[](float level) { return std::clamp(level, -32.0f, 32.0f); });
break;
}
if (module_id == 0x19) { if (module_id == 0x19) {
struct FxParam { struct FxParam {
Ngs2VoiceParamHeader header; Ngs2VoiceParamHeader header;
@@ -2660,24 +2701,22 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
submixer->voice_state.state_flags = Ngs2GetStateFlags(voice); submixer->voice_state.state_flags = Ngs2GetStateFlags(voice);
break; break;
} }
case Ngs2RackType::CustomMastering: { case Ngs2RackType::CustomMastering:
const auto configured_size =
voice->rack->option.custom_mastering.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomMasteringVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* mastering = reinterpret_cast<Ngs2CustomMasteringVoiceState*>(state);
mastering->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::CustomSampler: { case Ngs2RackType::CustomSampler: {
const bool sampler = voice->rack->type == Ngs2RackType::CustomSampler;
const auto configured_size = const auto configured_size =
voice->rack->option.custom_sampler.custom_rack_option.state_size; voice->rack->option.custom_sampler.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomSamplerVoiceState)); EXIT_NOT_IMPLEMENTED(configured_size < (sampler ? sizeof(Ngs2CustomSamplerVoiceState)
EXIT_NOT_IMPLEMENTED(state_size != configured_size); : sizeof(Ngs2CustomMasteringVoiceState)));
std::memset(state, 0, state_size); if (state_size < configured_size) {
auto* sampler = reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state); return static_cast<int32_t>(0x804a8011u);
Ngs2FillSamplerState(*voice, *sampler); }
std::memset(state, 0, configured_size);
if (sampler) {
Ngs2FillSamplerState(*voice, *reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state));
} else {
state->state_flags = Ngs2GetStateFlags(voice);
}
break; break;
} }
case Ngs2RackType::Sampler: { case Ngs2RackType::Sampler: {
+19 -1
View File
@@ -1636,6 +1636,8 @@ void RuntimeLinker::DeleteTls(Program* program, int thread_id) {
} }
} }
constexpr uint64_t GUEST_PAGE_SIZE = 0x4000;
static uint64_t CalcBaseSize(const Elf64_Ehdr* ehdr, const Elf64_Phdr* phdr) { static uint64_t CalcBaseSize(const Elf64_Ehdr* ehdr, const Elf64_Phdr* phdr) {
uint64_t base_size = 0; uint64_t base_size = 0;
for (Elf64_Half i = 0; i < ehdr->e_phnum; i++) { for (Elf64_Half i = 0; i < ehdr->e_phnum; i++) {
@@ -1649,6 +1651,20 @@ static uint64_t CalcBaseSize(const Elf64_Ehdr* ehdr, const Elf64_Phdr* phdr) {
return base_size; return base_size;
} }
// A module's E, R and RW LOAD segments use flexible memory in 16 KiB pages; GNU_RELRO adds none
// since it lies inside an RW LOAD.
uint64_t CalcFlexibleMemorySize(const Elf64_Phdr* phdr, Elf64_Half phnum) {
uint64_t size = 0;
for (Elf64_Half i = 0; i < phnum; i++) {
const auto flags = phdr[i].p_flags;
if (phdr[i].p_type == PT_LOAD &&
(flags == PF_X || flags == PF_R || flags == (PF_R | PF_W))) {
size += AlignUp(phdr[i].p_memsz, GUEST_PAGE_SIZE);
}
}
return size;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RuntimeLinker::LoadProgramToMemory(Program* program) { void RuntimeLinker::LoadProgramToMemory(Program* program) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
@@ -1669,7 +1685,6 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
EXIT_IF(phdr == nullptr || ehdr == nullptr); EXIT_IF(phdr == nullptr || ehdr == nullptr);
program->base_size = CalcBaseSize(ehdr, phdr); program->base_size = CalcBaseSize(ehdr, phdr);
constexpr uint64_t GUEST_PAGE_SIZE = 0x4000;
EXIT_IF(program->base_size > UINT64_MAX - (GUEST_PAGE_SIZE - 1)); EXIT_IF(program->base_size > UINT64_MAX - (GUEST_PAGE_SIZE - 1));
program->base_size_aligned = AlignUp(program->base_size, GUEST_PAGE_SIZE); program->base_size_aligned = AlignUp(program->base_size, GUEST_PAGE_SIZE);
@@ -1694,6 +1709,8 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
g_desired_base_addr, program->mapped_size, Common::VirtualMemory::Mode::ExecuteReadWrite, g_desired_base_addr, program->mapped_size, Common::VirtualMemory::Mode::ExecuteReadWrite,
Common::PathToString(program->file_name.filename()).c_str()); Common::PathToString(program->file_name.filename()).c_str());
EXIT_IF(program->base_vaddr == 0); EXIT_IF(program->base_vaddr == 0);
program->flexible_size = CalcFlexibleMemorySize(phdr, ehdr->e_phnum);
Libs::LibKernel::Memory::ChargeFlexibleMemory(program->flexible_size);
if (patch_guest_instructions) { if (patch_guest_instructions) {
const auto trampoline_addr = program->base_vaddr + program->base_size_aligned; const auto trampoline_addr = program->base_vaddr + program->base_size_aligned;
@@ -1879,6 +1896,7 @@ void RuntimeLinker::DeleteProgram(Program* p) {
} }
EXIT_IF( EXIT_IF(
!Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size)); !Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size));
Libs::LibKernel::Memory::ReleaseFlexibleMemory(program->flexible_size);
} }
} }
+4
View File
@@ -21,6 +21,7 @@ struct EntryParams {
}; };
class Elf64; class Elf64;
struct Elf64_Phdr;
struct Elf64_Sym; struct Elf64_Sym;
struct Elf64_Rela; struct Elf64_Rela;
class RuntimeLinker; class RuntimeLinker;
@@ -128,6 +129,7 @@ struct Program {
uint64_t base_size = 0; uint64_t base_size = 0;
uint64_t base_size_aligned = 0; uint64_t base_size_aligned = 0;
uint64_t mapped_size = 0; uint64_t mapped_size = 0;
uint64_t flexible_size = 0;
uint64_t instruction_trampoline_size = 0; uint64_t instruction_trampoline_size = 0;
std::unique_ptr<SymbolDatabase> export_symbols; std::unique_ptr<SymbolDatabase> export_symbols;
std::unique_ptr<SymbolDatabase> import_symbols; std::unique_ptr<SymbolDatabase> import_symbols;
@@ -203,6 +205,8 @@ private:
application_heap_posix_memalign_func_t m_application_heap_posix_memalign = nullptr; application_heap_posix_memalign_func_t m_application_heap_posix_memalign = nullptr;
}; };
uint64_t CalcFlexibleMemorySize(const Elf64_Phdr* phdr, uint16_t phnum);
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
bool TestMainEntryUsesGuestStack(); bool TestMainEntryUsesGuestStack();
bool TestModuleRelocationUsesWritableHostMapping(); bool TestModuleRelocationUsesWritableHostMapping();
+19 -9
View File
@@ -35,7 +35,7 @@ public:
bool GetParamInt(const char* name, int32_t* value); bool GetParamInt(const char* name, int32_t* value);
bool GetParamString(const char* name, std::string* value); bool GetParamString(const char* name, std::string* value);
bool GetParamString(const char* name, char* value, size_t value_size); bool GetParamString(const char* name, char* value, size_t value_size);
uint64_t GetFlexibleMemorySize(); MemoryParams GetMemoryParams();
void DbgPrint(); void DbgPrint();
@@ -61,7 +61,7 @@ private:
Common::Mutex m_mutex; Common::Mutex m_mutex;
bool m_opened = false; bool m_opened = false;
std::vector<Param> m_params; std::vector<Param> m_params;
uint64_t m_flexible_memory_size = 0; MemoryParams m_memory_params;
}; };
class PlayGo { class PlayGo {
@@ -172,11 +172,21 @@ void Psf::OpenJson(const std::filesystem::path& file_name) {
add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3"); add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3");
add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4"); add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4");
auto read_size = [](const nlohmann::ordered_json& object, const char* key, uint64_t* size,
uint64_t unit = 1) {
if (auto it = object.find(key); it != object.end()) {
*size = it->get<uint64_t>() * unit;
}
};
if (auto kernel = param_json.find("kernel"); if (auto kernel = param_json.find("kernel");
kernel != param_json.end() && kernel->is_object()) { kernel != param_json.end() && kernel->is_object()) {
if (auto it = kernel->find("flexibleMemorySize"); it != kernel->end()) { read_size(*kernel, "flexibleMemorySize", &m_memory_params.flexible_size);
m_flexible_memory_size = it->get<uint64_t>(); read_size(*kernel, "cpuPageTableSize", &m_memory_params.cpu_table_memory);
read_size(*kernel, "gpuPageTableSize", &m_memory_params.gpu_table_memory);
} }
if (auto amm = param_json.find("amm"); amm != param_json.end() && amm->is_object()) {
read_size(*amm, "pagetableMemorySizeInMib", &m_memory_params.amm_table_memory,
1024ull * 1024ull);
} }
auto localized = param_json.find("localizedParameters"); auto localized = param_json.find("localizedParameters");
@@ -227,7 +237,7 @@ void Psf::Close() {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
m_params.clear(); m_params.clear();
m_flexible_memory_size = 0; m_memory_params = {};
m_opened = false; m_opened = false;
} }
@@ -339,10 +349,10 @@ bool Psf::GetParamString(const char* name, char* value, size_t value_size) {
return false; return false;
} }
uint64_t Psf::GetFlexibleMemorySize() { MemoryParams Psf::GetMemoryParams() {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
return m_flexible_memory_size; return m_memory_params;
} }
PlayGo::~PlayGo() { PlayGo::~PlayGo() {
@@ -646,10 +656,10 @@ bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_
return sc->psf.GetParamString(name, value, value_size); return sc->psf.GetParamString(name, value, value_size);
} }
uint64_t SystemContentGetFlexibleMemorySize() { MemoryParams SystemContentGetMemoryParams() {
auto* sc = Common::Singleton<SystemContent>::Instance(); auto* sc = Common::Singleton<SystemContent>::Instance();
return sc->psf.GetFlexibleMemorySize(); return sc->psf.GetMemoryParams();
} }
bool SystemContentGetIconPath(std::filesystem::path* path) { bool SystemContentGetIconPath(std::filesystem::path* path) {
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more