mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
gpu: reflect tessellation strides from memory address dataflow
This commit is contained in:
@@ -511,53 +511,153 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
return result;
|
||||
}
|
||||
|
||||
struct TessellationAddress {
|
||||
enum class Kind { Unknown, Affine, PackedControlPoint };
|
||||
Kind kind = Kind::Unknown;
|
||||
uint32_t coefficient = 0;
|
||||
uint32_t constant = 0;
|
||||
};
|
||||
|
||||
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
|
||||
uint32_t control_points, uint32_t input_stride) {
|
||||
using namespace Decoder;
|
||||
using Address = TessellationAddress;
|
||||
using Kind = Address::Kind;
|
||||
std::array<Address, IR::NumVectorRegs> 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<uint32_t>(coefficient),
|
||||
static_cast<uint32_t>(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<const uint32_t> local, std::span<const uint32_t> 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);
|
||||
}
|
||||
|
||||
@@ -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<std::vector<u32>, 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<u32>{4, 12},
|
||||
"DS_WRITE2 must preserve both independently encoded offsets");
|
||||
Require(name, "offchip component addressing",
|
||||
stage != 2 || evaluation_offsets == std::set<u32>{28, 284},
|
||||
stage != 2 ||
|
||||
evaluation_offsets == std::set<u32>{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<u32>{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<std::pair<u32, u32>> adds,
|
||||
std::optional<std::pair<u32, u32>> late_add = {},
|
||||
|
||||
Reference in New Issue
Block a user