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; bool operator==(const TessellationAddress&) const = default;
}; };
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, // Reflects the LS output stride, or the HS output stride and patch constant range.
uint32_t control_points, uint32_t input_stride) { void ReflectTessellationLayout(const Decoder::Program& program, bool local,
ShaderTessellationInputInfo& info) {
using namespace Decoder; using namespace Decoder;
using Address = TessellationAddress; using Address = TessellationAddress;
using Kind = Address::Kind; using Kind = Address::Kind;
using Registers = std::array<Address, IR::NumVectorRegs>; 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] = registers[local ? 3u : 1u] =
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint}; local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; }; 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 // A forward branch joins its target with the fallthrough path. Definitions that
// differ between the paths are unknown there. // differ between the paths are unknown there.
std::unordered_map<uint32_t, Registers> joins; std::unordered_map<uint32_t, Registers> joins;
const auto merge = [](Registers& into, const Registers& from) { const auto merge = [](Registers& into, const Registers& from) {
for (uint32_t index = 0; index < into.size(); index++) { for (uint32_t index = 0; index < into.size(); index++) {
if (into[index] != from[index]) into[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) { for (const auto& inst: program.instructions) {
if (const auto join = joins.find(inst.pc); join != joins.end()) { if (const auto join = joins.find(inst.pc); join != joins.end()) {
merge(registers, join->second); merge(registers, join->second);
@@ -119,10 +122,15 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
inst.pc); inst.pc);
} }
if (address.coefficient != 0u) { 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 || EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
(expected != 0u && expected != address.coefficient)); (expected != 0u && expected != address.coefficient));
if (!control_read) stride = 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. // 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))) (rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
value = constant(0u); value = constant(0u);
break; 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_LO_U32: value = multiply(lhs, rhs); break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(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_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; registers.at(inst.dst.reg) = value;
} }
EXIT_NOT_IMPLEMENTED(!joins.empty() || stride == 0u); 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) { const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
@@ -184,6 +198,15 @@ const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
: nullptr; : 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 ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) { IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
using namespace IR; 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 (source == nullptr) return value;
if (const auto found = resolved.find(source); found != resolved.end()) return found->second; if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
if (source->GetOpcode() == ValueOpcode::SelectU32) { 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) ? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
: value; : 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; return value;
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved); std::array<Value, 3> args;
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved); bool changed = false;
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) { for (size_t index = 0; index < source->NumArgs(); index++) {
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs}, args[index] = ActiveAddress(block, before, source->Arg(index), predicate, resolved);
source->Flags<uint64_t>()); changed |= args[index] != source->Arg(index).Resolve();
value = Value(&*copy); }
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); resolved.emplace(source, value);
return 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); const auto local_program = Decoder::DecodeFrontProgram(local);
Decoder::Program control_program; Decoder::Program control_program;
Decoder::DecodeProgram(control, control_program); Decoder::DecodeProgram(control, control_program);
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u); ReflectTessellationLayout(local_program, true, info);
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points, ReflectTessellationLayout(control_program, false, info);
info.ls_stride); LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u "
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n", "patch_base=0x%x patch_size=%u\n",
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride); 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) { 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); 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* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) { for (auto it = block->begin(); it != block->end(); ++it) {
auto& inst = *it; auto& inst = *it;
@@ -291,8 +325,13 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
: options.stage != ShaderType::TessellationEvaluation); : options.stage != ShaderType::TessellationEvaluation);
if (address.IsImmediate() && if (address.IsImmediate() &&
address.U32() >= tess.hs_stride * tess.output_control_points) { address.U32() >= tess.hs_stride * tess.output_control_points) {
EXIT_NOT_IMPLEMENTED(!write); const auto begin = address.U32() + memory.offset;
kind = TessellationAttribute::PatchOutput; 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 { } else {
@@ -343,11 +382,14 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
ConstantPropagationPass(program.blocks, program.wave_size); ConstantPropagationPass(program.blocks, program.wave_size);
RemoveIdentities(program); RemoveIdentities(program);
EliminateDeadCode(program.blocks); 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::Local ? "LS"
: options.stage == ShaderType::TessellationControl ? "HS" : options.stage == ShaderType::TessellationControl ? "HS"
: "TES", : "TES",
reads, writes, factors); reads, writes, factors, patches);
} }
} // namespace Libs::Graphics::ShaderRecompiler } // namespace Libs::Graphics::ShaderRecompiler
@@ -104,9 +104,8 @@ struct EmitterState {
std::array<uint32_t, 3> u32_vector_types {}; std::array<uint32_t, 3> u32_vector_types {};
std::array<uint32_t, 3> i32_vector_types {}; std::array<uint32_t, 3> i32_vector_types {};
std::array<uint32_t, 3> f32_vector_types {}; std::array<uint32_t, 3> f32_vector_types {};
std::array<uint32_t, 6> tess_variables {}; std::array<uint32_t, IR::NumTessellationAttributes> tess_variables {};
uint32_t tess_inner_variable = 0; uint32_t tess_inner_variable = 0;
uint32_t tess_patch_base = 0;
uint32_t lane_count = 1; uint32_t lane_count = 1;
uint32_t lane_half = 0; uint32_t lane_half = 0;
@@ -5,6 +5,17 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace { namespace {
bool IsTessellationInput(IR::TessellationAttribute kind) {
using Attribute = IR::TessellationAttribute;
return kind == Attribute::ControlInput || kind == Attribute::EvaluationInput ||
kind == Attribute::PatchInput;
}
bool IsTessellationPatch(IR::TessellationAttribute kind) {
using Attribute = IR::TessellationAttribute;
return kind == Attribute::PatchOutput || kind == Attribute::PatchInput;
}
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state; auto& state = ctx.state;
using Attribute = IR::TessellationAttribute; using Attribute = IR::TessellationAttribute;
@@ -12,8 +23,9 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto& tess = state.input_info.vertex->tess; const auto& tess = state.input_info.vertex->tess;
const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind)); const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind));
EXIT_IF(variable == 0); EXIT_IF(variable == 0);
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; const bool patch = IsTessellationPatch(kind);
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; const auto storage =
IsTessellationInput(kind) ? spv::StorageClassInput : spv::StorageClassOutput;
const auto pointer = state.builder.AllocateId(); const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) { if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
@@ -26,15 +38,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
return pointer; return pointer;
} }
auto address = ctx.Arg(inst, 1); auto address = ctx.Arg(inst, 1);
if (kind == Attribute::PatchOutput) { if (patch) {
address = address = EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, tess.patch_base));
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
} }
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride; const auto stride = local ? tess.ls_stride : tess.hs_stride;
const auto offset = kind == Attribute::PatchOutput ? address const auto offset =
: EmitBinaryU32(state, spv::OpUMod, address, patch ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride));
ConstantU32(state, stride));
const auto attribute = const auto attribute =
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4)); EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
const auto component = const auto component =
@@ -42,7 +52,7 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)), EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
ConstantU32(state, 3)); ConstantU32(state, 3));
const auto type = TypePointer(state, storage, TypeU32(state)); const auto type = TypePointer(state, storage, TypeU32(state));
if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) { if (kind == Attribute::LocalOutput || patch) {
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
component); component);
} else { } else {
@@ -60,20 +70,12 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
void DefineTessellationInterfaces(EmitterState& state) { void DefineTessellationInterfaces(EmitterState& state) {
using Attribute = IR::TessellationAttribute; using Attribute = IR::TessellationAttribute;
std::array<bool, 6> used {}; std::array<bool, IR::NumTessellationAttributes> used {};
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
for (const auto* block: state.program.blocks) { for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) { for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute && if (inst.GetOpcode() == IR::ValueOpcode::GetTessellationAttribute ||
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute) inst.GetOpcode() == IR::ValueOpcode::SetTessellationAttribute)
continue; used.at(inst.Arg(0).U32()) = true;
const auto kind = inst.Arg(0).U32();
used.at(kind) = true;
if (kind == static_cast<uint32_t>(Attribute::PatchOutput)) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
patch_begin = std::min(patch_begin, inst.Arg(1).U32());
patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u);
}
} }
} }
if (std::ranges::none_of(used, [](bool value) { return value; })) return; if (std::ranges::none_of(used, [](bool value) { return value; })) return;
@@ -83,15 +85,16 @@ void DefineTessellationInterfaces(EmitterState& state) {
}; };
for (uint32_t index = 0; index < used.size(); index++) { for (uint32_t index = 0; index < used.size(); index++) {
if (!used[index]) continue; if (!used[index]) continue;
const auto kind = static_cast<Attribute>(index); const auto kind = static_cast<Attribute>(index);
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; const bool patch = IsTessellationPatch(kind);
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; const auto storage =
uint32_t type; IsTessellationInput(kind) ? spv::StorageClassInput : spv::StorageClassOutput;
uint32_t type;
if (kind == Attribute::Factor) { if (kind == Attribute::Factor) {
type = array(TypeF32(state), 4u); type = array(TypeF32(state), 4u);
} else if (kind == Attribute::PatchOutput) { } else if (patch) {
state.tess_patch_base = patch_begin; // HS and TES share the reflected patch layout so their interfaces match.
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u); type = array(TypeU32Vector(state, 4), (tess.patch_size + 15u) / 16u);
} else { } else {
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride; const auto stride = local ? tess.ls_stride : tess.hs_stride;
@@ -112,11 +115,10 @@ void DefineTessellationInterfaces(EmitterState& state) {
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationPatch); spv::DecorationPatch);
} else { } else {
state.builder.AddAnnotation( state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation,
spv::OpDecorate, variable, spv::DecorationLocation, patch ? (tess.hs_stride + 15u) / 16u : 0u);
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
} }
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) { if (kind == Attribute::Factor || patch) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
} }
} }
@@ -220,8 +220,11 @@ enum class TessellationAttribute {
ControlOutput, ControlOutput,
EvaluationInput, EvaluationInput,
PatchOutput, PatchOutput,
PatchInput,
Factor Factor
}; };
constexpr uint32_t NumTessellationAttributes =
static_cast<uint32_t>(TessellationAttribute::Factor) + 1u;
enum class StageInputKind { enum class StageInputKind {
VertexIndex, VertexIndex,
+2 -1
View File
@@ -441,7 +441,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
if (info.tess.input_control_points != 0) { if (info.tess.input_control_points != 0) {
const auto& tess = info.tess; const auto& tess = info.tess;
key.insert(key.end(), {tess.output_control_points, tess.ls_stride, tess.hs_stride, key.insert(key.end(), {tess.output_control_points, tess.ls_stride, tess.hs_stride,
tess.domain, tess.partitioning, tess.output_topology}); tess.patch_base, tess.patch_size, tess.domain, tess.partitioning,
tess.output_topology});
} }
for (int i = 0; i < info.resources_num; i++) { for (int i = 0; i < info.resources_num; i++) {
+2
View File
@@ -130,6 +130,8 @@ struct ShaderTessellationInputInfo {
uint32_t output_control_points = 0; uint32_t output_control_points = 0;
uint32_t ls_stride = 0; uint32_t ls_stride = 0;
uint32_t hs_stride = 0; uint32_t hs_stride = 0;
uint32_t patch_base = 0;
uint32_t patch_size = 0;
uint32_t domain = 0; uint32_t domain = 0;
uint32_t partitioning = 0; uint32_t partitioning = 0;
uint32_t output_topology = 0; uint32_t output_topology = 0;