gpu: compile RDNA2 tessellation through native LS HS and TES stages

This commit is contained in:
nmzik
2026-09-13 07:45:08 +04:00
parent 6d1ba5870a
commit 7516068662
26 changed files with 1035 additions and 161 deletions
@@ -277,7 +277,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
break;
}
case IR::ValueOpcode::LaneId:
requirements.subgroup_local_invocation_id = true;
requirements.subgroup_local_invocation_id |=
program.stage != ShaderType::TessellationControl;
break;
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
case IR::ValueOpcode::ImageGatherRaw:
@@ -306,8 +307,10 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) {
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::Local && program.stage != ShaderType::TessellationControl &&
program.stage != ShaderType::TessellationEvaluation) {
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
}
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
!program.shader_info_complete || !program.binding_layout_complete) {
@@ -250,7 +250,10 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
switch (stage) {
case ShaderType::Local:
case ShaderType::Vertex: return spv::ExecutionModelVertex;
case ShaderType::TessellationControl: return spv::ExecutionModelTessellationControl;
case ShaderType::TessellationEvaluation: return spv::ExecutionModelTessellationEvaluation;
case ShaderType::Mesh: return spv::ExecutionModelMeshEXT; // MeshEXT
case ShaderType::Pixel: return spv::ExecutionModelFragment;
default: return spv::ExecutionModelGLCompute;
@@ -51,6 +51,7 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
return bits;
}
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
@@ -58,7 +59,7 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == IR::StageInputKind::FragCoord) {
if (kind == IR::StageInputKind::FragCoord || kind == IR::StageInputKind::TessCoord) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
@@ -138,7 +139,7 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
if (input == nullptr || input->variable_id == 0) {
return ConstantU32(state, 0);
}
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto load_per_vertex = [&](uint32_t vertex) {
@@ -517,11 +518,95 @@ uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) {
void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(EmitterState& state) {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
: spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, semantics));
ConstantU32(state, memory_scope), ConstantU32(state, semantics));
}
uint32_t EmitLaneId(EmitterState& state) {
return state.program.stage == ShaderType::TessellationControl
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
: EmitSubgroupLocalInvocationId(state);
}
namespace {
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
using Attribute = IR::TessellationAttribute;
const auto kind = static_cast<Attribute>(inst.Arg(0).U32());
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 auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u;
EXIT_NOT_IMPLEMENTED(index >= 4u);
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u));
return pointer;
}
auto address = ctx.Arg(inst, 1);
if (kind == Attribute::PatchOutput) {
address =
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, 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 attribute =
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
const auto component =
EmitBinaryU32(state, spv::OpBitwiseAnd,
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) {
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
component);
} else {
const auto vertex =
kind == Attribute::ControlOutput
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
component);
}
return pointer;
}
} // namespace
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value,
TessellationPointer(ctx, inst));
return value;
});
}
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] {
auto value = ctx.Arg(inst, 2);
if (inst.Arg(0).U32() == static_cast<uint32_t>(IR::TessellationAttribute::Factor)) {
const auto floating = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
value = floating;
}
ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
});
}
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -155,6 +155,8 @@ inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg);
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index);
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst);
@@ -165,7 +167,7 @@ inline constexpr auto EmitUndefU64 = EmitUndefU1;
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
inline constexpr auto EmitLaneId = EmitSubgroupLocalInvocationId;
uint32_t EmitLaneId(EmitterState& state);
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -271,6 +273,7 @@ inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitPhi = EmitUnreachable;
inline constexpr auto EmitTessellationBase = EmitUnreachable;
inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable;
@@ -80,6 +80,10 @@ struct EmitterState {
Builder builder;
const IR::Program& program;
ShaderStageInputInfo input_info;
std::array<uint32_t, 6> tess_variables {};
uint32_t tess_inner_variable = 0;
uint32_t tess_patch_base = 0;
const SpirvRequirements requirements;
uint32_t lane_count = 1;
uint32_t lane_half = 0;
@@ -290,7 +290,8 @@ uint32_t GlslStd450(EmitterState& state) {
}
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
if (state.program.stage != ShaderType::Vertex || location >= ShaderVertexInputInfo::RES_MAX ||
if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) ||
location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
return VertexInputScalarKind::Float;
}
@@ -347,6 +348,9 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag
uint32_t BuiltInForInput(IR::StageInputKind kind) {
switch (kind) {
case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex;
case IR::StageInputKind::InvocationId: return spv::BuiltInInvocationId;
case IR::StageInputKind::PrimitiveId: return spv::BuiltInPrimitiveId;
case IR::StageInputKind::TessCoord: return spv::BuiltInTessCoord;
case IR::StageInputKind::InstanceIndex: return spv::BuiltInInstanceIndex;
case IR::StageInputKind::FragCoord: return spv::BuiltInFragCoord;
case IR::StageInputKind::FrontFacing: return spv::BuiltInFrontFacing;
@@ -387,6 +391,8 @@ void DefineInputs(EmitterState& state) {
uint32_t type = TypeU32(state);
switch (input.kind) {
case IR::StageInputKind::VertexIndex:
case IR::StageInputKind::InvocationId:
case IR::StageInputKind::PrimitiveId:
case IR::StageInputKind::InstanceIndex:
case IR::StageInputKind::Layer:
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
@@ -394,11 +400,13 @@ void DefineInputs(EmitterState& state) {
case IR::StageInputKind::LocalInvocationId:
case IR::StageInputKind::GlobalInvocationId: type = TypeU32Vector(state, 3); break;
case IR::StageInputKind::FragCoord: type = TypeF32Vector(state, 4); break;
case IR::StageInputKind::TessCoord:
case IR::StageInputKind::BaryCoordSmooth:
case IR::StageInputKind::BaryCoordNoPerspective: type = TypeF32Vector(state, 3); break;
case IR::StageInputKind::FrontFacing: type = TypeBool(state); break;
case IR::StageInputKind::Parameter:
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex ||
state.program.stage == ShaderType::Local) {
type = VertexParameterScalarType(
state, VertexParameterScalarKind(state, input.location));
const auto components = VertexParameterComponentCount(input);
@@ -536,6 +544,70 @@ void DefineOutputs(EmitterState& state) {
}
}
void DefineTessellationInterfaces(EmitterState& state) {
using Attribute = IR::TessellationAttribute;
std::array<bool, 6> used {};
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
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 (std::ranges::none_of(used, [](bool value) { return value; })) return;
const auto& tess = state.input_info.vertex->tess;
const auto array = [&](uint32_t type, uint32_t count) {
return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
};
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;
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 {
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u);
if (kind != Attribute::LocalOutput) {
type = array(type, local ? tess.input_control_points : tess.output_control_points);
}
}
auto& variable = state.tess_variables[index];
variable = DefineInterfaceVariable(state, type, storage, "tess_attributes");
if (kind == Attribute::Factor) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
spv::BuiltInTessLevelOuter);
state.tess_inner_variable =
DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner");
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
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);
}
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
}
}
}
} // namespace
void DefineModule(EmitterState& state) {
@@ -543,6 +615,7 @@ void DefineModule(EmitterState& state) {
state.program.info.outputs.size());
DefineInputs(state);
DefineOutputs(state);
DefineTessellationInterfaces(state);
DefineDescriptors(state);
if (state.requirements.function_lds) {
state.lds_variable = state.builder.AllocateId();
@@ -564,6 +637,21 @@ void DefineModule(EmitterState& state) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputPrimitivesEXT,
state.input_info.vertex->mesh.max_primitives);
}
if (state.program.stage == ShaderType::TessellationControl ||
state.program.stage == ShaderType::TessellationEvaluation) {
const auto& tess = state.input_info.vertex->tess;
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
tess.output_topology != 2u);
state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
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, spv::ExecutionModeVertexOrderCw);
}
}
state.entry_label = state.builder.AllocateId();
state.builder.RequireCapability(spv::CapabilityShader);