diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 6f2ea7cd7..f34827951 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -511,53 +511,153 @@ Decoder::Program DecodeFusedProgram(std::span front, std::span registers {}; + registers[local ? 3u : 1u] = + local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint}; + const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; }; + const auto read = [&](const Operand& operand) -> Address { + if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel || + operand.op_sel_hi || operand.negate_hi || operand.dpp) + return {}; + Address value; + if (operand.kind == OperandKind::IntegerInlineConstant || + operand.kind == OperandKind::LiteralConstant) { + value = constant(operand.value); + } else if (operand.kind == OperandKind::Vgpr) { + value = registers.at(operand.reg); + } + // The native packed HS ID's low byte is the patch ordinal. The logical + // interface addresses one patch, as in the frontend's v1 initialization. + if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u); + return operand.sdwa_sel == 6u ? value : Address {}; + }; + const auto affine = [](uint64_t coefficient, uint64_t offset) { + return coefficient <= UINT32_MAX && offset <= UINT32_MAX + ? Address {Kind::Affine, static_cast(coefficient), + static_cast(offset)} + : Address {}; + }; + const auto add = [&](Address lhs, Address rhs) { + return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine + ? affine(uint64_t {lhs.coefficient} + rhs.coefficient, + uint64_t {lhs.constant} + rhs.constant) + : Address {}; + }; + const auto multiply = [&](Address lhs, Address rhs) { + if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) || + (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u)) + return constant(0u); + if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {}; + if (rhs.coefficient != 0u) std::swap(lhs, rhs); + return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant, + uint64_t {lhs.constant} * rhs.constant) + : Address {}; + }; + const auto low24 = [&](Address value) { + if (value.kind == Kind::Affine && value.coefficient == 0u) + return constant(value.constant & 0xffffffu); + return value.kind == Kind::Affine && + uint64_t {value.coefficient} * (control_points - 1u) + value.constant <= + 0xffffffu + ? value + : Address {}; + }; + uint32_t stride = 0; + for (const auto& inst: program.instructions) { + // Stage exits preserve the active path's definitions. An internal join + // would require merging register definitions. + EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) && + inst.branch_target != program.instructions.back().pc); + const bool local_store = + inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32; + const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD || + inst.opcode == Opcode::BUFFER_STORE_DWORDX2 || + inst.opcode == Opcode::BUFFER_STORE_DWORDX3 || + inst.opcode == Opcode::BUFFER_STORE_DWORDX4; + const bool control_store = + !local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u; + const bool control_read = + !local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32); + if ((local && local_store) || control_store || control_read) { + const auto address = read(inst.src0); + if (address.kind != Kind::Affine) { + EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS", + inst.pc); + } + if (address.coefficient != 0u) { + const auto expected = control_read ? input_stride : stride; + EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u || + (expected != 0u && expected != address.coefficient)); + if (!control_read) stride = address.coefficient; + } + } + // Store vdata is a source, despite occupying the decoder's dst field. + if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue; + const auto lhs = read(inst.src0); + const auto rhs = read(inst.src1); + const auto third = read(inst.src2); + Address value; + switch (inst.opcode) { + case Opcode::V_BFE_U32: + if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine && + rhs.coefficient == 0u && third.kind == Kind::Affine && + third.coefficient == 0u) { + if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u}; + if (rhs.constant == 0u && third.constant == 8u) value = constant(0u); + } + break; + case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break; + case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break; + case Opcode::V_LSHL_ADD_U32: + if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u) + value = add(multiply(lhs, constant(1u << rhs.constant)), third); + break; + case Opcode::V_LSHLREV_B32: + if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u) + value = multiply(rhs, constant(1u << lhs.constant)); + break; + case Opcode::V_SUB_NC_U32: + if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine && + lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant) + value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant); + break; + default: break; + } + if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp || + inst.dst.dpp) + value = {}; + for (uint32_t index = 0; + index < DecodedDstSize(inst) && inst.dst.reg + index < registers.size(); index++) { + registers[inst.dst.reg + index] = {}; + } + registers.at(inst.dst.reg) = value; + } + EXIT_NOT_IMPLEMENTED(stride == 0u); + return stride; +} + } // namespace void AnalyzeTessellationPrograms(std::span local, std::span control, ShaderTessellationInputInfo& info) { - using namespace Decoder; - const auto constant = [](const Operand& operand, uint32_t value) { - return (operand.kind == OperandKind::IntegerInlineConstant || - operand.kind == OperandKind::LiteralConstant) && - operand.value == value; - }; - auto decoded = DecodeFrontProgram(local); - uint32_t local_address = UINT32_MAX; - for (const auto& inst: decoded.instructions) { - if (inst.opcode == Opcode::V_MUL_U32_U24 && inst.src1.kind == OperandKind::Vgpr && - inst.src1.reg == 3u && - (inst.src0.kind == OperandKind::IntegerInlineConstant || - inst.src0.kind == OperandKind::LiteralConstant)) { - EXIT_IF(info.ls_stride != 0u && info.ls_stride != inst.src0.value); - info.ls_stride = inst.src0.value; - local_address = inst.dst.reg; - } - if (inst.family == Family::DS) { - EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Vgpr || - inst.src0.reg != local_address); - } - } - decoded = {}; - DecodeProgram(control, decoded); - uint32_t control_point = UINT32_MAX; - uint32_t control_address = UINT32_MAX; - for (const auto& inst: decoded.instructions) { - if (inst.opcode == Opcode::V_BFE_U32 && inst.src0.kind == OperandKind::Vgpr && - inst.src0.reg == 1u && constant(inst.src1, 8u) && constant(inst.src2, 5u)) { - control_point = inst.dst.reg; - } - if (inst.opcode == Opcode::V_LSHL_ADD_U32 && inst.src0.kind == OperandKind::Vgpr && - inst.src0.reg == control_point && - (inst.src1.kind == OperandKind::IntegerInlineConstant || - inst.src1.kind == OperandKind::LiteralConstant) && - inst.src1.value < 32u) { - info.hs_stride = 1u << inst.src1.value; - control_address = inst.dst.reg; - } - } - EXIT_NOT_IMPLEMENTED(local_address == UINT32_MAX || control_address == UINT32_MAX || - info.ls_stride == 0u || info.hs_stride == 0u || - (info.ls_stride & 3u) != 0u || (info.hs_stride & 3u) != 0u); + const auto local_program = DecodeFrontProgram(local); + Decoder::Program control_program; + Decoder::DecodeProgram(control, control_program); + info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u); + info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points, + info.ls_stride); LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n", info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c921710f1..d091a4242 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -25514,16 +25514,21 @@ void CheckPs5GameExampleImageClearRuntimeShape() { std::printf("[host] %-32s ok\n", "Ps5GameExampleImageClear"); } -void CheckTessellationPrograms() { +void CheckTessellationProgram(const char *name, u32 ls_stride, u32 hs_stride) { using namespace ShaderRecompiler; - const char *name = "TessellationPrograms"; + const bool multiplied_stride = hs_stride == 112; + const u32 local_address = multiplied_stride ? 18 : 21; + const u32 control_point = multiplied_stride ? 30 : 34; + const u32 relative_patch = multiplied_stride ? 28 : 32; + const u32 output_address = multiplied_stride ? 31 : 35; + const u32 input_address = multiplied_stride ? 25 : 29; std::array, 3> code; auto &local = code[0]; - // The fused LS addresses one 124-byte control point using its v3 ordinal. - local.push_back(EncodeVop2(0x0b, 21, 255u, 3)); - local.push_back(124); + // The fused LS addresses one control point using its v3 ordinal. + local.push_back(EncodeVop2(0x0b, local_address, 255u, 3)); + local.push_back(ls_stride); local.push_back(EncodeDs0(0x0e, (3u << 8u) | 1u)); - local.push_back(EncodeDs1Ex(0, 5, 2, 21)); + local.push_back(EncodeDs1Ex(0, 5, 2, local_address)); local.push_back(EncodeSop1(0x20, 0, 6)); local.push_back(0xffffffffu); // Shader footer is not executable after LS handoff. @@ -25533,24 +25538,46 @@ void CheckTessellationPrograms() { control.push_back(EncodeSopp(0x0a)); control.push_back(EncodeVopc(0xd1, 249u, 3)); control.push_back(EncodeVopcSdwa(0, 0, 0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 1)); - AppendVop3(&control, 0x148, 32, Vgpr(1), InlineU32(0), InlineU32(8)); - AppendVop3(&control, 0x148, 34, Vgpr(1), InlineU32(8), InlineU32(5)); - AppendVop3(&control, 0x346, 35, Vgpr(34), InlineU32(7), InlineU32(0)); - control.push_back(EncodeVop2(0x0b, 29, 255u, 34)); - control.push_back(124); + AppendVop3(&control, 0x148, control_point, Vgpr(1), InlineU32(8), + InlineU32(5)); + AppendVop3(&control, 0x148, relative_patch, Vgpr(1), InlineU32(0), + InlineU32(8)); + if (multiplied_stride) { + // The later patch writes 112-byte outputs but reads 108-byte LS records. + // The second multiply overwrites packed v1; only memory consumers identify + // which of the two independently computed addresses supplies each stride. + control.push_back(EncodeVop2(0x0b, 0, 255u, control_point)); + control.push_back(hs_stride); + control.push_back(EncodeVop2(0x0b, 1, 255u, control_point)); + control.push_back(ls_stride); + AppendVop3(&control, 0x143, output_address, 255u, Vgpr(relative_patch), + Vgpr(0)); + control.push_back(3 * hs_stride); + AppendVop3(&control, 0x143, input_address, 255u, Vgpr(relative_patch), + Vgpr(1)); + control.push_back(3 * ls_stride); + } else { + control.push_back(EncodeSopk(0x00, 106, 3 * hs_stride)); + control.push_back(EncodeVop2(0x0b, 2, 249u, 1)); + control.push_back(EncodeVop2Sdwa(106, 6, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1)); + AppendVop3(&control, 0x346, output_address, Vgpr(control_point), InlineU32(7), + Vgpr(2)); + control.push_back(EncodeVop2(0x0b, input_address, 255u, control_point)); + control.push_back(ls_stride); + } control.push_back(EncodeDs0(0x37, (3u << 8u) | 1u)); - control.push_back(EncodeDs1Ex(10, 0, 0, 29)); + control.push_back(EncodeDs1Ex(10, 0, 0, input_address)); control.push_back(EncodeMubuf0(0x1d, 28)); - control.push_back(EncodeMubuf1(10, 2, 35, 2)); - // The HS prologue's wave lane computation must refer to its control point. - control.push_back(EncodeMubuf0(0x1c, 120)); - control.push_back(EncodeMubuf1(0, 2, 35, 2)); + control.push_back(EncodeMubuf1(10, 2, output_address, 2)); + // Keep the control-point-dependent value and the last written dword live. + control.push_back(EncodeMubuf0(0x1c, hs_stride - 8)); + control.push_back(EncodeMubuf1(0, 2, output_address, 2)); // The game reuses previously written data VGPRs for CP0-only ring addresses. AppendVMovLiteral(&control, 4, 0x42280000u); AppendVMovLiteral(&control, 5, 0x422c0000u); - control.push_back(EncodeVopc(0xd4, InlineU32(1), 34)); - control.push_back(EncodeVop2(0x1a, 4, InlineU32(4), 32)); - control.push_back(EncodeVop2(0x1a, 6, InlineU32(6), 32)); + control.push_back(EncodeVopc(0xd4, InlineU32(1), control_point)); + control.push_back(EncodeVop2(0x1a, 4, InlineU32(4), relative_patch)); + control.push_back(EncodeVop2(0x1a, 6, InlineU32(6), relative_patch)); control.push_back(EncodeVop2(0x26, 5, 255u, 6)); control.push_back(0x7fc0); for (u32 i = 0; i < 4; i++) { @@ -25565,10 +25592,12 @@ void CheckTessellationPrograms() { AppendEnd(&control); auto &evaluation = code[2]; + AppendVop3(&evaluation, 0x169, 24, 255u, Vgpr(7)); + evaluation.push_back(3 * hs_stride); evaluation.push_back(EncodeMubuf0(0x0d, 28)); - evaluation.push_back(EncodeMubuf1(10, 2, 7, 4)); - evaluation.push_back(EncodeMubuf0(0x0d, 256 + 28)); - evaluation.push_back(EncodeMubuf1(12, 2, 7, 4)); + evaluation.push_back(EncodeMubuf1(10, 2, 24, 4)); + evaluation.push_back(EncodeMubuf0(0x0d, 2 * hs_stride + 28)); + evaluation.push_back(EncodeMubuf1(12, 2, 24, 4)); evaluation.push_back(EncodeExp0(0x0c, 0xf)); evaluation.push_back(EncodeExp1(5, 6, 10, 12)); AppendEnd(&evaluation); @@ -25579,7 +25608,8 @@ void CheckTessellationPrograms() { .partitioning = 2, .output_topology = 2}; AnalyzeTessellationPrograms(local, control, tess); - Require(name, "decoded interface", tess.ls_stride == 124 && tess.hs_stride == 128, + Require(name, "decoded interface", + tess.ls_stride == ls_stride && tess.hs_stride == hs_stride, "captured LS and HS address arithmetic must produce distinct strides"); constexpr std::array stages{ShaderType::Local, ShaderType::TessellationControl, @@ -25623,7 +25653,8 @@ void CheckTessellationPrograms() { Require(name, "separate DS offsets", stage != 0 || local_offsets == std::set{4, 12}, "DS_WRITE2 must preserve both independently encoded offsets"); Require(name, "offchip component addressing", - stage != 2 || evaluation_offsets == std::set{28, 284}, + stage != 2 || + evaluation_offsets == std::set{28, 2 * hs_stride + 28}, "TES control point and component offsets must survive lowering"); Require(name, "triangle factor layout", stage != 1 || factor_offsets == std::set{0, 4, 8, 12}, @@ -25663,6 +25694,11 @@ void CheckTessellationPrograms() { std::printf("[host] %-32s ok\n", name); } +void CheckTessellationPrograms() { + CheckTessellationProgram("TessellationShiftedStride", 124, 128); + CheckTessellationProgram("TessellationMultipliedStride", 108, 112); +} + void CheckEmbeddedFetchVertexOffset() { const auto MakeFetch = [](std::initializer_list> adds, std::optional> late_add = {},