graphics: support RDNA2 custom pixel interpolation

Co-authored-by: Senaxx <Senaxx@users.noreply.github.com>
This commit is contained in:
nmzik
2026-08-21 23:33:40 +02:00
parent 17c2b4cc7d
commit 5e64efce4f
14 changed files with 414 additions and 28 deletions
@@ -32,6 +32,11 @@ bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
ShaderPixelParameterIsFlat(*state.input_info.pixel, attr);
}
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
return state.stage == ShaderType::Pixel &&
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
}
void SetError(std::string* error, const char* message) {
if (error != nullptr) {
*error = message;
@@ -47,8 +52,8 @@ bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind ki
void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program) {
for (const auto& input: program.info.inputs) {
state.inputs.push_back(
{input.kind, input.location, input.component_count, 0, input.debug_name});
state.inputs.push_back({input.kind, input.location, input.component_count, 0,
input.debug_name, input.per_vertex});
}
for (const auto& output: program.info.outputs) {
if (HasOutput(state.outputs, output.kind, output.index)) {
@@ -93,6 +93,18 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
return bits;
}
if (kind == IR::StageInputKind::BaryCoordSmooth ||
kind == IR::StageInputKind::BaryCoordNoPerspective) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain,
TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
variable, ConstantU32(state, component + 1u)});
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
return bits;
}
return EmitInputComponentU32(state, kind, component);
}
@@ -187,6 +199,43 @@ uint32_t EmitAttribute(ValueEmitContext& ctx, uint32_t attr, uint32_t chan) {
if (state.stage == ShaderType::Vertex) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto load_per_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)});
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
return value;
};
if (input->per_vertex) {
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
? IR::StageInputKind::BaryCoordNoPerspective
: IR::StageInputKind::BaryCoordSmooth;
const auto barycentric = InputVariableForKind(state, barycentric_kind);
uint32_t sum = 0;
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
const auto pointer = state.builder.AllocateId();
const auto weight = state.builder.AllocateId();
const auto product = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain,
TypePointer(state, StorageClassInput, TypeF32(state)),
pointer, barycentric, ConstantU32(state, vertex)});
state.builder.AddFunction({OpLoad, TypeF32(state), weight, pointer});
state.builder.AddFunction(
{OpFMul, TypeF32(state), product, load_per_vertex(vertex), weight});
if (vertex == 0u) {
sum = product;
} else {
const auto next = state.builder.AllocateId();
state.builder.AddFunction({OpFAdd, TypeF32(state), next, sum, product});
sum = next;
}
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, sum});
return bits;
}
const auto vector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
@@ -196,6 +245,35 @@ uint32_t EmitAttribute(ValueEmitContext& ctx, uint32_t attr, uint32_t chan) {
return bits;
}
uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32_t chan,
uint32_t mode) {
auto& state = ctx.state;
const auto* input = InputBindingForParameter(state, attr);
if (!input->per_vertex) {
return EmitAttribute(ctx, attr, chan);
}
const auto load_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)});
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
return value;
};
const auto selected_vertex = (mode + 1u) % 3u;
uint32_t value = load_vertex(selected_vertex);
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
const auto delta = state.builder.AllocateId();
state.builder.AddFunction({OpFSub, TypeF32(state), delta, value, load_vertex(0)});
value = delta;
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
return bits;
}
uint32_t MrtOutputMode(const EmitterState& state, const IR::ExportInfo& exp) {
if (state.stage != ShaderType::Pixel || exp.kind != IR::ExportTargetKind::Mrt ||
exp.index >= std::size(state.input_info.pixel->target_output_mode)) {
@@ -515,6 +593,10 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
case IR::ValueOpcode::GetAttribute:
ctx.Define(inst, EmitAttribute(ctx, inst.Arg(0).U32(), inst.Arg(1).U32()));
return true;
case IR::ValueOpcode::GetInterpolationParameter:
ctx.Define(inst, EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(),
inst.Arg(2).U32()));
return true;
case IR::ValueOpcode::SetAttribute: EmitExport(ctx, inst); return true;
case IR::ValueOpcode::GetShaderBase:
// Guest S_GETPC values stay shader-relative in SPIR-V, matching the runtime ABI. The
@@ -47,6 +47,7 @@ enum : uint32_t {
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilitySignedZeroInfNanPreserve = 4466,
CapabilityFragmentBarycentricKHR = 5284,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
@@ -71,6 +72,7 @@ enum : uint32_t {
DecorationBinding = 33,
DecorationDescriptorSet = 34,
DecorationOffset = 35,
DecorationPerVertexKHR = 5285,
};
enum : uint32_t {
@@ -86,6 +88,8 @@ enum : uint32_t {
BuiltInSubgroupLocalInvocationId = 41,
BuiltInVertexIndex = 42,
BuiltInInstanceIndex = 43,
BuiltInBaryCoordKHR = 5286,
BuiltInBaryCoordNoPerspKHR = 5287,
};
enum : uint32_t {
@@ -283,6 +287,7 @@ struct InputBinding {
uint32_t component_count = 1;
uint32_t variable_id = 0;
std::string debug_name;
bool per_vertex = false;
};
struct OutputBinding {
@@ -564,6 +569,8 @@ uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr);
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr);
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location);
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input);
@@ -441,6 +441,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
case IR::StageInputKind::InstanceIndex: return BuiltInInstanceIndex;
case IR::StageInputKind::FragCoord: return BuiltInFragCoord;
case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing;
case IR::StageInputKind::BaryCoordSmooth: return BuiltInBaryCoordKHR;
case IR::StageInputKind::BaryCoordNoPerspective: return BuiltInBaryCoordNoPerspKHR;
case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId;
case IR::StageInputKind::LocalInvocationId: return BuiltInLocalInvocationId;
case IR::StageInputKind::LocalInvocationIndex: return BuiltInLocalInvocationIndex;
@@ -464,11 +466,14 @@ void AddInputAnnotationsAndNames(EmitterState& state) {
state.builder.AddName(input.variable_id, input.debug_name.c_str());
if (input.kind == IR::StageInputKind::Parameter) {
const auto flat = PixelParameterIsFlat(state, input.location);
if (flat) {
if (input.per_vertex) {
state.builder.AddAnnotation(
{OpDecorate, input.variable_id, DecorationPerVertexKHR});
} else if (flat) {
state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat});
}
if (state.stage == ShaderType::Pixel && state.input_info.pixel->ps_no_perspective &&
!flat) {
!flat && !input.per_vertex) {
state.builder.AddAnnotation(
{OpDecorate, input.variable_id, DecorationNoPerspective});
}
@@ -625,6 +630,16 @@ void DefineModule(EmitterState& state) {
state.builder.RequireCapability(CapabilityComputeDerivativeGroupQuadsKHR);
state.builder.RequireExtension("SPV_KHR_compute_shader_derivatives");
}
const bool fragment_barycentric =
state.stage == ShaderType::Pixel &&
std::any_of(state.inputs.begin(), state.inputs.end(), [](const InputBinding& input) {
return input.per_vertex || input.kind == IR::StageInputKind::BaryCoordSmooth ||
input.kind == IR::StageInputKind::BaryCoordNoPerspective;
});
if (fragment_barycentric) {
state.builder.RequireCapability(CapabilityFragmentBarycentricKHR);
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
}
state.builder.RequireExtension("SPV_KHR_float_controls");
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
@@ -689,6 +704,10 @@ void DefineModule(EmitterState& state) {
case IR::StageInputKind::FragCoord:
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 4));
break;
case IR::StageInputKind::BaryCoordSmooth:
case IR::StageInputKind::BaryCoordNoPerspective:
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 3));
break;
case IR::StageInputKind::FrontFacing:
ptr_type = TypePointer(state, StorageClassInput, TypeBool(state));
break;
@@ -697,6 +716,10 @@ void DefineModule(EmitterState& state) {
const auto kind = VertexParameterScalarKind(state, input.location);
const auto components = VertexParameterComponentCount(state, input);
ptr_type = VertexParameterInputPointerType(state, kind, components);
} else if (input.per_vertex) {
const auto array_type = state.builder.Type(
OpTypeArray, {TypeF32Vector(state, 4), ConstantU32(state, 3)});
ptr_type = TypePointer(state, StorageClassInput, array_type);
} else {
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 4));
}
@@ -14,10 +14,14 @@ IR::ExportFlags Translator::AddExportInfo(const IR::Instruction& inst) {
bool Translator::TranslateAttributeOperation(const IR::Instruction& inst) {
if (inst.op == IR::Opcode::LoadInputF32) {
for (uint32_t component = 0; component < inst.input_info.component_count; component++) {
WriteOperand(OffsetOperand(inst.dst, component),
ir.Emit(IR::ValueOpcode::GetAttribute,
{IR::Value(inst.input_info.attr),
IR::Value(inst.input_info.chan + component)}));
const auto chan = inst.input_info.chan + component;
const auto value = inst.input_info.interpolation_mode < 3u
? ir.Emit(IR::ValueOpcode::GetInterpolationParameter,
{IR::Value(inst.input_info.attr), IR::Value(chan),
IR::Value(inst.input_info.interpolation_mode)})
: ir.Emit(IR::ValueOpcode::GetAttribute,
{IR::Value(inst.input_info.attr), IR::Value(chan)});
WriteOperand(OffsetOperand(inst.dst, component), value);
}
return true;
}
@@ -901,8 +901,15 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
first_bit));
}
} else if (source.stage == ShaderType::Pixel) {
const auto* ps = pixel_input_info;
uint32_t reg = ps->ps_system_input_base;
const auto* ps = pixel_input_info;
if (ps->ps_perspective_center_vgpr != UINT32_MAX) {
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(ps->ps_perspective_center_vgpr),
builtin(IR::StageInputKind::BaryCoordSmooth, 0));
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(ps->ps_perspective_center_vgpr + 1u),
builtin(IR::StageInputKind::BaryCoordSmooth, 1));
}
uint32_t reg = ps->ps_system_input_base;
if (ps->ps_pos_x) {
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 0));
@@ -866,9 +866,9 @@ bool LowerVInterpP1F32(const Decoder::Instruction& decoded, BasicBlock& block) {
bool LowerVInterpLoadF32(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error) {
if (decoded.opcode == Decoder::Opcode::V_INTERP_MOV_F32 && decoded.src0.value != 2u) {
if (decoded.opcode == Decoder::Opcode::V_INTERP_MOV_F32 && decoded.src0.value >= 3u) {
if (error != nullptr) {
*error = fmt::format("v_interp_mov_f32 mode {} is not implemented at pc 0x{:08x}",
*error = fmt::format("v_interp_mov_f32 mode {} is reserved at pc 0x{:08x}",
decoded.src0.value, decoded.pc);
}
return false;
@@ -879,6 +879,9 @@ bool LowerVInterpLoadF32(const Decoder::Instruction& decoded, BasicBlock& block,
inst.op = Opcode::LoadInputF32;
inst.input_info.attr = decoded.src1.value;
inst.input_info.chan = decoded.src2.value;
if (decoded.opcode == Decoder::Opcode::V_INTERP_MOV_F32) {
inst.input_info.interpolation_mode = decoded.src0.value;
}
if (!LowerRegisterOperand(decoded.dst, inst.dst, error)) {
return false;
}
+7 -3
View File
@@ -144,9 +144,10 @@ struct ExportInfo {
};
struct InputInfo {
uint32_t attr = 0;
uint32_t chan = 0;
uint32_t component_count = 1;
uint32_t attr = 0;
uint32_t chan = 0;
uint32_t component_count = 1;
uint32_t interpolation_mode = 3;
bool operator==(const InputInfo& other) const = default;
};
@@ -272,6 +273,8 @@ enum class StageInputKind {
InstanceIndex,
FragCoord,
FrontFacing,
BaryCoordSmooth,
BaryCoordNoPerspective,
WorkgroupId,
LocalInvocationId,
LocalInvocationIndex,
@@ -286,6 +289,7 @@ struct StageInput {
uint32_t location = 0;
uint32_t component_count = 1;
std::string debug_name;
bool per_vertex = false;
bool operator==(const StageInput& other) const = default;
};
@@ -274,6 +274,7 @@ VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(GetAttribute, U32, U32, U32)
VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32)
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
VALUE_OPCODE(ControlNop, Void)
@@ -9,14 +9,15 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
void AddInput(ShaderInfo& info, StageInputKind kind, uint32_t location, uint32_t components,
std::string name) {
std::string name, bool per_vertex = false) {
const auto input = std::find_if(info.inputs.begin(), info.inputs.end(), [=](const auto& value) {
return value.kind == kind && value.location == location;
});
if (input == info.inputs.end()) {
info.inputs.push_back({kind, location, components, std::move(name)});
info.inputs.push_back({kind, location, components, std::move(name), per_vertex});
} else {
input->component_count = std::max(input->component_count, components);
input->per_vertex = input->per_vertex || per_vertex;
}
}
@@ -79,6 +80,24 @@ bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& op
static_cast<uint32_t>(options.vertex->resources_num))) {
return Fail("vertex input reference is out of range");
}
if (program.stage == ShaderType::Pixel &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >= options.pixel->input_num)) {
return Fail("pixel input reference is out of range");
}
break;
}
case ValueOpcode::GetInterpolationParameter: {
if (program.stage != ShaderType::Pixel || !inst.Arg(0).IsImmediate() ||
inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() ||
inst.Arg(1).GetType() != Type::U32 || !inst.Arg(2).IsImmediate() ||
inst.Arg(2).GetType() != Type::U32) {
return Fail("interpolation parameter reference is invalid");
}
if (inst.Arg(0).U32() >= options.pixel->input_num || inst.Arg(1).U32() >= 4u ||
inst.Arg(2).U32() >= 3u) {
return Fail("interpolation parameter reference is out of range");
}
break;
}
case ValueOpcode::GetBuiltin: {
@@ -102,6 +121,12 @@ bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& op
return Fail("typed fragment-coordinate component is out of range");
}
break;
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordNoPerspective:
if (component >= 2u) {
return Fail("typed barycentric component is out of range");
}
break;
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
@@ -150,15 +175,40 @@ void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* ve
}
}
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixel,
ShaderInfo& info) {
if (pixel->HasPositionInput()) {
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
}
if (pixel->ps_front_face) {
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
}
std::array<bool, 32> per_vertex {};
std::array<bool, 32> interpolated {};
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
interpolated[inst.Arg(0).U32()] = true;
} else if (inst.GetOpcode() == ValueOpcode::GetInterpolationParameter) {
const auto input = inst.Arg(0).U32();
const auto mode = inst.Arg(2).U32();
per_vertex[input] =
per_vertex[input] || mode < 2u || !ShaderPixelParameterIsFlat(*pixel, input);
}
}
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input));
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input),
per_vertex[input]);
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
if (interpolated[input] && per_vertex[input]) {
const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective
: StageInputKind::BaryCoordSmooth;
AddInput(info, kind, 0, 3,
pixel->ps_no_perspective ? "gl_BaryCoordNoPerspKHR" : "gl_BaryCoordKHR");
break;
}
}
}
@@ -195,6 +245,12 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
break;
case StageInputKind::BaryCoordSmooth:
AddInput(info, kind, 0, 3, "gl_BaryCoordKHR");
break;
case StageInputKind::BaryCoordNoPerspective:
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
break;
case StageInputKind::WorkgroupId:
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
break;
@@ -287,7 +343,7 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break;
case ShaderType::Pixel: CollectPixelInputs(options.pixel, next); break;
case ShaderType::Pixel: CollectPixelInputs(program, options.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(options.compute, next); break;
default: return false;
}