diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 32765ec13..c1408c47d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -337,6 +337,7 @@ struct PipelineCache::ProgramCache { if constexpr (std::is_same_v) { options.user_data_base = 8; + options.wave_size = input_info.wave_size; if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) { options.user_data_base = 0; options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index a83ac27f0..58622cc79 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1225,6 +1225,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& builtin(IR::StageInputKind::PackedAncillary)); } } else if (options.stage == ShaderType::Vertex) { + // Vulkan owns primitive assembly; each vertex subgroup is one NGG wave. + // Keep its full lane extent: mbcnt(-1) uses lane ordinals, not active counts. + entry_ir.SetScalarReg(static_cast(2), + IR::U32(IR::Value(options.wave_size << 12u))); + entry_ir.SetScalarReg(static_cast(3), + IR::U32(IR::Value((1u << 28u) | options.wave_size))); entry_ir.SetVectorReg(static_cast(5), builtin(IR::StageInputKind::VertexIndex)); entry_ir.SetVectorReg(static_cast(8), diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 748ceb459..76c207ef1 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -651,6 +651,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector(info.fetch_attrib_reg)); key.push_back(static_cast(info.fetch_buffer_reg)); key.push_back(info.resources_num); + key.push_back(info.wave_size); key.push_back(info.scratch_size_dwords); key.push_back(info.pa_cl_vs_out_cntl); key.push_back(static_cast(info.clip_space.enabled)); @@ -764,6 +765,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) { EXIT("failed to prepare vertex shader program\n"); } + info.wave_size = (context.GetShaderStages() & 0x00400000u) != 0 ? 32u : 64u; return params; } // NGG user SGPRs start at s8; a separately compiled GS back half also receives diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 695121756..7667629e5 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -134,6 +134,7 @@ struct ShaderVertexInputInfo { int fetch_attrib_reg = 0; int fetch_buffer_reg = 0; int buffers_num = 0; + uint32_t wave_size = 64; uint32_t scratch_size_dwords = 0; uint32_t pa_cl_vs_out_cntl = 0; ShaderClipSpaceTransform clip_space; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 0f8f78224..5f5670222 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -1702,6 +1702,64 @@ void TestShaderStageBarriers() { "independent vertex invocations retained a workgroup barrier"); } +void TestNggVertexEntryState() { + using namespace ShaderRecompiler; + // PPSA03309 shader 5a39eb2021a5d2c1: retain its NGG prologue and replace + // the resource-dependent vertex body with a position export. + const uint32_t shader[] = { + 0xbfa00003u, 0x93ebff03u, 0x00040018u, 0xbefe03c1u, + 0x9380ff02u, 0x00090016u, 0x9381ff02u, 0x0009000cu, + 0xbf8a0000u, 0xbf076b80u, 0xbf850003u, 0x8f6a8c00u, + 0x887c6a01u, 0xbf900009u, 0xd7650001u, 0x000100c1u, + 0xd7460001u, 0x04050a6bu, 0x7da80200u, 0xbf880002u, + 0xf8000941u, 0x00000000u, 0xbf8cff0fu, 0xbefe03c1u, + 0x7da80201u, EncodeSopp(0x08, 2), + EncodeExp0(0x0c, 0xf), EncodeExp1(5, 5, 5, 5), EncodeSopp(0x01), + }; + HW::VertexShaderInfo regs{}; + regs.es_regs.data_addr = reinterpret_cast(shader); + ShaderUserData user_data{}; + ShaderMappedData mapped{}; + mapped.user_data = &user_data; + mapped.code_size_bytes = sizeof(shader); + ShaderMapUserData(regs.es_regs.data_addr, mapped); + std::vector previous_key; + for (const uint32_t wave_size : {32u, 64u}) { + HW::Context context; + context.SetShaderStages(wave_size == 32u ? 0x00400000u : 0u); + ShaderVertexInputInfo input{}; + const auto params = PrepareProgram(regs, context, HW::UserConfig{}, input); + Check(input.wave_size == wave_size, + "vertex preparation lost the native NGG wave size"); + const auto key = MakeStageStaticKey(input); + Check(previous_key != key, "NGG wave sizes shared a shader cache key"); + previous_key = key; + auto options = MakeCompileOptions(ShaderType::Vertex); + options.user_data_base = 8; + options.user_data = params.user_data; + options.input_info.vertex = &input; + options.wave_size = input.wave_size; + const auto result = RecompileForTest(params.code, options); + bool live_vertex_range = false; + for (const auto *block : result.program.blocks) { + for (const auto &inst : *block) { + if (inst.GetOpcode() == IR::ValueOpcode::UGreaterThan32) { + const auto count = inst.Arg(0).Resolve(); + live_vertex_range |= count.IsImmediate() && count.U32() == wave_size; + } + } + } + Check(live_vertex_range && std::ranges::any_of(result.program.info.outputs, + [](const auto &output) { + return output.kind == IR::StageOutputKind::Position; + }), + "NGG launch counts suppressed the captured vertex export"); + Check(result.program.wave_size == wave_size, + "NGG wave size was lost during translation"); + CheckSpirvBinaryValidates(result.spirv); + } +} + void TestNewShaderRecompilerSopkWaitcntMarkers() { const uint32_t shader[] = { EncodeSopk(0x17, 125, 0xffff), // s_waitcnt_vscnt null, 0xffff @@ -13419,6 +13477,7 @@ int main() { TestDemandDrivenSpirvDeclarations(); TestNewShaderRecompilerSMovB32(); TestShaderStageBarriers(); + TestNggVertexEntryState(); TestNewShaderRecompilerClipDisabledPosition(); TestNewShaderRecompilerAuxPositionExports(); TestNewShaderRecompilerNativeWideScalarMemoryIr();