From fa8216c4d95bee2dacbc6b2b7c09c3ba49fd1aac Mon Sep 17 00:00:00 2001 From: 0xcrstn <342244990+0xcrstn@users.noreply.github.com> Date: Tue, 20 Oct 2026 02:49:58 +0200 Subject: [PATCH] shader: size per-thread LDS emulation by the guest LDS allocation --- .../spirv/spirvEmitterMemoryHelpers.cpp | 20 +++++++++++++++++-- src/graphics/shader/shader.cpp | 9 +++++++-- src/graphics/shader/shader.h | 4 ++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp index 043aa6b7d..d9cc9bfbe 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp @@ -157,9 +157,25 @@ void EmitMemoryOffsets(EmitterState& state) { } } +// Stages without a workgroup emulate LDS with one private array per invocation. A GPU keeps +// hundreds of thousands of invocations in flight, so this size multiplies into driver local memory: +// a fixed 8192 dwords (32 KB) asked for gigabytes and killed the device (a write fault in the +// first heavy pixel shader that touched LDS). A pixel shader can only address the LDS that +// EXTRA_LDS_SIZE reserves, a vertex shader the LDS_SIZE of its GS stage; hardware reads zero and +// drops writes beyond it, as the bounds checks do. +constexpr uint32_t FUNCTION_LDS_DWORDS = 1024u; + uint32_t LdsDwordCount(const EmitterState& state) { - const auto* workgroup = ShaderWorkgroupInput(state.program.stage, state.input_info); - return workgroup != nullptr ? workgroup->lds_size_dwords : 8192u; + if (const auto* workgroup = ShaderWorkgroupInput(state.program.stage, state.input_info)) { + return workgroup->lds_size_dwords; + } + if (state.program.stage == ShaderType::Pixel && state.input_info.pixel != nullptr) { + return std::max(state.input_info.pixel->lds_size_dwords, 1u); + } + if (state.program.stage == ShaderType::Vertex && state.input_info.vertex != nullptr) { + return std::max(state.input_info.vertex->lds_size_dwords, 1u); + } + return FUNCTION_LDS_DWORDS; } void EnsureLdsStorage(EmitterState& state) { diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 80b111818..60fa9260c 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -315,6 +315,7 @@ static void ShaderGetStaticInputInfoPS( ps_info = {}; ps_info.scratch_size_dwords = data.scratch_size_dwords; + ps_info.lds_size_dwords = static_cast(regs.ps_regs.rsrc2.extra_lds_size) * 128u; // SPI_PS_IN_CONTROL: NUM_INTERP occupies bits 5:0 and PS_W32_EN is bit 15. ps_info.input_num = sh.ps_in_control & 0x3fu; @@ -411,6 +412,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector(info.clip_space.enabled)); if (info.clip_space.enabled) { @@ -454,6 +456,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector EXIT_IF(info.input_num > std::size(info.interpolator_settings)); key.clear(); key.push_back(info.scratch_size_dwords); + key.push_back(info.lds_size_dwords); key.push_back(info.input_num); key.push_back(info.wave_size); key.push_back(info.ps_system_input_base); @@ -528,6 +531,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& EXIT("failed to prepare vertex shader program\n"); } info.wave_size = (context.GetShaderStages() & 0x00400000u) != 0 ? 32u : 64u; + info.lds_size_dwords = static_cast(regs.gs_regs.rsrc2.lds_size) * 128u; return params; } // NGG user SGPRs start at s8; a separately compiled GS back half also receives @@ -746,7 +750,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_no_perspective = %s\n" "\t ps_pixel_kill_enable = %s\n" "\t ps_early_z = %s\n" - "\t ps_execute_on_noop = %s\n", + "\t ps_execute_on_noop = %s\n" + "\t lds_size_dwords = %u\n", info.input_num, info.ps_system_input_base, info.custom_interpolation_mask, info.ps_perspective_center_vgpr, info.ps_perspective_sample_vgpr, info.ps_perspective_centroid_vgpr, info.ps_pos_x ? "true" : "false", @@ -755,7 +760,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { info.ps_front_face_all_bits ? "true" : "false", info.ps_ancillary ? "true" : "false", info.ps_sample_shading ? "true" : "false", info.ps_no_perspective ? "true" : "false", info.ps_pixel_kill_enable ? "true" : "false", info.ps_early_z ? "true" : "false", - info.ps_execute_on_noop ? "true" : "false"); + info.ps_execute_on_noop ? "true" : "false", info.lds_size_dwords); for (uint32_t i = 0; i < info.input_num; i++) { LOGF("\t interpolator_settings[%u] = %u\n", i, info.interpolator_settings[i]); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 17a00bfbe..c44e095df 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -149,6 +149,8 @@ struct ShaderVertexInputInfo { int buffers_num = 0; uint32_t wave_size = 64; uint32_t scratch_size_dwords = 0; + // LDS_SIZE of the GS stage a non-NGG vertex shader runs on; usually zero. + uint32_t lds_size_dwords = 0; uint32_t pa_cl_vs_out_cntl = 0; ShaderClipSpaceTransform clip_space; ShaderMeshInputInfo mesh; @@ -191,6 +193,8 @@ struct ShaderPixelInputInfo { // CB round mode per converted export: zero adds half an LSB, one truncates. uint8_t target_round_mode = 0; uint32_t scratch_size_dwords = 0; + // EXTRA_LDS_SIZE: LDS reserved beyond the interpolants (VGPR spills to LDS). + uint32_t lds_size_dwords = 0; bool ps_pos_x = false; bool ps_pos_y = false; bool ps_pos_z = false;