mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
shader: lower HS neighbour LDS reads, control-point output read-back, looped HS reflection and integer partitioning
Co-authored-by: NeySlim <4509984+NeySlim@users.noreply.github.com>
This commit is contained in:
@@ -34,8 +34,8 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
|
||||
using Registers = std::array<Address, IR::NumVectorRegs>;
|
||||
|
||||
const auto control_points = local ? info.input_control_points : info.output_control_points;
|
||||
Registers registers {};
|
||||
registers[local ? 3u : 1u] =
|
||||
Registers entry {}, registers;
|
||||
entry[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 {
|
||||
@@ -85,101 +85,115 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
|
||||
? value
|
||||
: Address {};
|
||||
};
|
||||
// A forward branch joins its target with the fallthrough path. Definitions that
|
||||
// differ between the paths are unknown there.
|
||||
std::unordered_map<uint32_t, Registers> joins;
|
||||
// A branch joins its target with the fallthrough path. Definitions that differ between
|
||||
// the paths are unknown there. Loop headers repeat the walk until back edges are stable.
|
||||
std::unordered_map<uint32_t, Registers> joins, loops;
|
||||
const auto merge = [](Registers& into, const Registers& from) {
|
||||
bool changed = false;
|
||||
for (uint32_t index = 0; index < into.size(); index++) {
|
||||
if (into[index] != from[index]) into[index] = {};
|
||||
if (into[index] == from[index]) continue;
|
||||
changed |= into[index] != Address {};
|
||||
into[index] = {};
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
uint32_t stride = 0, patch_begin = UINT32_MAX, patch_end = 0;
|
||||
for (const auto& inst: program.instructions) {
|
||||
if (const auto join = joins.find(inst.pc); join != joins.end()) {
|
||||
merge(registers, join->second);
|
||||
joins.erase(join);
|
||||
}
|
||||
if (IsDirectBranch(inst.opcode)) {
|
||||
EXIT_NOT_IMPLEMENTED(inst.branch_target <= inst.pc);
|
||||
if (const auto [join, inserted] = joins.try_emplace(inst.branch_target, registers);
|
||||
!inserted)
|
||||
merge(join->second, registers);
|
||||
}
|
||||
const bool local_store = IsDsWriteOpcode(inst.opcode);
|
||||
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);
|
||||
uint32_t stride = 0, patch_begin = 0, patch_end = 0;
|
||||
for (bool repeat = true; repeat;) {
|
||||
repeat = false;
|
||||
registers = entry;
|
||||
stride = 0, patch_begin = UINT32_MAX, patch_end = 0;
|
||||
for (const auto& inst: program.instructions) {
|
||||
if (const auto join = joins.find(inst.pc); join != joins.end()) {
|
||||
merge(registers, join->second);
|
||||
joins.erase(join);
|
||||
}
|
||||
if (address.coefficient != 0u) {
|
||||
const auto expected = control_read ? info.ls_stride : stride;
|
||||
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
|
||||
(expected != 0u && expected != address.coefficient));
|
||||
if (!control_read) stride = address.coefficient;
|
||||
} else if (control_store) {
|
||||
// Control-point-independent HS outputs are patch constants.
|
||||
const auto begin = address.constant + inst.offset;
|
||||
patch_begin = std::min(patch_begin, begin);
|
||||
patch_end = std::max(patch_end, begin + 4u * inst.data_dwords);
|
||||
if (const auto loop = loops.find(inst.pc); loop != loops.end())
|
||||
merge(registers, loop->second);
|
||||
if (IsDirectBranch(inst.opcode)) {
|
||||
const bool back = inst.branch_target <= inst.pc;
|
||||
auto [join, inserted] =
|
||||
(back ? loops : joins).try_emplace(inst.branch_target, registers);
|
||||
const bool changed = !inserted && merge(join->second, registers);
|
||||
repeat |= back && (inserted || changed);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
const bool local_store = IsDsWriteOpcode(inst.opcode);
|
||||
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;
|
||||
if ((local && local_store) || control_store) {
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case Opcode::V_AND_B32:
|
||||
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) ||
|
||||
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
|
||||
value = constant(0u);
|
||||
break;
|
||||
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
|
||||
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); 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 (address.coefficient != 0u) {
|
||||
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
|
||||
(stride != 0u && stride != address.coefficient));
|
||||
stride = address.coefficient;
|
||||
} else if (control_store) {
|
||||
// Control-point-independent HS outputs are patch constants.
|
||||
const auto begin = address.constant + inst.offset;
|
||||
patch_begin = std::min(patch_begin, begin);
|
||||
patch_end = std::max(patch_end, begin + 4u * inst.data_dwords);
|
||||
}
|
||||
}
|
||||
// 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_AND_B32:
|
||||
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) ||
|
||||
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
|
||||
value = constant(0u);
|
||||
break;
|
||||
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
|
||||
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); 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 < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size();
|
||||
index++) {
|
||||
registers[inst.dst.reg + index] = {};
|
||||
}
|
||||
registers.at(inst.dst.reg) = value;
|
||||
}
|
||||
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 < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) {
|
||||
registers[inst.dst.reg + index] = {};
|
||||
}
|
||||
registers.at(inst.dst.reg) = value;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(!joins.empty() || stride == 0u);
|
||||
(local ? info.ls_stride : info.hs_stride) = stride;
|
||||
@@ -318,20 +332,22 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
|
||||
kind = TessellationAttribute::Factor;
|
||||
factors += components;
|
||||
} else {
|
||||
kind = write ? TessellationAttribute::ControlOutput
|
||||
: TessellationAttribute::EvaluationInput;
|
||||
EXIT_NOT_IMPLEMENTED(write
|
||||
? options.stage != ShaderType::TessellationControl
|
||||
: options.stage != ShaderType::TessellationEvaluation);
|
||||
// After its barrier, the HS may read back outputs of other control points.
|
||||
const bool output = options.stage == ShaderType::TessellationControl;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!output && (write || options.stage != ShaderType::TessellationEvaluation));
|
||||
kind = output ? TessellationAttribute::ControlOutput
|
||||
: TessellationAttribute::EvaluationInput;
|
||||
if (address.IsImmediate() &&
|
||||
address.U32() >= tess.hs_stride * tess.output_control_points) {
|
||||
const auto begin = address.U32() + memory.offset;
|
||||
EXIT_NOT_IMPLEMENTED(begin < tess.patch_base ||
|
||||
begin + 4u * components >
|
||||
tess.patch_base + tess.patch_size);
|
||||
kind = write ? TessellationAttribute::PatchOutput
|
||||
: TessellationAttribute::PatchInput;
|
||||
patches += components;
|
||||
kind = output ? TessellationAttribute::PatchOutput
|
||||
: TessellationAttribute::PatchInput;
|
||||
// HS read-backs must not hide misplaced patch stores from the check below.
|
||||
if (write || !output) patches += components;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -56,8 +56,10 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
|
||||
component);
|
||||
} else {
|
||||
// An HS writes only its own control point but may read any other.
|
||||
const auto vertex =
|
||||
kind == Attribute::ControlOutput
|
||||
kind == Attribute::ControlOutput &&
|
||||
inst.GetOpcode() == IR::ValueOpcode::SetTessellationAttribute
|
||||
? EmitLaneId(state)
|
||||
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
|
||||
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
|
||||
@@ -126,7 +128,9 @@ void DefineTessellationInterfaces(EmitterState& state) {
|
||||
|
||||
void DefineTessellationExecutionModes(EmitterState& state) {
|
||||
const auto& tess = state.input_info.vertex->tess;
|
||||
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
|
||||
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output.
|
||||
EXIT_NOT_IMPLEMENTED(tess.domain != 1u ||
|
||||
(tess.partitioning != 0u && tess.partitioning != 2u) ||
|
||||
tess.output_topology != 2u);
|
||||
state.builder.RequireCapability(spv::CapabilityTessellation);
|
||||
if (state.program.stage == ShaderType::TessellationControl) {
|
||||
@@ -134,7 +138,9 @@ void DefineTessellationExecutionModes(EmitterState& state) {
|
||||
tess.output_control_points);
|
||||
} else {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd);
|
||||
state.builder.AddExecutionMode(
|
||||
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
|
||||
: spv::ExecutionModeSpacingFractionalOdd);
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user