Compare commits

...

26 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
nmzik 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 Uenal 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
6d4m 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
Matthew Hurley 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
nmzik 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
Saadi 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 Hedouin 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
nmzik d72a79af2d graphics: skip compute dispatches with BVH instructions
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 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
nmzik cce0cd950c videoout: add RGB10A2 PQ buffer support
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 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
nmzik 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
56 changed files with 2262 additions and 858 deletions
+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)
+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;
}
+3 -39
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) {
@@ -1443,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) {
@@ -1463,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) {
@@ -1492,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) {
@@ -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;
}
@@ -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 {};
@@ -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;
@@ -413,6 +417,10 @@ 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 = m_compute_bindings;
+3 -18
View File
@@ -473,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
@@ -522,14 +513,6 @@ 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 auto draw_writes = depth.AttachmentWriteAspects();
vk::ImageAspectFlags sampled_aspects;
@@ -686,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;
}
@@ -739,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();
-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,
+13 -16
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);
@@ -1546,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;
}
@@ -1680,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,
@@ -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");
}
@@ -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);
@@ -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[] = {
@@ -245,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);
@@ -268,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;
@@ -303,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;
@@ -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);
@@ -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,
@@ -711,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;
@@ -729,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) {
@@ -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) {
@@ -397,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);
@@ -420,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;
@@ -437,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);
@@ -457,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);
@@ -501,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);
@@ -536,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));
@@ -549,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));
@@ -561,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;
@@ -573,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();
@@ -588,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);
@@ -600,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++) {
@@ -615,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);
@@ -636,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);
@@ -676,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);
@@ -690,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,
@@ -746,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) {
@@ -755,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));
@@ -783,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);
@@ -797,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));
@@ -827,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)));
@@ -862,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:
@@ -891,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:
@@ -1087,7 +1097,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
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);
}
}
@@ -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 ||
@@ -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;
};
@@ -1001,10 +1001,10 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
}
}
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,
@@ -1014,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;
@@ -1206,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;
+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]);
}
+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 -2
View File
@@ -101,6 +101,7 @@ private:
uint32_t freq = 0;
Format format = Format::Unknown;
uint64_t last_output_time = 0;
bool queue_primed = false;
int channels_num = 0;
int volume[12] = {};
@@ -360,20 +361,34 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
const auto prepared_size =
BytesPerSample(port->format) * output_channels * port->samples_num;
uint32_t min_queued_size = 0;
if (blocking) {
constexpr uint64_t target_latency_us = 40000;
const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0;
const auto buffers =
buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us)
: 2u;
const auto min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u);
min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u);
const auto wait_start = LibKernel::KernelGetProcessTime();
while (SDL_GetAudioStreamQueued(port->stream) > static_cast<int>(min_queued_size)) {
auto queued = SDL_GetAudioStreamQueued(port->stream);
if (queued < static_cast<int>(prepared_size)) {
port->queue_primed = false;
}
while (queued > static_cast<int>(min_queued_size)) {
if (LibKernel::KernelGetProcessTime() - wait_start > 200000) {
SDL_ClearAudioStream(port->stream);
port->queue_primed = false;
break;
}
Common::Thread::SleepMicro(1000);
queued = SDL_GetAudioStreamQueued(port->stream);
}
if (port->queue_primed) {
const auto next_time = port->last_output_time + buffer_us;
const auto now = LibKernel::KernelGetProcessTime();
if (next_time > now) {
Common::Thread::SleepMicro(next_time - now);
}
}
}
@@ -381,6 +396,10 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError());
return false;
}
if (blocking && !port->queue_primed &&
SDL_GetAudioStreamQueued(port->stream) >= static_cast<int>(min_queued_size)) {
port->queue_primed = true;
}
return true;
}
+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;
+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
+389 -117
View File
@@ -10,11 +10,13 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstring>
#include <deque>
#include <limits>
#include <magic_enum.hpp>
#include <memory>
#include <numbers>
#include <vector>
#include "libatrac9.h"
@@ -384,8 +386,114 @@ struct Ngs2RackInternal {
std::array<Ngs2UserFxOption, 24> fx {};
};
enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped };
enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill };
enum class Ngs2VoicePlayState : uint32_t { Empty = 0, Playing = 3, Paused = 5, Stopped = 0xb };
// The 64-bit bypass mask precedes the filter type and FCQ parameters.
struct Ngs2SamplerFilterParam {
uint32_t index;
uint32_t location;
uint64_t channel_mask;
uint32_t type;
float frequency;
float q;
float level;
uint32_t reserved[3];
};
static_assert(sizeof(Ngs2SamplerFilterParam) == 48);
struct Ngs2SamplerFilter {
struct History {
double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
};
bool enabled = false;
bool warned = false;
uint64_t channel_mask = 0;
double b0 = 1, b1 = 0, b2 = 0, a1 = 0, a2 = 0;
std::vector<History> history;
void Configure(const Ngs2SamplerFilterParam& param, uint32_t rate, uint32_t channels) {
if (param.type == 0) {
enabled = false;
history.clear();
return;
}
if (param.location != 1 || param.type != 1 || !std::isfinite(param.frequency) ||
param.frequency < 0 || !std::isfinite(param.q) || param.q <= 0 ||
!std::isfinite(param.level) || param.level < 0 || rate == 0) {
enabled = false;
history.clear();
if (!warned) {
Log::WriteToConsoleAndLog(fmt::sprintf(
"warning: unsupported NGS2 sampler filter (location=%u, type=%u)\n",
param.location, param.type));
warned = true;
}
return;
}
channel_mask = param.channel_mask;
history.resize(channels);
enabled = true;
if (param.frequency == 0 || param.frequency >= rate * 0.5) {
b0 = param.frequency == 0 ? 0 : param.level;
b1 = b2 = a1 = a2 = 0;
return;
}
// RBJ low-pass biquad, normalized by a0 (Audio EQ Cookbook).
const double omega = (2.0 * std::numbers::pi) * param.frequency / rate;
const double cosine = std::cos(omega);
const double alpha = std::sin(omega) / (2.0 * param.q);
const double inverse_a0 = 1.0 / (1.0 + alpha);
b0 = (1.0 - cosine) * 0.5 * inverse_a0 * param.level;
b1 = 2.0 * b0;
b2 = b0;
a1 = -2.0 * cosine * inverse_a0;
a2 = (1.0 - alpha) * inverse_a0;
}
bool Bypasses(size_t channel) const {
return channel < 64 && (channel_mask & (uint64_t {1} << channel)) != 0;
}
bool HasHistory() const {
if (!enabled) {
return false;
}
for (size_t c = 0; c < history.size(); ++c) {
if (Bypasses(c)) {
continue;
}
const auto& h = history[c];
if (h.x1 != 0 || h.x2 != 0 || h.y1 != 0 || h.y2 != 0) {
return true;
}
}
return false;
}
void Process(std::vector<float>& samples, uint32_t channels, uint32_t grain) {
if (!enabled) {
return;
}
for (uint32_t c = 0; c < channels; ++c) {
if (Bypasses(c)) {
continue;
}
auto& h = history[c];
for (uint32_t i = 0; i < grain; ++i) {
const double x = samples[c * grain + i];
double y = b0 * x + b1 * h.x1 + b2 * h.x2 - a1 * h.y1 - a2 * h.y2;
if (std::abs(y) < 1e-20) {
y = 0;
}
h.x2 = h.x1;
h.x1 = x;
h.y2 = h.y1;
h.y1 = y;
samples[c * grain + i] = static_cast<float>(y);
}
}
}
};
struct Ngs2VoiceInternal {
struct Port {
@@ -400,6 +508,7 @@ struct Ngs2VoiceInternal {
uint32_t cursor = 0;
size_t data_cursor = 0;
uint32_t skip_remaining = info.num_skip_samples;
uint32_t repeated_count = 0;
};
struct Module {
std::vector<uint8_t> param, work, state;
@@ -407,8 +516,8 @@ struct Ngs2VoiceInternal {
bool control_warned = false;
bool render_warned = false;
};
Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None;
Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty;
uint32_t state_flags = 0;
Ngs2RackInternal* rack = nullptr;
uintptr_t callback = 0;
uintptr_t callback_data = 0;
@@ -427,13 +536,45 @@ struct Ngs2VoiceInternal {
std::unique_ptr<Ajm::AjmAt9Decoder> decoder;
std::vector<float> decoded_frame;
bool accepts_blocks = true;
bool accepts_blocks = true;
uint32_t sample_rate = 0;
uint64_t sample_phase = 0;
uint64_t sample_step = 0;
uint64_t decoded_samples = 0;
uint64_t decoded_bytes = 0;
const uint8_t* waveform_end = nullptr;
std::vector<Ngs2SamplerFilter> filters;
const uint8_t* WaveformData() const {
if (blocks.empty()) {
return waveform_end;
}
const auto& block = blocks.front();
return block.data + (decoder != nullptr
? block.data_cursor
: (uint64_t(block.info.num_skip_samples) + block.cursor) *
channels * sizeof(int16_t));
}
void SetupSampler(const Ngs2WaveformFormat& format) {
EXIT_NOT_IMPLEMENTED(format.sample_rate != rack->ngs->option.sample_rate ||
format.frame_margin != 0 || format.frame_offset != 0);
channels = format.num_channels;
EXIT_NOT_IMPLEMENTED(format.frame_margin != 0 || format.frame_offset != 0 ||
format.sample_rate == 0 || format.num_channels == 0 ||
(format.waveform_type == NGS2_WAVEFORM_TYPE_ATRAC9 &&
format.sample_rate != rack->ngs->option.sample_rate));
SetEvent(4);
std::ranges::fill(ports, Port {});
for (auto& matrix: matrices) {
matrix.clear();
}
channels = format.num_channels;
sample_rate = format.sample_rate;
sample_phase = 0;
sample_step = uint64_t(sample_rate) << 32u;
decoded_samples = 0;
decoded_bytes = 0;
waveform_end = nullptr;
blocks.clear();
filters.clear();
decoder.reset();
decoded_frame.clear();
accepts_blocks = true;
@@ -472,12 +613,30 @@ struct Ngs2VoiceInternal {
}
void SetEvent(uint32_t id) {
switch (id) {
case 1: event = Ngs2VoicePlayEvent::Play; break;
case 2: event = Ngs2VoicePlayEvent::Stop; break;
case 4: event = Ngs2VoicePlayEvent::StopImm; break;
case 8: event = Ngs2VoicePlayEvent::Kill; break;
case 16: event = Ngs2VoicePlayEvent::Pause; break;
case 32: event = Ngs2VoicePlayEvent::Resume; break;
case 1:
if (state == Ngs2VoicePlayState::Empty || state == Ngs2VoicePlayState::Stopped) {
state = Ngs2VoicePlayState::Playing;
// Reserve the voice immediately; publish playback flags after rendering.
state_flags |= 1;
}
break;
case 2:
if (state == Ngs2VoicePlayState::Playing || state == Ngs2VoicePlayState::Paused) {
state = Ngs2VoicePlayState::Stopped;
}
break;
case 4:
case 8: state = Ngs2VoicePlayState::Empty; break;
case 16:
if (state == Ngs2VoicePlayState::Playing) {
state = Ngs2VoicePlayState::Paused;
}
break;
case 32:
if (state == Ngs2VoicePlayState::Paused) {
state = Ngs2VoicePlayState::Playing;
}
break;
default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", id);
}
}
@@ -563,6 +722,16 @@ struct Ngs2SamplerVoiceState {
const void* waveform_data;
};
struct Ngs2CustomSamplerVoiceState {
Ngs2VoiceState voice_state;
const void* waveform_data;
uint64_t num_decoded_samples;
uint64_t decoded_data_size;
uint64_t user_data;
uint32_t reserved;
uint32_t reserved2;
};
static Ngs2Internal* g_ngs_list = nullptr;
static Ngs2RackInternal* g_racks_list = nullptr;
static std::atomic_uint32_t g_next_ngs_uid = 1;
@@ -578,19 +747,13 @@ static_assert(sizeof(Ngs2VoiceState) == 8);
static_assert(sizeof(Ngs2SubmixerVoiceState) == 20);
static_assert(sizeof(Ngs2CustomMasteringVoiceState) == 16);
static_assert(sizeof(Ngs2SamplerVoiceState) == 56);
static_assert(sizeof(Ngs2CustomSamplerVoiceState) == 48);
static_assert(sizeof(Ngs2WaveformFormat) == 24);
static_assert(sizeof(Ngs2WaveformBlock) == 40);
static_assert(sizeof(Ngs2WaveformInfo) == 232);
static uint32_t Ngs2GetStateFlags(const Ngs2VoiceInternal* voice) {
switch (voice->state) {
case Ngs2VoicePlayState::Empty: return 0;
case Ngs2VoicePlayState::Playing: return 0x3;
case Ngs2VoicePlayState::Paused: return 0x5;
case Ngs2VoicePlayState::Stopped: return 0xb;
}
return 0;
return voice->state_flags;
}
static Ngs2SystemOption Ngs2DefaultSystemOption() {
@@ -1215,105 +1378,139 @@ int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle) {
return OK;
}
static void Ngs2ApplyEvent(Ngs2VoiceInternal& voice) {
switch (voice.event) {
case Ngs2VoicePlayEvent::None: break;
case Ngs2VoicePlayEvent::Play:
if (voice.state == Ngs2VoicePlayState::Empty) {
voice.state = Ngs2VoicePlayState::Playing;
}
break;
case Ngs2VoicePlayEvent::Pause:
if (voice.state == Ngs2VoicePlayState::Playing) {
voice.state = Ngs2VoicePlayState::Paused;
}
break;
case Ngs2VoicePlayEvent::Resume:
if (voice.state == Ngs2VoicePlayState::Paused) {
voice.state = Ngs2VoicePlayState::Playing;
}
break;
case Ngs2VoicePlayEvent::Stop:
if (voice.state == Ngs2VoicePlayState::Playing) {
voice.state = Ngs2VoicePlayState::Stopped;
}
break;
case Ngs2VoicePlayEvent::StopImm:
case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break;
struct Ngs2VoiceCallbackInfo {
uintptr_t data, voice;
uint32_t flag, reserved;
uintptr_t user;
const void* block_data;
size_t size;
uint32_t repeats, attributes;
};
static_assert(sizeof(Ngs2VoiceCallbackInfo) == 56);
static void Ngs2FinishBlock(Ngs2VoiceInternal& voice) {
auto& block = voice.blocks.front();
const bool repeat = block.info.num_repeats != 0;
voice.waveform_end = voice.WaveformData();
if (repeat) {
if (block.info.num_repeats != UINT32_MAX) {
--block.info.num_repeats;
}
++block.repeated_count;
block.cursor = 0;
}
const Ngs2VoiceCallbackInfo info {voice.callback_data,
reinterpret_cast<uintptr_t>(&voice),
repeat ? 2u : 1u,
0,
block.info.user_data,
block.data,
block.info.data_size,
block.repeated_count,
0};
if (!repeat) {
voice.blocks.pop_front();
}
if (voice.callback != 0 && (voice.callback_flags & info.flag) != 0) {
reinterpret_cast<void KYTY_SYSV_ABI (*)(const Ngs2VoiceCallbackInfo*)>(voice.callback)(
&info);
}
}
static void Ngs2AdvancePcm(Ngs2VoiceInternal& voice) {
const auto output_rate = uint64_t(voice.rack->ngs->option.sample_rate) << 32u;
// Completion callbacks may interrupt advancement across multiple blocks.
while (voice.state == Ngs2VoicePlayState::Playing && !voice.blocks.empty() &&
voice.sample_phase >= output_rate) {
auto& block = voice.blocks.front();
const auto available = block.info.num_samples - block.cursor;
const auto advance = std::min(voice.sample_phase / output_rate,
uint64_t(available));
block.cursor += static_cast<uint32_t>(advance);
voice.decoded_samples += advance;
voice.decoded_bytes += advance * voice.channels * sizeof(int16_t);
voice.sample_phase -= advance * output_rate;
if (block.cursor == block.info.num_samples) {
Ngs2FinishBlock(voice);
}
}
if (voice.blocks.empty() && !voice.accepts_blocks) {
voice.sample_phase = 0;
}
voice.event = Ngs2VoicePlayEvent::None;
}
static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) {
uint32_t output = 0;
while (output < grain && !voice.blocks.empty()) {
while (voice.state == Ngs2VoicePlayState::Playing && output < grain && !voice.blocks.empty()) {
if (voice.decoder == nullptr) {
Ngs2AdvancePcm(voice);
if (voice.state != Ngs2VoicePlayState::Playing || voice.blocks.empty()) {
break;
}
const auto& block = voice.blocks.front();
const auto* pcm = reinterpret_cast<const int16_t*>(voice.WaveformData());
const auto* next = pcm;
if (block.cursor + 1 < block.info.num_samples) {
next += voice.channels;
} else if (block.info.num_repeats != 0) {
next = reinterpret_cast<const int16_t*>(block.data) +
block.info.num_skip_samples * voice.channels;
} else if (voice.blocks.size() > 1) {
const auto& next_block = voice.blocks[1];
next = reinterpret_cast<const int16_t*>(next_block.data) +
next_block.info.num_skip_samples * voice.channels;
}
const float fraction = static_cast<float>(voice.sample_phase) /
static_cast<float>(uint64_t(voice.rack->ngs->option.sample_rate)
<< 32u);
for (uint32_t c = 0; c < voice.channels; ++c) {
voice.samples[c * grain + output] =
std::lerp(static_cast<float>(pcm[c]), static_cast<float>(next[c]), fraction) /
32768.0f;
}
voice.sample_phase += voice.sample_step;
++output;
continue;
}
auto& block = voice.blocks.front();
auto count = std::min(grain - output, block.info.num_samples - block.cursor);
const int16_t* pcm = nullptr;
const float* decoded = nullptr;
if (voice.decoder != nullptr) {
const auto skip =
std::min(block.skip_remaining,
static_cast<uint32_t>(voice.decoded_frame.size() / voice.channels));
Ajm::AjmGaplessState gapless;
gapless.Set({block.info.num_samples - block.cursor, static_cast<uint16_t>(skip), 0},
true);
const auto result = voice.decoder->Decode(
block.data + block.data_cursor, block.info.data_size - block.data_cursor,
voice.decoded_frame.data(), voice.decoded_frame.size() * sizeof(float), false,
&gapless);
EXIT_NOT_IMPLEMENTED(result.result != OK || result.input_consumed == 0);
block.data_cursor += result.input_consumed;
block.skip_remaining -= skip - gapless.current.skip_samples;
const auto decoded_samples =
static_cast<uint32_t>(result.output_written / (sizeof(float) * voice.channels));
if (decoded_samples == 0) {
continue;
}
EXIT_NOT_IMPLEMENTED(decoded_samples > count);
count = decoded_samples;
decoded = voice.decoded_frame.data();
} else {
pcm = reinterpret_cast<const int16_t*>(block.data) +
(block.info.num_skip_samples + block.cursor) * voice.channels;
const auto skip =
std::min(block.skip_remaining,
static_cast<uint32_t>(voice.decoded_frame.size() / voice.channels));
Ajm::AjmGaplessState gapless;
gapless.Set({block.info.num_samples - block.cursor, static_cast<uint16_t>(skip), 0},
true);
const auto result = voice.decoder->Decode(
block.data + block.data_cursor, block.info.data_size - block.data_cursor,
voice.decoded_frame.data(), voice.decoded_frame.size() * sizeof(float), false,
&gapless);
EXIT_NOT_IMPLEMENTED(result.result != OK || result.input_consumed == 0);
block.data_cursor += result.input_consumed;
block.skip_remaining -= skip - gapless.current.skip_samples;
const auto decoded_samples =
static_cast<uint32_t>(result.output_written / (sizeof(float) * voice.channels));
if (decoded_samples == 0) {
continue;
}
EXIT_NOT_IMPLEMENTED(decoded_samples > count);
count = decoded_samples;
for (uint32_t c = 0; c < voice.channels; ++c) {
for (uint32_t i = 0; i < count; ++i) {
const auto index = i * voice.channels + c;
voice.samples[c * grain + output + i] =
decoded != nullptr ? decoded[index] : pcm[index] / 32768.0f;
voice.samples[c * grain + output + i] = voice.decoded_frame[index];
}
}
output += count;
block.cursor += count;
voice.decoded_samples += count;
if (block.cursor == block.info.num_samples) {
struct CallbackInfo {
uintptr_t data, voice;
uint32_t flag, reserved;
uintptr_t user;
const void* block_data;
size_t size;
uint32_t repeats, attributes;
} info {voice.callback_data,
reinterpret_cast<uintptr_t>(&voice),
1,
0,
block.info.user_data,
block.data,
block.info.data_size,
0,
0};
static_assert(sizeof(CallbackInfo) == 56);
voice.blocks.pop_front();
if (voice.blocks.empty() && !voice.accepts_blocks) {
voice.state = Ngs2VoicePlayState::Empty;
}
if (voice.callback != 0 && (voice.callback_flags & 1u) != 0) {
reinterpret_cast<void KYTY_SYSV_ABI (*)(const CallbackInfo*)>(voice.callback)(
&info);
}
voice.decoded_bytes += block.info.data_size;
Ngs2FinishBlock(voice);
}
}
if (voice.decoder == nullptr) {
Ngs2AdvancePcm(voice);
}
voice.has_samples = output != 0;
}
@@ -1327,8 +1524,22 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
voice.samples.assign(grain * voice.channels, 0.0f);
voice.has_samples = false;
if (voice.state == Ngs2VoicePlayState::Playing) {
if (voice.rack->type == Ngs2RackType::CustomSampler) {
if (voice.rack->type == Ngs2RackType::Sampler ||
voice.rack->type == Ngs2RackType::CustomSampler) {
Ngs2ConsumeSamples(voice, grain);
// A playing stream can have no source data but still have a filter tail.
for (auto& filter: voice.filters) {
if (voice.has_samples || filter.HasHistory()) {
filter.Process(voice.samples, voice.channels, grain);
voice.has_samples = true;
}
}
if (voice.state == Ngs2VoicePlayState::Playing && voice.blocks.empty() &&
!voice.accepts_blocks &&
std::ranges::none_of(voice.filters,
[](const auto& filter) { return filter.HasHistory(); })) {
voice.state = Ngs2VoicePlayState::Empty;
}
}
for (auto* source: voices) {
for (const auto& port: source->ports) {
@@ -1410,11 +1621,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
}
auto* items = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) {
Ngs2ApplyEvent(items[i]);
items[i].rendered = false;
if (items[i].channels != 0) {
voices.push_back(items + i);
}
voices.push_back(items + i);
}
}
}
@@ -1425,6 +1633,9 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
}
const auto grain = ngs->option.num_grain_samples;
for (auto* voice: voices) {
if (voice->channels == 0) {
continue;
}
Ngs2RenderVoice(*voice, voices, grain);
if (voice->rack->type != Ngs2RackType::Mastering || !voice->has_samples) {
continue;
@@ -1447,6 +1658,13 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
}
}
}
for (auto* voice: voices) {
// No envelope is configured, so a release stop finishes in this render.
if (voice->state == Ngs2VoicePlayState::Stopped) {
voice->state = Ngs2VoicePlayState::Empty;
}
voice->state_flags = static_cast<uint32_t>(voice->state);
}
++ngs->render_count;
return OK;
}
@@ -1858,7 +2076,6 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
}
break;
}
case 0x1000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler); break;
case 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break;
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break;
case 0x3000: {
@@ -1907,8 +2124,11 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
module.flags |= 2;
break;
}
case 0x1000:
case 0x4001: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSampler);
EXIT_NOT_IMPLEMENTED(
voice->rack->type !=
(rack_id == 0x1000 ? Ngs2RackType::Sampler : Ngs2RackType::CustomSampler));
switch (param->id & 0xffffu) {
case 0: {
const auto& format =
@@ -1925,11 +2145,21 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
};
const auto& blocks = *reinterpret_cast<const BlocksParam*>(param);
EXIT_NOT_IMPLEMENTED(!voice->accepts_blocks ||
(blocks.flags != 0 && blocks.flags != 0x11));
(blocks.flags != 0 && blocks.flags != 1 &&
blocks.flags != 0x11 &&
blocks.flags != 0x4));
if (blocks.flags == 0x4) {
voice->blocks.clear();
voice->sample_phase = 0;
voice->waveform_end = nullptr;
}
voice->accepts_blocks = (blocks.flags & 1u) != 0;
for (uint32_t i = 0; i < blocks.count; ++i) {
const auto& block = blocks.blocks[i];
EXIT_NOT_IMPLEMENTED(block.num_repeats != 0 || block.num_samples == 0);
EXIT_NOT_IMPLEMENTED(block.num_samples == 0);
EXIT_NOT_IMPLEMENTED(block.num_repeats != 0 &&
(voice->rack->type != Ngs2RackType::Sampler ||
voice->decoder != nullptr));
EXIT_NOT_IMPLEMENTED(
voice->decoder == nullptr &&
(uint64_t(block.num_skip_samples) + block.num_samples) *
@@ -1939,9 +2169,39 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
}
break;
}
case 5:
EXIT_NOT_IMPLEMENTED(*reinterpret_cast<const float*>(param + 1) != 1.0f);
case 4: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler);
for (auto& block: voice->blocks) {
if (block.info.num_repeats != 0) {
block.info.num_repeats = 0;
break;
}
}
break;
}
case 5: {
const float ratio = *reinterpret_cast<const float*>(param + 1);
EXIT_NOT_IMPLEMENTED(!std::isfinite(ratio) || ratio < 0.0f || ratio > 4.0f ||
(voice->decoder != nullptr && ratio != 1.0f));
voice->sample_step = static_cast<uint64_t>(std::llround(
static_cast<double>(voice->sample_rate) * ratio * 4294967296.0));
break;
}
case 0xa: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler);
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
sizeof(Ngs2SamplerFilterParam));
const auto& filter =
*reinterpret_cast<const Ngs2SamplerFilterParam*>(param + 1);
EXIT_NOT_IMPLEMENTED(filter.index >=
voice->rack->option.sampler.max_filters);
if (voice->filters.size() <= filter.index) {
voice->filters.resize(filter.index + 1);
}
voice->filters[filter.index].Configure(
filter, voice->rack->ngs->option.sample_rate, voice->channels);
break;
}
default: EXIT("unsupported sampler control: 0x%08" PRIx32 "\n", param->id);
}
break;
@@ -2041,8 +2301,17 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
mastering->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::Sampler:
case Ngs2RackType::CustomSampler: {
const auto configured_size =
voice->rack->option.custom_sampler.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomSamplerVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* sampler = reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state);
sampler->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::Sampler: {
if (state_size != sizeof(Ngs2SamplerVoiceState)) {
LOGF("\t warning: sampler state_size = 0x%016" PRIx64 ", expected 0x%016" PRIx64
"\n",
@@ -2060,9 +2329,12 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
sampler->envelope_height = 1.0f;
sampler->peak_height = 0.0f;
sampler->reserved = 0;
sampler->num_decoded_samples = 0;
sampler->user_data = 0;
sampler->waveform_data = nullptr;
sampler->num_decoded_samples = voice->decoded_samples;
sampler->decoded_data_size = voice->decoded_bytes;
sampler->waveform_data = voice->WaveformData();
if (!voice->blocks.empty()) {
sampler->user_data = voice->blocks.front().info.user_data;
}
break;
}
default: EXIT("unknown type: %s\n", magic_enum::enum_name(voice->rack->type));
-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");
}
+2 -2
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
+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);
}
}
+358 -6
View File
@@ -2480,6 +2480,22 @@ public:
cb_db_release_label == 0 &&
gpu_scheduler.CurrentTick() == cb_db_tick;
for (const auto gcr : {0u, 1u << 9u}) {
const auto before = Sync::ReadReferenceClock();
auto timestamp =
make_release_mem(3, 0, &cb_db_release_label, UINT64_MAX, 0x14u, gcr);
Pm4Execution timestamp_execution;
const auto timestamp_result =
processor->Process(timestamp_execution, timestamp);
Require("GpuCommandLane", "timestamp write",
timestamp_result == Pm4ProcessResult::Complete &&
cb_db_release_label >= before &&
cb_db_release_label <= Sync::ReadReferenceClock() &&
gpu_scheduler.CurrentTick() == cb_db_tick,
"timestamp write failed, returned an invalid time, or "
"submitted extra GPU work");
}
auto gds_interrupt_only =
make_release_mem(5, 1, &interrupt_only_gds_label, 1ull << 16u);
Pm4Execution gds_interrupt_execution;
@@ -2521,6 +2537,14 @@ public:
gpu.SendCommandSync([&] {
processor->BufferInit();
const auto clock_before = Sync::ReadReferenceClock();
processor->WriteAtEndOfPipe64(0, 0, 0x04, 0x38, 5, 4,
&release_label, UINT64_MAX, 2);
Require("GpuCommandLane", "EOP reference clock with interrupt",
release_label >= clock_before &&
release_label <= Sync::ReadReferenceClock(),
"clock write with writeback and interrupt lost its data");
auto immediate = make_release_mem(1, 0, &release_label, 0x11223344u);
Pm4Execution immediate_execution;
const auto immediate_tick = gpu_scheduler.CurrentTick();
@@ -16457,6 +16481,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode 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:
@@ -19018,6 +19043,52 @@ TestCase Vop2SdwaAshrrevCapturedWord0SignExtends() {
return test;
}
TestCase Vop2SdwaMaxI32CapturedHighWord(u32 wave_size) {
using O = ShaderOpcode;
struct MaxCase { u32 lhs, packed_rhs, expected, exec = 1; };
constexpr std::array<MaxCase, 9> cases{{
{0xfffffffbu, 0xfffe1234u, 0xfffffffeu}, // -5 versus sign-extended -2.
{0xffffffffu, 0x80007fffu, 0xffffffffu},
{3, 0x0007ffffu, 7}, // Ignore the low word.
{0xffffffffu, 0x0000ffffu, 0},
{0x80000000u, 0x80000000u, 0xffff8000u},
{0x7fffffffu, 0x8000ffffu, 0x7fffffffu},
{0x0000ffffu, 0x7fffffffu, 0x0000ffffu}, // Source 0 stays 32-bit.
{0x00007fffu, 0x7fff8000u, 0x00007fffu},
{0x80000001u, 0x7fff0123u, 0x80000001u, 0}, // Inactive destination.
}};
TestCase test;
test.name = wave_size == 64 ? "Vop2SdwaMaxI32HighWordWave64"
: "Vop2SdwaMaxI32HighWordWave32";
for (const auto &entry : cases) {
test.initial.insert(test.initial.end(), {entry.lhs, entry.packed_rhs});
}
test.initial.resize(cases.size() * 3u, 0xdeadbeefu);
test.expected = test.initial;
auto &code = test.code;
for (u32 i = 0; i < cases.size(); ++i) {
AppendVMovU32(&code, 30, i * 8u);
AppendBufferLoadDword(&code, 11, 30);
AppendVMovU32(&code, 30, i * 8u + 4u);
AppendBufferLoadDword(&code, 22, 30);
code.push_back(EncodeSMovB32(126, InlineU32(cases[i].exec)));
// Exact game encoding: v11 = max(v11, sign_extend(v22.word1)).
code.insert(code.end(), {0x24162cf9u, 0x0d06060bu});
code.push_back(EncodeSMovB32(126, InlineU32(1)));
const u32 out = cases.size() * 2u + i;
AppendStoreVgpr(&code, 11, out);
test.expected[out] = cases[i].expected;
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_MAX_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_MAX_I32 v11, v11, v22.sdwa(sel=5,sext=1)", cases.size()}};
test.required_spirv = {"OpBitFieldSExtract"};
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop2SdwaLshrrevCapturedByte1Source() {
using O = ShaderOpcode;
@@ -22298,6 +22369,69 @@ TestCase VectorCompareExecOps() {
O::S_ENDPGM}};
}
TestCase VectorVopcCmpxOrderedCapturedExecMask() {
using O = ShaderOpcode;
struct CompareCase {
u32 lhs;
u32 rhs;
u32 initial_exec;
u32 expected_exec;
bool captured;
};
constexpr std::array<CompareCase, 8> cases{{
{0x3f800000u, 0x3f800000u, 1u, 1u, true}, // Finite value.
{0x7fc00000u, 0x7fc00000u, 1u, 0u, true}, // Quiet NaN.
{0x7f800001u, 0x7f800001u, 1u, 0u, true}, // Signaling NaN.
{0x7f800000u, 0x7f800000u, 1u, 1u, true}, // Infinity is ordered.
{0x80000000u, 0x80000000u, 1u, 1u, true}, // Signed zero is ordered.
{0x3f800000u, 0x3f800000u, 0u, 0u, true}, // Inactive lane.
{0x3f800000u, 0x7fc00000u, 1u, 0u, false}, // NaN in SRC1.
{0x7fc00000u, 0x3f800000u, 1u, 0u, false}, // NaN in SRC0.
}};
constexpr u32 vcc_lo = 0x13579bdfu;
constexpr u32 vcc_hi = 0x2468ace0u;
TestCase test;
test.name = "VectorVopcCmpxOrderedCapturedExecMask";
auto &code = test.code;
for (const auto &entry : cases) {
test.initial.push_back(entry.lhs);
test.initial.push_back(entry.rhs);
}
test.expected = test.initial;
for (u32 i = 0; i < cases.size(); ++i) {
const auto &entry = cases[i];
AppendVMovU32(&code, 30, i * 8u);
AppendBufferLoadDword(&code, 1, 30);
AppendVMovU32(&code, 30, i * 8u + 4u);
AppendBufferLoadDword(&code, 2, 30);
AppendSMovLiteral(&code, 106, vcc_lo);
AppendSMovLiteral(&code, 107, vcc_hi);
code.push_back(EncodeSMovB32(126, InlineU32(entry.initial_exec)));
code.push_back(entry.captured ? 0x7c2e0301u
: EncodeVopc(0x17u, Vgpr(1), 2));
code.push_back(EncodeSMovB32(20, 126));
code.push_back(EncodeSMovB32(21, 127));
code.push_back(EncodeSMovB32(22, 106));
code.push_back(EncodeSMovB32(23, 107));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
const u32 out = static_cast<u32>(cases.size()) * 2u + i * 4u;
AppendStoreSgprPair(&code, 20, out);
AppendStoreSgprPair(&code, 22, out + 2u);
test.expected.insert(test.expected.end(),
{entry.expected_exec, 0u, vcc_lo, vcc_hi});
}
test.initial.resize(test.expected.size());
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_CMPX_O_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_CMPX_O_F32 exec_lo, v1, v1", 6u},
{"V_CMPX_O_F32 exec_lo, v1, v2", 2u}};
test.compute_info.wave_size = 32;
test.has_compute_info = true;
return test;
}
TestCase VectorVop3FloatCompareNegSourceModifier() {
using O = ShaderOpcode;
@@ -22446,6 +22580,74 @@ TestCase VectorVopcCmpxLtU16CapturedSdwaExecMask() {
return test;
}
TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() {
using O = ShaderOpcode;
struct CompareCase {
u32 lhs;
u32 rhs;
u32 incoming_exec;
u32 expected_exec;
u32 encoding = 0; // 0: SDWA, 1: compact, 2: VOP3, 3: SDWA sign-extend.
};
const std::array<CompareCase, 12> cases{{
{0x12340000u, 0x56780000u, 1, 1}, // Equal low half; ignore high half.
{0xffff0001u, 0xabcd0001u, 1, 1},
{0x00000000u, 0x12340000u, 1, 1},
{0xaaaa5555u, 0xbbbb5555u, 1, 1},
{0x00000000u, 0x00000001u, 1, 0},
{0x0000ffffu, 0x00000000u, 1, 0},
{0x7fff8000u, 0x00018000u, 1, 1}, // Equal low halves despite different high halves.
{0x00000000u, 0x00010000u, 1, 1},
{0x12340001u, 0x12340002u, 1, 0, 1},
{0x00008000u, 0x0000ffffu, 1, 0, 2},
{0x0001ffffu, 0x12340001u, 1, 0, 3}, // Sign-extended low halves differ.
{0x00000001u, 0x00010001u, 0, 0}, // CMPX cannot reactivate an inactive lane.
}};
constexpr u32 vcc_hi = 0x89abcdefu;
TestCase test;
test.name = "VectorVopcCmpxEqU16SdwaCompactVop3ExecMask";
for (const auto &entry : cases) {
test.initial.push_back(entry.lhs);
}
test.expected = test.initial;
auto &code = test.code;
for (u32 i = 0; i < cases.size(); ++i) {
const auto &entry = cases[i];
AppendVMovU32(&code, 30, i * 4u);
AppendBufferLoadDword(&code, 0, 30); // Runtime input prevents constant folding.
AppendVMovU32(&code, 1, entry.rhs);
AppendSMovLiteral(&code, 106, entry.rhs);
AppendSMovLiteral(&code, 107, vcc_hi);
code.push_back(EncodeSMovB32(126, InlineU32(entry.incoming_exec)));
switch (entry.encoding) {
case 1: code.push_back(0x7d740300u); break; // compact v0, v1
case 2: code.insert(code.end(), {0xd4ba007eu, 0x00020300u}); break;
case 3:
code.push_back(EncodeVopc(0xba, 249u, 1u));
code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 6u, 6u, 1u, 1u));
break;
default:
code.push_back(EncodeVopc(0xba, 249u, 1u));
code.push_back(EncodeVopcSdwa(0u));
break;
}
code.push_back(EncodeSMovB32(20, 126)); // Snapshot EXEC before restoring it.
code.push_back(EncodeSMovB32(21, 127));
code.push_back(EncodeSMovB32(126, InlineU32(1u)));
const u32 out = static_cast<u32>(cases.size()) + i * 4u;
AppendStoreSgprPair(&code, 20, out);
AppendStoreSgprPair(&code, 106, out + 2u); // CMPX must not overwrite either VCC half.
test.expected.insert(test.expected.end(),
{entry.expected_exec, 0u, entry.rhs, vcc_hi});
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_CMPX_EQ_U16, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_CMPX_EQ_U16", cases.size()}};
test.required_spirv = {"OpIEqual"};
return test;
}
TestCase VectorVopcCmpNgtF16CapturedSdwaAndEdges() {
using O = ShaderOpcode;
enum Encoding { Captured, Compact, Vop3NegAbs, HighWord, ScalarDst, SdwaNegAbs };
@@ -22783,6 +22985,31 @@ TestCase BranchVccnzUsesWaveMask() {
return test;
}
TestCase ScalarMemRealtimeCapturedPlaceholder() {
using O = ShaderOpcode;
namespace D = ShaderRecompiler::Decoder;
std::vector<u32> code = {0xf4940300u, 0xfa000000u};
D::Instruction decoded;
D::DecodeInstruction(code, 0, decoded);
Require("ScalarMemRealtimeCapturedPlaceholder", "decode",
decoded.opcode == O::S_MEMREALTIME && decoded.word_count == 2 &&
decoded.dst.kind == D::OperandKind::Sgpr && decoded.dst.reg == 12 &&
decoded.data_dwords == 2 && decoded.src_count == 0 &&
decoded.src0.kind == D::OperandKind::Unknown &&
decoded.src1.kind == D::OperandKind::Unknown,
"captured clock instruction must write s12:s13 without memory operands");
AppendStoreSgprPair(&code, 12, 0);
code.insert(code.end(), {0xf4940300u, 0xfa000000u});
AppendStoreSgprPair(&code, 12, 2);
AppendEnd(&code);
return {"ScalarMemRealtimeCapturedPlaceholder",
code,
{},
{UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX},
{O::S_MEMREALTIME, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarMemoryLoadVariants() {
using O = ShaderOpcode;
@@ -23171,7 +23398,7 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() {
return test;
}
TestCase BufferLoadsGpuSelectedDescriptors() {
TestCase BufferLoadsGpuSelectedDescriptors(bool xyz) {
using O = ShaderOpcode;
constexpr uint64_t GuestBase = 0x0000000110000000ull;
struct DescriptorCase {
@@ -23181,6 +23408,8 @@ TestCase BufferLoadsGpuSelectedDescriptors() {
};
const DescriptorCase cases[] = {
{24, 2, 0, 0, false, true, {7, 8, 9, 10, 11, 12}},
// PPSA01417 selects XYZ vertices from a table of stride-16 descriptors.
{16, 2, 0, 0, false, true, {5, 6, 7, 8, 0, 0}},
{12, 2, 0, 0, false, true, {4, 5, 6, 0, 0, 0}},
{12, 2, 1, 0, false, true, {4, 5, 6, 7, 8, 9}},
{12, 1, 2, 0, false, true, {4, 5, 6, 7, 8, 9}},
@@ -23194,7 +23423,8 @@ TestCase BufferLoadsGpuSelectedDescriptors() {
{12, 4, 3, 4, true, true, {}},
};
TestCase test;
test.name = "BufferLoadsGpuSelectedDescriptors";
test.name = xyz ? "BufferLoadDwordx3GpuSelectedDescriptors"
: "BufferLoadsGpuSelectedDescriptors";
test.initial.resize(2048);
for (u32 i = 0; i < std::size(cases); ++i) {
const auto &input = cases[i];
@@ -23220,10 +23450,10 @@ TestCase BufferLoadsGpuSelectedDescriptors() {
test.code.push_back(EncodeSmem1(520, 20));
AppendSMovLiteral(&test.code, 22, cases[selected].soffset);
AppendVMovU32(&test.code, 21, 1);
test.code.push_back(EncodeMubuf0(0x0e, 0, true, false));
test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0e, 0, true, false));
test.code.push_back(EncodeMubuf1(0, 2, 21, 22));
test.code.push_back(EncodeMubuf0(0x0d, 16, true, false));
test.code.push_back(EncodeMubuf1(4, 2, 21, 22));
test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0d, xyz ? 12 : 16, true, false));
test.code.push_back(EncodeMubuf1(xyz ? 3 : 4, 2, 21, 22));
for (u32 component = 0; component < 6; ++component) {
AppendStoreVgpr(&test.code, component, i * 6 + component);
const u32 expected = cases[selected].expected[component];
@@ -23234,12 +23464,24 @@ TestCase BufferLoadsGpuSelectedDescriptors() {
test.bda_mappings = {{GuestBase, 0}};
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4,
O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
if (xyz) {
test.opcodes.push_back(O::BUFFER_LOAD_DWORDX3);
} else {
test.opcodes.insert(test.opcodes.end(), {O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2});
}
test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer"};
return test;
}
TestCase BufferLoadsGpuSelectedDescriptors() {
return BufferLoadsGpuSelectedDescriptors(false);
}
TestCase BufferLoadDwordx3GpuSelectedDescriptors() {
return BufferLoadsGpuSelectedDescriptors(true);
}
TestCase BufferStoreDwordx4DropsOnlyOutOfBoundsTail() {
using O = ShaderOpcode;
@@ -25905,6 +26147,76 @@ TestCase Wave64AppendConsumeHighHalf() {
return test;
}
TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false) {
using O = ShaderOpcode;
constexpr u32 groups = 31;
TestCase test;
test.name = dlc_only ? "BufferWorkgroupPublicationDlc"
: wave_size == 64 ? "BufferWorkgroupPublicationWave64"
: "BufferWorkgroupPublicationWave32";
auto &code = test.code;
// Allocate work in execution order so every polled predecessor has started.
// Only one lane per workgroup owns a ticket and publishes its prefix total.
code.push_back(EncodeSop1(0x04, 126, InlineU32(1)));
AppendVMovU32(&code, 1, 1);
AppendVMovU32(&code, 20, 0);
AppendBufferStoreOpcode(&code, 0x32, 1, 20, true);
code.push_back(EncodeVop1(0x02, 20, Vgpr(1)));
code.push_back(EncodeVop2(0x1a, 20, InlineU32(2), 1));
AppendVMovU32(&code, 2, 0);
code.push_back(EncodeSMovB32(21, InlineU32(0)));
AppendSMovLiteral(&code, 22, 1000000);
code.push_back(EncodeSopc(0x06, 20, InlineU32(0)));
const auto first_ticket = code.size();
code.push_back(0);
const auto poll = code.size();
code.push_back(EncodeMubuf0(0x0c, 0, false, true, !dlc_only) | (1u << 15u));
code.push_back(EncodeMubuf1(2, 12, 20));
code.push_back(EncodeSopp(0x0c, 0)); // S_WAITCNT vmcnt(0).
code.push_back(EncodeVop1(0x02, 23, Vgpr(2)));
code.push_back(EncodeSopc(0x07, 23, InlineU32(0)));
const auto ready = code.size();
code.push_back(0);
code.push_back(EncodeSop2(0x00, 21, 21, InlineU32(1)));
code.push_back(EncodeSopc(0x0a, 21, 22));
code.push_back(EncodeSopp(0x05, static_cast<u32>(poll - code.size() - 1)));
// A broken poll reports an incorrect result instead of hanging the test GPU.
AppendVMovLiteral(&code, 2, 0x80000000u);
const auto publish = code.size();
code[first_ticket] = EncodeSopp(0x05, publish - first_ticket - 1);
code[ready] = EncodeSopp(0x05, publish - ready - 1);
code.push_back(EncodeVop2(0x25, 2, Vgpr(1), 2));
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 2));
code.push_back(EncodeVop2(0x25, 20, InlineU32(4), 20));
// RDNA2 stores publish to L2 even without GLC/DLC on the producer.
AppendBufferStoreDword(&code, 2, 20);
AppendEnd(&code);
test.initial.assign(groups + 1, 0);
test.expected = {groups};
for (u32 ticket = 0; ticket < groups; ++ticket) {
test.expected.push_back((ticket + 1) * (ticket + 2) / 2);
}
test.opcodes = {O::S_MOV_B64, O::S_MOV_B32, O::V_MOV_B32,
O::BUFFER_ATOMIC_ADD, O::V_READFIRSTLANE_B32,
O::V_LSHLREV_B32, O::S_CMP_EQ_U32, O::S_CMP_LG_U32,
O::S_CMP_LT_U32, O::S_CBRANCH_SCC1, O::S_ADD_U32,
O::S_WAITCNT, O::BUFFER_LOAD_DWORD, O::V_ADD_NC_U32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"Coherent", "Volatile", "OpAtomicIAdd", "OpLoopMerge"};
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
test.dispatch_x = groups;
// DLC alone does not bypass RDNA2's L0, so only check its translated policy.
test.compile_only = dlc_only;
return test;
}
TestCase BufferAtomicVariants() {
using O = ShaderOpcode;
@@ -28265,6 +28577,9 @@ std::vector<TestCase> MakeCases() {
auto AddCase = [&cases](TestCase (*factory)()) {
cases.push_back(factory());
};
cases.push_back(BufferWorkgroupPublication(32));
cases.push_back(BufferWorkgroupPublication(64));
cases.push_back(BufferWorkgroupPublication(32, true));
AddCase(IntegerAddSubMul);
AddCase(BitwiseOps);
@@ -28330,6 +28645,8 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop2SdwaSubNcExactByte2Destination);
AddCase(Vop2SdwaAddNcCapturedHighWordDestination);
AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends);
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(32));
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64));
AddCase(Vop2SdwaLshrrevCapturedByte1Source);
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
AddCase(Vop3CvtPkI16I32Captured);
@@ -28422,11 +28739,13 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop3CndmaskUsesSgprMaskLaneBits);
AddCase(Vop3CndmaskAllowsDataSourceModifier);
AddCase(VectorCompareExecOps);
AddCase(VectorVopcCmpxOrderedCapturedExecMask);
AddCase(VectorVop3FloatCompareNegSourceModifier);
AddCase(VectorVop3CmpxWritesExecMask);
AddCase(VectorVopcSdwaCmpxWritesExecMask);
AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask);
AddCase(VectorVopcCmpxLtU16CapturedSdwaExecMask);
AddCase(VectorVopcCmpxEqU16SdwaCompactVop3ExecMask);
AddCase(VectorVopcCmpNgtF16CapturedSdwaAndEdges);
AddCase(VectorVopcCmpNltF16CapturedSdwaAndEdges);
AddCase(VectorVopcCmpxNgtF16CapturedSdwaExecMask);
@@ -28436,6 +28755,7 @@ std::vector<TestCase> MakeCases() {
AddCase(SharedReturnKeepsSelectedValues);
AddCase(BranchVccnzUsesWaveMask);
AddCase(BranchVccnzUsesCarryProducedWaveMask);
AddCase(ScalarMemRealtimeCapturedPlaceholder);
AddCase(ScalarMemoryLoadVariants);
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
@@ -28453,6 +28773,7 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress);
AddCase(BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail);
AddCase(BufferLoadsGpuSelectedDescriptors);
AddCase(BufferLoadDwordx3GpuSelectedDescriptors);
AddCase(BufferStoreDwordx4DropsOnlyOutOfBoundsTail);
AddCase(BufferLoadFormatXyzwRejectsPartialRecord);
AddCase(BufferStoreFormatXyzwDropsPartialRecord);
@@ -33172,6 +33493,11 @@ int main(int argc, char **argv) {
std::setvbuf(stdout, nullptr, _IONBF, 0);
EnsureConfigInitialized();
CheckLeastRecentlyUsedCacheOrdering();
if (argc == 2 && std::strcmp(argv[1], "--s-memrealtime-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ScalarMemRealtimeCapturedPlaceholder());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--packed-integer-neg-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
@@ -33187,6 +33513,11 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--cmpx-o-f32-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorVopcCmpxOrderedCapturedExecMask());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-snorm-store-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
@@ -33218,6 +33549,11 @@ int main(int argc, char **argv) {
CheckEmbeddedFetchVertexOffset();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--cmpx-eq-u16-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorVopcCmpxEqU16SdwaCompactVop3ExecMask());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-u16-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorVopcCmpxLtU16CapturedSdwaExecMask());
@@ -33276,6 +33612,7 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--indirect-buffer-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
RunCase(&vulkan, BufferLoadDwordx3GpuSelectedDescriptors());
RunCase(&vulkan, BufferLoadDwordx4SnapshotsOverlappingAddress());
RunCase(&vulkan, BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail());
RunCase(&vulkan, BufferLoadDwordIdxenUsesDescriptorStride());
@@ -33383,6 +33720,14 @@ int main(int argc, char **argv) {
RunCase(&vulkan, BufferOffsetsUsePackedLaneAndStorageFallback());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-publication-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, BufferWorkgroupPublication(32));
RunCase(&vulkan, BufferWorkgroupPublication(64));
RunCase(&vulkan, BufferWorkgroupPublication(32, true));
RunCase(&vulkan, BufferAtomicVariants());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-cmpswap-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, BufferAtomicCmpSwapExactRaw());
@@ -33442,6 +33787,13 @@ int main(int argc, char **argv) {
RunCase(&vulkan, Vop2SdwaAshrrevCapturedWord0SignExtends());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-max-i32-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop2SdwaMaxI32CapturedHighWord(32));
RunCase(&vulkan, Vop2SdwaMaxI32CapturedHighWord(64));
RunCase(&vulkan, VectorIntegerOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-addc-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
+42
View File
@@ -7855,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
@@ -13517,6 +13555,8 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
CheckSpirvPhiParents(dispatcher_result.spirv);
}
#include "ShaderRayTracingTests.inc"
} // namespace
} // namespace Libs::Graphics
@@ -13524,6 +13564,7 @@ int main() {
using namespace Libs::Graphics;
EnsureConfigInitialized();
TestRayTracingDispatchDetection();
TestResourceDescriptorClassification();
TestShaderBufferResourceSize();
TestNativeShaderResourceDependencies();
@@ -13583,6 +13624,7 @@ int main() {
TestNewShaderRecompilerCfgLoopHeaderDsReadStructured();
TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured();
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
TestNewShaderRecompilerCfgLoopExitSharedWithSelection();
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher();
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();