shader: reflect HS patch constants and lower TES patch inputs through predicated addresses

This commit is contained in:
Mr. Big Britches
2026-10-20 08:04:50 +02:00
committed by nmzik
parent 4a2961cdf9
commit 0957f088ad
6 changed files with 109 additions and 60 deletions
+67 -25
View File
@@ -25,13 +25,16 @@ struct TessellationAddress {
bool operator==(const TessellationAddress&) const = default;
};
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
uint32_t control_points, uint32_t input_stride) {
// Reflects the LS output stride, or the HS output stride and patch constant range.
void ReflectTessellationLayout(const Decoder::Program& program, bool local,
ShaderTessellationInputInfo& info) {
using namespace Decoder;
using Address = TessellationAddress;
using Kind = Address::Kind;
using Registers = std::array<Address, IR::NumVectorRegs>;
Registers registers {};
const auto control_points = local ? info.input_control_points : info.output_control_points;
Registers 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}; };
@@ -85,13 +88,13 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
// 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;
const auto merge = [](Registers& into, const Registers& from) {
for (uint32_t index = 0; index < into.size(); index++) {
if (into[index] != from[index]) into[index] = {};
}
};
uint32_t stride = 0;
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);
@@ -119,10 +122,15 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
inst.pc);
}
if (address.coefficient != 0u) {
const auto expected = control_read ? input_stride : stride;
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);
}
}
// Store vdata is a source, despite occupying the decoder's dst field.
@@ -145,6 +153,7 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
(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;
@@ -173,7 +182,12 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
registers.at(inst.dst.reg) = value;
}
EXIT_NOT_IMPLEMENTED(!joins.empty() || stride == 0u);
return stride;
(local ? info.ls_stride : info.hs_stride) = stride;
if (patch_end != 0u) {
EXIT_NOT_IMPLEMENTED(patch_begin < stride * control_points);
info.patch_base = patch_begin;
info.patch_size = patch_end - patch_begin;
}
}
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
@@ -184,6 +198,15 @@ const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
: nullptr;
}
// Whether an access predicate, possibly a conjunction of EXEC masks, implies a condition.
bool Implies(IR::Value predicate, IR::Value condition) {
predicate = predicate.Resolve();
if (predicate == condition.Resolve()) return true;
const auto* inst = predicate.TryInstruction();
return inst != nullptr && inst->GetOpcode() == IR::ValueOpcode::LogicalAnd &&
(Implies(inst->Arg(0), condition) || Implies(inst->Arg(1), condition));
}
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
using namespace IR;
@@ -192,18 +215,28 @@ IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value
if (source == nullptr) return value;
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
if (source->GetOpcode() == ValueOpcode::SelectU32) {
return source->Arg(0).Resolve() == predicate.Resolve()
return Implies(predicate, source->Arg(0))
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
: value;
}
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
// Pure address arithmetic that stays behind EXEC selects; mul24 lowers to IMul32 of BFEs.
const auto opcode = source->GetOpcode();
if (opcode != ValueOpcode::IAdd32 && opcode != ValueOpcode::ISub32 &&
opcode != ValueOpcode::IMul32 && opcode != ValueOpcode::BitFieldUExtract)
return value;
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
source->Flags<uint64_t>());
value = Value(&*copy);
std::array<Value, 3> args;
bool changed = false;
for (size_t index = 0; index < source->NumArgs(); index++) {
args[index] = ActiveAddress(block, before, source->Arg(index), predicate, resolved);
changed |= args[index] != source->Arg(index).Resolve();
}
if (changed) {
const auto flags = source->Flags<uint64_t>();
const auto copy =
source->NumArgs() == 2u
? block.PrependNewInst(before, opcode, {args[0], args[1]}, flags)
: block.PrependNewInst(before, opcode, {args[0], args[1], args[2]}, flags);
value = Value(&*copy);
}
resolved.emplace(source, value);
return value;
@@ -216,11 +249,12 @@ void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<cons
const auto local_program = Decoder::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);
ReflectTessellationLayout(local_program, true, info);
ReflectTessellationLayout(control_program, false, info);
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u "
"patch_base=0x%x patch_size=%u\n",
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride,
info.patch_base, info.patch_size);
}
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
@@ -241,7 +275,7 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
}
}
ConstantPropagationPass(program.blocks, program.wave_size);
uint32_t reads = 0, writes = 0, factors = 0;
uint32_t reads = 0, writes = 0, factors = 0, patches = 0;
for (auto* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
auto& inst = *it;
@@ -291,8 +325,13 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
: options.stage != ShaderType::TessellationEvaluation);
if (address.IsImmediate() &&
address.U32() >= tess.hs_stride * tess.output_control_points) {
EXIT_NOT_IMPLEMENTED(!write);
kind = TessellationAttribute::PatchOutput;
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;
}
}
} else {
@@ -343,11 +382,14 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
ConstantPropagationPass(program.blocks, program.wave_size);
RemoveIdentities(program);
EliminateDeadCode(program.blocks);
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
// Patch constant stores whose EXEC selects were not stripped would land in control points.
EXIT_NOT_IMPLEMENTED(options.stage == ShaderType::TessellationControl &&
tess.patch_size != 0u && patches == 0u);
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u patches=%u\n",
options.stage == ShaderType::Local ? "LS"
: options.stage == ShaderType::TessellationControl ? "HS"
: "TES",
reads, writes, factors);
reads, writes, factors, patches);
}
} // namespace Libs::Graphics::ShaderRecompiler