Compare commits

...
Author SHA1 Message Date
nmzik 7e1c2d1532 avplayer: auto-start playback without an event callback 2026-09-25 16:39:54 +04:00
nmzik 0ec43a8d3d graphics: name Vulkan images and views only on creation 2026-09-25 16:15:05 +04:00
nmzikandMac bfdbba3aa4 graphics: share EOP clock and immediate write handling
Co-authored-by: Mac <me@itsmemac.com>
2026-09-25 16:14:40 +04:00
Devran Cosmo Uenalandnmzik f7ae6e0a83 audio: support NGS2 PCM16 samplers and rear low-pass filters (#825)
* audio: support standard NGS2 PCM16 sampler playback

Extend the existing fixed-point PCM path to standard samplers, loops, and progress reporting. Apply voice events immediately and honor callback interruption during source advancement.

Reuse upstream rate and pitch handling. Add regression coverage for streaming, routing, callbacks, loops, and custom sampler controls; ngs2_sampler passes.

* audio: apply NGS2 rear low-pass filters

Handle rear low-pass FCQ controls at the output sample rate with per-channel history and channel masks. Preserve filter tails through streaming gaps and use the standard pi constant for coefficients.

Add regression coverage for frequency response, control layout, channel isolation, and starvation/refill continuity. Emulator build and both audio test suites pass.

* Preserve resampling phase for accepting voices

Only reset voice.sample_phase when there are no blocks and the voice does not accept blocks (change in src/libs/ngs2.cpp). Add TestResamplingPhaseAfterStarvation to tests to ensure sampler and custom sampler retain resampling phase and pending source advancement after starvation, and that SetupSampler still resets the phase. This prevents resampling phase loss during starvation/refill across sample rates.

* audio: correct sampler controls, lifecycle, and filter tails

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-25 15:44:58 +04:00
nmzik 3585cd8f03 shader: simplify translator dispatch to void handlers 2026-09-25 14:27:09 +04:00
nmzik ff54608569 shader: add V_CMPX_O_F32 opcode 2026-09-25 14:27:09 +04:00
nmzik 6bb68a3fd9 shader: stop repeated CFG merge gateway splitting 2026-09-25 14:27:09 +04:00
nmzik 93185953f1 shader: fuse image specialization setup with materialization 2026-09-25 14:27:09 +04:00
6d4mandnmzik 7855c980cb audio: keep blocking AudioOut writes on an even cadence (#822)
* audio: keep blocking AudioOut writes on an even cadence

The blocking queue check only enforced a minimum cushion, so the guest
could queue a burst of buffers and then be blocked for the whole cushion.
Measured on PPSA03647, intervals between guest writes alternated between
0 ms and ~44 ms instead of one 5.33 ms buffer period, while the average
rate stayed correct at 48000 frames/s. Mixers that run on a timer cannot
track that cadence and the music turns slow and warbling.

Keep the cushion as-is, but also hold writes to the buffer period, and
skip that wait when the stream is running low so it cannot underrun.

Intervals become ~5.4 ms with microsecond-level deviation and the menu
music plays correctly.

* audio: reuse output clock after SDL queue priming

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-25 12:33:01 +04:00
5a705dd15f shader: support V_CMPX_EQ_U16 (#816)
* shader: support V_CMPX_EQ_U16

Decode gfx11 VOPC 0xba and lower it as an unsigned 16-bit equality
compare with CMPX EXEC-mask semantics.

🤖 Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>

* shader: correct CMPX EQ U16 runtime expectations

Remove the added CFG tests and correct two EXEC expectations in the compute regression case.

---------

Co-authored-by: Matthew Hurley <171001439+theantipopau@users.noreply.github.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-25 02:27:44 +04:00
nmzik e73324c2e0 Fix Linux fiber stack overflow with Clang 18 (#813)
Keep function-name formatting off small fiber stacks
2026-09-25 02:08:35 +04:00
Mac a20fb7f9f8 agc: trace per-frame packet parameters only with graphics debug dump
The per-frame ACB submit, indirect dispatch, reset-queue and patch-address
parameter dumps, and the flip-pending poll, are logged only with the graphics
debug dump, the way the DCB submit path already is. Otherwise they were about
7.5 MB of log a minute in the Spider-Man world.
2026-09-25 00:03:08 +04:00
nmzikandMac 1cc0ac7ed8 ajm: share control handling to fully reset gapless decoders
Co-authored-by: Mac <me@itsmemac.com>
2026-09-25 00:01:51 +04:00
nmzik 83628a436e audio: fix NGS2 playback 2026-09-24 22:34:55 +04:00
nmzik 2165c710c3 audio: share block completion 2026-09-24 22:34:55 +04:00
Saadiandnmzik b9a0bd2791 loader: wrap elapsed time in Timer::GetTime (#679)
loader: format elapsed log timestamps beyond 24 hours

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-24 22:02:19 +04:00
6d4m 96874909ab cmake: pass -Map as discrete link option on non-clang-cl builds (#806)
LINK_FLAGS is a space-delimited string, so when the build path contains
spaces the linker map option is split into bogus arguments and the final
link fails:

  clang++: error: no such file or directory:
  'pro/KytyPS5/_Build/linux/kyty_emulator_clang_lld.map'

Move -Wl,-Map=<path> out of LINK_FLAGS into target_link_options, which
CMake passes to the linker as a single argument. This completes #26 for
the WIN32 OR LINUX branch (clang/gcc builds); the clang-cl branch was
already converted there.
2026-09-24 21:23:13 +04:00
Romain HedouinandClaude Sonnet 5 d646fd92d1 graphics: hoist loop-invariant checks out of draw/attrib hot paths (#807)
* graphics: hoist loop-invariant checks out of draw/attrib hot paths

DrawIndirectMulti recomputed the index_type_and_size switch on every
sub-draw of a multi-draw-indirect call, and ShaderApplyAttribSemantics
re-checked the debug-dump config flag on every vertex attribute. Both
values are invariant across their respective loops, so compute them
once before iterating instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* graphics: cache vertex buffer descriptor size in AcquireVertexBuffers

VertexBufferDescriptorSize was computed twice per vertex-buffer slot
on every draw call: once while collecting guest ranges, and again
while rebuilding slot bindings. Both calls use identical inputs, so
cache the first pass's result and reuse it in the second.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 21:02:23 +04:00
nmzikandMac d72a79af2d graphics: skip compute dispatches with BVH instructions
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzikandMac dc9280d75d shader: add GLC/DLC buffer cache policy support
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 6a9ae4ae43 shader: add SDWA support for V_MAX_I32 2026-09-24 20:46:04 +04:00
nmzik 392f39e2f6 shader: support indirect BUFFER_LOAD_DWORDX3 2026-09-24 20:46:04 +04:00
nmzikandMac cce0cd950c videoout: add RGB10A2 PQ buffer support
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
e43c242683 shader: add S_MEMREALTIME opcode
Co-authored-by: Maxime Allanic <maxime@allanic.me>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzikandMac 732057b072 videoout: fix VRR status import signature
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 2aaf0c9ba3 graphics: clamp raster depth after polygon offset
Enable native Vulkan depth clamping independently of guest depth clipping
2026-09-24 14:09:02 +04:00
nmzik db7745ef41 update README 2026-09-24 12:30:27 +04:00
nmzik 02578f3c6b docs: update screenshots 2026-09-24 12:18:55 +04:00
fcf0f3b67e shader: fold the lane test after a pixel shader's entry s_wqm (#801)
Preserve live lane predicates through RDNA2 whole-quad expansion

Keep the source-live predicate when WQM expands a scalar mask, so entry masks remain known live without an instruction pre-scan. Preserve scalar mask values, SCC, half writes, and raw-source behavior.

Cover entry pixel masks, cross-half copies, and scalar/high-word sources with existing shader regression suites.

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Douglas <douglasproducoes21@gmail.com>
2026-09-24 12:02:19 +04:00
nmzik 84d015120c Remove duplicate image scratch setup from push constant test 2026-09-24 11:13:33 +04:00
nmzikandTarkusR a6248c7c7c Convert formatted half-float buffer stores from F32
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 3bd5de646d Reuse prepared shader bindings across draws and dispatches 2026-09-24 11:13:33 +04:00
nmzik 0382d0ca58 Publish reserved guest mappings atomically after backing commit 2026-09-24 11:13:33 +04:00
nmzik a670dc84b3 Test RDNA2 wave32 ReadLane material key selection 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 963a0817dc Bound uniformized image keys by guarded material mask
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 1617ee77e5 Share CFG reachability for descriptor bounds analysis 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 66cc2fe26d Track depth and stencil attachment feedback across graphics stages
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 51c34d1a24 Test zero scalar shift folding on RDNA2 compute shader 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 3b89e36d1e Track bounded indirect image tables from signed shader loops
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
João Lobo 3dc3fd0197 shader: implement DS_INC_U32 and DS_DEC_U32 (#790) 2026-09-24 00:45:12 +04:00
Deniz ŞAHİN fc86509aac shader: support DS_WRITE_B8_D16_HI (#794)
Decode GFX10 DS opcode 0xa0 as DS_WRITE_B8_D16_HI (next to
DS_WRITE_B16_D16_HI at 0xa1) and lower it through the existing 8-bit
shared-memory write. The stored byte is bits 23:16 of the data VGPR,
selected with SDWA BYTE_2 on src1.

Add captured-instruction decode coverage (from GTA V, PPSA04264) and a
runtime regression that writes one byte of a distinctive source value
into a pre-filled LDS dword.
2026-09-24 00:44:39 +04:00
569ec04d1a Fix save metadata and timestamps
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
Co-authored-by: CheesyPoofs346 <jjs777406@gmail.com>
2026-09-23 22:50:51 +04:00
nmzik 7d7267cc56 Translate NGG mesh prolog with guest wave32 width 2026-09-23 22:50:51 +04:00
nmzik 2aa0a64f63 materialize compressed video-out DCC clears before flip
PPSA27624
2026-09-23 22:50:51 +04:00
nmzik fc04db7596 NGS2: preserve ATRAC9 config word byte order 2026-09-23 22:50:51 +04:00
nmzik 1275d65bb3 Audio: split ACM, Audio3D, and NGS2 implementations 2026-09-23 22:50:51 +04:00
nmzik 3742ad9980 NGS2: warn and skip unsupported custom audio modules 2026-09-23 22:50:51 +04:00
nmzik c437de9721 Presenter: avoid lock 2026-09-23 22:50:51 +04:00
Daniil Shushakov ba579088d7 loader: refresh imports after dynamic module loads (#511)
#307: loader: refresh imports after dynamic module loads
2026-09-23 21:04:10 +04:00
82 changed files with 7095 additions and 3752 deletions

No files matched your search

+13 -1
View File
@@ -465,6 +465,13 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
target_link_libraries(audio_out2_port_tests common fmt::fmt)
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/Ngs2SamplerTests.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
)
@@ -555,6 +562,7 @@ if(BUILD_TESTING)
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME shader_recompiler_alignbyte
@@ -620,6 +628,7 @@ if(BUILD_TESTING)
shader_recompiler_compute_tests
virtual_memory_allocation_tests
audio_out2_port_tests
ngs2_sampler_tests
)
endif()
@@ -677,7 +686,10 @@ if(KYTY_CLANG_CL)
)
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
# LINK_FLAGS is a space-delimited string: a -Map path containing spaces would
# be split into bogus arguments, so pass it as a discrete link option instead.
target_link_options(kyty_emulator PRIVATE "-Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
if(LINUX)
+18 -17
View File
@@ -4,13 +4,13 @@
[![Build KytyPS5 (Linux)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Linux%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (macOS)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28macOS%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20Linux%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status)
[![Status](https://img.shields.io/badge/status-active%20development-orange.svg)](#current-status)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
with experimental macOS support. It is based on a heavily modified version of
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
compatibility is limited and behavior may change significantly between builds.
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
can change significantly between builds.
> [!IMPORTANT]
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
@@ -20,12 +20,12 @@ compatibility is limited and behavior may change significantly between builds.
## Current Status
KytyPS5 can boot 2D games and a selection of 3D games, including titles built with Unreal Engine
4/5, Unity, and custom engines. No external low-level emulation modules are currently required.
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
planned.
Development is focused on compatibility and boot reliability.
Development is currently focused on expanding game compatibility and improving boot reliability.
Windows is the primary platform and receives the most testing. Linux builds and runs; see
[Building on Linux](#building-on-linux).
Windows and Linux are the primary platforms and receive the most testing.
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
@@ -36,16 +36,16 @@ Community game test results are available in the
## Bugs and Issues
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
graphical glitches, low compatibility, and poor performance.
Compatibility, stability, and performance can vary between versions. You may encounter crashes
or graphical glitches, so please include the version you tested when reporting an issue.
## Screenshots
<table align="center">
<tr>
<td align="center">
<strong>Disgaea 6</strong><br>
<img src="docs/screenshots/ps5-01.png" width="300" alt="Disgaea 6 running in KytyPS5">
<strong>Astro Bot</strong><br>
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
</td>
<td align="center">
<strong>Dreaming Sarah</strong><br>
@@ -64,12 +64,12 @@ graphical glitches, low compatibility, and poor performance.
</tr>
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Hellboy running in KytyPS5">
<strong>Demon's Souls</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Demon's Souls running in KytyPS5">
</td>
<td align="center">
<strong>Paleo Pines</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Paleo Pines running in KytyPS5">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Hellboy running in KytyPS5">
</td>
</tr>
</table>
@@ -370,5 +370,6 @@ licenses included with those components.
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
of the original Kyty project.
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for memory-model
understanding and the AVPlayer implementation.
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for understanding PS4
memory behavior, GPU resource aliasing and cache coherency,
and the AVPlayer implementation.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

+1 -10
View File
@@ -38,16 +38,7 @@ bool Timer::IsPaused() const {
// return time in milliseconds
double Timer::GetTimeMs() const {
if (m_is_paused) {
return 1000.0 * (static_cast<double>(m_PauseTime - m_StartTime)) /
static_cast<double>(m_Frequency);
}
uint64_t current_time = 0;
SysQueryPerformanceCounter(&current_time);
return 1000.0 * (static_cast<double>(current_time - m_StartTime)) /
static_cast<double>(m_Frequency);
return GetTimeS() * 1000.0;
}
// return time in seconds
+16 -49
View File
@@ -976,6 +976,16 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size);
uint64_t index_size = 0;
if (indexed) {
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
case 1: index_size = 4; break;
case 2: index_size = 1; break;
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
}
}
for (uint32_t i = 0; i < draw_count; i++) {
const auto args_addr = m_draw_indirect_args_base_addr + data_offset +
static_cast<uint64_t>(i) * stride_in_bytes;
@@ -993,14 +1003,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
uint64_t index_size = 0;
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
case 1: index_size = 4; break;
case 2: index_size = 1; break;
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
}
auto* index_addr = reinterpret_cast<const void*>(
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
@@ -1203,8 +1205,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
}
break;
case 0x02:
case 0x04:
if constexpr (sizeof(T) == sizeof(uint32_t)) {
if (eop_event_type == 0x2f && event_index == 0x06) {
if (event_write_source == 0x02 && eop_event_type == 0x2f && event_index == 0x06) {
switch (cache_action) {
case 0x00: write32(false); return;
case 0x38: write32(true); return;
@@ -1212,6 +1215,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
}
}
} else {
if (event_write_source == 0x04) {
value = Sync::ReadReferenceClock();
}
auto write64 = [&](bool with_writeback) {
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
std::memcpy(dst, &value, sizeof(value));
@@ -1268,7 +1274,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
case 0x14:
case 0x28:
if (((eop_event_type == 0x04 || eop_event_type == 0x28) &&
event_index == 0x05 && !with_interrupt) ||
event_index == 0x05) ||
(event_index == 0x00)) {
write64(true);
return;
@@ -1300,45 +1306,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
}
}
break;
case 0x04:
if constexpr (sizeof(T) == sizeof(uint64_t)) {
const auto clock = Sync::ReadReferenceClock();
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
std::memcpy(dst, &clock, sizeof(clock));
switch (cache_action) {
case 0x00:
if ((eop_event_type == 0x04 && event_index == 0x05) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterrupt64(
m_submit_id, command, dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, command,
dst, clock);
}
return;
}
break;
case 0x38:
if ((eop_event_type == 0x04 &&
(event_index == 0x00 || event_index == 0x05)) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, command, dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
m_submit_id, command, dst, clock);
}
return;
}
break;
default: break;
}
}
break;
default: break;
}
+18 -50
View File
@@ -107,36 +107,6 @@ constexpr uint64_t NumFramesBeforeRemoval = 32;
return true;
}
[[nodiscard]] const char* BindingTypeName(TextureCache::BindingType type) {
switch (type) {
case TextureCache::BindingType::Texture: return "Texture";
case TextureCache::BindingType::Storage: return "StorageTexture";
case TextureCache::BindingType::RenderTarget: return "ColorTarget";
case TextureCache::BindingType::DepthTarget: return "DepthTarget";
case TextureCache::BindingType::VideoOut: return "VideoOut";
}
return "Image";
}
void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view,
TextureCache::BindingType type, const ImageViewInfo& view_info) {
const auto* role = BindingTypeName(type);
SetVulkanObjectNameF(
graphics.device, image.backing.image,
"Kyty.{}.Image[guest=0x{:016x} size=0x{:x} extent={}x{}x{} format={} mips={} layers={} "
"samples={}]",
role, image.info.data.address, image.info.data.size, image.info.extent.width,
image.info.extent.height, image.info.extent.depth,
static_cast<uint32_t>(image.info.pixel_format), image.info.resources.levels,
image.info.resources.layers, image.info.samples);
SetVulkanObjectNameF(
graphics.device, view,
"Kyty.{}.View[guest=0x{:016x} format={} aspect=0x{:x} mip={}+{} layer={}+{}]", role,
image.info.data.address, static_cast<uint32_t>(view_info.format),
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
view_info.level_count, view_info.base_layer, view_info.layer_count);
}
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
uint64_t slice_stride,
vk::ImageAspectFlags aspect) {
@@ -1210,7 +1180,9 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
}
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
if (desc.type != BindingType::VideoOut) {
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
}
}
}
@@ -1331,16 +1303,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
}
}
auto& image = m_slot_images[result];
if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
const bool native_current =
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
if (!native_current) {
EXIT("TextureCache: compressed video-out read requires clean native GPU "
"contents\n");
}
}
if (view_mip >= 0) {
desc.view_info.base_level = static_cast<uint32_t>(view_mip);
}
@@ -1351,6 +1313,18 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
TouchImage(image);
}
MaterializeDccClear(result, desc, metadata_base_layer);
if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
std::scoped_lock lock {m_lock};
const auto& image = m_slot_images[result];
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
const bool native_current =
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
if (!native_current) {
EXIT("TextureCache: compressed video-out read requires clean native GPU "
"contents\n");
}
}
return result;
}
@@ -1439,9 +1413,7 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
break;
default: EXIT("TextureCache: invalid texture binding\n");
}
const auto view = image.FindView(desc.view_info);
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
return view;
return image.FindView(desc.view_info);
}
vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc) {
@@ -1459,9 +1431,7 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
RefreshImage(id);
CommitGpuWrite(image);
TrackImageDownload(id, image);
const auto view = image.FindView(desc.view_info);
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
return view;
return image.FindView(desc.view_info);
}
vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
@@ -1488,9 +1458,7 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
if (desc.info.HasStencil()) {
RefreshImage(AssociateStencil(id, desc.info.stencil));
}
const auto view = image.FindView(desc.view_info);
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
return view;
return image.FindView(desc.view_info);
}
void TextureCache::MarkGpuWritten(ImageId id) {
@@ -41,13 +41,42 @@ struct RenderDepthInfo {
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
};
inline vk::ImageAspectFlags DepthFeedbackAspects(vk::ImageAspectFlags draw_writes,
const ImageViewInfo& target,
const ImageViewInfo& sampled) {
if (!ImageRangeOverlaps(target.base_level, target.level_count, sampled.base_level,
sampled.level_count) ||
!ImageRangeOverlaps(target.base_layer, target.layer_count, sampled.base_layer,
sampled.layer_count)) {
return {};
}
return draw_writes & sampled.aspect;
}
inline vk::ImageAspectFlags DepthReadableAspects(vk::ImageLayout layout) {
switch (layout) {
case vk::ImageLayout::eDepthReadOnlyOptimal:
case vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal:
return vk::ImageAspectFlagBits::eDepth;
case vk::ImageLayout::eStencilReadOnlyOptimal:
case vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal:
return vk::ImageAspectFlagBits::eStencil;
case vk::ImageLayout::eDepthStencilReadOnlyOptimal:
case vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT:
case vk::ImageLayout::eGeneral:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default:
return {};
}
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
const auto available = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
const auto writes = depth.AttachmentWriteAspects();
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
// LoadOp clears are separate from guest depth writes.
const bool depth_write = depth.depth_write_enable;
// The attachment layout must permit load clears as well as draw writes.
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
@@ -690,6 +690,12 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
create.extent.width, create.extent.height, create.extent.depth,
static_cast<int>(create.format), create.arrayLayers, create.mipLevels);
}
SetVulkanObjectNameF(
graphics.device, backing.image,
"Kyty.Image[guest=0x{:016x} size=0x{:x} extent={}x{}x{} format={} mips={} layers={} samples={}]",
info.data.address, info.data.size, info.extent.width, info.extent.height, info.extent.depth,
static_cast<uint32_t>(info.pixel_format), info.resources.levels, info.resources.layers,
info.samples);
}
uint64_t Image::HashGuestEdges() const {
@@ -329,7 +329,9 @@ struct VideoOutFormatPolicy {
VideoOutPixelFormatInfo info;
};
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ = 0x8100070422000000ull;
inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8000000000000000ull,
@@ -338,6 +340,8 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ,
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
{0xc001000600000000ull,
@@ -391,6 +391,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
view_info.level_count, view_info.base_layer, view_info.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
}
SetVulkanObjectNameF(
m_graphics.device, view,
"Kyty.ImageView[guest=0x{:016x} format={} aspect=0x{:x} mip={}+{} layer={}+{}]",
info.data.address, static_cast<uint32_t>(normalized.format),
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
normalized.level_count, normalized.base_layer, normalized.layer_count);
views.push_back({normalized, view});
return view;
}
@@ -17,6 +17,7 @@
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
@@ -749,18 +750,27 @@ void RenderExecutor::ResetBindings() {
m_bound_images.clear();
}
PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime) {
void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
PreparedBindings prepared;
prepared.runtime = &runtime;
prepared.images.reserve(program.info.images.size());
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
prepared.flattened_srt = {};
prepared.shader_data_buffer = {};
prepared.buffer_sources.clear();
prepared.buffers.clear();
prepared.images.resize(program.info.images.size());
prepared.samplers.clear();
prepared.shader_data.clear();
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
prepared.images.push_back(std::move(binding));
binding.mip_views.swap(prepared.images[i].mip_views);
binding.mip_views.clear();
prepared.images[i] = std::move(binding);
}
prepared.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
@@ -775,7 +785,6 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
prepared.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
}
return prepared;
}
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
@@ -989,19 +998,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(storage || host_view == image.views.end());
const auto aspect = host_view->info.aspect;
const bool depth_feedback =
layout == vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT &&
program.stage == ShaderType::Pixel;
const bool depth_read =
depth_feedback || layout == vk::ImageLayout::eDepthReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
const bool stencil_read =
layout == vk::ImageLayout::eStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
if ((aspect & vk::ImageAspectFlagBits::eDepth && !depth_read) ||
(aspect & vk::ImageAspectFlagBits::eStencil && !stencil_read)) {
if (aspect & ~DepthReadableAspects(layout)) {
EXIT("sampling a writable depth/stencil attachment aspect\n");
}
}
@@ -204,6 +204,7 @@ struct PipelineCache::ProgramCache {
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
std::vector<Permutation> permutations;
bool skip_dispatch = false;
};
struct ProgramKeyHash {
@@ -283,6 +284,9 @@ struct PipelineCache::ProgramCache {
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
BuildStageStaticKey(input_info, lookup_key.static_state);
auto entry = programs.find(lookup_key);
if (entry != programs.end() && entry->second.skip_dispatch) {
return {};
}
const ShaderRecompiler::IR::SrtRuntime runtime {
.user_data = user_data,
.shader_base = params.Base(),
@@ -348,6 +352,11 @@ struct PipelineCache::ProgramCache {
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
if (translated.skip_dispatch) {
entry = programs.try_emplace(lookup_key, ShaderRecompiler::IR::ResourcePlan {}).first;
entry->second.skip_dispatch = true;
return {};
}
if (entry == programs.end()) {
entry = programs.try_emplace(lookup_key,
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
@@ -368,6 +368,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
#if !defined(__APPLE__)
// The DB clamps depth to the viewport range after polygon offset is applied.
rasterizer.depthClampEnable = VK_TRUE;
rasterizer.pNext = &clip_ext;
#endif
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {};
+5 -2
View File
@@ -160,7 +160,7 @@ public:
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(PreparedBindings& bindings);
void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings);
@@ -196,7 +196,8 @@ private:
bool primitive_restart_enable);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel = std::nullopt);
vk::ImageAspectFlags& feedback_aspects,
std::span<PreparedBindings* const> stages = {});
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
@@ -209,6 +210,8 @@ private:
uint32_t group_y, uint32_t group_z, uint32_t mode);
RenderContext& m_context;
GraphicsBindings m_graphics_bindings;
PreparedBindings m_compute_bindings;
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
@@ -256,6 +256,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
input_info.dispatch_thread_dimensions = use_thread_dimensions;
const auto compute_program =
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
if (use_thread_dimensions) {
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
@@ -362,7 +366,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.PrepareBda();
@@ -412,9 +417,14 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
ShaderComputeInputInfo input_info {};
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
buffer.EndRendering();
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
const auto& program = *input_info.stage.program;
if (program.info.uses_dma) {
+35 -49
View File
@@ -453,8 +453,10 @@ struct DrawCallInfo {
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel) {
vk::ImageAspectFlags& feedback_aspects,
std::span<PreparedBindings* const> stages) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
feedback_aspects = {};
auto& cache = m_context.GetTextureCache();
RenderState state {};
state.width = std::numeric_limits<uint32_t>::max();
@@ -471,15 +473,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
}
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
"Kyty.MRT{}.Image[guest=0x{:016x} size=0x{:x} format={}]",
target.target_slot, image.info.data.address, image.info.data.size,
static_cast<uint32_t>(image.info.pixel_format));
SetVulkanObjectNameF(m_context.GetGraphics().device, image_view,
"Kyty.MRT{}.View[guest=0x{:016x} mip={} layer={}+{}]",
target.target_slot, image.info.data.address,
target.desc.view_info.base_level, target.desc.view_info.base_layer,
target.desc.view_info.layer_count);
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
const auto& view = target.desc.view_info;
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
@@ -520,37 +513,30 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
EXIT("failed to consume HTile clear state\n");
}
auto& image = cache.GetImage(depth.image_id);
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
"Kyty.DepthTarget.Image[guest=0x{:016x} size=0x{:x} format={}]",
image.info.data.address, image.info.data.size,
static_cast<uint32_t>(image.info.pixel_format));
SetVulkanObjectNameF(m_context.GetGraphics().device, image_view,
"Kyty.DepthTarget.View[guest=0x{:016x} layer={}+{}]",
image.info.data.address, depth.desc.view_info.base_layer,
depth.desc.view_info.layer_count);
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
const bool feedback = depth.depth_write_enable && pixel &&
std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) {
if (binding.image_id != depth.image_id ||
binding.desc.type != TextureCache::BindingType::Texture) {
return false;
}
const auto native =
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(native == image.views.end());
const auto& sampled = native->info;
const auto& target = depth.desc.view_info;
return (sampled.aspect & vk::ImageAspectFlagBits::eDepth) &&
ImageRangeOverlaps(sampled.base_level, sampled.level_count,
target.base_level, target.level_count) &&
ImageRangeOverlaps(sampled.base_layer, sampled.layer_count,
target.base_layer, target.layer_count);
});
if (feedback && !m_context.GetGraphics().attachment_feedback_loop_enabled) {
const auto draw_writes = depth.AttachmentWriteAspects();
vk::ImageAspectFlags sampled_aspects;
for (const auto* stage: stages) {
for (const auto& binding: stage->images) {
if (binding.image_id != depth.image_id ||
binding.desc.type != TextureCache::BindingType::Texture) continue;
const auto native =
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(native == image.views.end());
sampled_aspects |= native->info.aspect;
feedback_aspects |= DepthFeedbackAspects(draw_writes, depth.desc.view_info,
native->info);
}
}
if (feedback_aspects && !m_context.GetGraphics().attachment_feedback_loop_enabled) {
EXIT("depth attachment feedback loop is not supported by the host\n");
}
const auto layout = feedback ? vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
: depth_attachment_layout(depth);
auto layout = depth_attachment_layout(depth);
if (sampled_aspects & ~DepthReadableAspects(layout)) {
layout = m_context.GetGraphics().attachment_feedback_loop_enabled
? vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
: vk::ImageLayout::eGeneral;
}
// The attachment store writes even when guest depth/stencil tests do not.
const auto access = vk::AccessFlagBits2::eDepthStencilAttachmentRead |
vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
@@ -683,11 +669,13 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
// Collect the non-empty guest vertex ranges.
std::array<uint64_t, ShaderVertexInputInfo::RES_MAX> sizes {};
std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> ranges {};
uint32_t range_count = 0;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex, vs_input_info);
sizes[i] = size;
if (size == 0) {
continue;
}
@@ -736,7 +724,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex, vs_input_info);
const auto size = sizes[i];
if (size == 0) {
if (null_buffer == nullptr) {
null_buffer = cache.GetBuffer(NULL_BUFFER_ID).Handle();
@@ -1073,15 +1061,16 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
index_source.guest_element_size);
}
LogDrawPhase(draw.Name(), "PrepareBindings");
GraphicsBindings bindings;
auto& bindings = m_graphics_bindings;
std::array<PreparedBindings*, 4> descriptor_stages {};
uint32_t stage_count = 0;
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
PrepareBindings(state.vertex_info[i].stage, bindings.vertex[i]);
descriptor_stages[stage_count++] = &bindings.vertex[i];
}
if (state.ps_active) {
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
if (!bindings.pixel) bindings.pixel.emplace();
PrepareBindings(state.ps_input_info.stage, *bindings.pixel);
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
@@ -1100,9 +1089,10 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.programs);
vk::ImageAspectFlags feedback_aspects;
const auto rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
bindings.pixel);
feedback_aspects, stages);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1112,7 +1102,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (!mesh_active) {
CommitVertexBuffers(vk_buffer, vertex_bindings);
}
if (bindings.pixel && !draw.IsIndexed()) {
if (state.ps_active && !draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
@@ -1134,11 +1124,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
rendering.depth_stencil_attachment.image_layout ==
vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
? vk::ImageAspectFlags {vk::ImageAspectFlagBits::eDepth}
: vk::ImageAspectFlags {});
vk_buffer.setAttachmentFeedbackLoopEnableEXT(feedback_aspects);
}
LogDrawPhase(draw.Name(), "BeginRendering");
-20
View File
@@ -112,26 +112,6 @@ void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Write);
}
void WriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
uint64_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), 0,
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Write);
LOGF_COLOR(Log::Color::BrightGreen,
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
}
void WriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), 0,
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::WriteBack);
LOGF_COLOR(Log::Color::BrightGreen,
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
}
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
-4
View File
@@ -25,10 +25,6 @@ void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst
uint64_t value);
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t dw_offset, uint32_t dw_num);
void WriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
uint64_t value);
void WriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value);
void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value);
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
+19 -17
View File
@@ -24,6 +24,8 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <fmt/format.h>
#include <list>
#include <thread>
#include <vector>
@@ -541,6 +543,14 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
}
if (attribute.pixel_format == Graphics::VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(fmt::format(
"Warning: HDR-to-SDR conversion is not implemented; displaying PQ video-out "
"pixels unchanged (format=0x{:016x}).\n", attribute.pixel_format));
}
}
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
const auto pitch =
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
@@ -565,7 +575,12 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
info.bgra16 = pixel_format.bgra16;
info.mip_layout[0] = {0, total.size, pitch, attribute.height};
if (compression != Graphics::VideoOutCompression::Uncompressed) {
info.metadata.range = {buffer.metadata_address, 0};
Graphics::TileSizeAlign dcc_size {};
if (!Graphics::TileGetDccSize(attribute.width, attribute.height, 1,
pixel_format.bytes_per_element, 1, tile_mode, dcc_size)) {
EXIT("invalid video-out DCC footprint\n");
}
info.metadata.range = {buffer.metadata_address, dcc_size.size};
info.metadata.kind = Graphics::ImageMetadataKind::Dcc;
info.metadata.control = attribute.dcc_control;
info.metadata.compression = compression;
@@ -1541,7 +1556,9 @@ KYTY_SYSV_ABI int VideoOutIsFlipPending(int handle) {
VideoOutFlipStatus status {};
DriverState().GetFlipQueue().GetFlipStatus(*ctx, status);
LOGF("\t flipPendingNum = %d\n", status.flipPendingNum);
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("\t flipPendingNum = %d\n", status.flipPendingNum);
}
return status.flipPendingNum;
}
@@ -1675,21 +1692,6 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
return OK;
}
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status) {
PRINT_NAME();
if (status == nullptr) {
return VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
if (DriverState().Get(handle) == nullptr) {
return VIDEO_OUT_ERROR_INVALID_HANDLE;
}
// Kyty currently presents at a fixed refresh rate and does not negotiate VRR.
*status = 0;
return OK;
}
static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options,
void* reserved_ptr, uint64_t reserved) {
if (!DriverState().IsOpened(handle)) {
-1
View File
@@ -87,7 +87,6 @@ KYTY_SYSV_ABI int VideoOutGetEventData(const LibKernel::EventQueue::KernelEvent*
KYTY_SYSV_ABI int VideoOutGetEventCount(const LibKernel::EventQueue::KernelEvent* ev);
KYTY_SYSV_ABI int VideoOutWaitVblank(int handle);
KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status);
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status);
KYTY_SYSV_ABI int VideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
KYTY_SYSV_ABI int VideoOutIsOutputSupported(int handle, uint64_t mode,
const VideoOutOutputOptions* options,
@@ -171,6 +171,13 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height,
static_cast<int>(format));
}
auto& dst = image;
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
dst.extent.height == extent.height && dst.format == format;
if (compatible) {
return;
}
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
const auto required =
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
@@ -180,12 +187,6 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
}
auto& dst = image;
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
dst.extent.height == extent.height && dst.format == format;
if (compatible) {
return;
}
if (dst.image != nullptr) {
graphics.DeleteImage(dst);
}
@@ -238,6 +238,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
#endif
}
#if !defined(__APPLE__)
if (device_features2.features.depthClamp != VK_TRUE) {
LOGF("depthClamp is not supported\n");
skip_device = true;
}
if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) {
LOGF("fragmentShaderBarycentric is not supported\n");
skip_device = true;
@@ -607,6 +611,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
device_features.robustBufferAccess = VK_TRUE;
#if !defined(__APPLE__)
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
device_features.depthClamp = VK_TRUE;
#endif
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
device_features.shaderImageGatherExtended = VK_TRUE;
@@ -20,6 +20,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <fmt/format.h>
#include <map>
@@ -523,6 +524,20 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(decoded.instructions.size()), phase_ms());
// Temporary workaround for games that compile ray-tracing shaders before
// the player can select a mode without ray tracing.
if (options.stage == ShaderType::Compute && decoded.has_bvh) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
const auto& bvh = decoded.instructions.back();
Log::WriteToConsoleAndLog(fmt::format(
"Warning: ray tracing is not implemented; skipping compute dispatches containing "
"BVH intersection instructions (shader=0x{:016x}, pc=0x{:08x}, opcode=0x{:02x}).\n",
options.shader_hash, bvh.pc, bvh.opcode_id));
}
return {.skip_dispatch = true};
}
std::string decoded_dump;
if (options.dump_ir) {
decoded_dump = Decoder::ProgramToString(decoded);
@@ -615,6 +630,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
const IR::ResourceSpecialization& specialization,
uint32_t push_data_start_dword) {
EXIT_IF(translated.skip_dispatch);
const auto emit_begin = std::chrono::steady_clock::now();
auto& ir = translated.program;
IR::ApplyResourceSpecialization(ir, specialization);
@@ -28,6 +28,7 @@ struct TranslateResult {
IR::Program program;
std::string decoded_dump;
std::string cfg_dump;
bool skip_dispatch = false;
};
struct CompileResult {
@@ -227,6 +227,7 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
requirements.subgroup_local_invocation_id = true;
}
if (memory.kind == IR::ResourceKind::Buffer) {
requirements.coherent_buffers |= memory.coherent;
if (memory.resource >= program.info.buffers.size()) {
Fail(program, "buffer operation has invalid resource metadata");
}
@@ -784,11 +784,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
? &state.program.descriptor_sources[image.source]
: nullptr;
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
source->indirect_image->key_arg >= handle->NumArgs()) {
handle->NumArgs() == 0u) {
ctx.Fail(inst, "has invalid indirect image key provenance");
return;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
const auto key = ctx.Def(handle->Arg(0));
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
@@ -67,6 +67,7 @@ struct SpirvRequirements {
bool function_scratch = false;
bool pixel_valid_mask = false;
bool buffer_int64_atomics = false;
bool coherent_buffers = false;
};
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
@@ -371,12 +372,13 @@ void EmitMemoryOffsets(EmitterState& state);
uint32_t LdsDwordCount(const EmitterState& state);
struct MemoryResourceAccess {
IR::ResourceKind kind = IR::ResourceKind::None;
uint32_t object_pointer = 0;
uint32_t length = 0;
uint32_t index_offset = 0;
uint32_t byte_offset = 0;
bool add_index_offset = false;
IR::ResourceKind kind = IR::ResourceKind::None;
uint32_t object_pointer = 0;
uint32_t length = 0;
uint32_t index_offset = 0;
uint32_t byte_offset = 0;
bool add_index_offset = false;
spv::MemoryAccessMask memory_access = spv::MemoryAccessMaskNone;
};
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
@@ -313,7 +313,8 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
uint32_t index) {
const auto value = ctx.state.builder.AllocateId();
const auto pointer = EmitMemoryElementPointer(ctx.state, resource, index);
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer);
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer,
resource.memory_access);
return value;
}
@@ -498,21 +499,21 @@ void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
});
}
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
uint32_t data) {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data,
resource.memory_access);
}
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t data) {
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
StoreWordInBounds(ctx, resource, index, data);
});
}
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
uint32_t data) {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
}
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
@@ -710,10 +711,14 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
uint32_t data) {
if (component >= plan.info.component_count) return;
const auto bits = plan.info.component_bits[component];
if (plan.info.type == Format::ComponentType::Snorm && bits == 16u) {
if (bits == 16u && (plan.info.type == Format::ComponentType::Snorm ||
plan.info.type == Format::ComponentType::Float)) {
const auto value = EmitBitCastF32U32(ctx.state, data);
data = EmitPackSnorm2x16(
ctx.state, EmitCompositeConstructF32x2(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)));
const auto pair = EmitCompositeConstructF32x2(ctx.state, value,
ConstantF32Value(ctx.state, 0.0f));
data = plan.info.type == Format::ComponentType::Float
? EmitPackHalf2x16(ctx.state, pair)
: EmitPackSnorm2x16(ctx.state, pair);
}
if (bits == 8u || bits == 16u) {
StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component],
@@ -86,7 +86,9 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
}
const auto array_index =
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
MemoryResourceAccess access {.kind = mem.kind};
MemoryResourceAccess access {
.kind = mem.kind,
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
access.object_pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
ConstantU32(state, array_index));
@@ -201,6 +201,16 @@ void DefineDescriptors(EmitterState& state) {
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationAliased);
}
if (state.requirements.coherent_buffers) {
// RDNA2 stores publish to L2 even without GLC; every alias of the buffer
// must participate in visibility for cache-bypassing polling loads.
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_variable,
spv::DecorationCoherent);
if (state.storage_buffer_u64_variable != 0) {
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationCoherent);
}
}
break;
case IR::DescriptorBindingKind::BdaPagetable:
state.bda_pagetable_variable = Define(StorageBufferU64Type(state), "bda_pagetable");
@@ -1389,6 +1389,15 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
if (construct_predecessors.empty() || (!force_split && !has_external_predecessor)) {
return false;
}
if (construct_predecessors.size() == 1u) {
const auto* predecessor = graph.FindBlock(construct_predecessors.front());
if (predecessor != nullptr && predecessor->inst_begin == predecessor->inst_end &&
predecessor->terminator.kind == TerminatorKind::Branch &&
predecessor->terminator.true_block == merge &&
predecessor->start_pc == merge_block->start_pc) {
return false;
}
}
const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge);
auto* synthetic_block = graph.FindBlock(synthetic_merge);
if (synthetic_block != nullptr) {
@@ -21,6 +21,7 @@ constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
{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},
{0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32},
{0x25u, Opcode::S_MEMREALTIME, 2, 32},
};
constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
@@ -86,6 +87,7 @@ constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = {
constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x00u, Opcode::DS_ADD_U32, 1, 32}, {0x01u, Opcode::DS_SUB_U32, 1, 32},
{0x03u, Opcode::DS_INC_U32, 1, 32}, {0x04u, Opcode::DS_DEC_U32, 1, 32},
{0x05u, Opcode::DS_MIN_I32, 1, 32}, {0x06u, Opcode::DS_MAX_I32, 1, 32},
{0x07u, Opcode::DS_MIN_U32, 1, 32}, {0x08u, Opcode::DS_MAX_U32, 1, 32},
{0x09u, Opcode::DS_AND_B32, 1, 32}, {0x0au, Opcode::DS_OR_B32, 1, 32},
@@ -107,6 +109,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
{0xa0u, Opcode::DS_WRITE_B8_D16_HI, 1, 8},
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
{0xa6u, Opcode::DS_READ_U16_D16, 1, 16},
{0xa7u, Opcode::DS_READ_U16_D16_HI, 1, 16},
@@ -147,6 +150,7 @@ bool IsDsWriteOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
@@ -166,7 +170,9 @@ bool IsDsAtomicOpcode(Opcode opcode) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
@@ -240,6 +246,10 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
}
DecodeScalarDestination(sdst, pc, inst.dst);
if (inst.opcode == Opcode::S_MEMREALTIME) {
inst.src_count = 0;
return;
}
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
// pair index; their descriptor operand consumes four SGPRs from that base.
DecodeScalarSource(sbase * 2u, pc, inst.src0);
@@ -263,6 +273,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.dlc = ((word0 >> 15u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MUBUF;
inst.opcode_id = opcode;
@@ -298,6 +309,7 @@ void DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.dlc = ((word0 >> 15u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MTBUF;
inst.opcode_id = opcode;
@@ -429,7 +441,9 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
}
DecodeVectorGpr(addr, inst.src0);
DecodeVectorGpr(data0, inst.src1);
if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
if (inst.opcode == Opcode::DS_WRITE_B8_D16_HI) {
inst.src1.sdwa_sel = 2u;
} else if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
inst.src1.sdwa_sel = 5u;
}
DecodeVectorGpr(data1, inst.src2);
@@ -73,11 +73,11 @@ std::string FormatMemory(const Instruction& inst) {
text += OperandToString(*sources[i]);
}
text += fmt::format(" ; offset={} offset2={} dwords={} bits={} dfmt={} nfmt={} signed={} "
"typed={} formatted={} segment={} glc={} slc={} idxen={} offen={}",
"typed={} formatted={} segment={} glc={} dlc={} slc={} idxen={} offen={}",
inst.offset, inst.secondary_offset, inst.data_dwords, inst.data_bits,
inst.data_format, inst.number_format, inst.data_signed ? 1u : 0u,
inst.typed ? 1u : 0u, inst.formatted ? 1u : 0u, inst.memory_segment,
inst.glc ? 1u : 0u, inst.slc ? 1u : 0u, inst.idxen ? 1u : 0u,
inst.glc ? 1u : 0u, inst.dlc ? 1u : 0u, inst.slc ? 1u : 0u, inst.idxen ? 1u : 0u,
inst.offen ? 1u : 0u);
return text;
}
@@ -403,6 +403,7 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
program.instructions.clear();
program.instructions.reserve(code.size());
program.code = code;
program.has_bvh = false;
std::vector<bool> branch_targets;
for (uint32_t word_index = 0; word_index < code.size();) {
@@ -411,6 +412,10 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
const auto& inst = program.instructions.back();
word_index += inst.word_count;
if (inst.family == Family::MIMG && (inst.opcode_id == 0xe6u || inst.opcode_id == 0xe7u)) {
program.has_bvh = true;
return;
}
if (IsDirectBranch(inst.opcode)) {
const auto target_index = inst.branch_target / sizeof(uint32_t);
@@ -659,7 +664,9 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
@@ -694,6 +701,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_READ_B128:
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
@@ -354,6 +354,7 @@ enum class Opcode {
V_CMPX_GT_F32,
V_CMPX_LG_F32,
V_CMPX_GE_F32,
V_CMPX_O_F32,
V_CMPX_NGE_F32,
V_CMPX_NLG_F32,
V_CMPX_NGT_F32,
@@ -404,6 +405,7 @@ enum class Opcode {
V_CMP_LE_U16,
V_CMP_GT_U16,
V_CMPX_LT_U16,
V_CMPX_EQ_U16,
V_CMPX_GT_U16,
V_CMP_NE_U16,
V_CMP_GE_U16,
@@ -439,6 +441,7 @@ enum class Opcode {
S_BUFFER_LOAD_DWORDX4,
S_BUFFER_LOAD_DWORDX8,
S_BUFFER_LOAD_DWORDX16,
S_MEMREALTIME,
BUFFER_LOAD_FORMAT_X,
BUFFER_LOAD_FORMAT_XY,
BUFFER_LOAD_FORMAT_XYZ,
@@ -502,7 +505,9 @@ enum class Opcode {
DS_ADD_RTN_U32,
DS_SUB_U32,
DS_SUB_RTN_U32,
DS_INC_U32,
DS_INC_RTN_U32,
DS_DEC_U32,
DS_DEC_RTN_U32,
DS_MIN_I32,
DS_MIN_RTN_I32,
@@ -541,6 +546,7 @@ enum class Opcode {
DS_READ_B128,
DS_WRITE_B8,
DS_WRITE_B16,
DS_WRITE_B8_D16_HI,
DS_WRITE_B16_D16_HI,
DS_WRITE2_B32,
DS_WRITE2ST64_B32,
@@ -708,6 +714,7 @@ struct Instruction {
bool formatted = false;
bool gds = false;
bool glc = false;
bool dlc = false;
bool slc = false;
bool idxen = false;
bool offen = false;
@@ -726,6 +733,7 @@ struct Instruction {
struct Program {
std::span<const uint32_t> code;
std::vector<Instruction> instructions;
bool has_bvh = false;
};
// Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs.
@@ -40,7 +40,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32},
{0x12u, Opcode::V_MAX_I32},
{0x12u, Opcode::V_MAX_I32, Vop2SdwaProfile::IntegerFullDestination},
{0x13u, Opcode::V_MIN_U32, Vop2SdwaProfile::IntegerPartialDestination},
{0x14u, Opcode::V_MAX_U32, Vop2SdwaProfile::IntegerFullDestination},
{0x15u, Opcode::V_LSHR_B32},
@@ -214,6 +214,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0x11u, Opcode::V_CMPX_LT_F32}, {0x12u, Opcode::V_CMPX_EQ_F32},
{0x13u, Opcode::V_CMPX_LE_F32}, {0x14u, Opcode::V_CMPX_GT_F32},
{0x15u, Opcode::V_CMPX_LG_F32}, {0x16u, Opcode::V_CMPX_GE_F32},
{0x17u, Opcode::V_CMPX_O_F32},
{0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32},
{0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32},
{0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32},
@@ -231,7 +232,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
{0xb9u, Opcode::V_CMPX_LT_U16, false},
{0xb9u, Opcode::V_CMPX_LT_U16, false}, {0xbau, Opcode::V_CMPX_EQ_U16, false},
{0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32},
{0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32},
{0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32},
@@ -820,6 +821,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
case Opcode::V_CMPX_GT_F32:
case Opcode::V_CMPX_LG_F32:
case Opcode::V_CMPX_GE_F32:
case Opcode::V_CMPX_O_F32:
case Opcode::V_CMPX_NGE_F32:
case Opcode::V_CMPX_NLG_F32:
case Opcode::V_CMPX_NGT_F32:
@@ -1432,6 +1434,7 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_GT_F32:
case Opcode::V_CMPX_LG_F32:
case Opcode::V_CMPX_GE_F32:
case Opcode::V_CMPX_O_F32:
case Opcode::V_CMPX_NGE_F32:
case Opcode::V_CMPX_NLG_F32:
case Opcode::V_CMPX_NGT_F32:
@@ -1454,6 +1457,7 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_NE_I64:
case Opcode::V_CMPX_NE_U64:
case Opcode::V_CMPX_LT_U16:
case Opcode::V_CMPX_EQ_U16:
case Opcode::V_CMPX_GT_U16:
case Opcode::V_CMPX_LT_F16:
case Opcode::V_CMPX_EQ_F16:
@@ -106,12 +106,12 @@ void Translator::EXP(const Decoder::Instruction& inst) {
ir.Emit(IR::ValueOpcode::SetAttribute, {data, ir.GetExec()}, AddExportInfo(inst));
}
bool Translator::EmitInterpolation(const Decoder::Instruction& inst) {
void Translator::EmitInterpolation(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return true;
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return true;
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return true;
default: return false;
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return;
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return;
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return;
default: return FailMissingTranslation(inst);
}
}
@@ -47,12 +47,12 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false, cmpx);
}
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered) {
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx) {
const auto lhs = IR::F32(ReadOperand(inst.src0, IR::Type::F32));
const auto rhs = IR::F32(ReadOperand(inst.src1, IR::Type::F32));
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {lhs})),
IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {rhs})));
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, false);
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
}
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx) {
@@ -302,23 +302,95 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) {
}
void Translator::S_WQM(const Decoder::Instruction& inst, bool wide) {
const auto source = wide ? ReadU64(inst.src0)
: ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u)));
const bool source_is_mask = inst.src0.kind == Decoder::OperandKind::Sgpr ||
inst.src0.kind == Decoder::OperandKind::ExecLo ||
inst.src0.kind == Decoder::OperandKind::VccLo ||
(!wide && IsExecOrVcc(inst.src0));
const auto source_valid = ReadMaskValid(inst.src0);
auto source_live = IR::U1(IR::Value(false));
if (source_is_mask) {
if (inst.src0.kind == Decoder::OperandKind::VccLo ||
inst.src0.kind == Decoder::OperandKind::VccHi) {
source_live = ir.GetVcc();
} else {
source_live = ReadMask(inst.src0);
}
}
const auto retained_live = ir.LogicalAnd(source_valid, source_live);
IR::U64 source;
if (wide) {
source = ReadU64(inst.src0);
} else {
source = ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u)));
}
const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {source}));
if (!wide) {
const auto low = ir.CompositeExtract(result, 0);
const bool destination_is_mask = IsExecOrVcc(inst.dst);
const bool destination_is_exec = inst.dst.kind == Decoder::OperandKind::ExecLo ||
inst.dst.kind == Decoder::OperandKind::ExecHi;
auto old_live = IR::U1(IR::Value(false));
if (destination_is_mask) {
if (destination_is_exec) {
old_live = ir.GetExec();
} else {
old_live = ir.GetVcc();
}
}
// A 32-bit write preserves the other EXEC/VCC half and invalidates any
// overlapping SGPR-pair mask provenance through the ordinary scalar path.
WriteRawU32(inst.dst, low);
if (destination_is_mask) {
const auto is_high_half = [](const Decoder::Operand& operand) {
return operand.kind == Decoder::OperandKind::ExecHi ||
operand.kind == Decoder::OperandKind::VccHi;
};
const bool destination_is_high = is_high_half(inst.dst);
auto affected_lanes = IR::U1(IR::Value(!destination_is_high));
if (program.wave_size == 64u) {
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
affected_lanes = ir.ULessThan(lane, IR::U32(IR::Value(32u)));
if (destination_is_high) {
affected_lanes = ir.LogicalNot(affected_lanes);
}
}
IR::U1 live;
if (destination_is_exec) {
live = ir.GetExec();
} else {
live = ir.GetVcc();
}
// A cross-half copy refers to different lanes, so only scalar bits apply.
if (is_high_half(inst.src0) == destination_is_high) {
live = ir.LogicalOr(retained_live, live);
}
live = IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {affected_lanes, live, old_live}));
if (destination_is_exec) {
ir.SetExec(live);
} else {
ir.SetVcc(live);
}
}
ir.SetScc(ir.INotEqual(low, IR::U32(IR::Value(0u))));
return;
}
const auto mask_valid = ReadMaskValid(inst.src0);
// WQM adds live lanes but never removes a source lane. Keep that fact separate
// from the scalar mask: reconstructing an entry-true predicate from a ballot
// otherwise introduces a spurious dependency into every vector write.
const auto live = ir.LogicalOr(retained_live, ThreadBit(ExtractU64(result)));
WriteOperand(DestinationOperand(inst), result);
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
if (inst.dst.kind == Decoder::OperandKind::ExecLo) {
ir.SetExec(live);
} else if (inst.dst.kind == Decoder::OperandKind::VccLo) {
ir.SetVcc(live);
} else if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
ir.SetThreadBitScalarReg(dst, ThreadBit(ExtractU64(result)));
ir.SetScalarMaskTag(dst, mask_valid);
ir.SetThreadBitScalarReg(dst, live);
ir.SetScalarMaskTag(dst, source_valid);
}
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
}
@@ -3,6 +3,11 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
void Translator::FailMissingTranslation(const Decoder::Instruction& inst) {
EXIT("opcode %s at pc 0x%08x has no IR translation",
Decoder::InstructionToString(inst).c_str(), inst.pc);
}
void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
current_opcode = inst.opcode;
current_pc = inst.pc;
@@ -16,35 +21,26 @@ void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
default: break;
}
bool translated = false;
switch (inst.family) {
case Decoder::Family::SOP1:
case Decoder::Family::SOP2:
case Decoder::Family::SOPK:
case Decoder::Family::SOPC:
case Decoder::Family::SOPP: translated = EmitScalar(inst); break;
case Decoder::Family::SOPP: return EmitScalar(inst);
case Decoder::Family::VOP1:
case Decoder::Family::VOP2:
case Decoder::Family::VOP3:
case Decoder::Family::VOP3P:
case Decoder::Family::VOPC: translated = EmitVector(inst); break;
case Decoder::Family::VOPC: return EmitVector(inst);
case Decoder::Family::SMEM:
case Decoder::Family::MUBUF:
case Decoder::Family::MTBUF:
case Decoder::Family::FLAT:
case Decoder::Family::DS:
case Decoder::Family::MIMG: translated = EmitMemory(inst); break;
case Decoder::Family::VINTRP: translated = EmitInterpolation(inst); break;
case Decoder::Family::EXP:
EXP(inst);
translated = true;
break;
default: break;
}
if (!translated) {
EXIT("opcode %s at pc 0x%08x has no IR translation",
Decoder::InstructionToString(inst).c_str(), inst.pc);
case Decoder::Family::MIMG: return EmitMemory(inst);
case Decoder::Family::VINTRP: return EmitInterpolation(inst);
case Decoder::Family::EXP: return EXP(inst);
default: return FailMissingTranslation(inst);
}
}
@@ -5,7 +5,7 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
bool Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool accumulator, bool quiet_snan) {
const auto translate_lane = [&](bool high) {
const auto lhs = ReadF16LaneAsF32(inst.src0, high, true);
@@ -43,16 +43,15 @@ bool Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode
result = PackU16Lanes(override_lane(false), override_lane(true));
}
WriteOperand(raw, result);
return true;
}
bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool invalid_negative) {
const auto argument = ReadF16AsF32(inst.src0);
auto result = IR::F32(ir.Emit(opcode, {argument}));
if (!invalid_negative) {
WriteF16(DestinationOperand(inst), result);
return true;
return;
}
const auto negative =
IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {argument, IR::Value::F32(0.0f)}));
@@ -60,10 +59,9 @@ bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
Write16Bits(DestinationOperand(inst), ir.Select(negative, invalid, bits));
return true;
}
bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto argument = ReadF16AsF32(inst.src0);
const auto magnitude =
ir.BitwiseAnd(ir.BitCastU32(argument), IR::U32(IR::Value(0x7fffffffu)));
@@ -96,18 +94,16 @@ bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode o
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
Write16Bits(DestinationOperand(inst), ir.Select(infinite, invalid, bits));
return true;
}
bool Translator::Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool reverse) {
const auto lhs = ReadF16AsF32(reverse ? inst.src1 : inst.src0);
const auto rhs = ReadF16AsF32(reverse ? inst.src0 : inst.src1);
WriteF16(DestinationOperand(inst), IR::F32(ir.Emit(opcode, {lhs, rhs})));
return true;
}
bool Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool accumulator, bool mix) {
std::array<IR::Value, 3> args;
for (uint32_t index = 0; index < args.size(); index++) {
@@ -115,16 +111,14 @@ bool Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcod
args[index] = mix ? IR::Value(ReadMixF32(operand)) : IR::Value(ReadF16AsF32(operand));
}
WriteF16(DestinationOperand(inst), IR::F32(ir.Emit(opcode, {args[0], args[1], args[2]})));
return true;
}
bool Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto type = IR::ArgTypeOf(opcode, 0);
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
return true;
}
bool Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool reverse) {
std::array<IR::Value, 2> args;
for (uint32_t index = 0; index < args.size(); index++) {
@@ -132,10 +126,9 @@ bool Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode o
args[index] = ReadOperand(operand, IR::ArgTypeOf(opcode, index));
}
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {args[0], args[1]}));
return true;
}
bool Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool accumulator, bool mix) {
std::array<IR::Value, 3> args;
for (uint32_t index = 0; index < args.size(); index++) {
@@ -145,10 +138,9 @@ bool Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode
: ReadOperand(operand, type);
}
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {args[0], args[1], args[2]}));
return true;
}
bool Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
void Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
const auto bits = ReadU32(inst.src0);
const auto exponent =
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(23u), IR::Value(8u)}));
@@ -166,10 +158,9 @@ bool Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
ir.Select(zero, bits, subnormal));
const auto result = ir.Select(ir.IEqual(exponent, IR::U32(IR::Value(0xffu))), bits, finite);
WriteOperand(DestinationOperand(inst), ir.BitCastF32(result));
return true;
}
bool Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
void Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
auto a = inst.src0;
a.op_sel = false;
a.op_sel_hi = true;
@@ -183,26 +174,25 @@ bool Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
const auto acc = ReadMixF32(inst.dst);
const auto lo = IR::F32(ir.Emit(IR::ValueOpcode::FPFma32, {a_lo, b_lo, acc}));
WriteOperand(DestinationOperand(inst), ir.Emit(IR::ValueOpcode::FPFma32, {a_hi, b_hi, lo}));
return true;
}
bool Translator::V_CUBEID_F32(const Decoder::Instruction& inst) {
void Translator::V_CUBEID_F32(const Decoder::Instruction& inst) {
return FloatCube(inst, 0u);
}
bool Translator::V_CUBESC_F32(const Decoder::Instruction& inst) {
void Translator::V_CUBESC_F32(const Decoder::Instruction& inst) {
return FloatCube(inst, 1u);
}
bool Translator::V_CUBETC_F32(const Decoder::Instruction& inst) {
void Translator::V_CUBETC_F32(const Decoder::Instruction& inst) {
return FloatCube(inst, 2u);
}
bool Translator::V_CUBEMA_F32(const Decoder::Instruction& inst) {
void Translator::V_CUBEMA_F32(const Decoder::Instruction& inst) {
return FloatCube(inst, 3u);
}
bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kind) {
void Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kind) {
const auto x = ReadMixF32(inst.src0);
const auto y = ReadMixF32(inst.src1);
const auto z = ReadMixF32(inst.src2);
@@ -242,7 +232,6 @@ bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kin
default: EXIT("invalid cube result kind");
}
WriteOperand(DestinationOperand(inst), result);
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
@@ -4,25 +4,23 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
bool Translator::Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic) {
const auto value = ReadU16AsU32(inst.src1, arithmetic);
const auto count = ir.BitwiseAnd(ReadU16AsU32(inst.src0, false), IR::U32(IR::Value(15u)));
const auto result = IR::U32(ir.Emit(opcode, {value, count}));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool sign) {
const auto lhs = ReadU16AsU32(inst.src0, sign);
const auto rhs = ReadU16AsU32(inst.src1, sign);
const auto result = IR::U32(ir.Emit(opcode, {lhs, rhs}));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
const auto lhs = ReadU16AsU32(inst.src0, true);
const auto rhs = ReadU16AsU32(inst.src1, true);
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
@@ -31,18 +29,16 @@ bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)}));
}
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
bool Translator::V_MED3_I16(const Decoder::Instruction& inst) {
void Translator::V_MED3_I16(const Decoder::Instruction& inst) {
const auto result = IR::U32(ir.Emit(
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
ReadU16AsU32(inst.src2, true)}));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
bool Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic) {
const auto translate_lane = [&](bool high) {
const auto count =
@@ -52,10 +48,9 @@ bool Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::Valu
};
WriteOperand(DestinationOperand(inst),
PackU16Lanes(translate_lane(false), translate_lane(true)));
return true;
}
bool Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto translate_lane = [&](bool high) {
const auto lhs = ReadU16LaneAsU32(inst.src0, high, false);
const auto rhs = ReadU16LaneAsU32(inst.src1, high, false);
@@ -63,10 +58,9 @@ bool Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::Val
};
WriteOperand(DestinationOperand(inst),
PackU16Lanes(translate_lane(false), translate_lane(true)));
return true;
}
bool Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign) {
void Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign) {
const auto translate_lane = [&](bool high) {
const auto lhs = ReadU16LaneAsU32(inst.src0, high, false);
const auto rhs = ReadU16LaneAsU32(inst.src1, high, false);
@@ -74,10 +68,9 @@ bool Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign)
};
WriteOperand(DestinationOperand(inst),
PackU16Lanes(translate_lane(false), translate_lane(true)));
return true;
}
bool Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool sign) {
const auto translate_lane = [&](bool high) {
const auto lhs = ReadU16LaneAsU32(inst.src0, high, sign);
@@ -86,7 +79,6 @@ bool Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::Val
};
WriteOperand(DestinationOperand(inst),
PackU16Lanes(translate_lane(false), translate_lane(true)));
return true;
}
IR::U1 Translator::U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
@@ -100,7 +92,7 @@ IR::U1 Translator::U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpco
return negate_result ? ir.LogicalNot(result) : result;
}
bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result,
bool unary) {
const auto invocation_result =
@@ -119,7 +111,7 @@ bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
const auto mask = WriteMask(inst.dst, invocation_result, true);
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
return true;
return;
}
const auto lhs = ReadU32Pair(inst.src0);
@@ -146,10 +138,9 @@ bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
ir.SetScalarMaskTag(dst, mask_valid);
}
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
return true;
}
bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
IR::Type type, bool reverse, bool mask_shift_count,
bool update_scc) {
std::array<IR::Value, 3> args;
@@ -177,10 +168,9 @@ bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
ir.SetScc(ir.INotEqual(IR::U32(result), IR::U32(IR::Value(0u))));
}
}
return true;
}
bool Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
void Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
// Signed 64-bit sources sign-extend literals; generic B64 operands zero-extend them.
const auto source =
inst.src0.kind == Decoder::OperandKind::LiteralConstant
@@ -192,10 +182,9 @@ bool Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {source, ReadU32(inst.src1)});
WriteOperand(inst.dst, result);
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
return true;
}
bool Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool negate_rhs, bool negate_result, bool update_scc) {
const auto lhs = ReadU32(inst.src0);
auto rhs = ReadU32(inst.src1);
@@ -210,31 +199,27 @@ bool Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::Val
if (update_scc) {
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
}
return true;
}
bool Translator::V_AND_OR_B32(const Decoder::Instruction& inst) {
void Translator::V_AND_OR_B32(const Decoder::Instruction& inst) {
const auto result =
ir.BitwiseOr(ir.BitwiseAnd(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_OR3_B32(const Decoder::Instruction& inst) {
void Translator::V_OR3_B32(const Decoder::Instruction& inst) {
const auto result =
ir.BitwiseOr(ir.BitwiseOr(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
void Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
const auto result =
ir.BitwiseXor(ir.BitwiseXor(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
void Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
const auto source = ExtractU64(ReadU64(inst.src0));
const auto low_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[0]}));
const auto high_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[1]}));
@@ -243,10 +228,9 @@ bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
ir.Select(ir.INotEqual(source[1], IR::U32(IR::Value(0u))),
high_position, IR::U32(IR::Value(0xffffffffu))));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
const auto source = ReadU32(inst.src0);
const auto value = sign ? ir.BitwiseXor(
source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))))
@@ -256,10 +240,9 @@ bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
const auto result = ir.Select(ir.INotEqual(value, IR::U32(IR::Value(0u))), position,
IR::U32(IR::Value(0xffffffffu)));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
void Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
const auto source = ReadU64(inst.src0);
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb64, {source}));
const auto position = ir.ISub(IR::U32(IR::Value(63u)), msb);
@@ -268,10 +251,9 @@ bool Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
{source, ir.ConstructU64(IR::U32(IR::Value(0u)), IR::U32(IR::Value(0u)))}));
const auto result = ir.Select(nonzero, position, IR::U32(IR::Value(0xffffffffu)));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
const auto extract24 = [&](IR::U32 value) {
return IR::U32(
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
@@ -284,10 +266,9 @@ bool Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
result = ir.IAdd(result, ReadU32(inst.src2));
}
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
const auto lhs = ReadU32(inst.src0);
const auto rhs = ReadU32(inst.src1);
const auto add = ExtractU64(ReadU64(inst.src2));
@@ -305,36 +286,32 @@ bool Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
inst.dst2.kind != Decoder::OperandKind::Unknown) {
WriteMask(inst.dst2, ir.LogicalOr(carry0, carry1));
}
return true;
}
bool Translator::V_SAD_U32(const Decoder::Instruction& inst) {
void Translator::V_SAD_U32(const Decoder::Instruction& inst) {
const auto lhs = ReadU32(inst.src0);
const auto rhs = ReadU32(inst.src1);
const auto lo = IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {lhs, rhs}));
const auto hi = IR::U32(ir.Emit(IR::ValueOpcode::UMax32, {lhs, rhs}));
const auto result = ir.IAdd(ir.ISub(hi, lo), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_ADD3_U32(const Decoder::Instruction& inst) {
void Translator::V_ADD3_U32(const Decoder::Instruction& inst) {
const auto result =
ir.IAdd(ir.IAdd(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_BITSET_B32(const Decoder::Instruction& inst, bool set) {
void Translator::S_BITSET_B32(const Decoder::Instruction& inst, bool set) {
const auto offset = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(31u)));
const auto bit = ir.ShiftLeftLogical(IR::U32(IR::Value(1u)), offset);
const auto old = ReadU32(inst.dst);
const auto result = set ? ir.BitwiseOr(old, bit) : ir.BitwiseAnd(old, ir.BitwiseNot(bit));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
void Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
const auto offset = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(63u)));
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
const auto bit = ir.ShiftLeftLogical(IR::U32(IR::Value(1u)), word_bit);
@@ -347,16 +324,14 @@ bool Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
{offset, IR::Value(32u)}));
WriteU32Pair(inst.dst,
{ir.Select(high, old[0], low_value), ir.Select(high, high_value, old[1])});
return true;
}
bool Translator::V_BCNT_U32_B32(const Decoder::Instruction& inst) {
void Translator::V_BCNT_U32_B32(const Decoder::Instruction& inst) {
const auto count = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {ReadU32(inst.src0)}));
WriteOperand(DestinationOperand(inst), ir.IAdd(count, ReadU32(inst.src1)));
return true;
}
bool Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
void Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
const auto local = ir.BitwiseAnd(lane, IR::U32(IR::Value(31u)));
const auto below =
@@ -368,10 +343,9 @@ bool Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
const auto active = ir.BitwiseAnd(ReadU32(inst.src0), thread_mask);
const auto count = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {active}));
WriteOperand(DestinationOperand(inst), ir.IAdd(count, ReadU32(inst.src1)));
return true;
}
bool Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
const auto replicate = [&](IR::U32 value) {
auto bits = ir.BitwiseOr(value, ir.ShiftLeftLogical(value, IR::U32(IR::Value(8u))));
bits = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x00ff00ffu)));
@@ -387,10 +361,9 @@ bool Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
const auto low = ir.BitwiseAnd(source, IR::U32(IR::Value(0xffffu)));
const auto high = ir.ShiftRightLogical(source, IR::U32(IR::Value(16u)));
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
return true;
}
bool Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
const auto compact = [&](IR::U32 value) {
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u))));
@@ -409,16 +382,14 @@ bool Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u)));
WriteOperand(DestinationOperand(inst), result);
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
return true;
}
bool Translator::BFM_B32(const Decoder::Instruction& inst) {
void Translator::BFM_B32(const Decoder::Instruction& inst) {
const auto count = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(31u)));
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
const auto result = ir.Emit(IR::ValueOpcode::BitFieldInsert,
{IR::Value(0u), IR::Value(0xffffffffu), offset, count});
WriteOperand(DestinationOperand(inst), result);
return true;
}
IR::U32 Translator::RightMask32(IR::U32 count) {
@@ -435,15 +406,14 @@ IR::U64 Translator::RightMask64(IR::U32 count) {
return ir.ConstructU64(RightMask32(low_count), RightMask32(high_count));
}
bool Translator::S_BFM_B64(const Decoder::Instruction& inst) {
void Translator::S_BFM_B64(const Decoder::Instruction& inst) {
const auto count = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(63u)));
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
const auto result = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {RightMask64(count), offset});
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
void Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
const auto source = ReadU32(inst.src0);
const auto field = ReadU32(inst.src1);
const auto offset =
@@ -457,10 +427,9 @@ bool Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
const auto result = IR::U32(ir.Emit(opcode, {source, offset, count}));
WriteOperand(DestinationOperand(inst), result);
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
return true;
}
bool Translator::S_BFE_U64(const Decoder::Instruction& inst) {
void Translator::S_BFE_U64(const Decoder::Instruction& inst) {
const auto source = ReadU64(inst.src0);
const auto field = ReadU32(inst.src1);
const auto offset =
@@ -473,10 +442,9 @@ bool Translator::S_BFE_U64(const Decoder::Instruction& inst) {
const auto result = ir.Emit(IR::ValueOpcode::BitwiseAnd64, {shifted, RightMask64(count)});
WriteOperand(DestinationOperand(inst), result);
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
return true;
}
bool Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
void Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
const auto source = ReadU32(inst.src0);
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
const auto raw_count = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
@@ -485,29 +453,26 @@ bool Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
const auto opcode =
sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract;
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {source, offset, count}));
return true;
}
bool Translator::V_BFI_B32(const Decoder::Instruction& inst) {
void Translator::V_BFI_B32(const Decoder::Instruction& inst) {
const auto bits = ReadU32(inst.src0);
const auto insert = ReadU32(inst.src1);
const auto base = ReadU32(inst.src2);
const auto result =
ir.BitwiseOr(ir.BitwiseAnd(bits, insert), ir.BitwiseAnd(ir.BitwiseNot(bits), base));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
void Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
const auto value = ReadU32(inst.src0);
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
const auto bit =
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
return true;
}
bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
const auto value = ReadU32Pair(inst.src0);
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
@@ -516,10 +481,9 @@ bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
const auto bit =
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
return true;
}
bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
void Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
const auto hi = ReadU32(inst.src0);
const auto lo = ReadU32(inst.src1);
const auto shift = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
@@ -530,10 +494,9 @@ bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
const auto hi_part =
ir.Select(ir.INotEqual(shift, IR::U32(IR::Value(0u))), hi_part_raw, IR::U32(IR::Value(0u)));
WriteOperand(DestinationOperand(inst), ir.BitwiseOr(lo_part, hi_part));
return true;
}
bool Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
void Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
const auto hi = ReadU32(inst.src0);
const auto lo = ReadU32(inst.src1);
const auto byte_offset = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
@@ -546,39 +509,34 @@ bool Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
IR::U1(ir.Emit(IR::ValueOpcode::ULessThan32, {byte_offset, IR::Value(8u)}));
WriteOperand(DestinationOperand(inst),
ir.Select(in_range, ExtractU64(shifted)[0], IR::U32(IR::Value(0u))));
return true;
}
bool Translator::V_LSHL_ADD_U32(const Decoder::Instruction& inst) {
void Translator::V_LSHL_ADD_U32(const Decoder::Instruction& inst) {
const auto shift = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
const auto result = ir.IAdd(ir.ShiftLeftLogical(ReadU32(inst.src0), shift), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_ADD_LSHL_U32(const Decoder::Instruction& inst) {
void Translator::V_ADD_LSHL_U32(const Decoder::Instruction& inst) {
const auto shift = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
const auto result = ir.ShiftLeftLogical(ir.IAdd(ReadU32(inst.src0), ReadU32(inst.src1)), shift);
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_XAD_U32(const Decoder::Instruction& inst) {
void Translator::V_XAD_U32(const Decoder::Instruction& inst) {
const auto result =
ir.IAdd(ir.BitwiseXor(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_LSHL_OR_B32(const Decoder::Instruction& inst) {
void Translator::V_LSHL_OR_B32(const Decoder::Instruction& inst) {
const auto shift = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
const auto result =
ir.BitwiseOr(ir.ShiftLeftLogical(ReadU32(inst.src0), shift), ReadU32(inst.src2));
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
void Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
Decoder::Operand mask_operand;
mask_operand.kind = Decoder::OperandKind::VccLo;
if (inst.src_count >= 3u) {
@@ -594,17 +552,15 @@ bool Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
result = ir.Select(condition, ReadU32(inst.src1), ReadU32(inst.src0));
}
WriteOperand(DestinationOperand(inst), result);
return true;
}
bool Translator::PackB16(const Decoder::Instruction& inst, bool high0, bool high1) {
void Translator::PackB16(const Decoder::Instruction& inst, bool high0, bool high1) {
const auto lo = high0 ? ir.ShiftRightLogical(ReadU32(inst.src0), IR::U32(IR::Value(16u)))
: ReadU32(inst.src0);
const auto hi = high1 ? ir.ShiftRightLogical(ReadU32(inst.src1), IR::U32(IR::Value(16u)))
: ReadU32(inst.src1);
const auto result = PackU16Lanes(lo, hi);
WriteOperand(DestinationOperand(inst), result);
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
@@ -1,8 +1,11 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm>
#include <array>
#include <atomic>
namespace Libs::Graphics::ShaderRecompiler::Frontend {
@@ -123,6 +126,11 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
memory.image_r128 = decoded.image_r128;
memory.idxen = decoded.idxen;
memory.offen = decoded.offen;
// Vector loads use GLC/DLC to bypass L0/GL1; atomics use GLC only to return data.
const bool buffer_atomic = decoded.opcode >= Decoder::Opcode::BUFFER_ATOMIC_SWAP &&
decoded.opcode <= Decoder::Opcode::BUFFER_ATOMIC_FMAX;
memory.coherent = memory.kind == ResourceKind::Buffer && !buffer_atomic &&
(decoded.glc || decoded.dlc);
memory.resource = ResourceIndexFromOperand(decoded.src1);
memory.sampler = ResourceIndexFromOperand(decoded.src2);
if (memory.kind == ResourceKind::ScalarBuffer) {
@@ -235,6 +243,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
return index == 0u ? decoded.src1 : index == 1u ? decoded.src0 : decoded.src2;
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B8_D16_HI:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
@@ -396,7 +405,7 @@ IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
: ir.Emit(IR::ValueOpcode::ConvertU16U32, {value});
}
bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
void Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource =
raw ? GetScalarAddressResource(RawScalarLoadBase(inst.src0)) : GetBufferResource(memory);
@@ -419,10 +428,9 @@ bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
for (uint32_t component = 0; component < memory.data_dwords; component++) {
WriteOperand(ScalarDestinationOperand(inst.dst, component), loaded[component]);
}
return true;
}
bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
IR::ValueOpcode opcode;
const auto bits = memory.data_bits;
@@ -436,10 +444,15 @@ bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
case 2u: opcode = IR::ValueOpcode::LoadBufferU32x2; break;
case 3u: opcode = IR::ValueOpcode::LoadBufferU32x3; break;
case 4u: opcode = IR::ValueOpcode::LoadBufferU32x4; break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported buffer load dword count %u",
Decoder::InstructionToString(inst).c_str(), inst.pc,
memory.data_dwords);
}
break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported buffer load width %u",
Decoder::InstructionToString(inst).c_str(), inst.pc, bits);
}
const auto resource = GetBufferResource(memory);
const auto address = ReadBufferAddress(inst, 0);
@@ -456,10 +469,9 @@ bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
ir.CompositeExtract(loaded, component));
}
}
return true;
}
bool Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
void Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetBufferResource(memory);
const auto address = ReadBufferAddress(inst, 1);
@@ -500,17 +512,21 @@ bool Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
ReadU32(OffsetOperand(data_src, 2u)),
ReadU32(OffsetOperand(data_src, 3u))});
break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported buffer store dword count %u",
Decoder::InstructionToString(inst).c_str(), inst.pc,
memory.data_dwords);
}
break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported buffer store width %u",
Decoder::InstructionToString(inst).c_str(), inst.pc, memory.data_bits);
}
ir.Emit(opcode, {resource, address.index, address.offset, address.soffset, value, ir.GetExec()},
AddMemoryInfo(memory, inst.pc));
return true;
}
bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetBufferResource(memory);
const auto address = ReadBufferAddress(inst, 1);
@@ -535,10 +551,9 @@ bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
if (inst.glc) {
WriteOperand(inst.dst, result);
}
return true;
}
bool Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 1));
@@ -548,10 +563,9 @@ bool Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
if (inst.glc) {
WriteOperand(inst.dst, result);
}
return true;
}
bool Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool returns_value) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto address = ReadU32(MemorySourceAt(inst, 1));
@@ -560,10 +574,9 @@ bool Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opc
if (returns_value) {
WriteOperand(inst.dst, result);
}
return true;
}
bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
void Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
IR::ValueOpcode opcode;
const auto bits = memory.data_bits;
@@ -572,7 +585,9 @@ bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
case 8u: opcode = IR::ValueOpcode::LoadAddressU8; break;
case 16u: opcode = IR::ValueOpcode::LoadAddressU16; break;
case 32u: opcode = IR::ValueOpcode::LoadAddressU32; break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported flat load width %u",
Decoder::InstructionToString(inst).c_str(), inst.pc, bits);
}
const auto address = ReadAddressOperands(inst, 0);
const auto active = ir.GetExec();
@@ -587,10 +602,9 @@ bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
WriteOperand(OffsetOperand(inst.dst, index),
bits == 32u ? loaded : WidenSubdword(loaded, bits, sign));
}
return true;
}
bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
void Translator::FLAT_STORE(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto data_op = MemorySourceAt(inst, 0);
const auto address = ReadAddressOperands(inst, 1);
@@ -599,7 +613,9 @@ bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
case 8u: opcode = IR::ValueOpcode::StoreAddressU8; break;
case 16u: opcode = IR::ValueOpcode::StoreAddressU16; break;
case 32u: opcode = IR::ValueOpcode::StoreAddressU32; break;
default: return false;
default:
EXIT("opcode %s at pc 0x%08x has unsupported flat store width %u",
Decoder::InstructionToString(inst).c_str(), inst.pc, memory.data_bits);
}
const auto count = memory.data_bits == 32u ? memory.data_dwords : 1u;
for (uint32_t index = 0; index < count; index++) {
@@ -614,20 +630,18 @@ bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
ir.Emit(opcode, {address.resource, address.low, address.high, value, ir.GetExec()},
AddMemoryInfo(component, inst.pc));
}
return true;
}
bool Translator::IMAGE_GET_RESINFO(const Decoder::Instruction& inst) {
void Translator::IMAGE_GET_RESINFO(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
const auto result = ir.Emit(IR::ValueOpcode::ImageQueryDimensions, {resource, address},
AddMemoryInfo(memory, inst.pc));
WriteImageComponents(inst.dst, result, memory, 4u);
return true;
}
bool Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
void Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto sampler = GetSamplerResource(memory);
@@ -635,30 +649,27 @@ bool Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
const auto result = ir.Emit(IR::ValueOpcode::ImageQueryLod, {resource, sampler, address},
AddMemoryInfo(memory, inst.pc));
WriteImageComponents(inst.dst, result, memory, 2u);
return true;
}
bool Translator::IMAGE_LOAD(const Decoder::Instruction& inst) {
void Translator::IMAGE_LOAD(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
const auto result = ir.Emit(IR::ValueOpcode::ImageRead, {resource, address, ir.GetExec()},
AddMemoryInfo(memory, inst.pc));
WriteImageComponents(inst.dst, result, memory, 4u);
return true;
}
bool Translator::IMAGE_STORE(const Decoder::Instruction& inst) {
void Translator::IMAGE_STORE(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 1));
const auto data = ConstructU32x4(MemorySourceAt(inst, 0), memory.data_dwords);
ir.Emit(IR::ValueOpcode::ImageWrite, {resource, address, data, ir.GetExec()},
AddMemoryInfo(memory, inst.pc));
return true;
}
bool Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
void Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto sampler = GetSamplerResource(memory);
@@ -675,10 +686,9 @@ bool Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
} else {
WriteImageComponents(inst.dst, result, memory, 4u);
}
return true;
}
bool Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto sampler = GetSamplerResource(memory);
@@ -689,7 +699,6 @@ bool Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
WriteOperand(OffsetOperand(inst.dst, index),
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)}));
}
return true;
}
IR::Value Translator::LoadSharedU32(uint32_t width, IR::U32 address, const IR::MemoryInfo& memory,
@@ -745,7 +754,7 @@ void Translator::WriteSharedU32(uint32_t width, IR::U32 address,
}
}
bool Translator::DS_READ(const Decoder::Instruction& inst) {
void Translator::DS_READ(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto address = ReadU32(MemorySourceAt(inst, 0));
if (memory.data_bits == 32u) {
@@ -754,16 +763,15 @@ bool Translator::DS_READ(const Decoder::Instruction& inst) {
for (uint32_t index = 0; index < width; index++) {
WriteOperand(OffsetOperand(inst.dst, index), ExtractSharedU32(loaded, width, index));
}
return true;
return;
}
const auto opcode =
memory.data_bits == 8u ? IR::ValueOpcode::LoadSharedU8 : IR::ValueOpcode::LoadSharedU16;
const auto loaded = ir.Emit(opcode, {address, ir.GetExec()}, AddMemoryInfo(memory, inst.pc));
WriteOperand(inst.dst, WidenSubdword(loaded, memory.data_bits, memory.data_signed));
return true;
}
bool Translator::DS_READ2(const Decoder::Instruction& inst) {
void Translator::DS_READ2(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto width = memory.data_dwords / 2u;
const auto address = ReadU32(MemorySourceAt(inst, 0));
@@ -782,10 +790,9 @@ bool Translator::DS_READ2(const Decoder::Instruction& inst) {
WriteOperand(OffsetOperand(inst.dst, width + index),
ExtractSharedU32(second_value, width, index));
}
return true;
}
bool Translator::DS_WRITE(const Decoder::Instruction& inst) {
void Translator::DS_WRITE(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto width = memory.data_dwords;
const auto data_operand = MemorySourceAt(inst, 0);
@@ -796,16 +803,15 @@ bool Translator::DS_WRITE(const Decoder::Instruction& inst) {
const auto address = ReadU32(MemorySourceAt(inst, 1));
if (memory.data_bits == 32u) {
WriteSharedU32(width, address, values, memory, inst.pc);
return true;
return;
}
const auto opcode =
memory.data_bits == 8u ? IR::ValueOpcode::WriteSharedU8 : IR::ValueOpcode::WriteSharedU16;
ir.Emit(opcode, {address, NarrowSubdword(IR::U32(values[0]), memory.data_bits), ir.GetExec()},
AddMemoryInfo(memory, inst.pc));
return true;
}
bool Translator::DS_WRITE2(const Decoder::Instruction& inst) {
void Translator::DS_WRITE2(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto width = memory.data_dwords / 2u;
const auto address = ReadU32(MemorySourceAt(inst, 1));
@@ -826,28 +832,25 @@ bool Translator::DS_WRITE2(const Decoder::Instruction& inst) {
second.offset = memory.secondary_offset;
WriteSharedU32(width, address, second_values, second, inst.pc);
}
return true;
}
bool Translator::DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto memory = MemoryInfoFromDecoded(inst);
ir.Emit(opcode,
{ReadU32(MemorySourceAt(inst, 1)), ReadU32(MemorySourceAt(inst, 0)),
ReadU32(MemorySourceAt(inst, 2)), ir.GetExec()},
AddMemoryInfo(memory, inst.pc));
return true;
}
bool Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
const auto memory = MemoryInfoFromDecoded(inst);
WriteOperand(inst.dst, ir.Emit(opcode,
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec(), ir.GetExecLo(),
ir.GetExecHi()},
AddMemoryInfo(memory, inst.pc)));
return true;
}
bool Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
void Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto base =
ir.BitwiseAnd(ReadU32(MemorySourceAt(inst, write ? 1u : 0u)), IR::U32(IR::Value(0xffffu)));
@@ -861,24 +864,21 @@ bool Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::LoadSharedU32, {address, ir.GetExec()},
AddMemoryInfo(memory, inst.pc)));
}
return true;
}
bool Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
void Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::SwizzleU32,
{ReadU32(MemorySourceAt(inst, 0)),
ReadU32(MemorySourceAt(inst, 1)), ir.GetExec()}));
return true;
}
bool Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
void Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
const auto address = ir.IAdd(ReadU32(inst.src0), IR::U32(IR::Value(inst.offset)));
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::BpermuteU32,
{ReadU32(inst.src1), address, ir.GetExec()}));
return true;
}
bool Translator::EmitMemory(const Decoder::Instruction& inst) {
void Translator::EmitMemory(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
@@ -890,6 +890,17 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX4:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX8:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX16: return S_LOAD(inst, false);
case Decoder::Opcode::S_MEMREALTIME: {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"Warning: S_MEMREALTIME uses placeholder UINT64_MAX; real-time clock not implemented.\n");
}
for (uint32_t component = 0; component < 2; component++) {
WriteOperand(ScalarDestinationOperand(inst.dst, component), IR::Value(UINT32_MAX));
}
return;
}
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
case Decoder::Opcode::BUFFER_LOAD_SBYTE:
@@ -962,8 +973,12 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, false);
case Decoder::Opcode::DS_SUB_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, true);
case Decoder::Opcode::DS_INC_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicInc32, false);
case Decoder::Opcode::DS_INC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicInc32, true);
case Decoder::Opcode::DS_DEC_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicDec32, false);
case Decoder::Opcode::DS_DEC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicDec32, true);
case Decoder::Opcode::DS_MIN_I32:
@@ -1076,12 +1091,13 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::DS_WRITE2ST64_B64: return DS_WRITE2(inst);
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B8_D16_HI:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
case Decoder::Opcode::DS_WRITE_B96:
case Decoder::Opcode::DS_WRITE_B128: return DS_WRITE(inst);
default: return false;
default: return FailMissingTranslation(inst);
}
}
@@ -2,106 +2,106 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
bool Translator::EmitScalar(const Decoder::Instruction& inst) {
void Translator::EmitScalar(const Decoder::Instruction& inst) {
using O = Decoder::Opcode;
switch (inst.opcode) {
case O::S_MOV_B32:
case O::S_MOVK_I32: MOV_B32(inst, false); return true;
case O::S_MOV_B64: S_MOV_B64(inst); return true;
case O::S_WQM_B32: S_WQM(inst, false); return true;
case O::S_WQM_B64: S_WQM(inst, true); return true;
case O::S_GETPC_B64: S_GETPC_B64(inst); return true;
case O::S_SETPC_B64: return true;
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return true;
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return true;
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true;
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return true;
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return true;
case O::S_SETREG_B32: EmitControlNop(); return true;
case O::S_WAITCNT: EmitWaitcnt(); return true;
case O::S_MOVK_I32: MOV_B32(inst, false); return;
case O::S_MOV_B64: S_MOV_B64(inst); return;
case O::S_WQM_B32: S_WQM(inst, false); return;
case O::S_WQM_B64: S_WQM(inst, true); return;
case O::S_GETPC_B64: S_GETPC_B64(inst); return;
case O::S_SETPC_B64: return;
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return;
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return;
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return;
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return;
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return;
case O::S_SETREG_B32: EmitControlNop(); return;
case O::S_WAITCNT: EmitWaitcnt(); return;
case O::S_AND_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, false);
return true;
return;
case O::S_ANDN1_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, false);
return true;
return;
case O::S_ORN2_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, false);
return true;
return;
case O::S_AND_SAVEEXEC_B64:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, true);
return true;
return;
case O::S_ANDN1_SAVEEXEC_B64:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, true);
return true;
return;
case O::S_ORN2_SAVEEXEC_B64:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, true);
return true;
case O::S_ADD_U32: ADD_U32(inst, false, false); return true;
case O::S_ADDC_U32: ADD_U32(inst, false, true); return true;
case O::S_SUB_U32: SUB_U32(inst, false, false); return true;
case O::S_SUBB_U32: SUBB_U32(inst, false, false); return true;
case O::S_ABSDIFF_I32: S_ABSDIFF_I32(inst); return true;
case O::S_ADD_I32: S_ADD_SUB_I32(inst, false); return true;
case O::S_SUB_I32: S_ADD_SUB_I32(inst, true); return true;
case O::S_LSHL1_ADD_U32: S_LSHL_ADD_U32(inst, 1u); return true;
case O::S_LSHL2_ADD_U32: S_LSHL_ADD_U32(inst, 2u); return true;
case O::S_LSHL3_ADD_U32: S_LSHL_ADD_U32(inst, 3u); return true;
case O::S_LSHL4_ADD_U32: S_LSHL_ADD_U32(inst, 4u); return true;
return;
case O::S_ADD_U32: ADD_U32(inst, false, false); return;
case O::S_ADDC_U32: ADD_U32(inst, false, true); return;
case O::S_SUB_U32: SUB_U32(inst, false, false); return;
case O::S_SUBB_U32: SUBB_U32(inst, false, false); return;
case O::S_ABSDIFF_I32: S_ABSDIFF_I32(inst); return;
case O::S_ADD_I32: S_ADD_SUB_I32(inst, false); return;
case O::S_SUB_I32: S_ADD_SUB_I32(inst, true); return;
case O::S_LSHL1_ADD_U32: S_LSHL_ADD_U32(inst, 1u); return;
case O::S_LSHL2_ADD_U32: S_LSHL_ADD_U32(inst, 2u); return;
case O::S_LSHL3_ADD_U32: S_LSHL_ADD_U32(inst, 3u); return;
case O::S_LSHL4_ADD_U32: S_LSHL_ADD_U32(inst, 4u); return;
case O::S_MIN_I32:
ScalarMinMax32(inst, IR::ValueOpcode::SMin32, IR::ValueOpcode::SLessThan32);
return true;
return;
case O::S_MAX_I32:
ScalarMinMax32(inst, IR::ValueOpcode::SMax32, IR::ValueOpcode::SGreaterThan32);
return true;
return;
case O::S_MIN_U32:
ScalarMinMax32(inst, IR::ValueOpcode::UMin32, IR::ValueOpcode::ULessThan32);
return true;
return;
case O::S_MAX_U32:
ScalarMinMax32(inst, IR::ValueOpcode::UMax32, IR::ValueOpcode::UGreaterThan32);
return true;
return;
case O::S_CMP_EQ_U32:
case O::S_CMP_EQ_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_LG_U32:
case O::S_CMP_LG_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_GT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_GE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
false);
return true;
return;
case O::S_CMP_LT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_LE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_GT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_GE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
false);
return true;
return;
case O::S_CMP_LT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_LE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true, false);
return true;
return;
case O::S_CMP_EQ_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true, false);
return true;
return;
case O::S_CMP_LG_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true, false);
return true;
return;
case O::S_AND_B64:
return S_U64_MASK(inst, IR::ValueOpcode::LogicalAnd, IR::ValueOpcode::BitwiseAnd32,
@@ -217,12 +217,12 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_NOP:
case O::S_SLEEP:
case O::S_SETPRIO:
case O::S_TRAP: EmitControlNop(); return true;
case O::S_WAITCNT_DEPCTR: EmitWaitcnt(); return true;
case O::S_BARRIER: S_BARRIER(); return true;
case O::S_SENDMSG: S_SENDMSG(inst); return true;
case O::S_TTRACEDATA: S_TTRACEDATA(); return true;
case O::S_INST_PREFETCH: S_INST_PREFETCH(); return true;
case O::S_TRAP: EmitControlNop(); return;
case O::S_WAITCNT_DEPCTR: EmitWaitcnt(); return;
case O::S_BARRIER: S_BARRIER(); return;
case O::S_SENDMSG: S_SENDMSG(inst); return;
case O::S_TTRACEDATA: S_TTRACEDATA(); return;
case O::S_INST_PREFETCH: S_INST_PREFETCH(); return;
case O::S_BRANCH:
case O::S_CBRANCH_SCC0:
case O::S_CBRANCH_SCC1:
@@ -231,8 +231,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_CBRANCH_EXECZ:
case O::S_CBRANCH_EXECNZ:
case O::S_CBRANCH_CDBGSYS:
case O::S_ENDPGM: return true;
default: return false;
case O::S_ENDPGM: return;
default: return FailMissingTranslation(inst);
}
}
@@ -1063,8 +1063,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
}
} else if (options.stage == ShaderType::Mesh) {
const auto& mesh = options.input_info.vertex->mesh;
EXIT_NOT_IMPLEMENTED(options.wave_size != 64u || mesh.primitives_per_group == 0u ||
mesh.vertices_per_group > 64u || total_threads > 15u * 64u);
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 64u ||
total_threads > 15u * options.wave_size);
const auto u32 = [](uint32_t value) { return IR::U32(IR::Value(value)); };
const auto draw = [&](uint32_t index) {
return IR::U32(
@@ -1089,12 +1089,15 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
entry_ir.IAdd(IR::U32(entry_ir.Emit(IR::ValueOpcode::UDiv32,
{subtract_saturate(vertices, size), step})),
u32(1)));
const auto wave = entry_ir.ShiftRightLogical(local, u32(6));
const auto wave_base = entry_ir.BitwiseAnd(local, u32(~63u));
const auto vertex_count = minimum(subtract_saturate(vertices, wave_base), u32(64));
const auto primitive_count = minimum(subtract_saturate(primitives, wave_base), u32(64));
const auto wave = entry_ir.ShiftRightLogical(local, u32(options.wave_size == 32u ? 5u : 6u));
const auto wave_base = entry_ir.BitwiseAnd(local, u32(~(options.wave_size - 1u)));
const auto vertex_count =
minimum(subtract_saturate(vertices, wave_base), u32(options.wave_size));
const auto primitive_count =
minimum(subtract_saturate(primitives, wave_base), u32(options.wave_size));
const auto wave_info = entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(wave, u32(24)),
u32(((total_threads + 63u) / 64u) << 28u));
u32(((total_threads + options.wave_size - 1u) /
options.wave_size) << 28u));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(3),
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
@@ -80,34 +80,34 @@ private:
BufferAddress ReadBufferAddress(const Decoder::Instruction& inst, uint32_t source_offset);
IR::U32 WidenSubdword(IR::Value value, uint32_t bits, bool sign);
IR::Value NarrowSubdword(IR::U32 value, uint32_t bits);
bool S_LOAD(const Decoder::Instruction& inst, bool raw);
bool BUFFER_LOAD(const Decoder::Instruction& inst);
bool BUFFER_STORE(const Decoder::Instruction& inst);
bool BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool returns_value);
bool FLAT_LOAD(const Decoder::Instruction& inst);
bool FLAT_STORE(const Decoder::Instruction& inst);
bool IMAGE_GET_RESINFO(const Decoder::Instruction& inst);
bool IMAGE_GET_LOD(const Decoder::Instruction& inst);
bool IMAGE_LOAD(const Decoder::Instruction& inst);
bool IMAGE_STORE(const Decoder::Instruction& inst);
bool IMAGE_SAMPLE(const Decoder::Instruction& inst);
bool IMAGE_GATHER(const Decoder::Instruction& inst);
void S_LOAD(const Decoder::Instruction& inst, bool raw);
void BUFFER_LOAD(const Decoder::Instruction& inst);
void BUFFER_STORE(const Decoder::Instruction& inst);
void BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool returns_value);
void FLAT_LOAD(const Decoder::Instruction& inst);
void FLAT_STORE(const Decoder::Instruction& inst);
void IMAGE_GET_RESINFO(const Decoder::Instruction& inst);
void IMAGE_GET_LOD(const Decoder::Instruction& inst);
void IMAGE_LOAD(const Decoder::Instruction& inst);
void IMAGE_STORE(const Decoder::Instruction& inst);
void IMAGE_SAMPLE(const Decoder::Instruction& inst);
void IMAGE_GATHER(const Decoder::Instruction& inst);
IR::Value LoadSharedU32(uint32_t width, IR::U32 address, const IR::MemoryInfo& memory,
uint32_t pc);
IR::Value ExtractSharedU32(IR::Value value, uint32_t width, uint32_t index);
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
const IR::MemoryInfo& memory, uint32_t pc);
bool DS_READ(const Decoder::Instruction& inst);
bool DS_READ2(const Decoder::Instruction& inst);
bool DS_WRITE(const Decoder::Instruction& inst);
bool DS_WRITE2(const Decoder::Instruction& inst);
bool DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool DS_ADDTID(const Decoder::Instruction& inst, bool write);
bool DS_SWIZZLE_B32(const Decoder::Instruction& inst);
bool DS_BPERMUTE_B32(const Decoder::Instruction& inst);
void DS_READ(const Decoder::Instruction& inst);
void DS_READ2(const Decoder::Instruction& inst);
void DS_WRITE(const Decoder::Instruction& inst);
void DS_WRITE2(const Decoder::Instruction& inst);
void DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void DS_ADDTID(const Decoder::Instruction& inst, bool write);
void DS_SWIZZLE_B32(const Decoder::Instruction& inst);
void DS_BPERMUTE_B32(const Decoder::Instruction& inst);
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
IR::U32 ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
@@ -124,7 +124,7 @@ private:
bool signed_value, bool cmpx);
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
bool cmpx);
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered);
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx);
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx);
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
void V_CVT_F32_U32(const Decoder::Instruction& inst);
@@ -143,77 +143,77 @@ private:
void V_CVT_PKNORM_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void V_CVT_PK_U8_F32(const Decoder::Instruction& inst);
void V_PACK_B32_F16(const Decoder::Instruction& inst);
bool PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
void PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
bool quiet_snan);
bool Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool invalid_negative);
bool Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
bool Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
void Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
bool mix);
bool FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
bool FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
bool mix);
bool V_FREXP_MANT_F32(const Decoder::Instruction& inst);
bool V_DOT2C_F32_F16(const Decoder::Instruction& inst);
bool V_CUBEID_F32(const Decoder::Instruction& inst);
bool V_CUBESC_F32(const Decoder::Instruction& inst);
bool V_CUBETC_F32(const Decoder::Instruction& inst);
bool V_CUBEMA_F32(const Decoder::Instruction& inst);
bool FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
bool Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
bool Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
bool V_MAD_I16(const Decoder::Instruction& inst);
bool V_MED3_I16(const Decoder::Instruction& inst);
bool PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void V_FREXP_MANT_F32(const Decoder::Instruction& inst);
void V_DOT2C_F32_F16(const Decoder::Instruction& inst);
void V_CUBEID_F32(const Decoder::Instruction& inst);
void V_CUBESC_F32(const Decoder::Instruction& inst);
void V_CUBETC_F32(const Decoder::Instruction& inst);
void V_CUBEMA_F32(const Decoder::Instruction& inst);
void FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void V_MAD_I16(const Decoder::Instruction& inst);
void V_MED3_I16(const Decoder::Instruction& inst);
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic);
bool PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
bool PackedInteger16Mad(const Decoder::Instruction& inst, bool sign);
bool PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
bool S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void PackedInteger16Mad(const Decoder::Instruction& inst, bool sign);
void PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result, bool unary);
IR::U1 U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool negate_rhs,
bool negate_result);
bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
void SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
bool reverse, bool mask_shift_count, bool update_scc);
bool S_ASHR_I64(const Decoder::Instruction& inst);
bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
void S_ASHR_I64(const Decoder::Instruction& inst);
void ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool negate_rhs, bool negate_result, bool update_scc);
bool V_AND_OR_B32(const Decoder::Instruction& inst);
bool V_OR3_B32(const Decoder::Instruction& inst);
bool V_XOR3_B32(const Decoder::Instruction& inst);
bool S_FF1_I32_B64(const Decoder::Instruction& inst);
bool V_FFBH_32(const Decoder::Instruction& inst, bool sign);
bool S_FLBIT_I32_B64(const Decoder::Instruction& inst);
bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
bool V_MAD_U64_U32(const Decoder::Instruction& inst);
bool V_SAD_U32(const Decoder::Instruction& inst);
bool V_ADD3_U32(const Decoder::Instruction& inst);
bool S_BITSET_B32(const Decoder::Instruction& inst, bool set);
bool S_BITSET_B64(const Decoder::Instruction& inst, bool set);
bool V_BCNT_U32_B32(const Decoder::Instruction& inst);
bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
bool S_QUADMASK_B64(const Decoder::Instruction& inst);
bool BFM_B32(const Decoder::Instruction& inst);
void V_AND_OR_B32(const Decoder::Instruction& inst);
void V_OR3_B32(const Decoder::Instruction& inst);
void V_XOR3_B32(const Decoder::Instruction& inst);
void S_FF1_I32_B64(const Decoder::Instruction& inst);
void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
void S_FLBIT_I32_B64(const Decoder::Instruction& inst);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
void V_MAD_U64_U32(const Decoder::Instruction& inst);
void V_SAD_U32(const Decoder::Instruction& inst);
void V_ADD3_U32(const Decoder::Instruction& inst);
void S_BITSET_B32(const Decoder::Instruction& inst, bool set);
void S_BITSET_B64(const Decoder::Instruction& inst, bool set);
void V_BCNT_U32_B32(const Decoder::Instruction& inst);
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
void S_QUADMASK_B64(const Decoder::Instruction& inst);
void BFM_B32(const Decoder::Instruction& inst);
IR::U32 RightMask32(IR::U32 count);
IR::U64 RightMask64(IR::U32 count);
bool S_BFM_B64(const Decoder::Instruction& inst);
bool S_BFE_U32(const Decoder::Instruction& inst, bool sign);
bool S_BFE_U64(const Decoder::Instruction& inst);
bool V_BFE_U32(const Decoder::Instruction& inst, bool sign);
bool V_BFI_B32(const Decoder::Instruction& inst);
bool S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
bool S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
bool V_ALIGNBIT_B32(const Decoder::Instruction& inst);
bool V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
bool V_LSHL_ADD_U32(const Decoder::Instruction& inst);
bool V_ADD_LSHL_U32(const Decoder::Instruction& inst);
bool V_XAD_U32(const Decoder::Instruction& inst);
bool V_LSHL_OR_B32(const Decoder::Instruction& inst);
bool V_CNDMASK_B32(const Decoder::Instruction& inst);
bool PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
void S_BFM_B64(const Decoder::Instruction& inst);
void S_BFE_U32(const Decoder::Instruction& inst, bool sign);
void S_BFE_U64(const Decoder::Instruction& inst);
void V_BFE_U32(const Decoder::Instruction& inst, bool sign);
void V_BFI_B32(const Decoder::Instruction& inst);
void S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
void S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
void V_ALIGNBIT_B32(const Decoder::Instruction& inst);
void V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
void V_LSHL_ADD_U32(const Decoder::Instruction& inst);
void V_ADD_LSHL_U32(const Decoder::Instruction& inst);
void V_XAD_U32(const Decoder::Instruction& inst);
void V_LSHL_OR_B32(const Decoder::Instruction& inst);
void V_CNDMASK_B32(const Decoder::Instruction& inst);
void PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
void S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin);
void S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec,
@@ -249,10 +249,11 @@ private:
void V_INTERP_MOV_F32(const Decoder::Instruction& inst);
void EXP(const Decoder::Instruction& inst);
bool EmitScalar(const Decoder::Instruction& inst);
bool EmitVector(const Decoder::Instruction& inst);
bool EmitInterpolation(const Decoder::Instruction& inst);
bool EmitMemory(const Decoder::Instruction& inst);
void EmitScalar(const Decoder::Instruction& inst);
void EmitVector(const Decoder::Instruction& inst);
void EmitInterpolation(const Decoder::Instruction& inst);
void EmitMemory(const Decoder::Instruction& inst);
static void FailMissingTranslation(const Decoder::Instruction& inst);
IR::Program& program;
IR::IREmitter ir;
@@ -2,318 +2,322 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
bool Translator::EmitVector(const Decoder::Instruction& inst) {
void Translator::EmitVector(const Decoder::Instruction& inst) {
using O = Decoder::Opcode;
switch (inst.opcode) {
case O::V_NOP: return true;
case O::V_ADD_I32: ADD_U32(inst, true, false); return true;
case O::V_ADDC_U32: ADD_U32(inst, true, true); return true;
case O::V_SUB_I32: SUB_U32(inst, true, false); return true;
case O::V_SUBREV_I32: SUB_U32(inst, true, true); return true;
case O::V_SUB_CO_CI_U32: SUBB_U32(inst, true, false); return true;
case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true, true); return true;
case O::V_NOP: return;
case O::V_ADD_I32: ADD_U32(inst, true, false); return;
case O::V_ADDC_U32: ADD_U32(inst, true, true); return;
case O::V_SUB_I32: SUB_U32(inst, true, false); return;
case O::V_SUBREV_I32: SUB_U32(inst, true, true); return;
case O::V_SUB_CO_CI_U32: SUBB_U32(inst, true, false); return;
case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true, true); return;
case O::V_MOV_B32: MOV_B32(inst, true); return true;
case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return true;
case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return true;
case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return true;
case O::V_READLANE_B32: V_READLANE_B32(inst); return true;
case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return true;
case O::V_PERMLANE16_B32: V_PERMLANE16_B32(inst, false); return true;
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return true;
case O::V_MOV_B32: MOV_B32(inst, true); return;
case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return;
case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return;
case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return;
case O::V_READLANE_B32: V_READLANE_B32(inst); return;
case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return;
case O::V_PERMLANE16_B32: V_PERMLANE16_B32(inst, false); return;
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
case O::V_CMP_F_I32:
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return true;
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return;
case O::V_CMP_T_I32:
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return true;
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return;
case O::V_CMP_EQ_U32:
case O::V_CMP_EQ_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_EQ_U32:
case O::V_CMPX_EQ_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_NE_U32:
case O::V_CMP_NE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_NE_U32:
case O::V_CMPX_NE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_GT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_GT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_GE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
false);
return true;
return;
case O::V_CMPX_GE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
true);
return true;
return;
case O::V_CMP_LT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_LT_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_LE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
false);
return true;
return;
case O::V_CMPX_LE_U32:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_GT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_GT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_GE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
false);
return true;
return;
case O::V_CMPX_GE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
true);
return true;
return;
case O::V_CMP_LT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
return true;
return;
case O::V_CMPX_LT_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_LE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
false);
return true;
return;
case O::V_CMPX_LE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
return true;
return;
case O::V_CMP_EQ_I64:
case O::V_CMP_EQ_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
return true;
return;
case O::V_CMP_LT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
return true;
return;
case O::V_CMP_GT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
return true;
return;
case O::V_CMP_NE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
return true;
return;
case O::V_CMPX_NE_I64:
case O::V_CMPX_NE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, true);
return true;
return;
case O::V_CMP_EQ_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
return true;
return;
case O::V_CMP_EQ_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
return true;
return;
case O::V_CMP_NE_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
return true;
return;
case O::V_CMP_NE_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
return true;
return;
case O::V_CMP_GT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
return true;
return;
case O::V_CMPX_GT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
return true;
return;
case O::V_CMP_GE_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
return true;
return;
case O::V_CMP_LT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
return true;
return;
case O::V_CMPX_LT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
return true;
return;
case O::V_CMPX_EQ_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
return;
case O::V_CMP_LE_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
return true;
return;
case O::V_CMP_GT_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
return true;
return;
case O::V_CMP_GE_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
return true;
return;
case O::V_CMP_LT_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
return true;
return;
case O::V_CMP_LE_I16:
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
return true;
return;
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return true;
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return true;
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
case O::V_CMP_EQ_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, false);
return true;
return;
case O::V_CMPX_EQ_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, true);
return true;
return;
case O::V_CMP_LG_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
return true;
return;
case O::V_CMPX_LG_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
return true;
return;
case O::V_CMP_GT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
return true;
return;
case O::V_CMPX_GT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
return true;
return;
case O::V_CMP_GE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
return true;
return;
case O::V_CMPX_GE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
return true;
return;
case O::V_CMP_LT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
return true;
return;
case O::V_CMPX_LT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
return true;
return;
case O::V_CMP_LE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
return true;
return;
case O::V_CMPX_LE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
return true;
return;
case O::V_CMP_NLG_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
return true;
return;
case O::V_CMPX_NLG_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
return true;
return;
case O::V_CMP_NEQ_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
return true;
return;
case O::V_CMPX_NEQ_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
return true;
return;
case O::V_CMP_NLE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
return true;
return;
case O::V_CMPX_NLE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
return true;
return;
case O::V_CMP_NLT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
return true;
return;
case O::V_CMPX_NLT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
return true;
return;
case O::V_CMP_NGE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
return true;
return;
case O::V_CMPX_NGE_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
return true;
return;
case O::V_CMP_NGT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
return true;
return;
case O::V_CMPX_NGT_F32:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
return true;
return;
case O::V_CMP_EQ_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
return true;
return;
case O::V_CMPX_EQ_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
return true;
return;
case O::V_CMP_LG_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
return true;
return;
case O::V_CMP_GT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
return true;
return;
case O::V_CMPX_GT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
return true;
return;
case O::V_CMP_GE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
return true;
return;
case O::V_CMPX_GE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
return true;
return;
case O::V_CMP_LT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
return true;
return;
case O::V_CMPX_LT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
return true;
return;
case O::V_CMP_LE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
return true;
return;
case O::V_CMPX_LE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
return true;
return;
case O::V_CMP_NGT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
return true;
return;
case O::V_CMPX_NGT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
return true;
return;
case O::V_CMP_NEQ_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
return true;
return;
case O::V_CMPX_NEQ_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
return true;
return;
case O::V_CMP_NLT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
return true;
return;
case O::V_CMPX_NLT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
return true;
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true); return true;
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false); return true;
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return true;
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return true;
return;
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return true;
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return true;
case O::V_CVT_F32_UBYTE2: V_CVT_F32_UBYTE(inst, 2); return true;
case O::V_CVT_F32_UBYTE3: V_CVT_F32_UBYTE(inst, 3); return true;
case O::V_CVT_F32_U32: V_CVT_F32_U32(inst); return true;
case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return true;
case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return true;
case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return true;
case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return true;
case O::V_CVT_F32_F16: V_CVT_F32_F16(inst); return true;
case O::V_CVT_F16_U16: V_CVT_F16_16(inst, false); return true;
case O::V_CVT_F16_I16: V_CVT_F16_16(inst, true); return true;
case O::V_CVT_U16_F16: V_CVT_16_F16(inst, false); return true;
case O::V_CVT_I16_F16: V_CVT_16_F16(inst, true); return true;
case O::V_CVT_RPI_I32_F32: V_CVT_RPI_I32_F32(inst); return true;
case O::V_CVT_FLR_I32_F32: V_CVT_FLR_I32_F32(inst); return true;
case O::V_FREXP_EXP_I32_F32: V_FREXP_EXP_I32_F32(inst); return true;
case O::V_CVT_OFF_F32_I4: V_CVT_OFF_F32_I4(inst); return true;
case O::V_CVT_PKRTZ_F16_F32: V_CVT_PKRTZ_F16_F32(inst); return true;
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
case O::V_CVT_F32_UBYTE2: V_CVT_F32_UBYTE(inst, 2); return;
case O::V_CVT_F32_UBYTE3: V_CVT_F32_UBYTE(inst, 3); return;
case O::V_CVT_F32_U32: V_CVT_F32_U32(inst); return;
case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return;
case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return;
case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return;
case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return;
case O::V_CVT_F32_F16: V_CVT_F32_F16(inst); return;
case O::V_CVT_F16_U16: V_CVT_F16_16(inst, false); return;
case O::V_CVT_F16_I16: V_CVT_F16_16(inst, true); return;
case O::V_CVT_U16_F16: V_CVT_16_F16(inst, false); return;
case O::V_CVT_I16_F16: V_CVT_16_F16(inst, true); return;
case O::V_CVT_RPI_I32_F32: V_CVT_RPI_I32_F32(inst); return;
case O::V_CVT_FLR_I32_F32: V_CVT_FLR_I32_F32(inst); return;
case O::V_FREXP_EXP_I32_F32: V_FREXP_EXP_I32_F32(inst); return;
case O::V_CVT_OFF_F32_I4: V_CVT_OFF_F32_I4(inst); return;
case O::V_CVT_PKRTZ_F16_F32: V_CVT_PKRTZ_F16_F32(inst); return;
case O::V_CVT_PKNORM_I16_F32:
V_CVT_PKNORM_F32(inst, IR::ValueOpcode::PackSnorm2x16);
return true;
return;
case O::V_CVT_PKNORM_U16_F32:
V_CVT_PKNORM_F32(inst, IR::ValueOpcode::PackUnorm2x16);
return true;
case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return true;
case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return true;
return;
case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return;
case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return;
case O::V_CVT_PK_U16_U32:
case O::V_CVT_PK_I16_I32: return PackB16(inst, false, false);
@@ -534,7 +538,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_XAD_U32: return V_XAD_U32(inst);
case O::V_LSHL_OR_B32: return V_LSHL_OR_B32(inst);
case O::V_CNDMASK_B32: return V_CNDMASK_B32(inst);
default: return false;
default: return FailMissingTranslation(inst);
}
}
@@ -454,10 +454,8 @@ void ValidateProgram(const Program& program, bool require_ssa) {
ValueOpcodeName(inst.GetOpcode())));
}
if (memory.kind == ResourceKind::IndirectBuffer &&
(memory.formatted || memory.typed ||
(inst.GetOpcode() != ValueOpcode::LoadBufferU32x2 &&
inst.GetOpcode() != ValueOpcode::LoadBufferU32x4))) {
return Fail("indirect buffer requires a raw DWORD x2/x4 load");
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) {
return Fail("indirect buffer requires a raw DWORD x2/x3/x4 load");
}
if (buffer_components > 1u &&
(!vector_buffer || memory.data_bits != 32u ||
+11 -2
View File
@@ -67,8 +67,15 @@ struct MemoryInfo {
bool image_r128 = false;
bool idxen = false;
bool offen = false;
bool coherent = false;
bool planning_only = false;
[[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const {
return !formatted && !typed && data_bits == 32u &&
(opcode == ValueOpcode::LoadBufferU32x2 || opcode == ValueOpcode::LoadBufferU32x3 ||
opcode == ValueOpcode::LoadBufferU32x4);
}
bool operator==(const MemoryInfo& other) const = default;
};
@@ -463,9 +470,9 @@ struct DescriptorSource {
uint32_t table_source = 0;
uint32_t selector_stride = 0;
uint32_t selector_offset = 0;
uint32_t key_arg = 0;
uint32_t table_offset = 0;
uint32_t key_count = 0;
Value key_count;
Value selector_mask;
bool operator==(const IndirectImage& other) const = default;
};
@@ -541,6 +548,7 @@ struct ResourcePlan {
std::vector<SrtRead> srt_reads;
std::vector<uint8_t> clean_flat_slots;
bool requires_specialization_memory = false;
bool has_address_writes = false;
bool srt_plan_complete = false;
bool resource_tracking_complete = false;
ShaderInfo info;
@@ -553,6 +561,7 @@ struct ResourcePlan {
mutable std::vector<uint8_t> visited_blocks;
mutable std::vector<uint32_t> pending_blocks;
mutable std::vector<uint32_t> material_keys;
mutable std::vector<std::pair<uint64_t, uint64_t>> specialization_reads;
};
struct Program: ResourcePlan {
@@ -200,6 +200,13 @@ void FoldInstruction(Block& block, Block::iterator instruction,
switch (inst.GetOpcode()) {
case ValueOpcode::Phi: FoldPhi(inst); return;
case ValueOpcode::SelectU1:
if (!FoldSelect(inst) && IsImmediate(Arg(inst, 2), Type::U1) &&
!Arg(inst, 2).U1()) {
auto result = block.PrependNewInst(instruction, ValueOpcode::LogicalAnd,
{Arg(inst, 0), Arg(inst, 1)});
Replace(inst, Value(&*result));
}
return;
case ValueOpcode::SelectU32:
case ValueOpcode::SelectF32: FoldSelect(inst); return;
case ValueOpcode::BitFieldInsert: {
@@ -431,6 +438,10 @@ void FoldInstruction(Block& block, Block::iterator instruction,
return;
case ValueOpcode::ShiftRightLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a >> (b & 31u); })) {
if (IsImmediate(Arg(inst, 0), Type::U32) && Arg(inst, 0).U32() == 0u) {
Replace(inst, Value(0u));
return;
}
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
@@ -573,6 +584,23 @@ void FoldInstruction(Block& block, Block::iterator instruction,
if (!FoldLogical(inst, [](bool a, bool b) { return a && b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
const auto simplify = [&](Value assumption, Value expression) {
const auto* disjunction = expression.TryInstruction();
if (disjunction == nullptr || disjunction->GetOpcode() != ValueOpcode::LogicalOr) {
return false;
}
for (uint32_t i = 0; i < 2u; ++i) {
const auto* inverse = disjunction->Arg(i).Resolve().TryInstruction();
if (inverse != nullptr && inverse->GetOpcode() == ValueOpcode::LogicalNot &&
inverse->Arg(0).Resolve() == assumption) {
inst.SetArg(assumption == lhs ? 1u : 0u,
disjunction->Arg(i ^ 1u));
return true;
}
}
return false;
};
if (simplify(lhs, rhs) || simplify(rhs, lhs)) return;
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? rhs : lhs);
} else if (IsImmediate(rhs, Type::U1)) {
@@ -138,6 +138,50 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
return true;
}
struct ReadCapture {
SrtRuntime source;
std::vector<std::pair<uint64_t, uint64_t>>& ranges;
};
bool CaptureStrictRead(void* userdata, uint64_t address, std::span<uint32_t> values) {
auto& capture = *static_cast<ReadCapture*>(userdata);
if (!capture.source.read_specialization_memory(capture.source.userdata, address, values)) {
return false;
}
capture.ranges.emplace_back(address, values.size_bytes());
return true;
}
bool CaptureOrdinaryRead(void* userdata, uint64_t address, std::span<uint32_t> values) {
auto& capture = *static_cast<ReadCapture*>(userdata);
if (!capture.source.read_memory(capture.source.userdata, address, values)) {
return false;
}
capture.ranges.emplace_back(address, values.size_bytes());
return true;
}
bool WrittenBuffersDisjoint(const ResourcePlan& program, const ResourceSnapshot& snapshot,
std::span<const std::pair<uint64_t, uint64_t>> reads) {
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
if (!program.info.buffers[i].written) continue;
ShaderBufferResource buffer;
if (!DecodeBufferDescriptor(snapshot.buffers[i], buffer)) return false;
const auto base = buffer.Base48();
const auto size = buffer.GetSize();
if (size == 0u) continue;
if (size - 1u > AddressMask - base) return false;
for (const auto [address, bytes]: reads) {
if (bytes == 0u) continue;
if (address > AddressMask || bytes - 1u > AddressMask - address ||
(address <= base ? base - address < bytes : address - base < size)) {
return false;
}
}
}
return true;
}
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
if (source >= program.descriptor_sources.size()) {
return nullptr;
@@ -146,12 +190,15 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
}
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
std::span<uint8_t> slots) {
if (source == nullptr) {
std::span<uint8_t> slots, Value extra = {}) {
if (source == nullptr && extra.IsEmpty()) {
return;
}
std::vector<Value> pending(source->dwords.begin(),
source->dwords.begin() + source->dword_count);
std::vector<Value> pending;
if (source != nullptr) {
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
}
if (!extra.IsEmpty()) pending.push_back(extra);
std::vector<const Inst*> visited;
while (!pending.empty()) {
auto value = pending.back().Resolve();
@@ -198,7 +245,8 @@ bool MaterializeIndirectImage(const ResourcePlan& program,
const DescriptorSource::IndirectImage& indirect,
const DescriptorValue& material_value,
const DescriptorValue& table_value, uint32_t image_index,
const SrtRuntime& runtime, ResourceSnapshot& snapshot,
const SrtRuntime& runtime, SrtWalker& clean,
ResourceSnapshot& snapshot,
ResourceSpecialization& specialization) {
uint64_t table_base = 0;
uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds.
@@ -214,12 +262,41 @@ bool MaterializeIndirectImage(const ResourcePlan& program,
auto& keys = program.material_keys;
keys.clear();
if (indirect.material_source == UINT32_MAX) {
if (table_value.dword_count != 2u || indirect.key_count == 0u ||
indirect.key_count > MaxIndirectImageProbes) {
uint32_t key_count = 0;
const bool evaluated = clean.Evaluate(indirect.key_count, key_count);
if (std::bit_cast<int32_t>(key_count) <= 0) key_count = 0;
if (table_value.dword_count != 2u || !evaluated ||
key_count > MaxIndirectImageProbes ||
uint64_t {indirect.table_offset} + uint64_t {key_count} * 32u > UINT32_MAX + 1ull) {
return false;
}
keys.resize(indirect.key_count);
keys.resize(key_count);
std::iota(keys.begin(), keys.end(), 0u);
} else if (!indirect.selector_mask.IsEmpty()) {
uint32_t mask = 0;
uint32_t count = 0;
if (material_value.dword_count != 2u || table_value.dword_count != 2u ||
!clean.Evaluate(indirect.selector_mask, mask) ||
!clean.Evaluate(indirect.key_count, count) || count == 0u || count > 32u) {
return false;
}
if (count < 32u) mask &= (1u << count) - 1u;
const auto material_base =
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
keys.reserve(std::popcount(mask));
while (mask != 0u) {
const auto index = std::countr_zero(mask);
const auto offset = static_cast<uint64_t>(indirect.selector_offset) +
static_cast<uint64_t>(index) * indirect.selector_stride;
if (offset > UINT32_MAX) return false;
uint32_t key = 0;
if (!ReadScalarTable(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
runtime, {&key, 1})) return false;
keys.push_back(key);
mask &= mask - 1u;
}
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
} else {
ShaderBufferResource material;
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u ||
@@ -779,6 +856,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
plan.memory_info = program.memory_info;
plan.srt_plan_complete = program.srt_plan_complete;
plan.resource_tracking_complete = program.resource_tracking_complete;
plan.has_address_writes = program.has_address_writes;
std::unordered_map<const Inst*, Inst*> cloned;
std::function<Value(Value)> Clone = [&](Value value) -> Value {
@@ -816,6 +894,10 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
auto& target = plan.descriptor_sources.emplace_back();
target.dword_count = source.dword_count;
target.indirect_image = source.indirect_image;
if (target.indirect_image.has_value()) {
target.indirect_image->key_count = Clone(target.indirect_image->key_count);
target.indirect_image->selector_mask = Clone(target.indirect_image->selector_mask);
}
for (uint32_t dword = 0; dword < source.dword_count; dword++) {
target.dwords[dword] = Clone(source.dwords[dword]);
}
@@ -840,7 +922,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
}
plan.requires_specialization_memory = true;
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->material_source),
plan.clean_flat_slots);
plan.clean_flat_slots, source->indirect_image->selector_mask);
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->table_source),
plan.clean_flat_slots);
}
@@ -853,8 +935,29 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
(program.requires_specialization_memory && runtime.read_specialization_memory == nullptr)) {
return false;
}
SrtWalker clean(program, CleanRuntime(runtime));
SrtWalker walker(program, runtime, program.clean_flat_slots, &clean);
const bool masked_image = std::ranges::any_of(program.info.images, [&](const auto& image) {
const auto* source = Source(program, image.source);
return source != nullptr && source->indirect_image.has_value() &&
!source->indirect_image->selector_mask.IsEmpty();
});
if (masked_image &&
(program.has_address_writes ||
std::ranges::any_of(program.info.images, &ImageResource::written))) {
return false;
}
const bool capture_reads = masked_image &&
std::ranges::any_of(program.info.buffers, &BufferResource::written);
auto& reads = program.specialization_reads;
ReadCapture capture {runtime, reads};
SrtRuntime observed = runtime;
if (capture_reads) {
reads.clear();
observed.userdata = &capture;
observed.read_specialization_memory = CaptureStrictRead;
if (observed.read_memory != nullptr) observed.read_memory = CaptureOrdinaryRead;
}
SrtWalker clean(program, CleanRuntime(observed));
SrtWalker walker(program, observed, program.clean_flat_slots, &clean);
const auto active = clean.FindActiveSources();
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
return false;
@@ -888,11 +991,20 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return false;
}
}
if (capture_reads) {
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
const auto& buffer = program.info.buffers[i];
if (!buffer.written || (!active.empty() && !active[buffer.source])) continue;
DescriptorValue strict;
if (!clean.EvaluateDescriptor(buffer.source, strict) ||
strict != snapshot.buffers[i]) return false;
}
}
snapshot.images.resize(program.info.images.size());
specialization.images.clear();
specialization.images.reserve(program.info.images.size());
for (const auto& image: program.info.images) {
specialization.images.push_back({
specialization.images.resize(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
const auto& image = program.info.images[i];
specialization.images[i] = {
.numeric_class = image.numeric_class,
.dimension = image.dimension,
.mip_count = image.mip_count,
@@ -902,10 +1014,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
.indirect_mapping_offset = image.indirect_mapping_offset,
.indirect_search_iterations = image.indirect_search_iterations,
.cube = image.cube,
});
}
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
const auto& image = program.info.images[i];
};
const auto* source = Source(program, image.source);
if (source == nullptr) {
return false;
@@ -921,7 +1030,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
if ((indirect.material_source != UINT32_MAX &&
!clean.EvaluateDescriptor(indirect.material_source, material)) ||
!clean.EvaluateDescriptor(indirect.table_source, table) ||
!MaterializeIndirectImage(program, indirect, material, table, i, runtime, snapshot,
!MaterializeIndirectImage(program, indirect, material, table, i, observed, clean, snapshot,
specialization)) {
return false;
}
@@ -940,6 +1049,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return false;
}
}
if (capture_reads && !WrittenBuffersDisjoint(program, snapshot, reads)) return false;
snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
return BuildResourceSpecialization(program, snapshot, specialization);
}
@@ -313,9 +313,20 @@ private:
for (uint32_t candidate = 0; candidate < m_sources.size(); candidate++) {
const auto& current = m_sources[candidate];
if (current.dword_count != descriptor.dword_count ||
current.indirect_image != descriptor.indirect_image) {
current.indirect_image.has_value() != descriptor.indirect_image.has_value()) {
continue;
}
if (current.indirect_image.has_value()) {
const auto& a = *current.indirect_image;
const auto& b = *descriptor.indirect_image;
if (a.material_source != b.material_source || a.table_source != b.table_source ||
a.selector_stride != b.selector_stride || a.selector_offset != b.selector_offset ||
a.table_offset != b.table_offset ||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
a.selector_mask.IsEmpty() != b.selector_mask.IsEmpty() ||
(!a.selector_mask.IsEmpty() &&
!EquivalentValue(m_program, a.selector_mask, b.selector_mask))) continue;
}
bool same = true;
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
same = same && EquivalentValue(m_program, current.dwords[i], descriptor.dwords[i]);
@@ -504,6 +515,355 @@ private:
}
}
const Block* FindBlock(uint32_t id) const {
const auto info = std::ranges::find(m_program.block_info, id, &BlockInfo::id);
return info == m_program.block_info.end()
? nullptr
: m_program.blocks[info - m_program.block_info.begin()];
}
bool CanReach(const Block* start, const Block* target, const Block* avoid) const {
std::vector<const Block*> pending {start};
std::vector<const Block*> visited;
while (!pending.empty()) {
const auto* block = pending.back();
pending.pop_back();
if (block == nullptr || block == avoid ||
std::ranges::find(visited, block) != visited.end()) continue;
if (block == target) return true;
visited.push_back(block);
for (const auto* next: block->ImmSuccessors()) pending.push_back(next);
}
return false;
}
bool ConditionalEdge(const Block* from, const Block* to, Value& condition,
bool& positive) const {
const auto position = std::ranges::find(m_program.blocks, from);
const auto target = std::ranges::find(m_program.blocks, to);
if (position == m_program.blocks.end() || target == m_program.blocks.end()) return false;
const auto& info = m_program.block_info[position - m_program.blocks.begin()];
const auto& term = info.terminator;
const auto id = m_program.block_info[target - m_program.blocks.begin()].id;
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
(term.true_block == id) == (term.false_block == id)) return false;
condition = info.condition;
positive = term.true_block == id;
while (const auto* inst = condition.Resolve().TryInstruction()) {
if (inst->GetOpcode() != ValueOpcode::LogicalNot) break;
condition = inst->Arg(0);
positive = !positive;
}
condition = condition.Resolve();
return true;
}
Value PositiveUseGuard(const Block* use) const {
if (use == nullptr || use->ImmPredecessors().size() != 1u) return {};
Value condition;
bool positive = false;
return ConditionalEdge(use->ImmPredecessors()[0], use, condition, positive) && positive
? condition : Value {};
}
Value SimplifyGuard(Value guard) const {
const auto invariant = ResolveInvariantPhi(m_program, guard);
return (invariant.IsEmpty() ? guard : invariant).Resolve();
}
bool Implies(Value guard, Value required) const {
guard = SimplifyGuard(guard);
required = required.Resolve();
if (EquivalentValue(m_program, guard, required)) return true;
const auto* inst = guard.TryInstruction();
return inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd &&
(Implies(inst->Arg(0), required) || Implies(inst->Arg(1), required));
}
Value EqualLocalKey(Value guard, Value key) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return {};
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
const auto left = EqualLocalKey(inst->Arg(0), key);
return left.IsEmpty() ? EqualLocalKey(inst->Arg(1), key) : left;
}
if (inst->GetOpcode() != ValueOpcode::IEqual32 || inst->NumArgs() != 2u) return {};
if (EquivalentValue(m_program, inst->Arg(0), key)) return inst->Arg(1).Resolve();
if (EquivalentValue(m_program, inst->Arg(1), key)) return inst->Arg(0).Resolve();
return {};
}
struct AffineOffset {
Value index;
uint64_t stride = 0;
uint64_t offset = 0;
};
bool MatchAffineOffset(Value value, Value guard, AffineOffset& out,
uint32_t depth = 0) const {
if (depth > 16u) return false;
value = value.Resolve();
uint32_t immediate = 0;
if (ImmediateU32(value, immediate)) {
out.offset = immediate;
return true;
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::SelectU32 && inst->NumArgs() == 3u &&
Implies(guard, inst->Arg(0))) {
return MatchAffineOffset(inst->Arg(1), guard, out, depth + 1u);
}
if (inst->GetOpcode() == ValueOpcode::IAdd32 && inst->NumArgs() == 2u) {
AffineOffset left, right;
if (!MatchAffineOffset(inst->Arg(0), guard, left, depth + 1u) ||
!MatchAffineOffset(inst->Arg(1), guard, right, depth + 1u) ||
(!left.index.IsEmpty() && !right.index.IsEmpty() &&
!EquivalentValue(m_program, left.index, right.index))) return false;
out.index = left.index.IsEmpty() ? right.index : left.index;
out.stride = left.stride + right.stride;
out.offset = left.offset + right.offset;
return out.stride <= UINT32_MAX && out.offset <= UINT32_MAX;
}
if (inst->GetOpcode() == ValueOpcode::ShiftLeftLogical32 && inst->NumArgs() == 2u &&
ImmediateU32(inst->Arg(1), immediate) && immediate < 32u) {
if (!MatchAffineOffset(inst->Arg(0), guard, out, depth + 1u)) return false;
out.stride <<= immediate;
out.offset <<= immediate;
return out.stride <= UINT32_MAX && out.offset <= UINT32_MAX;
}
out.index = value;
out.stride = 1u;
return value.GetType() == Type::U32;
}
bool ImpliesNonzero(Value guard, Value value) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
return ImpliesNonzero(inst->Arg(0), value) ||
ImpliesNonzero(inst->Arg(1), value);
}
if (inst->GetOpcode() != ValueOpcode::INotEqual32 || inst->NumArgs() != 2u)
return false;
uint32_t zero = 1u;
return (ImmediateU32(inst->Arg(0), zero) && zero == 0u &&
EquivalentValue(m_program, inst->Arg(1), value)) ||
(ImmediateU32(inst->Arg(1), zero) && zero == 0u &&
EquivalentValue(m_program, inst->Arg(0), value));
}
bool ImpliesIndexBelow32(Value guard, Value index) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
return ImpliesIndexBelow32(inst->Arg(0), index) ||
ImpliesIndexBelow32(inst->Arg(1), index);
}
if (inst->NumArgs() != 2u) return false;
uint32_t limit = 0;
return (inst->GetOpcode() == ValueOpcode::SLessThan32 &&
EquivalentValue(m_program, inst->Arg(0), index) &&
ImmediateU32(inst->Arg(1), limit) && limit == 32u) ||
(inst->GetOpcode() == ValueOpcode::SGreaterThan32 &&
ImmediateU32(inst->Arg(0), limit) && limit == 32u &&
EquivalentValue(m_program, inst->Arg(1), index));
}
bool ClearsFirstSetBit(Value value, Value mask) const {
const auto* update = value.Resolve().TryInstruction();
if (update == nullptr || update->GetOpcode() != ValueOpcode::BitwiseXor32 ||
update->NumArgs() != 2u) return false;
Value bit;
if (EquivalentValue(m_program, update->Arg(0), mask)) bit = update->Arg(1);
else if (EquivalentValue(m_program, update->Arg(1), mask)) bit = update->Arg(0);
else return false;
const auto* shift = bit.Resolve().TryInstruction();
uint32_t one = 0;
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(0), one) || one != 1u)
return false;
Value position = shift->Arg(1).Resolve();
const auto* masked = position.TryInstruction();
uint32_t lane_mask = 0;
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
masked->NumArgs() == 2u) {
if (ImmediateU32(masked->Arg(0), lane_mask) && lane_mask == 31u)
position = masked->Arg(1);
else if (ImmediateU32(masked->Arg(1), lane_mask) && lane_mask == 31u)
position = masked->Arg(0);
}
const auto* first = position.Resolve().TryInstruction();
return first != nullptr && first->GetOpcode() == ValueOpcode::FindILsb32 &&
first->NumArgs() == 1u && EquivalentValue(m_program, first->Arg(0), mask);
}
Value InitialClearingMask(Value value, const Block* update_block) const {
const auto* phi = value.Resolve().TryInstruction();
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->NumArgs() != 2u || phi->GetType() != Type::U32) return {};
for (uint32_t back = 0; back < 2u; ++back) {
if (phi->PhiBlock(back) != update_block ||
!ClearsFirstSetBit(phi->Arg(back), value)) continue;
const auto initial = phi->Arg(back ^ 1u).Resolve();
return ValidateRuntimeValue(m_program, initial, RuntimeValueType::Integer)
? initial : Value {};
}
return {};
}
bool EdgeOn(const Block* from, const Block* to, Value predicate, bool positive) const {
Value condition;
bool taken = false;
return ConditionalEdge(from, to, condition, taken) && taken == positive &&
EquivalentValue(m_program, condition, predicate);
}
Value BoundedSetBitMask(Value index, Value guard) const {
const auto* phi = index.Resolve().TryInstruction();
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->NumArgs() != 3u || phi->GetType() != Type::U32 ||
!ImpliesIndexBelow32(guard, index)) return {};
for (uint32_t bit_arm = 0; bit_arm < 3u; ++bit_arm) {
const auto bit_guard = PositiveUseGuard(phi->PhiBlock(bit_arm));
const auto* selected = phi->Arg(bit_arm).Resolve().TryInstruction();
if (selected == nullptr || selected->GetOpcode() != ValueOpcode::SelectU32 ||
selected->NumArgs() != 3u ||
!Implies(bit_guard, selected->Arg(0))) continue;
const auto* first = selected->Arg(1).Resolve().TryInstruction();
if (first == nullptr || first->GetOpcode() != ValueOpcode::FindILsb32 ||
first->NumArgs() != 1u || !ImpliesNonzero(bit_guard, first->Arg(0)))
continue;
const auto initial_mask = InitialClearingMask(first->Arg(0), phi->PhiBlock(bit_arm));
if (initial_mask.IsEmpty()) continue;
for (uint32_t sentinel_arm = 0; sentinel_arm < 3u; ++sentinel_arm) {
if (sentinel_arm == bit_arm) continue;
const auto sentinel_guard = PositiveUseGuard(phi->PhiBlock(sentinel_arm));
const auto* sentinel = phi->Arg(sentinel_arm).Resolve().TryInstruction();
uint32_t bound = 0;
if (sentinel == nullptr || sentinel->GetOpcode() != ValueOpcode::SelectU32 ||
sentinel->NumArgs() != 3u ||
!Implies(sentinel_guard, sentinel->Arg(0)) ||
!ImmediateU32(sentinel->Arg(1), bound) || bound != 32u) continue;
const auto loop_active = sentinel->Arg(0).Resolve();
const auto* active_phi = loop_active.TryInstruction();
if (active_phi == nullptr || active_phi->GetOpcode() != ValueOpcode::Phi ||
active_phi->NumArgs() != 2u) continue;
bool invariant = false;
for (uint32_t initial = 0; initial < 2u; ++initial) {
invariant = Implies(guard, active_phi->Arg(initial)) &&
EdgeOn(active_phi->PhiBlock(initial ^ 1u), active_phi->Parent(),
active_phi->Arg(initial ^ 1u), true);
if (invariant) break;
}
if (!invariant) continue;
const auto other_arm = 3u - bit_arm - sentinel_arm;
if (EdgeOn(phi->PhiBlock(other_arm), phi->Parent(), loop_active, false))
return initial_mask;
}
}
return {};
}
bool MatchUniformizedMaterialKey(Value key, const Inst& image,
DescriptorSource::IndirectImage& indirect,
DescriptorSource& material_source, uint32_t pc) {
const auto guard = PositiveUseGuard(image.Parent());
if (guard.IsEmpty()) return false;
const auto local = EqualLocalKey(guard, key);
const auto* selected = local.Resolve().TryInstruction();
if (selected == nullptr || selected->GetOpcode() != ValueOpcode::SelectU32 ||
selected->NumArgs() != 3u || !Implies(guard, selected->Arg(0))) return false;
const auto active = selected->Arg(0).Resolve();
const auto* read = selected->Arg(1).Resolve().TryInstruction();
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadAddressU32 ||
read->NumArgs() != 4u || !EquivalentValue(m_program, read->Arg(3), active))
return false;
uint32_t high = 1u;
if (!ImmediateU32(read->Arg(2), high) || high != 0u) return false;
const auto memory_index = read->Flags<MemoryFlags>().index;
if (memory_index >= m_program.memory_info.size()) return false;
const auto& memory = m_program.memory_info[memory_index];
if (memory.kind != ResourceKind::Global || memory.data_bits != 32u ||
memory.data_dwords != 1u || !MemoryIndexBelongsTo(memory_index, *read)) return false;
const auto* material_handle = read->Arg(0).Resolve().TryInstruction();
if (material_handle == nullptr ||
material_handle->GetOpcode() != ValueOpcode::GetAddressResource ||
!MakeRuntimeTableSource(*material_handle, pc, material_source)) return false;
AffineOffset offset;
if (!MatchAffineOffset(read->Arg(1), active, offset) || offset.index.IsEmpty() ||
offset.stride == 0u || offset.offset + memory.offset > UINT32_MAX ||
offset.offset + memory.offset + 31u * offset.stride + 4u >
static_cast<uint64_t>(UINT32_MAX) + 1u) return false;
const auto mask = BoundedSetBitMask(offset.index, active);
if (mask.IsEmpty()) return false;
indirect.material_source = InternSource(material_source);
indirect.selector_stride = static_cast<uint32_t>(offset.stride);
indirect.selector_offset = static_cast<uint32_t>(offset.offset + memory.offset);
indirect.key_count = Value(32u);
indirect.selector_mask = mask;
return true;
}
Value BoundedLoopCount(Value key, const Block* use) const {
const auto* phi = key.Resolve().TryInstruction();
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->GetType() != Type::U32 || phi->NumArgs() != 2u ||
m_program.blocks.size() != m_program.block_info.size()) {
return {};
}
bool induction = false;
for (uint32_t initial = 0; initial < 2u; ++initial) {
const auto zero = phi->Arg(initial).Resolve();
const auto* step = phi->Arg(initial ^ 1u).Resolve().TryInstruction();
if (!zero.IsImmediate() || zero.GetType() != Type::U32 || zero.U32() != 0u ||
step == nullptr || step->GetOpcode() != ValueOpcode::IAdd32 ||
step->Parent() != phi->PhiBlock(initial ^ 1u)) {
continue;
}
uint32_t increment = 0;
induction = (step->Arg(0).Resolve() == key &&
ImmediateU32(step->Arg(1), increment) && increment == 1u) ||
(step->Arg(1).Resolve() == key &&
ImmediateU32(step->Arg(0), increment) && increment == 1u);
if (induction) break;
}
if (!induction) return {};
const auto contains = [&](auto&& self, Value value, const Inst* comparison) -> bool {
value = value.Resolve();
if (value.TryInstruction() == comparison) return true;
const auto* inst = value.TryInstruction();
return inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd &&
(self(self, inst->Arg(0), comparison) || self(self, inst->Arg(1), comparison));
};
for (const auto& use_of_key: phi->Uses()) {
const auto* compare = use_of_key.user;
if (compare->GetOpcode() != ValueOpcode::SLessThan32 || use_of_key.operand != 0u ||
!ValidateRuntimeValue(m_program, compare->Arg(1))) continue;
for (uint32_t i = 0; i < m_program.block_info.size(); ++i) {
const auto& info = m_program.block_info[i];
const auto* negated = info.condition.Resolve().TryInstruction();
if (info.terminator.kind != CFG::TerminatorKind::ConditionalBranch ||
compare->Parent() != m_program.blocks[i] || negated == nullptr ||
negated->GetOpcode() != ValueOpcode::LogicalNot ||
!contains(contains, negated->Arg(0), compare) ||
use == m_program.blocks[i] ||
CanReach(m_program.blocks.front(), use, m_program.blocks[i]) ||
CanReach(phi->Parent(), use, m_program.blocks[i]) ||
CanReach(FindBlock(info.terminator.true_block), use, phi->Parent()) ||
!CanReach(FindBlock(info.terminator.false_block), use, phi->Parent())) {
continue;
}
return compare->Arg(1);
}
}
return {};
}
bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) {
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) {
return false;
@@ -562,13 +922,20 @@ private:
indirect.table_offset = table_offset;
if (table_source.dword_count == 2u) {
const auto* selector = key.Resolve().TryInstruction();
if (m_shader_writes || selector == nullptr ||
selector->GetOpcode() != ValueOpcode::FindILsb32 || selector->NumArgs() != 1u ||
!NonzeroOnEntry(selector->Arg(0), handle.Parent()) ||
(table_offset & 3u) != 0u || table_offset > UINT32_MAX - (32u * 32u - 1u)) {
const bool bitscan = selector != nullptr && selector->GetOpcode() == ValueOpcode::FindILsb32 &&
selector->NumArgs() == 1u && !m_shader_writes &&
NonzeroOnEntry(selector->Arg(0), handle.Parent());
if (bitscan) {
indirect.key_count = Value(32u);
} else {
indirect.key_count = BoundedLoopCount(key, handle.Parent());
}
if (indirect.key_count.IsEmpty() &&
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, pc)) {
return false;
}
indirect.key_count = 32u;
if ((table_offset & 3u) != 0u ||
(bitscan && table_offset > UINT32_MAX - (32u * 32u - 1u))) return false;
} else {
auto* material_read = key.Resolve().TryInstruction();
uint32_t material_memory_index = 0;
@@ -839,11 +1206,10 @@ private:
if (buffer != BufferAccess::None) {
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
source)) {
if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed ||
(op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) {
if (memory.kind != ResourceKind::Buffer || !memory.SupportsIndirectBufferLoad(op)) {
Fail(flags.pc,
"buffer descriptor is not a valid runtime value; GPU-selected access "
"requires a raw DWORD x2/x4 load");
"requires a raw DWORD x2/x3/x4 load");
}
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
m_info.uses_dma = true;
@@ -873,6 +1239,9 @@ private:
return;
}
ValidateAddressHandle(inst.Arg(0), flags.pc);
if (address_info.access == AddressAccess::Write) {
m_program.has_address_writes = true;
}
m_info.uses_dma = true;
return;
}
+3 -1
View File
@@ -388,6 +388,8 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
EXIT_IF(attrib == nullptr || buffer == nullptr);
const bool debug_dump = Config::GraphicsDebugDumpEnabled();
for (uint32_t i = 0; i < num_input_semantics; i++) {
const auto& in = input_semantics[i];
@@ -396,7 +398,7 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
uint32_t reg = in.hardware_mapping;
uint32_t size = in.size_in_elements;
if (Config::GraphicsDebugDumpEnabled()) {
if (debug_dump) {
LOGF("reg = %u, size = %u, va[%u] = 0x%08" PRIx32 "\n", reg, size, i,
attrib[in.semantic]);
}
+83 -112
View File
@@ -200,6 +200,10 @@ static bool IsPrivateCommittedRangeType(VirtualRangeType type) {
type == VirtualRangeType::Runtime;
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
static bool g_test_fail_next_range_replace = false;
#endif
class VirtualRanges {
public:
struct Range {
@@ -288,36 +292,22 @@ public:
return true;
}
bool ConsumeReserved(uint64_t start, uint64_t size,
VirtualRangeType type = VirtualRangeType::Reserved) {
bool ReplaceSpan(uint64_t start, uint64_t size, VirtualRangeType expected_type,
uint64_t offset, int protection, int memory_type, VirtualRangeType type,
const char* name, bool disallow_merge = false) {
Common::LockGuard lock(m_mutex);
auto end = End(start, size);
for (const auto& r: m_ranges) {
if (r.type == type && start >= r.start && end <= End(r.start, r.size)) {
RemoveUnlocked(start, size);
MergeUnlocked();
return true;
}
}
return false;
}
bool ConsumeReservedSpan(uint64_t start, uint64_t size, Range* first_range = nullptr,
VirtualRangeType type = VirtualRangeType::Reserved) {
Common::LockGuard lock(m_mutex);
if (size == 0) {
if (start == 0 || size == 0 || size > UINT64_MAX - start) {
return false;
}
auto current = start;
auto end = End(start, size);
const auto end = start + size;
while (current < end) {
const Range* candidate = nullptr;
for (const auto& r: m_ranges) {
if (r.type == type && current >= r.start && current < End(r.start, r.size)) {
if (r.type == expected_type && current >= r.start &&
current < End(r.start, r.size)) {
candidate = &r;
break;
}
@@ -325,14 +315,29 @@ public:
if (candidate == nullptr) {
return false;
}
if (current == start && first_range != nullptr) {
*first_range = *candidate;
}
current = std::min(end, End(candidate->start, candidate->size));
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_fail_next_range_replace) {
g_test_fail_next_range_replace = false;
return false;
}
#endif
Range replacement {};
replacement.start = start;
replacement.size = size;
replacement.offset = offset;
replacement.protection = protection;
replacement.memory_type = memory_type;
replacement.type = type;
replacement.disallow_merge = disallow_merge;
CopyVirtualRangeName(replacement.name, name);
RemoveUnlocked(start, size);
MergeUnlocked();
auto position = LowerBound(start);
const auto index = static_cast<size_t>(position - m_ranges.begin());
m_ranges.insert(position, replacement);
MergeAroundUnlocked(index);
return true;
}
@@ -2250,10 +2255,9 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
EXIT("unknown prot: %d\n", prot);
}
auto in_addr = reinterpret_cast<uint64_t>(*addr_in_out);
uint64_t out_addr = 0;
bool consumed_reservation = false;
VirtualRanges::Range consumed_range {};
auto in_addr = reinterpret_cast<uint64_t>(*addr_in_out);
uint64_t out_addr = 0;
bool reserved_target = false;
if ((flags & GUEST_MAP_FIXED) != 0) {
if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1)) != 0 ||
@@ -2269,16 +2273,12 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
out_addr = in_addr;
}
reserved_target = true;
out_addr = in_addr;
}
if (!consumed_reservation && ReplaceFixedRangeWithReserved(in_addr, len) &&
g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) {
consumed_reservation = true;
out_addr = in_addr;
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
out_addr = in_addr;
}
} else {
const auto search_addr = (in_addr != 0 ? in_addr : DEFAULT_PS5_BASE);
@@ -2291,30 +2291,25 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
*addr_in_out = reinterpret_cast<void*>(out_addr);
if (out_addr == 0) {
if (consumed_reservation) {
g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name);
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_flexible_memory->Map(out_addr, len, prot, mode, gpu_mode, name)) {
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_virtual_ranges->Add(out_addr, len, 0, prot, 0, VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0)) {
const bool published = reserved_target
? g_virtual_ranges->ReplaceSpan(
out_addr, len, VirtualRangeType::Reserved, 0, prot, 0,
VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0)
: g_virtual_ranges->Add(out_addr, len, 0, prot, 0,
VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0);
if (!published) {
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
EXIT_IF(!g_flexible_memory->Unmap(out_addr, len, &rollback_gpu_mode));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
@@ -2946,25 +2941,17 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return KERNEL_ERROR_ENOMEM;
}
auto in_addr = reinterpret_cast<uint64_t>(*addr);
uint64_t out_addr = 0;
bool shared_backing = false;
bool consumed_reservation = false;
VirtualRanges::Range consumed_range {};
auto shared_failure = GuestBackingStore::FailureReason::None;
auto in_addr = reinterpret_cast<uint64_t>(*addr);
uint64_t out_addr = 0;
bool shared_backing = false;
bool reserved_target = false;
auto shared_failure = GuestBackingStore::FailureReason::None;
// Direct mappings must remain views of the single backing object. Anonymous fallbacks break
// aliasing and lose direct-memory contents when a range is unmapped and mapped again.
auto map_shared_fixed = [&](uint64_t target_addr) -> bool {
return g_guest_address_space->MapBacking(target_addr, len, direct_memory_start, mode,
&shared_failure);
};
auto map_consumed_reserved_fixed = [&]() {
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
};
if (fixed) {
if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1u)) != 0 ||
(alignment != 0 && in_addr % alignment != 0)) {
@@ -2980,20 +2967,18 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
map_consumed_reserved_fixed();
}
reserved_target = true;
}
if (!consumed_reservation && ReplaceFixedRangeWithReserved(in_addr, len) &&
g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) {
consumed_reservation = true;
map_consumed_reserved_fixed();
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
}
if (!consumed_reservation) {
if (!reserved_target) {
return KERNEL_ERROR_ENOMEM;
}
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
} else {
constexpr size_t DEFAULT_ALIGNMENT = 0x4000;
alignment = (alignment != 0 ? alignment : DEFAULT_ALIGNMENT);
@@ -3003,16 +2988,13 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
reserved_target = true;
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
}
if (!consumed_reservation) {
if (!reserved_target) {
out_addr = FindGuestFreeRange(in_addr, len, alignment);
if (out_addr != 0) {
UnmapGpuRange(out_addr, len);
@@ -3046,34 +3028,28 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
magic_enum::enum_name(gpu_mode), shared_backing ? "yes" : "no", shared_reason);
if (out_addr == 0) {
if (consumed_reservation) {
g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name);
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode)) {
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
PhysicalMemory::AllocatedBlock mapped_block {};
g_physical_memory->Find(direct_memory_start, false, &mapped_block);
if (!g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot, mapped_block.memory_type,
VirtualRangeType::Direct, "")) {
const bool published = reserved_target
? g_virtual_ranges->ReplaceSpan(
out_addr, len, VirtualRangeType::Reserved, direct_memory_start,
prot, mapped_block.memory_type, VirtualRangeType::Direct, "")
: g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot,
mapped_block.memory_type,
VirtualRangeType::Direct, "");
if (!published) {
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
EXIT_IF(!g_physical_memory->Unmap(out_addr, len, &rollback_gpu_mode));
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
@@ -3442,6 +3418,10 @@ void TestFailNextFixedReserveRangeRegistration() {
g_test_fail_next_fixed_reserve_range_add = true;
}
void TestFailNextVirtualRangeReplacement() {
g_test_fail_next_range_replace = true;
}
bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size) {
return g_guest_address_space->TestContainsFree(vaddr, size);
}
@@ -3965,12 +3945,8 @@ int KYTY_SYSV_ABI KernelMemoryPoolReserve(void* addr_in, size_t len, size_t alig
!IsReservedRangeType(reserved_range.type)) {
return KERNEL_ERROR_EBUSY;
}
g_virtual_ranges->Remove(out_vaddr, len);
if (!g_virtual_ranges->Add(out_vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved,
reserved_range.name)) {
g_virtual_ranges->Add(out_vaddr, len, reserved_range.offset, reserved_range.protection,
reserved_range.memory_type, reserved_range.type,
reserved_range.name);
if (!g_virtual_ranges->ReplaceSpan(out_vaddr, len, VirtualRangeType::Reserved, 0, 0, 0,
VirtualRangeType::PoolReserved, reserved_range.name)) {
return KERNEL_ERROR_EBUSY;
}
*addr_out = out_addr;
@@ -4011,17 +3987,13 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
VirtualRanges::Range old_range {};
if (!g_virtual_ranges->Query(vaddr, 0, &old_range) ||
old_range.type != VirtualRangeType::PoolReserved) {
return KERNEL_ERROR_EACCES;
}
if (!g_virtual_ranges->ConsumeReserved(vaddr, len, VirtualRangeType::PoolReserved)) {
old_range.type != VirtualRangeType::PoolReserved ||
len > old_range.size - (vaddr - old_range.start)) {
return KERNEL_ERROR_EACCES;
}
std::vector<PooledMemory::Mapping> mappings;
if (!g_pooled_memory->Allocate(vaddr, len, gpu_mode, &mappings)) {
g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name);
return KERNEL_ERROR_ENOMEM;
}
@@ -4034,7 +4006,6 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
if (!g_pooled_memory->Release(vaddr, len, &rollback_gpu_mode)) {
EXIT("failed to release pooled-memory rollback allocation\n");
}
g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name);
};
for (const auto& mapping: mappings) {
@@ -4050,8 +4021,8 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
mapped.push_back(mapping);
}
if (!g_virtual_ranges->Add(vaddr, len, 0, prot, type, VirtualRangeType::Pooled,
old_range.name)) {
if (!g_virtual_ranges->ReplaceSpan(vaddr, len, VirtualRangeType::PoolReserved, 0, prot, type,
VirtualRangeType::Pooled, old_range.name)) {
rollback();
return KERNEL_ERROR_EBUSY;
}
+1
View File
@@ -190,6 +190,7 @@ void TestFailNextPhysicalMemoryUnmap();
void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps);
void TestFailGuestBackingStoreUnmapAfter(uint32_t successful_unmaps);
void TestFailNextFixedReserveRangeRegistration();
void TestFailNextVirtualRangeReplacement();
bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
bool TestGuestBackingOutsideAddressSpace();
+177
View File
@@ -0,0 +1,177 @@
#include "libs/acm.h"
#include "common/assert.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include <algorithm>
#include <atomic>
#include <cstring>
namespace Libs::Audio {
namespace Acm {
LIB_NAME("Acm", "Acm");
struct AcmBatchInfo {
void* buffer;
size_t offset;
size_t buffer_size;
};
struct AcmBatchError {
uint32_t reserved[8];
};
static std::atomic_uint32_t g_acm_next_context {1};
static std::atomic_uint32_t g_acm_next_batch {1};
static void acm_advance_batch(AcmBatchInfo* info, size_t bytes) {
if (info == nullptr || info->buffer == nullptr || info->buffer_size == 0) {
return;
}
info->offset = std::min(info->buffer_size, info->offset + bytes);
}
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(context == nullptr);
*context = g_acm_next_context.fetch_add(1, std::memory_order_relaxed);
LOGF("\t context = %" PRIu32 "\n", *context);
return OK;
}
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context) {
PRINT_NAME();
LOGF("\t context = %" PRIu32 "\n", context);
return OK;
}
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(batch == nullptr);
if (batch_error != nullptr) {
std::memset(batch_error, 0, sizeof(AcmBatchError));
}
*batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed);
return OK;
}
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(batch_info_count != 0 && batch_info == nullptr);
EXIT_NOT_IMPLEMENTED(batch == nullptr);
if (batch_error != nullptr) {
std::memset(batch_error, 0, sizeof(AcmBatchError));
}
*batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed);
return OK;
}
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout) {
return OK;
}
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) {
PRINT_NAME();
acm_advance_batch(batch_info, 2 * 16);
return OK;
}
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]) {
PRINT_NAME();
(void)block_count;
(void)in;
(void)count;
(void)ir;
(void)gain;
(void)out;
acm_advance_batch(batch_info, 1024);
return OK;
}
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]) {
PRINT_NAME();
(void)block_count;
(void)ir;
(void)count;
(void)in;
(void)gain;
(void)out;
acm_advance_batch(batch_info, 1024);
return OK;
}
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags) {
PRINT_NAME();
(void)size;
(void)count;
(void)input_format;
(void)input;
(void)output_format;
(void)output;
(void)flags;
acm_advance_batch(batch_info, 256);
return OK;
}
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags) {
PRINT_NAME();
(void)size;
(void)count;
(void)input_format;
(void)input;
(void)output_format;
(void)output;
(void)flags;
acm_advance_batch(batch_info, 256);
return OK;
}
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]) {
PRINT_NAME();
(void)in_count;
(void)in;
(void)biquad_count;
(void)biquad_update_count;
(void)out_count;
(void)parameter;
(void)state;
(void)out_init;
(void)out;
acm_advance_batch(batch_info, 512);
return OK;
}
} // namespace Acm
} // namespace Libs::Audio
+48
View File
@@ -0,0 +1,48 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Audio {
namespace Acm {
struct AcmBatchInfo;
struct AcmBatchError;
using AcmContextId = uint32_t;
using AcmBatchId = uint32_t;
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context);
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context);
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]);
} // namespace Acm
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_ */
+30 -30
View File
@@ -2110,7 +2110,7 @@ uint32_t KYTY_SYSV_ABI AgcCbQueueEndOfPipeActionGetSize() {
uint32_t* KYTY_SYSV_ABI AgcAcbResetQueue(CommandBuffer* buf, uint32_t op) {
PRINT_NAME();
LOGF("\t op = 0x%08" PRIx32 "\n", op);
AgcTrace("\t op = 0x%08" PRIx32 "\n", op);
EXIT_NOT_IMPLEMENTED(buf == nullptr);
EXIT_NOT_IMPLEMENTED((op & ~0x1c2u) != 0);
@@ -2130,9 +2130,9 @@ uint32_t* KYTY_SYSV_ABI AgcAcbResetQueue(CommandBuffer* buf, uint32_t op) {
uint32_t* KYTY_SYSV_ABI AgcDcbResetQueue(CommandBuffer* buf, uint32_t op, uint32_t state) {
PRINT_NAME();
LOGF("\t op = 0x%08" PRIx32 "\n"
"\t state = 0x%08" PRIx32 "\n",
op, state);
AgcTrace("\t op = 0x%08" PRIx32 "\n"
"\t state = 0x%08" PRIx32 "\n",
op, state);
EXIT_NOT_IMPLEMENTED(buf == nullptr);
EXIT_NOT_IMPLEMENTED((op & ~0xfffu) != 0);
@@ -2749,9 +2749,9 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetBaseIndirectArgs(CommandBuffer* buf, uint32_t s
const volatile void* indirect_base_addr) {
PRINT_NAME();
LOGF("\t shader_type = %" PRIu32 "\n"
"\t indirect_base_addr = 0x%016" PRIx64 "\n",
shader_type, reinterpret_cast<uint64_t>(indirect_base_addr));
AgcTrace("\t shader_type = %" PRIu32 "\n"
"\t indirect_base_addr = 0x%016" PRIx64 "\n",
shader_type, reinterpret_cast<uint64_t>(indirect_base_addr));
EXIT_NOT_IMPLEMENTED(buf == nullptr);
@@ -2941,9 +2941,9 @@ uint32_t* KYTY_SYSV_ABI AgcDcbDispatchIndirect(CommandBuffer* buf, uint32_t data
uint32_t flags) {
PRINT_NAME();
LOGF("\t data_offset = 0x%" PRIx32 "\n"
"\t flags = 0x%08" PRIx32 "\n",
data_offset_in_bytes, flags);
AgcTrace("\t data_offset = 0x%" PRIx32 "\n"
"\t flags = 0x%08" PRIx32 "\n",
data_offset_in_bytes, flags);
EXIT_NOT_IMPLEMENTED(buf == nullptr);
@@ -3294,9 +3294,9 @@ uint32_t* KYTY_SYSV_ABI AgcAcbDispatchIndirect(CommandBuffer* buf,
uint32_t modifier) {
PRINT_NAME();
LOGF("\t indirect_args = 0x%016" PRIx64 "\n"
"\t modifier = 0x%08" PRIx32 "\n",
reinterpret_cast<uint64_t>(indirect_args), modifier);
AgcTrace("\t indirect_args = 0x%016" PRIx64 "\n"
"\t modifier = 0x%08" PRIx32 "\n",
reinterpret_cast<uint64_t>(indirect_args), modifier);
if (buf == nullptr) {
return nullptr;
@@ -3724,9 +3724,9 @@ int KYTY_SYSV_ABI AgcCondExecPatchSetCommandAddress(uint32_t* cmd
const volatile uint32_t* command) {
PRINT_NAME();
LOGF("\t cmd = 0x%016" PRIx64 "\n"
"\t command = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(command));
AgcTrace("\t cmd = 0x%016" PRIx64 "\n"
"\t command = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(command));
if (cmd == nullptr || command == nullptr) {
return GRAPHICS5_ERROR_INVALID_PACKET;
@@ -3868,9 +3868,9 @@ static uint32_t* get_agc_wait_packet(uint32_t* cmd) {
int KYTY_SYSV_ABI AgcWaitRegMemPatchAddress(uint32_t* cmd, const volatile void* address) {
PRINT_NAME();
LOGF("\t cmd = 0x%016" PRIx64 "\n"
"\t address = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(address));
AgcTrace("\t cmd = 0x%016" PRIx64 "\n"
"\t address = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(address));
auto* wait = get_agc_wait_packet(cmd);
if (wait == nullptr) {
@@ -3913,9 +3913,9 @@ int KYTY_SYSV_ABI AgcQueueEndOfPipeActionPatchAddress(uint32_t* cmd,
// Not sure
LOGF("\t cmd = 0x%016" PRIx64 "\n"
"\t address = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(address));
AgcTrace("\t cmd = 0x%016" PRIx64 "\n"
"\t address = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(cmd), reinterpret_cast<uint64_t>(address));
EXIT_NOT_IMPLEMENTED(cmd == nullptr);
@@ -4246,7 +4246,7 @@ static void submit_acb(uint32_t queue, uint32_t* acb, uint32_t size_in_dwords) {
}
for (uint32_t i = 0; i < std::min<uint32_t>(size_in_dwords, 8); i++) {
LOGF("\t acb[%u] = 0x%08" PRIx32 "\n", i, acb[i]);
AgcTrace("\t acb[%u] = 0x%08" PRIx32 "\n", i, acb[i]);
}
GraphicsDbgDumpDcb("a", size_in_dwords, acb);
@@ -4322,17 +4322,17 @@ int KYTY_SYSV_ABI AgcDriverSubmitMultiCommandBuffers(void* queue_cont
int KYTY_SYSV_ABI AgcDriverSubmitAcb(uint32_t queue, const Packet* packet) {
PRINT_NAME();
LOGF("\t queue = 0x%08" PRIx32 "\n"
"\t packet = 0x%016" PRIx64 "\n",
queue, reinterpret_cast<uint64_t>(packet));
AgcTrace("\t queue = 0x%08" PRIx32 "\n"
"\t packet = 0x%016" PRIx64 "\n",
queue, reinterpret_cast<uint64_t>(packet));
if (packet == nullptr) {
return OK;
}
LOGF("\t acb = 0x%016" PRIx64 "\n"
"\t size = 0x%08" PRIx32 "\n"
"\t flags = 0x%02" PRIx8 "\n",
reinterpret_cast<uint64_t>(packet->addr), packet->dw_num, packet->flags);
AgcTrace("\t acb = 0x%016" PRIx64 "\n"
"\t size = 0x%08" PRIx32 "\n"
"\t flags = 0x%02" PRIx8 "\n",
reinterpret_cast<uint64_t>(packet->addr), packet->dw_num, packet->flags);
submit_acb(queue, packet->addr, packet->dw_num);
return OK;
+5 -22
View File
@@ -682,16 +682,9 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
static AjmDecodeResult AjmSetGaplessDecode(uint32_t instance, const void* gapless_decode,
int reset) {
std::scoped_lock lock(g_ajm_instances_mutex);
auto* state = AjmFindInstanceLocked(instance);
if (state == nullptr || state->decoder == nullptr || gapless_decode == nullptr) {
AjmDecodeResult result {};
result.result = AJM_RESULT_INVALID_PARAMETER;
return result;
}
state->gapless.Set(*static_cast<const AjmSidebandGaplessDecode*>(gapless_decode), reset != 0);
return state->decoder->MakeResult();
const auto flags = AJM_FLAG_SIDEBAND_GAPLESS | (reset != 0 ? AJM_FLAG_CONTROL_RESET : 0);
return AjmControlInstance(instance, flags, gapless_decode, sizeof(AjmSidebandGaplessDecode),
nullptr, nullptr);
}
static AjmDecodeResult AjmGetGaplessDecode(uint32_t instance,
@@ -930,18 +923,8 @@ int KYTY_SYSV_ABI AjmBatchJobClearContext(AjmBatchInfo* info, uint32_t instance,
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(info == nullptr);
AjmDecodeResult decode_result {};
{
std::scoped_lock lock(g_ajm_instances_mutex);
auto* state = AjmFindInstanceLocked(instance);
if (state != nullptr && state->decoder != nullptr) {
state->gapless.Reset();
state->decoder->Reset();
decode_result = state->decoder->MakeResult();
} else {
decode_result.result = AJM_RESULT_INVALID_PARAMETER;
}
}
const auto decode_result =
AjmControlInstance(instance, AJM_FLAG_CONTROL_RESET, nullptr, 0, nullptr, nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decode_result);
LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
+21 -2524
View File
File diff suppressed because it is too large. Load diff
-126
View File
@@ -190,43 +190,6 @@ const char* KYTY_SYSV_ABI AjmStrError(int error);
} // namespace Ajm
namespace Acm {
struct AcmBatchInfo;
struct AcmBatchError;
using AcmContextId = uint32_t;
using AcmBatchId = uint32_t;
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context);
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context);
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]);
} // namespace Acm
namespace AvPlayer {
struct AvPlayerInitData;
@@ -271,95 +234,6 @@ int KYTY_SYSV_ABI AvPlayerSetLogCallback(void* callback, void* user_data);
} // namespace AvPlayer
namespace Audio3d {
struct Audio3dOpenParameters;
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved);
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p);
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id);
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size);
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available);
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id);
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking);
} // namespace Audio3d
namespace Ngs2 {
struct Ngs2SystemOption;
struct Ngs2SystemInfo;
struct Ngs2RackOption;
struct Ngs2BufferAllocator;
struct Ngs2VoiceParamHeader;
struct Ngs2RenderBufferInfo;
struct Ngs2ContextBufferInfo;
struct Ngs2VoiceState;
struct Ngs2WaveformFormat;
struct Ngs2WaveformBlock;
struct Ngs2WaveformInfo;
struct Ngs2PanWork;
struct Ngs2PanParam;
struct Ngs2GeomListenerParam;
struct Ngs2GeomListenerWork;
struct Ngs2GeomSourceParam;
struct Ngs2GeomAttribute;
int KYTY_SYSV_ABI Ngs2SystemResetOption(Ngs2SystemOption* option);
int KYTY_SYSV_ABI Ngs2SystemQueryBufferSize(const Ngs2SystemOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2SystemCreate(const Ngs2SystemOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info,
size_t info_size);
int KYTY_SYSV_ABI Ngs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples);
int KYTY_SYSV_ABI Ngs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemCreateWithAllocator(const Ngs2SystemOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackLock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
size_t num_commands);
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
size_t state_size);
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
uint32_t num_buffer_info);
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos,
uint32_t num_samples, Ngs2WaveformBlock* block);
int KYTY_SYSV_ABI Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle,
uint32_t num_speakers);
int KYTY_SYSV_ABI Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params,
uint32_t num_params, uint32_t matrix_format,
float* out_volume_matrix);
int KYTY_SYSV_ABI Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param);
int KYTY_SYSV_ABI Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param);
int KYTY_SYSV_ABI Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param,
Ngs2GeomListenerWork* out_work, uint32_t flags);
int KYTY_SYSV_ABI Ngs2GeomApply(const Ngs2GeomListenerWork* listener,
const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib,
uint32_t flags);
} // namespace Ngs2
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_AUDIO_H_ */
+319
View File
@@ -0,0 +1,319 @@
#include "libs/audio3d.h"
#include "common/assert.h"
#include "common/threads.h"
#include "kernel/semaphore.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include <atomic>
namespace Libs::Audio {
namespace Audio3d {
LIB_NAME("Audio3d", "Audio3d");
namespace Semaphore = LibKernel::Semaphore;
struct Audio3dOpenParameters {
size_t size = 0x20;
uint32_t granularity = 256;
uint32_t rate = 0;
uint32_t max_objects = 512;
uint32_t queue_depth = 2;
uint32_t buffer_mode = 2;
uint32_t pad = 0;
// uint32_t num_beds;
};
struct Audio3dData {
enum class State { Empty, Ready, Play };
std::atomic<State> state = State::Empty;
};
struct Audio3dInternal {
Audio3dData* data = nullptr;
Common::Mutex* data_mutex = nullptr;
uint64_t data_delay = 0;
Semaphore::KernelSema playback_sema = nullptr;
Audio3dOpenParameters params = {};
int user_id = 0;
float late_reverb_level = 0.0f;
float downmix_spread_radius = 2.0f;
int downmix_spread_height_aware = 0;
uint32_t data_index = 0;
bool used = false;
std::atomic_bool playback_finished = false;
};
constexpr uint32_t MAX_PORTS = 4;
static Audio3dInternal g_ports[MAX_PORTS] = {};
static void playback_simulate(void* arg) {
auto* port = static_cast<Audio3dInternal*>(arg);
EXIT_IF(port == nullptr);
EXIT_IF(port->data_mutex == nullptr);
EXIT_IF(port->data == nullptr);
for (;;) {
int result = Semaphore::KernelWaitSema(port->playback_sema, 1, nullptr);
if (result != OK) {
break;
}
Audio3dData* play_data = nullptr;
port->data_mutex->Lock();
{
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Play) {
play_data = &port->data[index];
break;
}
}
}
port->data_mutex->Unlock();
EXIT_IF(play_data == nullptr);
if (play_data != nullptr) {
// TODO(): Audio output is not yet implemented, so simulate audio delay
Common::Thread::SleepMicro(port->data_delay);
play_data->state = Audio3dData::State::Empty;
}
}
port->playback_finished = true;
}
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(reserved != 0);
return OK;
}
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(sizeof(Audio3dOpenParameters) != 0x20);
*p = Audio3dOpenParameters();
}
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(parameters == nullptr);
EXIT_NOT_IMPLEMENTED(id == nullptr);
EXIT_NOT_IMPLEMENTED(parameters->size != 0x20);
LOGF("\t user_id = %d\n"
"\t granularity = %u\n"
"\t rate = %u\n"
"\t max_objects = %u\n"
"\t queue_depth = %u\n"
"\t buffer_mode = %u\n",
user_id, parameters->granularity, parameters->rate, parameters->max_objects,
parameters->queue_depth, parameters->buffer_mode);
EXIT_NOT_IMPLEMENTED(parameters->buffer_mode != 2);
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 1);
uint32_t port = 0;
for (; port < MAX_PORTS; port++) {
if (!g_ports[port].used) {
break;
}
}
EXIT_NOT_IMPLEMENTED(port >= MAX_PORTS);
g_ports[port].user_id = user_id;
g_ports[port].params = *parameters;
g_ports[port].used = true;
EXIT_IF(g_ports[port].data != nullptr);
EXIT_IF(g_ports[port].data_mutex != nullptr);
EXIT_IF(g_ports[port].playback_sema != nullptr);
g_ports[port].data = new Audio3dData[parameters->queue_depth];
g_ports[port].data_index = 0;
g_ports[port].data_mutex = new Common::Mutex;
g_ports[port].data_delay = (1000000 * static_cast<uint64_t>(parameters->granularity)) / 48000;
for (uint32_t d = 0; d < parameters->queue_depth; d++) {
g_ports[port].data[d].state = Audio3dData::State::Empty;
}
int result = Semaphore::KernelCreateSema(&g_ports[port].playback_sema, "audio3d_play", 0x01, 0,
static_cast<int>(parameters->queue_depth), nullptr);
EXIT_NOT_IMPLEMENTED(result != OK);
g_ports[port].playback_finished = false;
Common::Thread playback_thread(playback_simulate, &g_ports[port]);
playback_thread.Detach();
*id = port;
return OK;
}
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
EXIT_NOT_IMPLEMENTED(attribute == nullptr);
LOGF("\t attribute_id = 0x%" PRIx32 "\n", attribute_id);
switch (attribute_id) {
case 0x10001:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].late_reverb_level = *static_cast<const float*>(attribute);
LOGF("\t late_reverb_level = %f\n", g_ports[port_id].late_reverb_level);
break;
case 0x10002:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].downmix_spread_radius = *static_cast<const float*>(attribute);
LOGF("\t downmix_spread_radius = %f\n", g_ports[port_id].downmix_spread_radius);
break;
case 0x10003:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].downmix_spread_height_aware = *static_cast<const int*>(attribute);
LOGF("\t downmix_spread_height_aware = %d\n",
g_ports[port_id].downmix_spread_height_aware);
break;
default: EXIT("unknown attribute: 0x%" PRIx32 "\n", attribute_id);
}
return OK;
}
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
EXIT_NOT_IMPLEMENTED(queue_level == nullptr && queue_available == nullptr);
auto* port = &g_ports[port_id];
uint32_t empty_num = 0;
port->data_mutex->Lock();
{
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Empty) {
empty_num++;
} else {
break;
}
}
}
port->data_mutex->Unlock();
EXIT_IF(empty_num > port->params.queue_depth);
LOGF("\t queue_available = %u\n", empty_num);
if (queue_level != nullptr) {
*queue_level = port->params.queue_depth - empty_num;
}
if (queue_available != nullptr) {
*queue_available = empty_num;
}
return OK;
}
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
auto* port = &g_ports[port_id];
port->data_mutex->Lock();
{
uint32_t current_index = port->data_index;
uint32_t next_index = (current_index + 1) % port->params.queue_depth;
if (port->data[current_index].state == Audio3dData::State::Empty) {
port->data[current_index].state = Audio3dData::State::Ready;
}
EXIT_NOT_IMPLEMENTED(port->data[current_index].state != Audio3dData::State::Ready);
port->data_index = next_index;
LOGF("\t %u -> %u\n", current_index, next_index);
}
port->data_mutex->Unlock();
return OK;
}
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
auto* port = &g_ports[port_id];
EXIT_NOT_IMPLEMENTED(blocking != 1);
LOGF("\t blocking = %u\n", blocking);
int data_num = 0;
Audio3dData* first_data = nullptr;
port->data_mutex->Lock();
{
first_data = port->data + port->data_index;
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Ready) {
port->data[index].state = Audio3dData::State::Play;
data_num++;
}
}
}
port->data_mutex->Unlock();
LOGF("\t push num = %d\n", data_num);
if (data_num > 0) {
Semaphore::KernelSignalSema(port->playback_sema, data_num);
if (blocking == 1) {
auto wait_time = port->data_delay / 8;
while (first_data->state != Audio3dData::State::Empty) {
Common::Thread::SleepMicro(wait_time);
}
}
}
return OK;
}
} // namespace Audio3d
} // namespace Libs::Audio
+28
View File
@@ -0,0 +1,28 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Audio {
namespace Audio3d {
struct Audio3dOpenParameters;
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved);
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p);
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id);
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size);
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available);
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id);
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking);
} // namespace Audio3d
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_ */
+1 -1
View File
@@ -1719,7 +1719,7 @@ static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem,
h->mem = mem;
h->file = file;
h->event = event;
h->auto_start = auto_start;
h->auto_start = auto_start || event.event_callback == nullptr;
h->video_buffers = std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16);
h->post_init.demux_video_buffer_size = 4 * 1024 * 1024;
return h;
+3
View File
@@ -1,6 +1,9 @@
#include "common/abi.h"
#include "libs/ajm/aac_decoder.h"
#include "libs/acm.h"
#include "libs/audio3d.h"
#include "libs/audio.h"
#include "libs/ngs2.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
+171 -55
View File
@@ -122,6 +122,20 @@ struct SaveDataParam {
uint8_t reserved[32];
};
struct SaveParamTextField {
char* data;
size_t size;
};
static SaveParamTextField param_text_field(SaveDataParam& param, uint32_t type) {
switch (type) {
case 1: return {param.title, sizeof(param.title)};
case 2: return {param.sub_title, sizeof(param.sub_title)};
case 3: return {param.detail, sizeof(param.detail)};
default: return {nullptr, 0};
}
}
struct SaveDataMountInfo {
uint64_t blocks;
uint64_t free_blocks;
@@ -345,6 +359,62 @@ static int read_save_blocks(const std::filesystem::path& directory, uint64_t* bl
return OK;
}
static int read_save_param(const std::filesystem::path& path, SaveDataParam* param) {
std::error_code error;
const bool exists = std::filesystem::exists(path, error);
if (error) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (!exists) {
return SAVE_DATA_ERROR_NOT_FOUND;
}
Common::File file(path, Common::File::Mode::Read);
if (file.IsInvalid()) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (file.Size() != sizeof(*param)) {
return SAVE_DATA_ERROR_BROKEN;
}
uint32_t read = 0;
file.Read(param, sizeof(*param), &read);
return read == sizeof(*param) ? OK : SAVE_DATA_ERROR_BROKEN;
}
static std::filesystem::path save_param_path(const std::filesystem::path& directory) {
return directory / "sce_sys" / "param.bin";
}
static int64_t newest_save_time(const std::filesystem::path& directory) {
int64_t mtime = 0;
std::error_code error;
for (std::filesystem::recursive_directory_iterator it(
directory, std::filesystem::directory_options::skip_permission_denied, error),
end;
it != end; it.increment(error)) {
if (error) {
error.clear();
continue;
}
if (it->is_regular_file(error) && !error) {
const auto time = Common::File::GetLastWriteTimeUTC(it->path());
if (!time.IsInvalid()) {
mtime = std::max(mtime, static_cast<int64_t>(time.ToUnix()));
}
}
error.clear();
}
return mtime;
}
static int load_save_param(const std::filesystem::path& directory, SaveDataParam* param) {
*param = {};
const int status = read_save_param(save_param_path(directory), param);
if (status == OK) {
param->mtime = newest_save_time(directory);
}
return status;
}
static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) {
std::error_code error;
std::filesystem::create_directories(directory, error);
@@ -385,23 +455,7 @@ static int load_memory(const std::filesystem::path& directory, SaveDataMemory* m
return SAVE_DATA_ERROR_BROKEN;
}
file.Close();
const bool has_param = std::filesystem::exists(directory / "param.bin", error);
if (error) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (has_param) {
if (!file.Open(directory / "param.bin", Common::File::Mode::Read)) {
return SAVE_DATA_ERROR_INTERNAL;
}
if (file.Size() != sizeof(memory->param)) {
return SAVE_DATA_ERROR_BROKEN;
}
file.Read(&memory->param, sizeof(memory->param), &read);
if (read != sizeof(memory->param)) {
return SAVE_DATA_ERROR_BROKEN;
}
}
return OK;
return read_save_param(directory / "param.bin", &memory->param);
}
static void queue_save_data_event(uint32_t type, int32_t user_id,
@@ -451,11 +505,11 @@ static bool dir_name_match(const char* str, const char* pattern) {
return *str == '\0' && *pattern == '\0';
}
static int mount_save_data(int slot, std::string_view dir_name, const std::string& directory,
static int mount_save_data(int slot, const std::filesystem::path& directory,
uint32_t status, SaveDataMountResult* result) {
const std::string mount_point = SaveDataMountSlots::MountPoint(static_cast<size_t>(slot));
LibKernel::FileSystem::Mount(directory, mount_point);
g_mount_slots.Mount(static_cast<size_t>(slot), dir_name);
g_mount_slots.Mount(static_cast<size_t>(slot), directory);
std::snprintf(result->mount_point.data, sizeof(result->mount_point.data), "%s",
mount_point.c_str());
result->required_blocks = 0;
@@ -542,7 +596,8 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
if (Common::File::IsDirectoryExisting(root)) {
for (const auto& entry: Common::File::GetDirEntries(root)) {
if (!entry.is_file && entry.name != "." && entry.name != ".." &&
!entry.name.starts_with("sce_") && valid_path_component(entry.name)) {
!entry.name.starts_with("sce_") && valid_path_component(entry.name) &&
Common::File::IsFileExisting(save_param_path(root / entry.name))) {
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
dir_name_match(Common::ToLower(entry.name).c_str(),
Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
@@ -574,7 +629,10 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s",
dir_list[i].c_str());
if (result->params != nullptr) {
result->params[i] = {};
const int status = load_save_param(root / dir_list[i], &result->params[i]);
if (status != OK) {
return status;
}
}
if (result->infos != nullptr) {
auto& info = result->infos[i];
@@ -615,12 +673,12 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
Common::LockGuard lock(g_mount_mutex);
const std::string dir_name = mount->dir_name->data;
const auto mount_dir = save_directory(get_title_id(), dir_name, mount->user_id).string();
const auto mount_dir = save_directory(get_title_id(), dir_name, mount->user_id);
const bool create = ((mount->mount_mode & 4u) != 0);
const bool create2 = ((mount->mount_mode & 32u) != 0);
const bool open = (!create && !create2 && ((mount->mount_mode & 3u) != 0));
const int slot = g_mount_slots.FindAvailable(dir_name);
const int slot = g_mount_slots.FindAvailable(mount_dir);
if (slot == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -645,7 +703,7 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
if (mount->blocks < SAVE_DATA_BLOCKS_MIN || mount->blocks > SAVE_DATA_BLOCKS_MAX) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto metadata = std::filesystem::path(mount_dir) / "sce_sys";
const auto metadata = mount_dir / "sce_sys";
std::error_code error;
std::filesystem::create_directories(metadata, error);
if (error) {
@@ -657,10 +715,17 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
std::filesystem::remove_all(mount_dir, error);
return status;
}
const SaveDataParam initial {};
const int param_status = write_save_file(save_param_path(mount_dir), &initial,
sizeof(initial));
if (param_status != OK) {
std::filesystem::remove_all(mount_dir, error);
return param_status;
}
created = true;
}
return mount_save_data(slot, dir_name, mount_dir, created ? 1u : 0u, mount_result);
return mount_save_data(slot, mount_dir, created ? 1u : 0u, mount_result);
}
int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param,
@@ -686,7 +751,7 @@ int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup
Common::LockGuard lock(g_mount_mutex);
const auto directory = memory_directory(setup_param->user_id, setup_param->slot_id);
if (g_save_data_memory.contains(directory) ||
g_mount_slots.FindAvailable(memory_dir_name(setup_param->slot_id)) ==
g_mount_slots.FindAvailable(directory) ==
SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -848,8 +913,8 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
Common::LockGuard lock(g_mount_mutex);
const std::string dir_name = mount->dir_name->data;
const auto mount_dir = save_directory(mount->title_id->data, dir_name, mount->user_id).string();
const int slot = g_mount_slots.FindAvailable(dir_name);
const auto mount_dir = save_directory(mount->title_id->data, dir_name, mount->user_id);
const int slot = g_mount_slots.FindAvailable(mount_dir);
if (slot == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -861,7 +926,7 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
return SAVE_DATA_ERROR_NOT_FOUND;
}
return mount_save_data(slot, dir_name, mount_dir, 0, mount_result);
return mount_save_data(slot, mount_dir, 0, mount_result);
}
int KYTY_SYSV_ABI SaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) {
@@ -946,7 +1011,7 @@ int KYTY_SYSV_ABI SaveDataDelete(const SaveDataDelete* del) {
Common::LockGuard lock(g_mount_mutex);
const auto dir = save_directory(del->title_id != nullptr ? del->title_id->data : get_title_id(),
del->dir_name->data, del->user_id);
if (g_mount_slots.FindAvailable(del->dir_name->data) == SaveDataMountSlots::BUSY ||
if (g_mount_slots.FindAvailable(dir) == SaveDataMountSlots::BUSY ||
g_save_data_memory.contains(dir)) {
return SAVE_DATA_ERROR_BUSY;
}
@@ -960,7 +1025,8 @@ int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32
void* param_buf, size_t param_buf_size, size_t* got_size) {
PRINT_NAME();
if (mount_point == nullptr || param_buf == nullptr) {
if (mount_point == nullptr || param_buf == nullptr || param_type > 5 ||
std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
@@ -968,21 +1034,44 @@ int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32
"\t param_type = %u\n"
"\t param_buf_size = %" PRIu64 "\n",
mount_point->data, param_type, param_buf_size);
if (param_type == 0) {
if (param_buf_size < sizeof(SaveDataParam)) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memset(param_buf, 0, sizeof(SaveDataParam));
if (got_size != nullptr) {
*got_size = sizeof(SaveDataParam);
}
return OK;
Common::LockGuard lock(g_mount_mutex);
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
std::memset(param_buf, 0, param_buf_size);
SaveDataParam param {};
const int status = load_save_param(g_mount_slots.Directory(static_cast<size_t>(slot)), &param);
if (status != OK) {
return status;
}
size_t size = 0;
if (param_type == 0) {
size = sizeof(param);
if (param_buf_size < size) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memcpy(param_buf, &param, size);
} else if (param_type == 4 || param_type == 5) {
size = param_type == 4 ? sizeof(param.user_param) : sizeof(param.mtime);
if (param_buf_size < size) {
return SAVE_DATA_ERROR_PARAMETER;
}
std::memcpy(param_buf, param_type == 4 ? static_cast<const void*>(&param.user_param)
: static_cast<const void*>(&param.mtime), size);
} else {
if (param_buf_size == 0) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto field = param_text_field(param, param_type);
size = std::min(static_cast<size_t>(
std::find(field.data, field.data + field.size, '\0') - field.data),
param_buf_size - 1);
std::memcpy(param_buf, field.data, size);
static_cast<char*>(param_buf)[size++] = '\0';
}
if (got_size != nullptr) {
*got_size = param_buf_size;
*got_size = size;
}
return OK;
@@ -1035,7 +1124,7 @@ int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const SaveDataMemorySync* sync_para
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
}
const auto dir_name = memory_dir_name(sync_param->slot_id);
if (g_mount_slots.FindAvailable(dir_name) == SaveDataMountSlots::BUSY) {
if (g_mount_slots.FindAvailable(it->first) == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
}
const int status = save_memory(it->first, it->second);
@@ -1108,26 +1197,53 @@ int KYTY_SYSV_ABI SaveDataSetParam(const SaveDataMountPoint* mount_point, uint32
const void* param_buf, size_t param_buf_size) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(mount_point == nullptr);
if (mount_point == nullptr || param_buf == nullptr || param_type > 4 ||
std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr ||
(param_type == 0 && param_buf_size < sizeof(SaveDataParam)) ||
(param_type == 4 && param_buf_size < sizeof(uint32_t)) ||
(param_type >= 1 && param_type <= 3 && param_buf_size == 0)) {
return SAVE_DATA_ERROR_PARAMETER;
}
LOGF("\t mount_point = %s\n"
"\t param_type = %u\n"
"\t param_buf_size = %" PRIu64 "\n",
mount_point->data, param_type, param_buf_size);
Common::LockGuard lock(g_mount_mutex);
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
if (param_type == 0) {
const auto* p = static_cast<const SaveDataParam*>(param_buf);
LOGF("\t title = %s\n"
"\t sub_title = %s\n"
"\t detail = %s\n"
LOGF("\t title = %.128s\n"
"\t sub_title = %.128s\n"
"\t detail = %.1024s\n"
"\t user_param = %u\n",
p->title, p->sub_title, p->detail, p->user_param);
} else {
LOGF("\t unsupported param_type, accepting as no-op\n");
return write_save_file(save_param_path(g_mount_slots.Directory(static_cast<size_t>(slot))),
p, sizeof(*p));
}
return OK;
SaveDataParam param {};
const auto path = save_param_path(g_mount_slots.Directory(static_cast<size_t>(slot)));
const int status = read_save_param(path, &param);
if (status != OK) {
return status;
}
if (param_type == 4) {
std::memcpy(&param.user_param, param_buf, sizeof(param.user_param));
} else {
const auto field = param_text_field(param, param_type);
const auto* text = static_cast<const char*>(param_buf);
const size_t length = std::min(param_buf_size, field.size - 1);
const auto* end = static_cast<const char*>(std::memchr(text, '\0', length));
const size_t count = end != nullptr ? static_cast<size_t>(end - text) : length;
std::memset(field.data, 0, field.size);
std::memcpy(field.data, text, count);
}
return write_save_file(path, &param, sizeof(param));
}
int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point,
@@ -1139,12 +1255,12 @@ int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point,
return SAVE_DATA_ERROR_PARAMETER;
}
Common::LockGuard lock(g_mount_mutex);
if (g_mount_slots.Find(mount_point->data) == SaveDataMountSlots::FULL) {
const int slot = g_mount_slots.Find(mount_point->data);
if (slot == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
uint64_t blocks = 0;
const int status = read_save_blocks(
LibKernel::FileSystem::GetRealFilename(mount_point->data), &blocks);
const int status = read_save_blocks(g_mount_slots.Directory(static_cast<size_t>(slot)), &blocks);
if (status != OK) {
return status;
}
+8 -1
View File
@@ -1,5 +1,6 @@
#include "common/abi.h"
#include "graphics/presentation/videoOut.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
@@ -51,8 +52,14 @@ namespace LibGen5::VrrStatus {
LIB_VERSION("VideoOutVrrStatus", 1, "VideoOut", 1, 1);
static KYTY_SYSV_ABI int VideoOutVrrStatus_kP2L8t3j_aM() {
// The observed guest call passes no arguments.
// Return success for Kyty's fixed-refresh path.
return OK;
}
LIB_DEFINE(InitVideoOutVrrStatus_1) {
LIB_FUNC("kP2L8t3j-aM", VideoOut::VideoOutGetVrrStatus);
LIB_FUNC("kP2L8t3j-aM", VideoOutVrrStatus_kP2L8t3j_aM);
}
} // namespace LibGen5::VrrStatus
+16 -6
View File
@@ -7,6 +7,8 @@
#include "common/threads.h"
#include "loader/timer.h" // IWYU pragma: keep
#include <fmt/format.h>
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PRINT_NAME_ENABLED g_print_name
@@ -49,12 +51,7 @@
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PRINT_NAME() \
if (PRINT_NAME_ENABLED) { \
if (Log::GetDirection() != Log::Direction::Silent) { \
const auto print_name_time = Loader::Timer::GetTime().ToString("HH24:MI:SS.FFF"); \
LOGF_COLOR(Log::Color::Cyan, "[%d][%s] %s::%s::%s()\n", \
Common::Thread::GetThreadIdUnique(), print_name_time.c_str(), g_library, \
g_module, __func__); \
} \
Libs::PrintName(g_library, g_module, __func__); \
}
namespace Loader {
@@ -63,6 +60,19 @@ class SymbolDatabase;
namespace Libs {
// Keep the formatting path from inflating fiber functions' stack frames under LTO.
[[gnu::noinline]] inline void PrintName(const char* library, const char* module, const char* function) {
if (Log::GetDirection() != Log::Direction::Silent) {
const auto elapsed_ms = static_cast<uint64_t>(Loader::Timer::GetTimeMs());
const auto print_name_time = fmt::format(
"{:02}:{:02}:{:02}.{:03}", elapsed_ms / 3600000, (elapsed_ms / 60000) % 60,
(elapsed_ms / 1000) % 60, elapsed_ms % 1000);
LOGF_COLOR(Log::Color::Cyan, "[%d][%s] %s::%s::%s()\n",
Common::Thread::GetThreadIdUnique(), print_name_time.c_str(), library, module,
function);
}
}
void InitAll(Loader::SymbolDatabase* s);
} // namespace Libs
+2363
View File
File diff suppressed because it is too large. Load diff
+83
View File
@@ -0,0 +1,83 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_NGS2_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_NGS2_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Audio {
namespace Ngs2 {
struct Ngs2SystemOption;
struct Ngs2SystemInfo;
struct Ngs2RackOption;
struct Ngs2BufferAllocator;
struct Ngs2VoiceParamHeader;
struct Ngs2RenderBufferInfo;
struct Ngs2ContextBufferInfo;
struct Ngs2VoiceState;
struct Ngs2WaveformFormat;
struct Ngs2WaveformBlock;
struct Ngs2WaveformInfo;
struct Ngs2PanWork;
struct Ngs2PanParam;
struct Ngs2GeomListenerParam;
struct Ngs2GeomListenerWork;
struct Ngs2GeomSourceParam;
struct Ngs2GeomAttribute;
int KYTY_SYSV_ABI Ngs2SystemResetOption(Ngs2SystemOption* option);
int KYTY_SYSV_ABI Ngs2SystemQueryBufferSize(const Ngs2SystemOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2SystemCreate(const Ngs2SystemOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info,
size_t info_size);
int KYTY_SYSV_ABI Ngs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples);
int KYTY_SYSV_ABI Ngs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemCreateWithAllocator(const Ngs2SystemOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackLock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
size_t num_commands);
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
size_t state_size);
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
uint32_t num_buffer_info);
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos,
uint32_t num_samples, Ngs2WaveformBlock* block);
int KYTY_SYSV_ABI Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle,
uint32_t num_speakers);
int KYTY_SYSV_ABI Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params,
uint32_t num_params, uint32_t matrix_format,
float* out_volume_matrix);
int KYTY_SYSV_ABI Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param);
int KYTY_SYSV_ABI Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param);
int KYTY_SYSV_ABI Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param,
Ngs2GeomListenerWork* out_work, uint32_t flags);
int KYTY_SYSV_ABI Ngs2GeomApply(const Ngs2GeomListenerWork* listener,
const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib,
uint32_t flags);
} // namespace Ngs2
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_LIBS_NGS2_H_ */
+16 -5
View File
@@ -2,9 +2,11 @@
#define EMULATOR_INCLUDE_EMULATOR_LIBS_SAVEDATAMOUNTSLOTS_H_
#include <array>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
namespace Libs::SaveData {
@@ -14,12 +16,15 @@ public:
static constexpr int FULL = -1;
static constexpr size_t COUNT = 16;
[[nodiscard]] int FindAvailable(std::string_view directory) const {
[[nodiscard]] int FindAvailable(const std::filesystem::path& directory) const {
int available = FULL;
for (size_t index = 0; index < m_directories.size(); index++) {
const auto& mounted = m_directories[index];
if (mounted == directory) {
return BUSY;
if (mounted.has_value()) {
std::error_code error;
if (*mounted == directory || std::filesystem::equivalent(*mounted, directory, error)) {
return BUSY;
}
}
if (!mounted.has_value() && available == FULL) {
available = static_cast<int>(index);
@@ -28,7 +33,13 @@ public:
return available;
}
void Mount(size_t slot, std::string_view directory) { m_directories[slot] = directory; }
void Mount(size_t slot, const std::filesystem::path& directory) {
m_directories[slot] = directory;
}
[[nodiscard]] const std::filesystem::path& Directory(size_t slot) const {
return *m_directories[slot];
}
void Release(size_t slot) {
if (slot < m_directories.size()) {
@@ -59,7 +70,7 @@ public:
}
private:
std::array<std::optional<std::string>, COUNT> m_directories;
std::array<std::optional<std::filesystem::path>, COUNT> m_directories;
};
} // namespace Libs::SaveData
+1 -1
View File
@@ -1324,7 +1324,7 @@ void RuntimeLinker::RelocateProgram(Program* program) {
EXIT_IF(program == nullptr);
EXIT_IF(std::find(m_programs.begin(), m_programs.end(), program) == m_programs.end());
Relocate(program);
RelocateAll();
if (!GamePatch::ApplyPending(program)) {
EXIT("Failed to apply pending game cheat\n");
}
-6
View File
@@ -1,7 +1,5 @@
#include "common/timer.h"
#include "common/abi.h"
#include "common/dateTime.h"
#include "loader/timer.h"
namespace Loader::Timer {
@@ -16,8 +14,4 @@ double GetTimeMs() {
return g_timer.GetTimeMs();
}
Common::Time GetTime() {
return Common::Time(static_cast<int>(GetTimeMs()));
}
} // namespace Loader::Timer
+2 -6
View File
@@ -1,14 +1,10 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_TIMER_H_
#define EMULATOR_INCLUDE_EMULATOR_LOADER_TIMER_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/dateTime.h"
namespace Loader::Timer {
void Start();
double GetTimeMs();
Common::Time GetTime();
void Start();
double GetTimeMs();
struct Lifecycle {
static constexpr const char* name = "Timer";
+689
View File
@@ -0,0 +1,689 @@
// Exercise sampler controls and rendering without a host audio device.
#include "libs/ngs2.cpp"
#include <bit>
#include <cstdio>
#include <cstdlib>
#include <numbers>
using namespace Libs::Audio::Ngs2;
namespace {
void Check(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "Ngs2SamplerTests: %s\n", message);
std::abort();
}
}
struct Fixture {
Ngs2Internal system;
Ngs2RackInternal rack {};
Ngs2VoiceInternal voice;
Fixture(uint32_t rate, uint32_t channels = 1, Ngs2RackType type = Ngs2RackType::Sampler) {
system.option.sample_rate = 48000;
rack.ngs = &system;
rack.type = type;
voice.rack = &rack;
struct Setup {
Ngs2VoiceParamHeader header {40, 0, 0x10000000};
Ngs2WaveformFormat format;
uint32_t flags = 0, reserved = 0;
} setup;
setup.header.id = type == Ngs2RackType::Sampler ? 0x10000000 : 0x40010000;
setup.format = {0x12, channels, rate};
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&voice), &setup.header) == OK,
"sampler setup failed");
voice.state = Ngs2VoicePlayState::Playing;
}
void Queue(const int16_t* data, uint32_t frames, uint32_t flags, uint32_t repeats = 0,
uint32_t skip = 0) {
Ngs2WaveformBlock block {
0, size_t(frames + skip) * voice.channels * sizeof(int16_t), repeats, skip, frames, 0,
123};
struct Blocks {
Ngs2VoiceParamHeader header {32, 0, 0x10000001};
const void* data;
uint32_t flags, count;
const Ngs2WaveformBlock* blocks;
} param {{32, 0, 0x10000001}, data, flags, 1, &block};
param.header.id = rack.type == Ngs2RackType::Sampler ? 0x10000001 : 0x40010001;
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&voice), &param.header) == OK,
"sampler queue failed");
}
void Render(uint32_t frames) {
voice.rendered = false;
Ngs2RenderVoice(voice, {}, frames);
}
};
void TestStreamingResample() {
Fixture f(44100, 2);
std::vector<int16_t> pcm(441 * 2);
for (int i = 0; i < 441; ++i) {
pcm[i * 2] = static_cast<int16_t>(i * 32);
pcm[i * 2 + 1] = static_cast<int16_t>(-i * 32);
}
f.Queue(pcm.data(), 200, 1);
f.Queue(pcm.data() + 400, 241, 0);
for (uint32_t grain = 0; grain < 3; ++grain) {
f.Render(160);
for (uint32_t i = 0; i < 160; ++i) {
const float expected = std::min((grain * 160 + i) * 44100.0 / 48000.0, 440.0) / 1024.0;
Check(std::abs(f.voice.samples[i] - expected) < 0.00001f,
"resampling discontinuity across grain or block boundary");
Check(std::abs(f.voice.samples[160 + i] + expected) < 0.00001f,
"stereo channels were mixed");
}
}
Check(f.voice.blocks.empty(), "final streaming block did not finish");
Check(f.voice.state == Ngs2VoicePlayState::Empty, "finished voice remained active");
Ngs2SamplerVoiceState state {};
Ngs2VoiceGetState(reinterpret_cast<uintptr_t>(&f.voice), &state.voice_state, sizeof(state));
Check(state.num_decoded_samples == 441 && state.decoded_data_size == pcm.size() * 2,
"streaming progress does not match consumed source data");
}
void TestPitchLoopAndSkip() {
Fixture f(24000);
const int16_t pcm[] = {-30000, 8192, 16384};
f.Queue(pcm, 2, 0, 1, 1);
struct Pitch {
Ngs2VoiceParamHeader header {16, 0, 0x10000005};
float ratio = 2.0f;
uint32_t reserved = 0;
} pitch;
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &pitch.header);
f.Render(5);
const float expected[] = {0.25f, 0.5f, 0.25f, 0.5f, 0.0f};
for (size_t i = 0; i < 5; ++i) {
Check(std::abs(f.voice.samples[i] - expected[i]) < 0.00001f,
"pitch, repeat count, or skipped samples incorrect");
}
}
void TestStarvationAndPause() {
Fixture f(48000);
const int16_t pcm[] = {16384, 8192};
f.Queue(pcm, 2, 1);
f.Render(4);
Check(f.voice.state == Ngs2VoicePlayState::Playing, "stream starvation stopped voice");
Check(f.voice.samples[2] == 0.0f, "starved output was not silent");
f.Queue(pcm, 2, 0);
f.voice.SetEvent(16);
f.Render(2);
Check(!f.voice.has_samples && f.voice.blocks.front().cursor == 0, "paused voice consumed data");
f.voice.SetEvent(32);
f.Render(2);
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == 0.25f,
"stream failed to resume after starvation/pause");
}
void TestMonoRateAndRouting() {
Fixture f(22050);
std::vector<int16_t> pcm(441, 16384);
f.Queue(pcm.data(), 441, 0);
Ngs2RackInternal mastering_rack {};
mastering_rack.type = Ngs2RackType::Mastering;
Ngs2VoiceInternal mastering;
mastering.rack = &mastering_rack;
mastering.channels = 2;
mastering.state = Ngs2VoicePlayState::Playing;
f.voice.matrices = {{0.5f, 0.25f}};
f.voice.ports.push_back({&mastering, 0, 1.0f, 0});
for (int i = 0; i < 3; ++i) {
mastering.rendered = false;
f.voice.rendered = false;
Ngs2RenderVoice(mastering, {&f.voice, &mastering}, 320);
Check(mastering.has_samples, "standard sampler did not reach mastering voice");
for (uint32_t j = 0; j < 320; ++j) {
Check(mastering.samples[j] == 0.25f && mastering.samples[320 + j] == 0.125f,
"mono PCM routing matrix was not applied");
}
}
Check(f.voice.decoded_samples == 441 && f.voice.blocks.empty(),
"22.05 kHz effect duration was incorrect");
}
void TestCustomPcmStillPlays() {
Fixture f(48000, 1, Ngs2RackType::CustomSampler);
const int16_t pcm[] = {8192, -16384};
f.Queue(pcm, 2, 0);
f.Render(4);
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == -0.5f &&
f.voice.samples[2] == 0.0f && f.voice.blocks.empty(),
"custom PCM sampler regressed");
}
void TestSamplerReuseResetsRouting() {
for (auto type: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
Fixture f(48000, 1, type);
Ngs2RackInternal mastering_rack {};
mastering_rack.type = Ngs2RackType::Mastering;
Ngs2VoiceInternal mastering;
mastering.rack = &mastering_rack;
mastering.channels = 2;
mastering.state = Ngs2VoicePlayState::Playing;
f.voice.ports.push_back({&mastering, 1, 0.25f, 0});
f.voice.matrices = {{0.5f, 0.25f}};
f.voice.SetupSampler({0x12, 2, 48000});
Check(f.voice.ports.size() == 1 && f.voice.ports[0].dest == nullptr &&
f.voice.ports[0].input == 0 && f.voice.ports[0].volume == 1.0f &&
f.voice.ports[0].matrix == -1 && f.voice.matrices.size() == 1 &&
f.voice.matrices[0].empty(),
"sampler setup retained routing from the previous channel layout");
const int16_t pcm[] = {8192, 16384, -8192, -16384};
f.Queue(pcm, 2, 0);
f.Render(1);
Check(!f.voice.has_samples && f.voice.blocks.front().cursor == 0,
"sampler setup started the new waveform without a play event");
f.voice.ports[0].dest = &mastering;
f.voice.SetEvent(1);
f.voice.rendered = false;
Ngs2RenderVoice(mastering, {&f.voice, &mastering}, 2);
Check(mastering.samples == std::vector<float> {0.25f, -0.25f, 0.5f, -0.5f},
"reused stereo sampler did not restore default channel routing");
}
}
void TestPlayStateBeforeRender() {
Fixture f(48000);
const int16_t pcm[] = {8192, -16384};
f.voice.state = Ngs2VoicePlayState::Empty;
f.Queue(pcm, 2, 0);
Ngs2VoiceEventParam event {{sizeof(Ngs2VoiceEventParam), 0, 6}, 1};
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
Ngs2VoiceControl(handle, &event.header);
uint32_t flags = 0;
Ngs2VoiceGetStateFlags(handle, &flags);
Check(flags == 1, "play must reserve the voice before rendering starts playback");
Ngs2SamplerVoiceState state {};
Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state));
Check(state.voice_state.state_flags == flags && state.num_decoded_samples == 0,
"state queries disagreed or play consumed samples before render");
f.Render(4);
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == -0.5f,
"one-shot effect was dropped before its first render");
}
void TestStatePublication() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
Ngs2RackOptionUnion option {};
Ngs2FillDefaultRackOption(0x1000, &option);
option.common.max_voices = 1;
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
std::byte storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
Ngs2ContextBufferInfo buffer {storage, sizeof(storage)};
uintptr_t rack_handle = 0;
const auto system_handle = reinterpret_cast<uintptr_t>(&system);
Check(Ngs2RackCreate(system_handle, 0x1000, &option.common, &buffer, &rack_handle) == OK,
"state publication rack creation failed");
auto& voice =
*reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(rack_handle) + 1);
voice.SetupSampler({0x12, 1, 48000});
const auto handle = reinterpret_cast<uintptr_t>(&voice);
auto check_flags = [&](uint32_t expected) {
uint32_t flags = 0;
Ngs2SamplerVoiceState state {};
Ngs2VoiceGetStateFlags(handle, &flags);
Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state));
Check(flags == expected && state.voice_state.state_flags == expected,
"voice state was published at the wrong render boundary");
};
struct Transition {
uint32_t event, before_render, after_render;
};
const Transition transitions[] = {{1, 1, 3}, {16, 3, 5}, {32, 5, 3}, {2, 3, 0},
{1, 1, 3}, {16, 3, 5}, {2, 5, 0}, {1, 1, 3},
{4, 3, 0}, {1, 1, 3}, {8, 3, 0}};
Ngs2RenderBufferInfo output {};
for (const auto& transition: transitions) {
Ngs2VoiceEventParam event {{sizeof(Ngs2VoiceEventParam), 0, 6}, transition.event};
Ngs2VoiceControl(handle, &event.header);
check_flags(transition.before_render);
Ngs2SystemRender(system_handle, &output, 1);
check_flags(transition.after_render);
}
Check(Ngs2RackDestroy(rack_handle, nullptr) == OK, "state publication rack cleanup failed");
}
void TestStopThenPlayBeforeRender() {
Fixture f(48000);
const int16_t pcm[] = {16384, -8192};
f.Queue(pcm, 2, 0);
f.voice.SetEvent(2);
f.voice.SetEvent(1);
Check(Ngs2GetStateFlags(&f.voice) == 1, "stopped voice could not restart");
f.voice.SetEvent(8);
f.voice.SetEvent(1);
Check(Ngs2GetStateFlags(&f.voice) == 1, "kill/play commands lost their ordering");
f.Render(2);
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == -0.25f,
"restarted voice produced no audio");
}
void SetLowPass(Fixture& f, float frequency = 1000.0f, uint64_t mask = 0) {
struct Param {
Ngs2VoiceParamHeader header {56, 0, 0x1000000a};
Ngs2SamplerFilterParam filter {0, 1, 0, 1, 1000.0f, 0.70710678f, 1.0f, {}};
} param;
param.filter.frequency = frequency;
param.filter.channel_mask = mask;
f.rack.option.sampler.max_filters = 8;
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &param.header) == OK,
"filter ABI command failed");
}
void TestLowPassResponse() {
// A second-order Butterworth LPF has unity DC gain and -3 dB at cutoff.
for (double frequency: {100.0, 1000.0, 10000.0}) {
Fixture f(48000);
SetLowPass(f);
std::vector<int16_t> pcm(48000);
for (size_t i = 0; i < pcm.size(); ++i) {
pcm[i] =
static_cast<int16_t>(8192 * std::sin((2.0 * std::numbers::pi) * frequency * i / 48000));
}
f.Queue(pcm.data(), static_cast<uint32_t>(pcm.size()), 0);
f.Render(static_cast<uint32_t>(pcm.size()));
double input = 0, output = 0;
for (size_t i = 24000; i < pcm.size(); ++i) {
input += std::pow(pcm[i] / 32768.0, 2);
output += std::pow(f.voice.samples[i], 2);
}
const double ratio = std::sqrt(output / input);
if (frequency == 100) {
Check(std::abs(ratio - 1.0) < 0.002, "low-pass lost bass");
}
if (frequency == 1000) {
Check(std::abs(ratio - std::sqrt(0.5)) < 0.002, "cutoff gain incorrect");
}
if (frequency == 10000) {
Check(ratio < 0.01, "low-pass did not reject high frequencies");
}
}
}
void TestFilterHistoryAndChannels() {
Fixture whole(48000, 2), split(48000, 2);
SetLowPass(whole, 1000, 1);
SetLowPass(split, 1000, 1);
std::vector<int16_t> pcm(256 * 2, 0);
pcm[0] = pcm[1] = 16384;
whole.Queue(pcm.data(), 256, 0);
split.Queue(pcm.data(), 32, 1);
split.Queue(pcm.data() + 64, 224, 0);
whole.Render(256);
for (uint32_t g = 0; g < 8; ++g) {
// Games resend unchanged parameters; this must not reset the delay line.
SetLowPass(split, 1000, 1);
split.Render(32);
for (uint32_t c = 0; c < 2; ++c) {
for (uint32_t i = 0; i < 32; ++i) {
Check(std::abs(split.voice.samples[c * 32 + i] -
whole.voice.samples[c * 256 + g * 32 + i]) < 1e-7,
"filter history changed at a grain/block/control boundary");
}
}
}
Check(whole.voice.samples[0] == 0.5f && whole.voice.samples[1] == 0.0f &&
whole.voice.samples[256] > 0.0f && whole.voice.samples[256] < 0.5f,
"channel mask must bypass selected channels and filter the others");
Fixture stereo(48000, 2);
SetLowPass(stereo);
pcm[1] = 0;
stereo.Queue(pcm.data(), 256, 0);
stereo.Render(256);
for (size_t i = 256; i < 512; ++i) {
Check(stereo.voice.samples[i] == 0.0f, "filter leaked history between channels");
}
stereo.voice.SetupSampler({0x12, 2, 48000});
Check(stereo.voice.filters.empty(), "reused voice retained old filters");
}
void TestFilterUsesOutputRate() {
Fixture f(22050);
SetLowPass(f, 6833.0f);
const auto& filter = f.voice.filters[0];
// Rear filtering operates at the 48 kHz system rate, not the PCM source rate.
const double w = (2.0 * std::numbers::pi) * 6833 / 48000;
const double expected = (1 - std::cos(w)) / (2 * (1 + std::sin(w) / (2 * double(0.70710678f))));
Check(std::abs(filter.b0 - expected) < 1e-10, "rear filter used source sample rate");
SetLowPass(f, 48000);
const int16_t pcm[] = {16384, 16384};
f.Queue(pcm, 2, 0);
f.Render(2);
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == 0.5f,
"above-Nyquist cutoff was unstable");
}
void TestFilterControlLayout() {
Fixture f(48000, 2);
f.rack.option.sampler.max_filters = 8;
// A 56-byte control command with nonzero trailing padding.
alignas(8) uint32_t words[] = {56, 0x1000000a, 0, 1, 0, 0, 1,
0x45d58800, 0x3fd55555, 0x3f800000, 0, 0, 0, 0x13f};
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice),
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
Check(f.voice.filters.size() == 1 && f.voice.filters[0].enabled,
"FCQ filter command was not recognized");
const double omega = (2.0 * std::numbers::pi) * 6833.0 / 48000.0;
const double q = static_cast<double>(std::bit_cast<float>(0x3fd55555u));
const double expected = (1 - std::cos(omega)) / (2 * (1 + std::sin(omega) / (2 * q)));
Check(std::abs(f.voice.filters[0].b0 - expected) < 1e-10,
"FCQ fields were read at the wrong offsets");
words[4] = 1; // Bypass channel 0 in the low half of the 64-bit mask.
words[5] = 1; // Bypass channel 32 in the high half.
Fixture wide(48000, 33);
wide.rack.option.sampler.max_filters = 8;
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&wide.voice),
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
const std::vector<int16_t> pcm(33, 16384);
wide.Queue(pcm.data(), 1, 1);
wide.Render(1);
Check(wide.voice.samples[0] == 0.5f && wide.voice.samples[32] == 0.5f &&
std::abs(wide.voice.samples[1] - expected * 0.5) < 1e-7,
"channel mask was not decoded as a 64-bit bypass mask");
words[6] = 0; // OFF ignores unused FCQ values and clears the old delay line.
words[7] = 0x7fc00000;
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&wide.voice),
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
Check(!wide.voice.filters[0].enabled && wide.voice.filters[0].history.empty() &&
!wide.voice.filters[0].warned,
"filter OFF was treated as an unsupported FCQ configuration");
wide.Queue(pcm.data(), 1, 1);
wide.Render(1);
Check(wide.voice.samples[1] == 0.5f, "filter OFF did not bypass audio");
SetLowPass(wide, 0);
wide.Queue(pcm.data(), 1, 1);
wide.Render(1);
Check(wide.voice.samples[1] == 0.0f, "zero-Hz low-pass leaked source audio");
}
struct LoopCallbacks {
std::vector<std::pair<uint32_t, uint32_t>> events;
bool pause = false;
};
void KYTY_SYSV_ABI RecordLoopCallback(const Ngs2VoiceCallbackInfo* info) {
auto& log = *reinterpret_cast<LoopCallbacks*>(info->data);
log.events.emplace_back(info->flag, info->repeats);
Check(info->user == 123 && info->attributes == 0, "loop callback metadata changed");
if (log.pause && info->flag == 2) {
reinterpret_cast<Ngs2VoiceInternal*>(info->voice)->SetEvent(16);
}
}
void TestLoopCallbacksAndExit() {
const int16_t pcm[] = {8192, 16384, 24576};
Fixture f(48000);
LoopCallbacks log;
f.voice.callback = reinterpret_cast<uintptr_t>(&RecordLoopCallback);
f.voice.callback_data = reinterpret_cast<uintptr_t>(&log);
f.voice.callback_flags = 3;
f.Queue(pcm, 2, 0, 2);
f.Render(6);
Check(log.events == std::vector<std::pair<uint32_t, uint32_t>> {{2, 1}, {2, 2}, {1, 2}},
"loop and completion callbacks lost the completed repeat count");
Check(f.voice.blocks.empty(), "finite loop did not complete");
Fixture active(48000);
log.events.clear();
log.pause = true;
active.voice.callback = reinterpret_cast<uintptr_t>(&RecordLoopCallback);
active.voice.callback_data = reinterpret_cast<uintptr_t>(&log);
active.voice.callback_flags = 2;
active.Queue(pcm, 2, 1, UINT32_MAX);
active.Queue(pcm + 2, 1, 0);
active.Render(6);
Check(log.events.size() == 1 && active.voice.decoded_samples == 2 &&
active.voice.blocks.front().cursor == 0 && active.voice.samples[2] == 0,
"loop callback pause consumed another iteration");
Ngs2VoiceParamHeader exit_loop {sizeof(Ngs2VoiceParamHeader), 0, 0x10000004};
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&active.voice), &exit_loop);
active.voice.SetEvent(32);
active.Render(3);
Check(active.voice.samples == std::vector<float> {0.25f, 0.5f, 0.75f} &&
active.voice.blocks.empty() && log.events.size() == 1,
"exiting infinite loop lost its final iteration or ignored callback flags");
Fixture pending(48000);
pending.Queue(pcm, 1, 1);
pending.Queue(pcm + 1, 1, 1, UINT32_MAX);
pending.Queue(pcm + 2, 1, 0);
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&pending.voice), &exit_loop);
pending.Render(3);
Check(pending.voice.samples == std::vector<float> {0.25f, 0.5f, 0.75f} &&
pending.voice.blocks.empty(),
"exiting a pending loop skipped its first iteration");
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&pending.voice), &exit_loop);
}
void TestWaveformReadAddress() {
Fixture f(48000, 2);
const int16_t pcm[] = {-100, -100, 8192, 4096, 16384, 8192};
f.Queue(pcm, 2, 1, 1, 1);
const auto next_data = [&]() {
Ngs2SamplerVoiceState state {};
Ngs2VoiceGetState(reinterpret_cast<uintptr_t>(&f.voice), &state.voice_state, sizeof(state));
return state.waveform_data;
};
Check(next_data() == pcm + 2, "read address ignored skipped source samples");
f.Render(1);
Check(next_data() == pcm + 4, "read address did not advance by a stereo frame");
f.Render(1);
Check(next_data() == pcm + 2, "read address did not wrap at a loop boundary");
f.Render(2);
Check(f.voice.blocks.empty() && next_data() == pcm + 6,
"starved read address did not preserve the consumed waveform end");
f.Queue(pcm + 2, 2, 0);
Check(next_data() == pcm + 2, "refilled read address retained the previous waveform end");
f.Render(2);
Check(next_data() == pcm + 6, "completed read address did not preserve waveform end");
f.voice.SetupSampler({0x12, 2, 48000});
Check(next_data() == nullptr, "setup retained an old waveform address");
}
struct CallbackEvent {
Ngs2VoiceInternal* voice;
uint32_t event;
uint32_t calls = 0;
};
void KYTY_SYSV_ABI ApplyCallbackEvent(const Ngs2VoiceCallbackInfo* info) {
auto& context = *reinterpret_cast<CallbackEvent*>(info->data);
if (++context.calls == 1) {
context.voice->SetEvent(context.event);
}
}
void TestCallbackStopsConsumption() {
for (auto rack: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
for (uint32_t event: {2u, 4u, 8u, 16u}) {
Fixture f(48000, 1, rack);
const int16_t pcm[] = {8192, 16384};
f.Queue(pcm, 1, 1);
f.Queue(pcm + 1, 1, 1);
CallbackEvent context {&f.voice, event};
f.voice.callback = reinterpret_cast<uintptr_t>(&ApplyCallbackEvent);
f.voice.callback_data = reinterpret_cast<uintptr_t>(&context);
f.voice.callback_flags = 1;
f.Render(2);
Check(f.voice.state != Ngs2VoicePlayState::Playing && context.calls == 1,
"completion callback failed to interrupt playback");
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == 0.0f &&
f.voice.decoded_samples == 1 && f.voice.blocks.size() == 1 &&
f.voice.blocks.front().cursor == 0,
"callback pause/stop consumed the next queued block");
if (event == 16) {
f.voice.SetEvent(32);
f.Render(1);
Check(f.voice.samples[0] == 0.5f && f.voice.decoded_samples == 2,
"callback pause failed to preserve queued audio for resume");
}
}
}
}
void TestCallbackPauseDuringLargeStep() {
Fixture f(48000);
const int16_t pcm[] = {8192, 16384, 24576, 4096};
f.Queue(pcm, 1, 1);
f.Queue(pcm + 1, 1, 1);
f.Queue(pcm + 2, 2, 1);
f.voice.sample_step = uint64_t(120000) << 32u;
CallbackEvent context {&f.voice, 16};
f.voice.callback = reinterpret_cast<uintptr_t>(&ApplyCallbackEvent);
f.voice.callback_data = reinterpret_cast<uintptr_t>(&context);
f.voice.callback_flags = 1;
f.Render(2);
Check(context.calls == 1 && f.voice.decoded_samples == 1 &&
f.voice.sample_phase == (uint64_t(72000) << 32u),
"pause advanced across additional blocks in a large resampling step");
f.voice.SetEvent(32);
f.Render(1);
Check(std::abs(f.voice.samples[0] - 0.4375f) < 1e-7f && context.calls == 3,
"resume lost fractional position or read past the next short block");
}
void TestUpstreamCustomSamplerControls() {
Fixture f(24000, 1, Ngs2RackType::CustomSampler);
const int16_t pcm[] = {8192, 16384, 24576, 4096};
f.Queue(pcm, 4, 1);
f.Render(1);
Check(f.voice.samples[0] == 0.25f, "custom sampler source rate was rejected");
f.Queue(pcm + 2, 2, 4);
f.Render(1);
Check(f.voice.samples[0] == 0.75f && f.voice.blocks.size() == 1,
"replacement blocks retained old data or fractional position");
struct Pitch {
Ngs2VoiceParamHeader header {16, 0, 0x40010005};
float ratio = 0.0f;
uint32_t reserved = 0;
} pitch;
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
Ngs2VoiceControl(handle, &pitch.header);
f.Render(2);
Check(f.voice.samples[0] == 0.4375f && f.voice.samples[1] == 0.4375f,
"zero custom sampler pitch advanced playback");
pitch.ratio = 4.0f;
Ngs2VoiceControl(handle, &pitch.header);
f.Render(1);
Check(f.voice.blocks.empty(), "custom sampler pitch did not advance playback");
f.rack.option.custom_sampler.custom_rack_option.state_size =
sizeof(Ngs2CustomSamplerVoiceState);
Ngs2CustomSamplerVoiceState state {};
Check(Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state)) == OK &&
state.voice_state.state_flags == Ngs2GetStateFlags(&f.voice),
"custom sampler state layout regressed");
}
void TestFilterTailDuringStarvation() {
Fixture reference(48000), stream(48000);
SetLowPass(reference);
SetLowPass(stream);
std::vector<int16_t> pcm(128, 0);
pcm[0] = pcm[64] = 16384;
reference.Queue(pcm.data(), 128, 1);
reference.Render(128);
stream.Queue(pcm.data(), 1, 1);
stream.Render(1);
Check(stream.voice.samples[0] == reference.voice.samples[0], "initial filter sample differs");
stream.Render(63);
Check(stream.voice.has_samples, "starved filter tail was not available for routing");
for (size_t i = 0; i < 63; ++i) {
Check(std::abs(stream.voice.samples[i] - reference.voice.samples[i + 1]) < 1e-7f,
"filter tail changed at a starvation/grain boundary");
}
stream.Queue(pcm.data() + 64, 64, 1);
stream.Render(64);
for (size_t i = 0; i < 64; ++i) {
Check(std::abs(stream.voice.samples[i] - reference.voice.samples[i + 64]) < 1e-7f,
"refilled stream reused stale filter history");
}
stream.Render(8192);
stream.Render(32);
Check(!stream.voice.has_samples && !stream.voice.filters[0].HasHistory(),
"fully decayed filter kept a silent stream active");
}
void TestResamplingPhaseAfterStarvation() {
for (auto rack: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
for (uint32_t rate: {44100u, 96000u}) {
Fixture reference(rate, 1, rack), stream(rate, 1, rack);
const int16_t pcm[] = {0, 0, 16384, 8192};
const uint32_t frames = rate == 44100 ? 2 : 1;
reference.Queue(pcm, 4, 0);
stream.Queue(pcm, 1, 1);
reference.Render(frames);
stream.Render(frames);
Check(stream.voice.blocks.empty() && stream.voice.state == Ngs2VoicePlayState::Playing,
"open stream did not enter starvation");
stream.Render(8);
Check(!stream.voice.has_samples, "starved stream produced audio without a filter");
stream.Queue(pcm + 1, 3, 0);
reference.Render(1);
stream.Render(1);
Check(std::abs(stream.voice.samples[0] - reference.voice.samples[0]) < 1e-7f,
"stream refill lost resampling phase");
Check(stream.voice.decoded_samples == reference.voice.decoded_samples,
"stream refill lost pending source advancement");
stream.voice.SetupSampler({0x12, 1, rate});
Check(stream.voice.sample_phase == 0, "sampler setup retained old resampling phase");
}
}
}
void TestFiniteFilterTail() {
Fixture whole(48000), split(48000);
SetLowPass(whole);
SetLowPass(split);
const int16_t impulse = 16384;
whole.Queue(&impulse, 1, 0);
split.Queue(&impulse, 1, 0);
whole.Render(256);
for (size_t i = 0; i < whole.voice.samples.size(); ++i) {
split.Render(1);
Check(std::abs(split.voice.samples[0] - whole.voice.samples[i]) < 1e-7f,
"finite filter tail changed with render grain size");
}
Check(split.voice.state == Ngs2VoicePlayState::Playing,
"finite voice stopped before its filter tail decayed");
split.Render(8192);
Check(split.voice.state == Ngs2VoicePlayState::Empty,
"finite voice remained active after its filter tail decayed");
split.Render(32);
Check(!split.voice.has_samples, "completed finite filter tail still produced samples");
}
} // namespace
int main() {
TestResamplingPhaseAfterStarvation();
TestSamplerReuseResetsRouting();
TestLoopCallbacksAndExit();
TestWaveformReadAddress();
TestFilterTailDuringStarvation();
TestFiniteFilterTail();
TestLowPassResponse();
TestFilterHistoryAndChannels();
TestFilterUsesOutputRate();
TestFilterControlLayout();
TestUpstreamCustomSamplerControls();
TestCallbackStopsConsumption();
TestCallbackPauseDuringLargeStep();
TestStreamingResample();
TestPitchLoopAndSkip();
TestStarvationAndPause();
TestMonoRateAndRouting();
TestCustomPcmStillPlays();
TestPlayStateBeforeRender();
TestStatePublication();
TestStopThenPlayBeforeRender();
std::puts("Ngs2SamplerTests: all cases passed");
}
+388 -1
View File
@@ -614,7 +614,8 @@ void TestGuardedDirectImageTable() {
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
Check(indirect && indirect->material_source == UINT32_MAX &&
indirect->selector_stride == 0u && indirect->table_offset == 344u &&
indirect->key_count == 32u &&
indirect->key_count.Resolve().IsImmediate() &&
indirect->key_count.Resolve().U32() == 32u &&
fixture.program.descriptor_sources[indirect->table_source].dword_count == 2u,
"guarded direct image table lost its pointer or proven selector range");
return ExtractResourcePlan(fixture.program);
@@ -699,6 +700,390 @@ void TestGuardedDirectImageTable() {
"batched descriptor read crossed the 48-bit endpoint");
}
void TestBoundedComputeImageLoop() {
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
enum class Variant {
Bounded, WrongGuard, EntryBypass, ExitBypass, GuardBlock, WrongStep
};
const auto make_plan = [](Variant variant) {
Fixture fixture;
auto *entry = fixture.block;
auto *header = fixture.AddBlock();
auto *body = fixture.AddBlock();
auto *latch = fixture.AddBlock();
auto *exit = fixture.AddBlock();
entry->AddBranch(header);
header->AddBranch(exit);
header->AddBranch(body);
body->AddBranch(latch);
latch->AddBranch(header);
if (variant == Variant::EntryBypass) entry->AddBranch(body);
if (variant == Variant::ExitBypass) exit->AddBranch(body);
fixture.program.block_info[0].terminator = {
.kind = variant == Variant::EntryBypass ? CFG::TerminatorKind::ConditionalBranch
: CFG::TerminatorKind::Branch,
.true_block = 1u, .false_block = 2u};
fixture.program.block_info[1].terminator = {
.kind = CFG::TerminatorKind::ConditionalBranch,
.true_block = 4u, .false_block = 2u};
fixture.program.block_info[2].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 3u};
fixture.program.block_info[3].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 1u};
fixture.program.block_info[4].terminator = {
.kind = variant == Variant::ExitBypass ? CFG::TerminatorKind::Branch
: CFG::TerminatorKind::Return,
.true_block = 2u};
auto &phi = header->AppendNewInst(ValueOpcode::Phi, {},
static_cast<uint64_t>(Type::U32));
const auto key = Value(&phi);
const auto count = fixture.UserData(2);
const auto in_range = fixture.Emit(ValueOpcode::SLessThan32,
{variant == Variant::WrongGuard
? Value(0u) : key,
count}, 0, header);
const auto allowed = fixture.Emit(ValueOpcode::LogicalAnd,
{in_range, Value(true)}, 0, header);
fixture.program.block_info[1].condition =
fixture.Emit(ValueOpcode::LogicalNot, {allowed}, 0, header);
const auto step = fixture.Emit(ValueOpcode::IAdd32,
{key, Value(variant == Variant::WrongStep ? 2u : 1u)},
0, latch);
phi.AddPhiOperand(entry, Value(0u));
phi.AddPhiOperand(latch, step);
fixture.block = variant == Variant::GuardBlock ? header : body;
const auto table = fixture.Address(fixture.UserData(0), fixture.UserData(1));
const auto offset = fixture.Emit(ValueOpcode::IAdd32,
{fixture.Emit(ValueOpcode::ShiftLeftLogical32, {key, Value(5u)}),
Value(0x6b0u)});
std::array<Value, 8> words;
for (uint32_t word = 0; word < words.size(); ++word) {
MemoryInfo memory;
memory.kind = ResourceKind::ScalarAddress;
memory.offset = word * sizeof(uint32_t);
words[word] = fixture.Emit(
ValueOpcode::LoadAddressU32,
{table, offset, Value(0u), Value(true)},
fixture.AddMemory(memory, 0x29c));
}
const auto image = fixture.Image(words, 0x29c);
const auto sampler = fixture.Sampler({Value(0u), Value(0u), Value(0u), Value(0u)});
MemoryInfo sample;
sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D;
fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture.ImageAddress()},
fixture.AddMemory(sample, 0x29c));
fixture.PlanAndTrack();
const auto source = fixture.program.info.images[0].source;
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
Check(indirect && indirect->material_source == UINT32_MAX &&
indirect->table_offset == 0x6b0u &&
indirect->key_count.Resolve() == count.Resolve(),
"bounded compute loop lost its runtime image count");
return ExtractResourcePlan(fixture.program);
};
auto plan = make_plan(Variant::Bounded);
CheckFatal([&] { make_plan(Variant::WrongGuard); },
"not a valid runtime value",
"compute image loop accepted an unrelated guard");
CheckFatal([&] { make_plan(Variant::EntryBypass); },
"not a valid runtime value",
"compute image loop accepted an entry bypass");
CheckFatal([&] { make_plan(Variant::ExitBypass); },
"not a valid runtime value",
"compute image loop accepted an exit bypass");
CheckFatal([&] { make_plan(Variant::GuardBlock); },
"not a valid runtime value",
"compute image loop accepted a descriptor read before the guard");
CheckFatal([&] { make_plan(Variant::WrongStep); },
"not a valid runtime value",
"compute image loop accepted a two-step induction");
LinearTestMemory memory;
const auto table = 0x1800u + 0x6b0u;
for (uint32_t key = 0; key < 3u; ++key) {
const auto word = (table - memory.base) / 4u + key * 8u;
memory.words[word] = 0x100u + key;
memory.words[word + 1u] = static_cast<uint32_t>(
Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u;
memory.words[word + 3u] = Libs::Graphics::DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
}
std::array<uint32_t, 3> user_data{0x1800u, 0u, 2u};
SrtRuntime runtime{.user_data = user_data,
.read_memory = ReadLinearTestMemory,
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
for (const uint32_t count : {2u, 3u, 2u}) {
user_data[2] = count;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.images.size() == count &&
specialization.images.size() == count &&
snapshot.flattened_srt[
specialization.images[0].indirect_mapping_offset] == count &&
snapshot.images.back().dwords[0] == 0x100u + count - 1u,
"compute image table did not refresh for a changed loop bound");
}
for (const uint32_t count : {0u, UINT32_MAX}) {
user_data[2] = count;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 1u &&
specialization.images.size() == 1u &&
std::ranges::all_of(snapshot.images[0].dwords,
[](uint32_t word) { return word == 0u; }) &&
specialization.images[0].indirect_root ==
ImageResource::NoIndirectImage &&
specialization.images[0].indirect_search_iterations == 0u,
"empty compute loop bound retained unreachable image candidates");
}
user_data[2] = 65537u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"oversized compute loop bound was accepted for image enumeration");
}
void TestUniformizedMaterialImageKeys() {
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
const auto make_plan = [](bool wrong_update, bool wrong_equality) {
Fixture fixture;
auto *entry = fixture.block;
auto *header = fixture.AddBlock();
auto *inactive = fixture.AddBlock();
auto *sentinel = fixture.AddBlock();
auto *bit = fixture.AddBlock();
auto *merge = fixture.AddBlock();
auto *choose = fixture.AddBlock();
auto *sample = fixture.AddBlock();
auto *done = fixture.AddBlock();
entry->AddBranch(header);
header->AddBranch(inactive);
inactive->AddBranch(merge);
inactive->AddBranch(sentinel);
sentinel->AddBranch(merge);
sentinel->AddBranch(bit);
bit->AddBranch(header);
bit->AddBranch(merge);
merge->AddBranch(choose);
choose->AddBranch(sample);
choose->AddBranch(done);
sample->AddBranch(done);
fixture.program.block_info[0].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 1u};
fixture.program.block_info[1].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 2u};
const auto active_on_entry = fixture.Emit(
ValueOpcode::INotEqual32, {fixture.UserData(5u), Value(0u)}, 0, entry);
auto &active_phi = header->AppendNewInst(
ValueOpcode::Phi, {}, static_cast<uint64_t>(Type::U1));
auto &mask_phi = header->AppendNewInst(
ValueOpcode::Phi, {}, static_cast<uint64_t>(Type::U32));
const auto mask = Value(&mask_phi);
const auto active = Value(&active_phi);
fixture.program.block_info[2].condition = fixture.Emit(
ValueOpcode::LogicalNot, {active}, 0, inactive);
fixture.program.block_info[2].terminator = {
.kind = CFG::TerminatorKind::ConditionalBranch,
.true_block = 5u, .false_block = 3u};
const auto nonzero = fixture.Emit(
ValueOpcode::INotEqual32, {Value(0u), mask}, 0, sentinel);
const auto bit_guard = fixture.Emit(
ValueOpcode::LogicalAnd, {active, nonzero}, 0, sentinel);
fixture.program.block_info[3].condition = fixture.Emit(
ValueOpcode::LogicalNot, {bit_guard}, 0, sentinel);
fixture.program.block_info[3].terminator = {
.kind = CFG::TerminatorKind::ConditionalBranch,
.true_block = 5u, .false_block = 4u};
const auto first = fixture.Emit(ValueOpcode::FindILsb32, {mask}, 0, bit);
const auto position = fixture.Emit(
ValueOpcode::BitwiseAnd32, {first, Value(31u)}, 0, bit);
const auto one_bit = fixture.Emit(
ValueOpcode::ShiftLeftLogical32, {Value(1u), position}, 0, bit);
const auto cleared = fixture.Emit(
wrong_update ? ValueOpcode::BitwiseOr32 : ValueOpcode::BitwiseXor32,
{mask, one_bit}, 0, bit);
const auto continuation = fixture.Emit(
ValueOpcode::LogicalAnd, {bit_guard, active_on_entry}, 0, bit);
fixture.program.block_info[4].condition = continuation;
fixture.program.block_info[4].terminator = {
.kind = CFG::TerminatorKind::ConditionalBranch,
.true_block = 1u, .false_block = 5u};
active_phi.AddPhiOperand(entry, active_on_entry);
active_phi.AddPhiOperand(bit, continuation);
mask_phi.AddPhiOperand(entry, fixture.UserData(4u));
mask_phi.AddPhiOperand(bit, cleared);
const auto arbitrary = fixture.UserData(6u);
const auto sentinel_index = fixture.Emit(
ValueOpcode::SelectU32, {active, Value(32u), arbitrary}, 0, sentinel);
const auto bit_index = fixture.Emit(
ValueOpcode::SelectU32, {bit_guard, first, sentinel_index}, 0, bit);
auto &index_phi = merge->AppendNewInst(
ValueOpcode::Phi, {}, static_cast<uint64_t>(Type::U32));
index_phi.AddPhiOperand(inactive, arbitrary);
index_phi.AddPhiOperand(sentinel, sentinel_index);
index_phi.AddPhiOperand(bit, bit_index);
const auto index = Value(&index_phi);
const auto below = fixture.Emit(
ValueOpcode::SGreaterThan32, {Value(32u), index}, 0, merge);
const auto material_guard = fixture.Emit(
ValueOpcode::LogicalAnd, {active_on_entry, below}, 0, merge);
fixture.program.block_info[5].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 6u};
const auto scaled = fixture.Emit(
ValueOpcode::ShiftLeftLogical32, {index, Value(4u)}, 0, choose);
const auto selected_scale = fixture.Emit(
ValueOpcode::SelectU32, {material_guard, scaled, Value(0u)}, 0, choose);
const auto times_eight = fixture.Emit(
ValueOpcode::ShiftLeftLogical32, {selected_scale, Value(3u)}, 0, choose);
const auto times_nine = fixture.Emit(
ValueOpcode::IAdd32, {times_eight, selected_scale}, 0, choose);
const auto material_offset = fixture.Emit(
ValueOpcode::SelectU32,
{material_guard,
fixture.Emit(ValueOpcode::IAdd32,
{times_nine, Value(0xc00u)}, 0, choose),
times_nine}, 0, choose);
const auto base = fixture.Address(fixture.UserData(0u), fixture.UserData(1u));
MemoryInfo material_memory;
material_memory.kind = ResourceKind::Global;
const auto loaded = fixture.Emit(
ValueOpcode::LoadAddressU32,
{base, material_offset, Value(0u), material_guard},
fixture.AddMemory(material_memory, 0x1a88u), choose);
const auto local = fixture.Emit(
ValueOpcode::SelectU32, {material_guard, loaded, arbitrary}, 0, choose);
const auto key = fixture.Emit(
ValueOpcode::ReadLane, {local, Value(0u)}, 0, choose);
const auto compared = fixture.Emit(
ValueOpcode::IEqual32,
{key, wrong_equality ? arbitrary : local}, 0, choose);
const auto sample_guard = fixture.Emit(
ValueOpcode::LogicalAnd, {material_guard, compared}, 0, choose);
fixture.program.block_info[6].condition = fixture.Emit(
ValueOpcode::LogicalNot, {sample_guard}, 0, choose);
fixture.program.block_info[6].terminator = {
.kind = CFG::TerminatorKind::ConditionalBranch,
.true_block = 8u, .false_block = 7u};
const auto table_offset = fixture.Emit(
ValueOpcode::IAdd32,
{fixture.Emit(ValueOpcode::ShiftLeftLogical32,
{key, Value(5u)}, 0, sample), Value(0x20e0u)}, 0, sample);
std::array<Value, 8> words;
for (uint32_t word = 0; word < words.size(); ++word) {
MemoryInfo memory;
memory.kind = ResourceKind::ScalarAddress;
memory.offset = word * 4u;
words[word] = fixture.Emit(
ValueOpcode::LoadAddressU32,
{base, table_offset, Value(0u), Value(true)},
fixture.AddMemory(memory, 0x1accu), sample);
}
const auto image = fixture.Emit(
ValueOpcode::GetImageResource,
{words[0], words[1], words[2], words[3],
words[4], words[5], words[6], words[7]}, 0, sample);
const auto sampler = fixture.Emit(
ValueOpcode::GetSamplerResource,
{Value(0u), Value(0u), Value(0u), Value(0u)}, 0, sample);
const auto address = fixture.Emit(
ValueOpcode::MakeImageAddress,
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u)}, 0, sample);
MemoryInfo image_memory;
image_memory.kind = ResourceKind::Image;
image_memory.image_dimension = Decoder::ImageDimension::Dim2D;
fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address},
fixture.AddMemory(image_memory, 0x1aecu), sample);
fixture.program.block_info[7].terminator = {
.kind = CFG::TerminatorKind::Branch, .true_block = 8u};
fixture.program.block_info[8].terminator.kind = CFG::TerminatorKind::Return;
const auto output = fixture.Emit(
ValueOpcode::GetBufferResource,
{fixture.UserData(8u), fixture.UserData(9u),
fixture.UserData(10u), fixture.UserData(11u)}, 0, done);
MemoryInfo output_memory;
output_memory.kind = ResourceKind::Buffer;
fixture.Emit(ValueOpcode::StoreBufferU32,
{output, Value(0u), Value(0u), Value(0u),
Value(1u), Value(true)},
fixture.AddMemory(output_memory, 0x1b00u), done);
fixture.PlanAndTrack();
const auto source = fixture.program.info.images[0].source;
const auto &indirect = fixture.program.descriptor_sources[source].indirect_image;
Check(indirect && indirect->selector_stride == 0x90u &&
indirect->selector_offset == 0xc00u &&
indirect->table_offset == 0x20e0u &&
!indirect->selector_mask.IsEmpty() &&
indirect->key_count.Resolve().IsImmediate() &&
indirect->key_count.Resolve().U32() == 32u,
"uniformized image key lost its finite material range");
return ExtractResourcePlan(fixture.program);
};
auto plan = make_plan(false, false);
Check(plan.requires_specialization_memory &&
plan.descriptor_sources[plan.info.images[0].source]
.indirect_image->selector_mask.Resolve().TryInstruction() != nullptr,
"uniformized image mask did not survive extraction");
LinearTestMemory memory;
memory.words.resize(0x23000u / 4u);
constexpr uint64_t base = 0x1000u;
constexpr uint64_t first_material = base + 0xc00u + 2u * 0x90u;
constexpr uint64_t second_material = base + 0xc00u + 29u * 0x90u;
constexpr uint64_t first_table = base + 0x20e0u + 7u * 32u;
constexpr uint64_t second_table = base + 0x20e0u + 4096u * 32u;
memory.words[(first_material - base) / 4u] = 7u;
memory.words[(second_material - base) / 4u] = 4096u;
const auto set_descriptor = [&](uint64_t address, uint32_t color) {
const auto word = (address - base) / 4u;
memory.words[word] = color;
memory.words[word + 1u] = static_cast<uint32_t>(
Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u;
memory.words[word + 3u] = Libs::Graphics::DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
};
set_descriptor(first_table, 0x200u);
set_descriptor(second_table, 0x400u);
std::array<uint32_t, 12> user_data{};
user_data[0] = base;
user_data[4] = (1u << 2u) | (1u << 29u);
user_data[5] = 1u;
user_data[8] = 0x400000u;
user_data[9] = 16u << 16u;
user_data[10] = 1u;
memory.fail_address = base + 0xc00u + 3u * 0x90u;
const SrtRuntime runtime{
.user_data = user_data, .read_memory = ReadLinearTestMemory,
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 2u &&
memory.reads == 4u && memory.descriptor_reads == 2u &&
snapshot.flattened_srt[
specialization.images[0].indirect_mapping_offset] == 2u &&
snapshot.flattened_srt[
specialization.images[0].indirect_mapping_offset + 1u] == 7u &&
snapshot.flattened_srt[
specialization.images[0].indirect_mapping_offset + 3u] == 4096u,
"material mask did not limit sparse descriptor reads");
user_data[8] = first_material;
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"written buffer alias with a material key was accepted");
user_data[8] = first_table;
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"written buffer alias with an image record was accepted");
CheckFatal([&] { make_plan(true, false); }, "not a valid runtime value",
"non-clearing material mask was accepted");
CheckFatal([&] { make_plan(false, true); }, "not a valid runtime value",
"unrelated ReadLane key was accepted");
}
void TestImageDescriptorFields() {
constexpr std::array<std::pair<uint32_t, uint32_t>, 5> reserved{
{{1u, 0x20000000u}, {2u, 0x70003000u}, {4u, 0xe000e000u},
@@ -2506,6 +2891,8 @@ int main() {
Run("dynamic storage mips", TestDynamicStorageMipTracking);
Run("invariant indirect images", TestInvariantIndirectImageMaterialization);
Run("guarded direct image table", TestGuardedDirectImageTable);
Run("bounded compute image loop", TestBoundedComputeImageLoop);
Run("uniformized material image keys", TestUniformizedMaterialImageKeys);
Run("image descriptor fields", TestImageDescriptorFields);
Run("draw-uniform scalar image", TestUniformScalarBufferImage);
Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization);
+130 -7
View File
@@ -27,8 +27,8 @@ bool SystemContentParamSfoGetString(const char*, std::string* value) {
}
void SymbolDatabase::Add(const SymbolResolve&, uint64_t, const std::string&) {}
namespace Timer {
Common::Time GetTime() {
return Common::Time(0);
double GetTimeMs() {
return 0.0;
}
} // namespace Timer
} // namespace Loader
@@ -330,11 +330,16 @@ std::vector<std::string> Search(int32_t user, const SceSaveDataTitleId* title =
void TestClassicSavePaths() {
Reset("CLASSIC");
const std::vector<std::string> names {"save.1", "save1", "slot@A", "slotA"};
fs::create_directories("_SaveData/CLASSIC/save.1");
{
std::ofstream saved("_SaveData/CLASSIC/save.1/progress");
saved << "save.1";
}
const auto existing = DirName("save.1");
struct SaveDataMount3 initial {};
initial.user_id = 1;
initial.dir_name = &existing;
initial.mount_mode = 4;
initial.blocks = 48;
SaveDataMountResult first {};
CHECK(SaveDataMount3(&initial, &first) == OK);
std::ofstream(g_mounted_directory / "progress") << "save.1";
CHECK(SaveDataUmount2(0, &first.mount_point) == OK);
CHECK(Search(1) == std::vector<std::string> {"save.1"});
for (const auto& text: names) {
auto name = DirName(text.c_str());
@@ -455,6 +460,123 @@ void TestSaveAllocations() {
CHECK(!fs::exists(metadata));
}
void TestClassicSaveParams() {
Reset("PARAMS");
const auto name = DirName("slot");
struct SaveDataMount3 mount {};
mount.user_id = 1;
mount.dir_name = &name;
mount.mount_mode = 4;
mount.blocks = 48;
SaveDataMountResult mounted {};
CHECK(SaveDataMount3(&mount, &mounted) == OK);
SaveDataParam param {};
std::strcpy(param.title, "First save");
std::strcpy(param.sub_title, "Chapter 2");
std::strcpy(param.detail, "Progress");
param.user_param = 42;
CHECK(SaveDataSetParam(&mounted.mount_point, 0, nullptr, sizeof(param)) ==
SAVE_DATA_ERROR_PARAMETER);
CHECK(SaveDataSetParam(&mounted.mount_point, 0, &param, sizeof(param) - 1) ==
SAVE_DATA_ERROR_PARAMETER);
CHECK(SaveDataSetParam(&mounted.mount_point, 0, &param, sizeof(param)) == OK);
const fs::path saved = "_SaveData/PARAMS/slot";
CHECK(fs::exists(saved / "sce_sys/param.bin"));
fs::create_directory(saved / "nested");
std::ofstream(saved / "nested/progress") << "payload";
SaveDataParam loaded {};
size_t got = 0;
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), &got) == OK);
CHECK(got == sizeof(loaded) && std::string(loaded.title) == "First save");
CHECK(std::string(loaded.sub_title) == "Chapter 2" && loaded.user_param == 42);
CHECK(loaded.mtime > 0);
fs::last_write_time(saved / "nested/progress",
fs::last_write_time(saved / "sce_sys/param.bin") + std::chrono::hours(2));
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
const int64_t nested_mtime = loaded.mtime;
CHECK(nested_mtime > static_cast<int64_t>(std::time(nullptr)) + 3600);
fs::create_directory(saved / "sce_sys/param.bin.tmp");
std::strcpy(param.title, "Failed update");
CHECK(SaveDataSetParam(&mounted.mount_point, 0, &param, sizeof(param)) ==
SAVE_DATA_ERROR_INTERNAL);
CHECK(fs::remove(saved / "sce_sys/param.bin.tmp"));
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
CHECK(std::string(loaded.title) == "First save");
std::strcpy(param.title, "Updated save");
CHECK(SaveDataSetParam(&mounted.mount_point, 0, &param, sizeof(param)) == OK);
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
CHECK(std::string(loaded.title) == "Updated save");
CHECK(SaveDataUmount2(0, &mounted.mount_point) == OK);
CHECK(SaveDataGetParam(&mounted.mount_point, 0, &loaded, sizeof(loaded), nullptr) ==
SAVE_DATA_ERROR_NOT_MOUNTED);
Reset("OTHER");
SceSaveDataTitleId source_title {};
std::strcpy(source_title.data, "PARAMS");
struct SaveDataTransferringMount transfer {};
transfer.user_id = 1;
transfer.title_id = &source_title;
transfer.dir_name = &name;
SaveDataMountResult transferred {};
CHECK(SaveDataTransferringMount(&transfer, &transferred) == OK);
CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
CHECK(std::string(loaded.title) == "Updated save");
std::error_code alias_error;
fs::create_directory_symlink("PARAMS", "_SaveData/P_ALIAS", alias_error);
if (!alias_error) {
SceSaveDataTitleId alias_title {};
std::strcpy(alias_title.data, "P_ALIAS");
transfer.title_id = &alias_title;
SaveDataMountResult duplicate {};
CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY);
transfer.title_id = &source_title;
CHECK(fs::remove("_SaveData/P_ALIAS"));
}
#ifdef _WIN32
SceSaveDataTitleId lowercase_title {};
std::strcpy(lowercase_title.data, "params");
transfer.title_id = &lowercase_title;
SaveDataMountResult duplicate {};
CHECK(SaveDataTransferringMount(&transfer, &duplicate) == SAVE_DATA_ERROR_BUSY);
transfer.title_id = &source_title;
#endif
SaveDataMountResult other {};
CHECK(SaveDataMount3(&mount, &other) == OK);
std::strcpy(param.title, "Other title");
CHECK(SaveDataSetParam(&other.mount_point, 0, &param, sizeof(param)) == OK);
constexpr char typed_title[] = "Typed title";
CHECK(SaveDataSetParam(&other.mount_point, 1, typed_title, sizeof(typed_title)) == OK);
char title[32] {};
CHECK(SaveDataGetParam(&other.mount_point, 1, title, sizeof(title), &got) == OK);
CHECK(std::string(title) == typed_title && got == sizeof(typed_title));
const uint32_t user_param = 77;
CHECK(SaveDataSetParam(&other.mount_point, 4, &user_param, sizeof(user_param)) == OK);
CHECK(SaveDataGetParam(&other.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
CHECK(std::string(loaded.title) == typed_title && loaded.user_param == user_param);
CHECK(SaveDataGetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime), nullptr) == OK);
CHECK(loaded.mtime > 0);
CHECK(SaveDataSetParam(&other.mount_point, 5, &loaded.mtime, sizeof(loaded.mtime)) ==
SAVE_DATA_ERROR_PARAMETER);
CHECK(SaveDataGetParam(&transferred.mount_point, 0, &loaded, sizeof(loaded), nullptr) == OK);
CHECK(std::string(loaded.title) == "Updated save");
CHECK(SaveDataUmount2(0, &other.mount_point) == OK);
CHECK(SaveDataUmount2(0, &transferred.mount_point) == OK);
std::array<SceSaveDataDirName, 1> names {};
std::array<SaveDataParam, 1> params {};
SaveDataDirNameSearchCond cond {};
cond.user_id = 1;
cond.title_id = &source_title;
SaveDataDirNameSearchResult result {};
result.dir_names = names.data();
result.dir_names_num = names.size();
result.params = params.data();
CHECK(SaveDataDirNameSearch(&cond, &result) == OK && result.set_num == 1);
CHECK(std::string(names[0].data) == "slot" && std::string(params[0].title) == "Updated save");
CHECK(params[0].mtime == nested_mtime);
}
void RunChild(const fs::path& executable, const char* mode) {
#ifdef _WIN32
CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode,
@@ -496,6 +618,7 @@ int main(int argc, char** argv) {
TestFailedWritePreservesSave();
TestClassicSavePaths();
TestSaveAllocations();
TestClassicSaveParams();
CHECK(SaveDataTerminate() == OK);
fs::current_path(previous);
fs::remove_all(temp);
+87
View File
@@ -0,0 +1,87 @@
void TestRayTracingDispatchDetection() {
namespace D = ShaderRecompiler::Decoder;
const auto check_decoded = [](std::span<const uint32_t> code, bool expected,
const char *message) {
D::Program decoded;
D::DecodeProgram(code, decoded);
Check(decoded.has_bvh == expected, message);
if (expected) {
const auto &last = decoded.instructions.back();
Check(last.family == D::Family::MIMG &&
(last.opcode_id == 0xe6u || last.opcode_id == 0xe7u),
"decoder continued past the first BVH instruction");
const auto result = ShaderRecompiler::TranslateProgram(
code, MakeCompileOptions(ShaderType::Compute));
Check(result.skip_dispatch && result.program.blocks.empty() && result.cfg_dump.empty(),
"BVH compute shader reached IR translation");
}
};
const auto check = [&](std::initializer_list<uint32_t> code, bool expected,
const char *message) {
check_decoded(std::span<const uint32_t>{code.begin(), code.size()}, expected, message);
};
check({0xf1989f01u, 0x00081014u, EncodeSopp(0x01)}, true,
"captured contiguous BVH instruction did not skip dispatch");
check({0xf1989f07u, 0x00071001u, 0x3d3e3c15u, 0x38095851u, 0x00003a39u,
EncodeSopp(0x01)}, true,
"captured NSA BVH instruction did not skip dispatch");
check({0xf19c9f01u, 0x00081014u, EncodeSopp(0x01)}, true,
"RDNA2 BVH64 instruction did not skip dispatch");
check({0xbe8e210eu, 0xf1989f01u, 0x00081014u, EncodeSopp(0x01)}, true,
"unsupported SWAPPC before BVH prevented dispatch detection");
check({0xf1989f01u, 0x00081014u, 0xc0000000u, EncodeSopp(0x01)}, true,
"unknown instruction after BVH prevented dispatch skipping");
for (uint32_t opcode : {0xe6u, 0xe7u}) {
for (uint32_t nsa = 0; nsa < 4; ++nsa) {
for (uint32_t slc = 0; slc < 2; ++slc) {
std::array<uint32_t, 6> code{
EncodeMimg0(opcode, 0xf, true) | (nsa << 1u) | (slc << 25u),
EncodeMimg1(0, 0, 0, 0)};
code[2 + nsa] = EncodeSopp(0x01);
check_decoded(std::span(code).first(3 + nsa), true,
"BVH cache flags or NSA length prevented dispatch detection");
}
}
}
for (uint32_t opcode : {0x66u, 0x67u, 0xe4u, 0xe5u, 0xe8u, 0xe9u}) {
check({EncodeMimg0(opcode, 0xf), EncodeMimg1(0, 0, 0, 0), EncodeSopp(0x01)},
false, "non-BVH image opcode was incorrectly skipped");
}
check({EncodeSMovB32(0, 255), 0xf1989f01u, EncodeSopp(0x01)}, false,
"scalar literal was mistaken for a BVH instruction");
check({EncodeMimg0(0x20, 0xf) | 6u, EncodeMimg1(0, 0, 0, 0),
0xf1989f01u, 0xf19c9f01u, 0u, EncodeSopp(0x01)}, false,
"NSA address words were mistaken for BVH instructions");
check({EncodeMimg0(0x20, 0xf), 0xf1989f01u, EncodeSopp(0x01)}, false,
"MIMG operand word was mistaken for a BVH instruction");
check({EncodeSopp(0x01), 0xf1989f01u, 0x00081014u}, false,
"data after the shader end was mistaken for a BVH instruction");
check({EncodeMimg0(0x20, 0xf), EncodeMimg1(0, 0, 0, 0), EncodeSopp(0x01)},
false, "ordinary image shader was incorrectly skipped");
// An end instruction on one branch does not end another branch's code.
check({EncodeSopp(0x04, 1), EncodeSopp(0x01), 0xf1989f01u, 0x00081014u,
EncodeSopp(0x01)}, true,
"BVH at a branch target after an early end was not detected");
const std::array bvh_code{0xf1989f01u, 0x00081014u, 0xf19c9f01u, 0x00081014u,
EncodeSopp(0x01)};
const std::array ordinary_code{EncodeSMovB32(0, 255), 0xf1989f01u, EncodeSopp(0x01)};
D::Program reused;
D::DecodeProgram(bvh_code, reused);
Check(reused.has_bvh && reused.instructions.size() == 1,
"decoder did not stop at the first BVH instruction");
D::DecodeProgram(ordinary_code, reused);
Check(!reused.has_bvh, "decoder retained stale BVH metadata");
for (const auto stage : {ShaderType::Compute, ShaderType::Vertex, ShaderType::Pixel}) {
const auto options = MakeCompileOptions(stage);
Check(!ShaderRecompiler::TranslateProgram(ordinary_code, options).skip_dispatch,
"BVH-shaped literal marked an ordinary shader for skipping");
const auto compiled = RecompileForTest(ordinary_code, options);
Check(!compiled.spirv.empty(), "ordinary shader did not compile");
CheckSpirvBinaryValidates(compiled.spirv);
}
}
File diff suppressed because it is too large. Load diff
+84
View File
@@ -462,6 +462,89 @@ void TestPrtBackingReadPreservesSparseResidency() {
std::printf("[host] %-48s ok\n", test);
}
void TestPrtReadDuringDirectCommit() {
const char* test = "PrtReadDuringDirectCommit";
constexpr uint64_t chunk_size = SceKernelMemoryPoolCommitLen;
constexpr uint64_t chunks = 32;
constexpr uint64_t size = chunk_size * chunks;
int64_t physical = -1;
CheckOk(test,
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), size, chunk_size,
SceKernelMtypeC, &physical),
"KernelAllocateDirectMemory");
void* arena = reinterpret_cast<void*>(0x1000000000ull);
CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange(&arena, size, 0, size),
"KernelReserveVirtualRange");
const auto base = reinterpret_cast<uint64_t>(arena);
CheckOk(test, Libs::LibKernel::Memory::KernelSetPrtAperture(2, arena, size),
"KernelSetPrtAperture");
const auto map_chunk = [&](uint64_t index) {
void* address = reinterpret_cast<void*>(base + index * chunk_size);
return Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&address, chunk_size, SceKernelProtCpuRw, SceKernelMapFixed,
physical + static_cast<int64_t>(index * chunk_size), chunk_size, "prt_direct");
};
CheckOk(test, map_chunk(0), "KernelMapNamedDirectMemory(first)");
CheckOk(test, map_chunk(chunks - 1), "KernelMapNamedDirectMemory(last)");
std::memset(reinterpret_cast<void*>(base), 0x3c, chunk_size);
std::memset(reinterpret_cast<void*>(base + (chunks - 1) * chunk_size), 0xa7, chunk_size);
std::vector<uint8_t> bytes(size, 0x5a);
Check(test, Libs::LibKernel::Memory::TryReadPrtBacking(base, bytes.data(), size),
"PRT read rejected a partly committed direct mapping");
Check(test, bytes.front() == 0x3c && bytes[chunk_size] == 0 && bytes.back() == 0xa7,
"PRT read lost resident bytes or sparse zeros");
Libs::LibKernel::Memory::TestFailNextVirtualRangeReplacement();
CheckFailed(test, map_chunk(1), "KernelMapNamedDirectMemory(injected replacement failure)");
ExpectRange(test, Query(test, base + chunk_size), base + chunk_size,
base + (chunks - 1) * chunk_size, 0, 0, 0, 0, 0, "anon");
Check(test, !Libs::LibKernel::Memory::TryReadBacking(base + chunk_size, bytes.data(),
chunk_size) &&
Libs::LibKernel::Memory::TryReadPrtBacking(base, bytes.data(), size) &&
bytes[chunk_size] == 0,
"failed direct publication did not restore sparse reservation and backing");
std::atomic<bool> started {false};
std::atomic<bool> stop {false};
std::atomic<bool> read_failed {false};
std::atomic<uint32_t> reads {0};
std::thread reader([&] {
std::vector<uint8_t> snapshot(size);
started.store(true, std::memory_order_release);
while (!stop.load(std::memory_order_acquire)) {
if (!Libs::LibKernel::Memory::TryReadPrtBacking(base, snapshot.data(), size) ||
snapshot.front() != 0x3c || snapshot.back() != 0xa7) {
read_failed.store(true, std::memory_order_relaxed);
}
reads.fetch_add(1, std::memory_order_relaxed);
}
});
while (!started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
int map_result = OK;
for (uint64_t index = 1; index + 1 < chunks; ++index) {
map_result = map_chunk(index);
if (map_result != OK) {
break;
}
}
stop.store(true, std::memory_order_release);
reader.join();
CheckOk(test, Libs::LibKernel::Memory::KernelSetPrtAperture(2, nullptr, 0),
"KernelSetPrtAperture(clear)");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, size), "KernelMunmap");
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(physical, size),
"KernelReleaseDirectMemory");
CheckOk(test, map_result, "KernelMapNamedDirectMemory(middle)");
Check(test, reads.load(std::memory_order_relaxed) != 0 &&
!read_failed.load(std::memory_order_relaxed),
"PRT read observed a gap while direct backing was committed");
std::printf("[host] %-48s ok\n", test);
}
void TestGuestAddressSpaceHasNoFixedFallback() {
const char* test = "GuestAddressSpaceHasNoFixedFallback";
const auto unowned_address = reinterpret_cast<void*>(0x10000);
@@ -3031,6 +3114,7 @@ int main(int argc, char** argv) {
RunTest(TestProsperoArgumentAndInfoSizeContracts);
RunTest(TestGuestAddressSpaceOwnsReservationsBeforeBacking);
RunTest(TestPrtBackingReadPreservesSparseResidency);
RunTest(TestPrtReadDuringDirectCommit);
RunTest(TestGuestAddressSpaceHasNoFixedFallback);
RunTest(TestGuestFreeRangeSearchDoesNotUnderflow);
RunTest(TestFlexibleMemoryCapacityIsBootFixed);
+97 -3
View File
@@ -4458,6 +4458,23 @@ void TestNewShaderDecoderArchitecture() {
d16_hi_write.src1.sdwa_sel == 5u,
"DS decoder rejected the captured high-half D16 write");
// Captured from GTA V (PPSA04264) compute shader, pc 0x1598.
const uint32_t d16_hi_byte_write_ds[] = {0xda800200u, 0x00001413u};
Instruction d16_hi_byte_write;
ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_byte_write_ds, 0u,
d16_hi_byte_write);
Check(d16_hi_byte_write.family == Family::DS &&
d16_hi_byte_write.opcode == Opcode::DS_WRITE_B8_D16_HI &&
d16_hi_byte_write.word_count == 2u &&
d16_hi_byte_write.src_count == 2u &&
d16_hi_byte_write.data_dwords == 1u &&
d16_hi_byte_write.data_bits == 8u &&
d16_hi_byte_write.offset == 0x200u && !d16_hi_byte_write.gds &&
d16_hi_byte_write.src0.reg == 19u &&
d16_hi_byte_write.src1.reg == 20u &&
d16_hi_byte_write.src1.sdwa_sel == 2u,
"DS decoder rejected the captured high-half byte write");
constexpr uint32_t packed_source_selectors[][2] = {
{0xcc0e0000u, 0x0c0a0300u}, // Source 0: instruction bit 59.
{0xcc0e0000u, 0x140a0300u}, // Source 1: instruction bit 60.
@@ -6694,9 +6711,9 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
Check(result.resources.buffers.size() == 1 &&
result.resources.buffers[0].dwords[2] == 5u,
"formatted store test did not preserve descriptor NumRecords");
Check((result.decoded_dump.find("buffer_store_format_x") != std::string::npos),
Check((result.decoded_dump.find("BUFFER_STORE_FORMAT_X") != std::string::npos),
"formatted store regression did not decode buffer_store_format_x");
Check((result.ir_dump.find("typed=0 formatted=1") != std::string::npos),
Check((result.decoded_dump.find("typed=0 formatted=1") != std::string::npos),
"formatted store regression did not preserve formatted metadata");
CheckSpirvBinaryValidates(result.spirv);
@@ -6706,6 +6723,15 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
Check(
!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
"formatted store SPIR-V baked descriptor NumRecords into a store guard");
user_data[1] = 8u << 16u;
user_data[3] = static_cast<uint32_t>(Prospero::BufferFormat::k16_16_16_16Float) << 12u;
options.user_data = user_data;
result = RecompileForTest(shader, options);
Check((DisassembleSpirvBinary(result.spirv).find("PackHalf2x16") !=
std::string::npos),
"formatted half-float store did not convert F32 to F16");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerTypedBufferTranslation() {
@@ -7829,6 +7855,44 @@ void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerCfgLoopExitSharedWithSelection() {
const uint32_t shader[] = {
EncodeSopc(0x0a, 0, 129), // loop condition
EncodeSopp(0x04, 10), // loop exit -> end
EncodeSopc(0x06, 1, 1), // selection within the loop
EncodeSopp(0x05, 3), // choose either arm
EncodeSopc(0x06, 2, 2), // first arm
EncodeSopp(0x04, 6), // first arm -> shared end
EncodeSopp(0x02, 3), // first arm -> repeat
EncodeSopc(0x06, 3, 3), // second arm
EncodeSopp(0x04, 3), // second arm -> shared end
EncodeSopp(0x02, 0), // second arm -> repeat
EncodeSop2(0x00, 0, 0, 129), // repeat work
EncodeSopp(0x02, 0xfff4u), // backedge
0xbf810000u,
};
ShaderRecompiler::Decoder::Program decoded;
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
const auto block_count = graph.blocks.size();
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
Check(block_count == 8u && graph.natural_loops.size() == 1u,
"shared loop-exit fixture has the wrong native CFG");
Check(!ShaderRecompiler::CFG::Structurize(graph) &&
graph.unsupported_reason.find("duplicate structured merge block") !=
std::string::npos,
"shared loop exit did not terminate at its structured merge conflict");
Check(graph.blocks.size() == block_count &&
CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
"shared loop-exit fallback changed semantic instruction coverage");
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
Check(result.program.dispatcher_fallback && SpirvContainsOpcode(result.spirv, 251),
"shared loop exit did not emit its dispatcher fallback");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() {
const uint32_t shader[] = {
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
@@ -9885,6 +9949,7 @@ void TestMeshInputAssembly() {
uint32_t capacity, count, group, lane, width, address_low, base_vertex;
uint32_t wave_info, first, second, third, byte_offset, vertex_id;
bool fetch;
uint32_t wave_size = 64;
};
const Case cases[] = {
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 2, 2, 0x1002, UINT32_MAX,
@@ -9931,11 +9996,14 @@ void TestMeshInputAssembly() {
0x40000c0c, 1, 0, 0, 52, 0xabcd0128, true},
{Prospero::PrimitiveType::kPointList, 12, 265, 1, 64, 4, 0x1000, 0,
0x41000000, 64, 0, 0, 304, 0, false},
{Prospero::PrimitiveType::kTriStrip, 40, 40, 0, 32, 0, 0, 11,
0x81000608, 32, 33, 34, 0, 43, false, 32},
};
for (const auto &test : cases) {
ShaderVertexInputInfo input{};
auto &mesh = input.mesh;
mesh.input_primitive = static_cast<uint32_t>(test.topology);
mesh.wave_size = test.wave_size;
mesh.primitives_per_group = mesh.InputPrimitiveCount(test.capacity);
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
mesh.threads_num[0] = 256;
@@ -9949,7 +10017,7 @@ void TestMeshInputAssembly() {
graph.entry_block = 0;
Frontend::TranslateOptions options{};
options.stage = ShaderType::Mesh;
options.wave_size = 64;
options.wave_size = test.wave_size;
options.user_data_count = 0;
options.input_info.vertex = &input;
auto program = Frontend::TranslateProgram(decoded, graph, options);
@@ -11478,6 +11546,27 @@ void TestNewShaderRecompilerVertexExportUsesInvocationExecMask() {
}
void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
for (uint32_t wave_size : {32u, 64u}) {
for (uint32_t opcode : {0x09u, 0x0au}) {
for (uint32_t source : {126u, 8u}) {
const uint32_t pixel_shader[] = {
EncodeSop1(0x04, 8, 126), // Save the entry live mask.
EncodeSop1(opcode, 126, source),
EncodeVop1(0x01, 0, 242), // v_mov_b32 v0, 1.0
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
EncodeSopp(0x01),
};
auto pixel_options = MakeCompileOptions(ShaderType::Pixel);
pixel_options.wave_size = wave_size;
const auto pixel_result = RecompileForTest(pixel_shader, pixel_options);
CheckSpirvBinaryValidates(pixel_result.spirv);
Check(DisassembleSpirvBinary(pixel_result.spirv).find("OpGroupNonUniformBallot") ==
std::string::npos,
"entry WQM lost the known live predicate through a scalar ballot");
}
}
}
const uint32_t local_shader[] = {
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
@@ -13466,6 +13555,8 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
CheckSpirvPhiParents(dispatcher_result.spirv);
}
#include "ShaderRayTracingTests.inc"
} // namespace
} // namespace Libs::Graphics
@@ -13473,6 +13564,7 @@ int main() {
using namespace Libs::Graphics;
EnsureConfigInitialized();
TestRayTracingDispatchDetection();
TestResourceDescriptorClassification();
TestShaderBufferResourceSize();
TestNativeShaderResourceDependencies();
@@ -13494,6 +13586,7 @@ int main() {
TestNewShaderRecompilerNativeWideBufferIr();
TestNewShaderRecompilerScalarB64LaneTranslation();
TestNewShaderRecompilerMubufFormatTranslation();
TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly();
TestNewShaderRecompilerTypedBufferTranslation();
TestNewShaderRecompilerDsReadWrite2Translation();
TestNewShaderRecompilerDsWideAndAtomicTranslation();
@@ -13531,6 +13624,7 @@ int main() {
TestNewShaderRecompilerCfgLoopHeaderDsReadStructured();
TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured();
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
TestNewShaderRecompilerCfgLoopExitSharedWithSelection();
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher();
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();