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
@@ -104,9 +104,8 @@ struct EmitterState {
std::array<uint32_t, 3> u32_vector_types {};
std::array<uint32_t, 3> i32_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_patch_base = 0;
uint32_t lane_count = 1;
uint32_t lane_half = 0;
@@ -5,6 +5,17 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
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) {
auto& state = ctx.state;
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 variable = state.tess_variables.at(static_cast<uint32_t>(kind));
EXIT_IF(variable == 0);
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
const bool patch = IsTessellationPatch(kind);
const auto storage =
IsTessellationInput(kind) ? spv::StorageClassInput : spv::StorageClassOutput;
const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
@@ -26,15 +38,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
return pointer;
}
auto address = ctx.Arg(inst, 1);
if (kind == Attribute::PatchOutput) {
address =
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
if (patch) {
address = EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, tess.patch_base));
}
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
const auto offset = kind == Attribute::PatchOutput ? address
: EmitBinaryU32(state, spv::OpUMod, address,
ConstantU32(state, stride));
const auto offset =
patch ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride));
const auto attribute =
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
const auto component =
@@ -42,7 +52,7 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
ConstantU32(state, 3));
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,
component);
} else {
@@ -60,20 +70,12 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
void DefineTessellationInterfaces(EmitterState& state) {
using Attribute = IR::TessellationAttribute;
std::array<bool, 6> used {};
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
std::array<bool, IR::NumTessellationAttributes> used {};
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute &&
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute)
continue;
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 (inst.GetOpcode() == IR::ValueOpcode::GetTessellationAttribute ||
inst.GetOpcode() == IR::ValueOpcode::SetTessellationAttribute)
used.at(inst.Arg(0).U32()) = true;
}
}
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++) {
if (!used[index]) continue;
const auto kind = static_cast<Attribute>(index);
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
uint32_t type;
const auto kind = static_cast<Attribute>(index);
const bool patch = IsTessellationPatch(kind);
const auto storage =
IsTessellationInput(kind) ? spv::StorageClassInput : spv::StorageClassOutput;
uint32_t type;
if (kind == Attribute::Factor) {
type = array(TypeF32(state), 4u);
} else if (kind == Attribute::PatchOutput) {
state.tess_patch_base = patch_begin;
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u);
} else if (patch) {
// HS and TES share the reflected patch layout so their interfaces match.
type = array(TypeU32Vector(state, 4), (tess.patch_size + 15u) / 16u);
} else {
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
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,
spv::DecorationPatch);
} else {
state.builder.AddAnnotation(
spv::OpDecorate, variable, spv::DecorationLocation,
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation,
patch ? (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);
}
}
@@ -220,8 +220,11 @@ enum class TessellationAttribute {
ControlOutput,
EvaluationInput,
PatchOutput,
PatchInput,
Factor
};
constexpr uint32_t NumTessellationAttributes =
static_cast<uint32_t>(TessellationAttribute::Factor) + 1u;
enum class StageInputKind {
VertexIndex,