diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 6fdb98c18..abee5306a 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1163,9 +1163,11 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& } entry_ir.SetExec(initial_exec); const auto initial_mask = entry_ir.Emit(IR::ValueOpcode::Ballot, {initial_exec}); - entry_ir.SetExecLo(entry_ir.CompositeExtract(initial_mask, 0)); - entry_ir.SetExecHi(options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1) - : IR::U32(IR::Value(0u))); + const auto initial_lo = entry_ir.CompositeExtract(initial_mask, 0); + const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1) + : IR::U32(IR::Value(0u)); + entry_ir.SetExecLo(initial_lo); + entry_ir.SetExecHi(initial_hi); if (options.stage == ShaderType::Compute) { const auto* cs = options.input_info.compute; const auto thread_ids = @@ -1381,11 +1383,16 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& } } 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. + // ESVertCount describes input vertices, independently of lane ordinals. + auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo})); + if (options.wave_size == 64u) { + vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit( + IR::ValueOpcode::BitCount32, {initial_hi}))); + } entry_ir.SetScalarReg(static_cast(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.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u)))); entry_ir.SetVectorReg(static_cast(5), builtin(IR::StageInputKind::VertexIndex)); entry_ir.SetVectorReg(static_cast(8), diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 6b77f72b4..22a735b01 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -16439,6 +16439,60 @@ public: wave_ids[0] != wave_ids[1] && wave_keys[0] != wave_keys[1], "wave32 and wave64 pixel programs shared a cache key"); + static const auto waterfall_vertex = [&] { + std::vector code; + AppendVMovU32(&code, 9, 0); + // d3e1ae284c4dea3b reconstructs EXEC twice from packed NGG s3. + code.insert(code.end(), {0xbefe04c1u, 0x906a8803u, 0x81ea6a80u, + 0x90fe6ac1u, 0x81ea0380u, 0x90fe6ac1u, + EncodeSop1(0x04, 10, 126)}); + const auto loop = code.size(); + code.push_back(EncodeSop1(0x14, 4, 10)); + AppendVop3(&code, 0x360, 6, Vgpr(5), 4); + code.push_back(EncodeVopc(0xc2, 6, 5)); + code.push_back(EncodeSop1(0x24, 18, 106)); + AppendVMovLiteral(&code, 9, 0x3f800000u); + code.push_back(EncodeSop2(0x15, 10, 10, 106)); + code.push_back(EncodeSop1(0x04, 126, 18)); + code.push_back(EncodeSopp(0x05, static_cast(loop - code.size() - 1u))); + const auto base = code.size(); + code.insert(code.end(), native_vertex.begin(), native_vertex.end()); + const auto position = std::find(code.begin() + base, code.end(), EncodeExp0(0x0c, 0xf)); + // Unprocessed vertices keep W=0, making waterfall progress observable. + *(position + 1) = EncodeExp1(3, 4, 0, 9); + return code; + }(); + native_vertex_regs.es_regs.data_addr = reinterpret_cast(waterfall_vertex.data()); + ShaderMapUserData(native_vertex_regs.es_regs.data_addr, + {.type = Prospero::ShaderBinaryType::kGs, .user_data = &native_user_data, + .code_size_bytes = static_cast(waterfall_vertex.size() * sizeof(u32))}); + registers.SetPsInControl(0x8008); + const auto saved_stages = registers.GetShaderStages(); + for (const u32 wave : {32u, 64u}) { + registers.SetShaderStages((saved_stages & ~0x00400000u) | + (wave == 32u ? 0x00400000u : 0u)); + const auto programs = context.GetPipelineCache().GetGraphicsPrograms( + native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(), + registers, user_config, export_mapping, true, native_vertex_info, pixel); + vertex_shader = programs.vertex[0]; + pixel_shader = programs.pixel; + vertex = native_vertex_info[0]; + Require(name, "native NGG waterfall wave", + vertex.stage.program->stage == ShaderType::Vertex && + vertex.stage.program->wave_size == wave, + "NGG waterfall bypassed the native vertex path or lost its guest wave width"); + for (const u32 count : {3u, 6u, 33u}) { + draw(pipeline(true, 2, 2), count); + const auto pixels = read_color(); + for (size_t component = 0; component < pixels.size(); ++component) + Require(name, "NGG partial-wave waterfall output", + pixels[component] == (component % 4 == 3 ? 0x3f800000u : 0x3e800000u), + "native vertex waterfall did not terminate with every input vertex processed"); + } + } + registers.SetShaderStages(saved_stages); + native_vertex_regs.es_regs.data_addr = vertex_address; + // Reversed RGBA targets must use logical Sa for RGB, with a separate // zero or unit source factor for separate alpha attenuation or accumulation. static const auto blend_pixel = [] {