From 5e64efce4f8114b537f145d53c78ca3be66b80cb Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 21 Aug 2026 23:33:40 +0200 Subject: [PATCH] graphics: support RDNA2 custom pixel interpolation Co-authored-by: Senaxx --- .../presentation/window/vulkanWindow.cpp | 50 ++++++- .../backend/spirv/spirvEmitterAnalysis.cpp | 9 +- .../backend/spirv/spirvEmitterFlow.cpp | 82 +++++++++++ .../backend/spirv/spirvEmitterInternal.h | 7 + .../backend/spirv/spirvEmitterModule.cpp | 27 +++- .../frontend/translate/Attribute.cpp | 12 +- .../frontend/translate/Translate.cpp | 11 +- .../shader/recompiler/ir/ShaderIR.cpp | 7 +- src/graphics/shader/recompiler/ir/ShaderIR.h | 10 +- .../recompiler/ir/opcodes/ValueOpcodes.inc | 1 + .../ir/passes/ShaderInfoCollection.cpp | 66 ++++++++- src/graphics/shader/shader.cpp | 29 +++- src/graphics/shader/shader.h | 3 + tests/shaderCfgTests.cpp | 128 ++++++++++++++++++ 14 files changed, 414 insertions(+), 28 deletions(-) diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index bfe8e7808..c5e62e827 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -199,7 +199,15 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa vk::PhysicalDeviceVulkan12Features features12 {}; features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features; +#if defined(__APPLE__) features12.pNext = &depth_clip_control; +#else + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {}; + fragment_barycentric.sType = + vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; + fragment_barycentric.pNext = &depth_clip_control; + features12.pNext = &fragment_barycentric; +#endif features13.pNext = &features12; device_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; @@ -231,6 +239,12 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa skip_device = true; #endif } +#if !defined(__APPLE__) + if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) { + LOGF("fragmentShaderBarycentric is not supported\n"); + skip_device = true; + } +#endif if (features12.samplerMirrorClampToEdge != VK_TRUE) { LOGF("samplerMirrorClampToEdge is not supported\n"); @@ -522,6 +536,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_features12.pNext = nullptr; supported_features13.pNext = &supported_features12; +#if !defined(__APPLE__) + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_fragment_barycentric {}; + supported_fragment_barycentric.sType = + vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; + supported_fragment_barycentric.pNext = nullptr; + supported_features12.pNext = &supported_fragment_barycentric; +#endif + const auto robustness2_ext_enabled = HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); @@ -529,7 +551,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT; supported_robustness2.pNext = nullptr; if (robustness2_ext_enabled) { +#if defined(__APPLE__) supported_features12.pNext = &supported_robustness2; +#else + supported_fragment_barycentric.pNext = &supported_robustness2; +#endif } vk::PhysicalDeviceFeatures2 supported_features2 {}; @@ -544,6 +570,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_features13.synchronization2 != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE); +#if !defined(__APPLE__) + EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE); +#endif features12.timelineSemaphore = VK_TRUE; if (Config::GpuAssistedValidationEnabled()) { @@ -576,7 +605,16 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {}; robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT; +#if defined(__APPLE__) robustness2.pNext = &features12; +#else + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {}; + fragment_barycentric.sType = + vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; + fragment_barycentric.pNext = &features12; + fragment_barycentric.fragmentShaderBarycentric = VK_TRUE; + robustness2.pNext = &fragment_barycentric; +#endif if (robustness2_ext_enabled) { robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2; robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2; @@ -586,9 +624,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V const bool subgroup_size_control_enabled = graphics.subgroup_size_control_enabled && supported_features13.subgroupSizeControl == VK_TRUE; - auto features13 = required_features13; - features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) - : static_cast(&features12); + auto features13 = required_features13; +#if defined(__APPLE__) + features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) + : static_cast(&features12); +#else + features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) + : static_cast(&fragment_barycentric); +#endif features13.robustImageAccess = supported_features13.robustImageAccess; features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE; @@ -922,6 +965,7 @@ void WindowContext::CreateVulkan() { #else device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); device_extensions.push_back(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME); #endif #ifdef KYTY_ENABLE_DEBUG_PRINTF diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index e8c2d87be..026ecaa20 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -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& 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)) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index cdc2b256f..e03ae48d7 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -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 diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 961840486..e33106b2e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -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); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 69d7096da..6f1e1f17e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -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)); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp index e3a2e96ac..ee9ebf2af 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp @@ -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; } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 1581a87dc..a7eb15c9c 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -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(ps->ps_perspective_center_vgpr), + builtin(IR::StageInputKind::BaryCoordSmooth, 0)); + entry_ir.SetVectorReg( + static_cast(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(reg++), builtin(IR::StageInputKind::FragCoord, 0)); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.cpp b/src/graphics/shader/recompiler/ir/ShaderIR.cpp index b744e9060..7a655e4f2 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIR.cpp @@ -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; } diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 60e8d3a60..01b754b20 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -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; }; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index e11b6c13b..1c9fa3829 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -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) diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 2afd8100c..e8a805328 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -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(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 per_vertex {}; + std::array 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; } diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index f488253ce..5fe8bebcf 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -85,7 +85,12 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, } bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input) { - return input < info.input_num && (info.interpolator_settings[input] & PsInputFlatShade) != 0; + return input < info.input_num && (info.interpolator_settings[input] & PsInputFlatShade) != 0 && + !ShaderPixelParameterIsCustom(info, input); +} + +bool ShaderPixelParameterIsCustom(const ShaderPixelInputInfo& info, uint32_t input) { + return input < 32u && (info.custom_interpolation_mask & (1u << input)) != 0; } struct ShaderBinaryInfo { @@ -854,9 +859,18 @@ static void ShaderGetStaticInputInfoPS( // SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control // flags in the hardware state and extract only the input count here. - ps_info.input_num = sh.ps_in_control & 0x3fu; - ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh); - const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr; + ps_info.input_num = sh.ps_in_control & 0x3fu; + ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh); + const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr; + if ((active_inputs & 0x00000002u) != 0) { + ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u; + } + for (uint32_t i = 0; i < data.num_input_semantics && i < ps_info.input_num && i < 32u; i++) { + const auto& semantic = data.input_semantics[i]; + if (semantic.is_custom != 0 && semantic.is_f16 == 0) { + ps_info.custom_interpolation_mask |= 1u << i; + } + } ps_info.ps_pos_x = (active_inputs & 0x00000100u) != 0; ps_info.ps_pos_y = (active_inputs & 0x00000200u) != 0; ps_info.ps_pos_xy = ps_info.ps_pos_x && ps_info.ps_pos_y; @@ -1297,6 +1311,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { LOGF("\t input_num = %u\n" "\t ps_system_input_base = %u\n" + "\t custom_interpolation_mask = 0x%08" PRIx32 "\n" + "\t ps_perspective_center_vgpr = %" PRIu32 "\n" "\t ps_pos_x = %s\n" "\t ps_pos_y = %s\n" "\t ps_pos_z = %s\n" @@ -1307,7 +1323,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_pixel_kill_enable = %s\n" "\t ps_early_z = %s\n" "\t ps_execute_on_noop = %s\n", - info.input_num, info.ps_system_input_base, info.ps_pos_x ? "true" : "false", + info.input_num, info.ps_system_input_base, info.custom_interpolation_mask, + info.ps_perspective_center_vgpr, info.ps_pos_x ? "true" : "false", info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false", info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false", info.ps_sample_shading ? "true" : "false", info.ps_no_perspective ? "true" : "false", @@ -1657,6 +1674,8 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn ret.ids.push_back(input_info.scratch_size_dwords); ret.ids.push_back(input_info.input_num); ret.ids.push_back(input_info.ps_system_input_base); + ret.ids.push_back(input_info.custom_interpolation_mask); + ret.ids.push_back(input_info.ps_perspective_center_vgpr); ret.ids.push_back(static_cast(input_info.ps_pos_x)); ret.ids.push_back(static_cast(input_info.ps_pos_y)); ret.ids.push_back(static_cast(input_info.ps_pos_z)); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index dbd033878..7314c1dd8 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -117,6 +117,8 @@ struct ShaderPixelInputInfo { uint32_t interpolator_settings[32] = {0}; uint32_t input_num = 0; uint32_t ps_system_input_base = 0; + uint32_t custom_interpolation_mask = 0; + uint32_t ps_perspective_center_vgpr = UINT32_MAX; uint8_t target_output_mode[8] = {}; std::array target_export_mapping = {}; uint32_t mrt_output_mask = 0; @@ -150,6 +152,7 @@ uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, ui uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, std::span active_inputs, uint32_t input); bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input); +bool ShaderPixelParameterIsCustom(const ShaderPixelInputInfo& info, uint32_t input); struct ShaderSharp { uint16_t offset_dw : 15; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 1c0a94241..09f164ce1 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -5377,6 +5377,133 @@ void TestNewShaderRecompilerVintrpLowering() { CheckSpirvBinaryValidates(no_perspective_result.spirv); } +void TestCustomVintrpMovLowering() { + const uint32_t shader[] = { + EncodeVintrp(2, 12, 0, 3, 2), EncodeVintrp(2, 13, 0, 3, 0), + EncodeVintrp(2, 14, 0, 3, 1), EncodeVop2(0x03, 15, 12 + 256, 0), + EncodeVop2(0x03, 16, 13 + 256, 1), EncodeExp0(0x00, 0xf), + EncodeExp1(15, 16, 14, 12), 0xbf810000u, + }; + ShaderPixelInputInfo custom_ps_info{}; + custom_ps_info.input_num = 1; + custom_ps_info.ps_system_input_base = 2; + custom_ps_info.custom_interpolation_mask = 1; + custom_ps_info.ps_perspective_center_vgpr = 0; + SetIdentityInterpolatorSettings(&custom_ps_info); + custom_ps_info.interpolator_settings[0] = 0x00000420u; + + auto options = MakeCompileOptions(ShaderType::Pixel); + options.input_info.pixel = &custom_ps_info; + options.dump_ir = true; + + ShaderRecompiler::CompileResult custom_result; + std::string error; + Check(ShaderRecompiler::TryRecompile(shader, options, custom_result, &error), + error.c_str()); + Check(Common::ContainsStr( + custom_result.ir_dump, + "GetInterpolationParameter 0x00000000, 0x00000003, 0x00000000"), + "custom VINTRP did not preserve P10 mode"); + Check(Common::ContainsStr( + custom_result.ir_dump, + "GetInterpolationParameter 0x00000000, 0x00000003, 0x00000001"), + "custom VINTRP did not preserve P20 mode"); + Check(Common::ContainsStr( + custom_result.ir_dump, + "GetInterpolationParameter 0x00000000, 0x00000003, 0x00000002"), + "custom VINTRP did not preserve P0 mode"); + Check(SpirvContainsCapability(custom_result.spirv, 5284u), + "custom VINTRP did not enable FragmentBarycentricKHR"); + Check(SpirvHasDecorationValueWithDecoration(custom_result.spirv, 30u, 0u, + 5285u), + "custom VINTRP input did not emit PerVertexKHR"); + Check( + !SpirvHasDecorationValueWithDecoration(custom_result.spirv, 30u, 0u, 14u), + "custom VINTRP input was misclassified as flat"); + Check(SpirvHasDecorationValue(custom_result.spirv, 11u, 5286u), + "perspective-center VGPRs did not use BaryCoordKHR"); + const auto source = DisassembleSpirvBinary(custom_result.spirv); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "in_param_0 %uint_0 %uint_3"), + "custom VINTRP P0 did not select vertex 0"); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "in_param_0 %uint_1 %uint_3"), + "custom VINTRP P10 did not select vertex 1"); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "in_param_0 %uint_2 %uint_3"), + "custom VINTRP P20 did not select vertex 2"); + Check(!Common::ContainsStr(source, "OpFSub"), + "custom VINTRP incorrectly applied hardware delta subtraction"); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "gl_BaryCoordKHR %uint_1") && + SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "gl_BaryCoordKHR %uint_2") && + !SpirvSourceHasInstructionUsing(source, "OpAccessChain", + "gl_BaryCoordKHR %uint_0"), + "guest perspective-center I/J did not map to BaryCoordKHR Y/Z"); + CheckSpirvBinaryValidates(custom_result.spirv); + + custom_ps_info.custom_interpolation_mask = 0; + custom_ps_info.interpolator_settings[0] = 0; + ShaderRecompiler::CompileResult standard_result; + Check( + ShaderRecompiler::TryRecompile(shader, options, standard_result, &error), + error.c_str()); + Check(SpirvInstructionOpcodeCount(standard_result.spirv, 131u) == 2u, + "standard VINTRP P10/P20 did not subtract P0 exactly once each"); + CheckSpirvBinaryValidates(standard_result.spirv); + + const uint32_t flat_shader[] = { + EncodeVintrp(2, 12, 0, 3, 2), + EncodeExp0(0x00, 0x1), + EncodeExp1(12, 0, 0, 0), + 0xbf810000u, + }; + ShaderPixelInputInfo flat_ps_info{}; + flat_ps_info.input_num = 1; + flat_ps_info.interpolator_settings[0] = 0x00000400u; + options.input_info.pixel = &flat_ps_info; + ShaderRecompiler::CompileResult flat_result; + Check( + ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error), + error.c_str()); + Check(!SpirvContainsCapability(flat_result.spirv, 5284u), + "flat P0 unexpectedly required fragment barycentric support"); + Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u), + "flat P0 did not retain ordinary flat interpolation"); + CheckSpirvBinaryValidates(flat_result.spirv); + + const uint32_t mixed_shader[] = { + EncodeVintrp(0, 12, 0, 3, 0), + EncodeVintrp(1, 12, 0, 3, 0), + EncodeVintrp(2, 13, 0, 3, 2), + EncodeVop2(0x03, 14, 12 + 256, 13), + EncodeExp0(0x00, 0x1), + EncodeExp1(14, 0, 0, 0), + 0xbf810000u, + }; + ShaderPixelInputInfo mixed_ps_info{}; + mixed_ps_info.input_num = 1; + options.input_info.pixel = &mixed_ps_info; + ShaderRecompiler::CompileResult mixed_result; + Check(ShaderRecompiler::TryRecompile(mixed_shader, options, mixed_result, + &error), + error.c_str()); + Check(SpirvHasDecorationValue(mixed_result.spirv, 11u, 5286u) && + SpirvInstructionOpcodeCount(mixed_result.spirv, 133u) == 3u, + "mixed ordinary/P0 input was not interpolated from per-vertex values"); + CheckSpirvBinaryValidates(mixed_result.spirv); + + mixed_ps_info.ps_no_perspective = true; + ShaderRecompiler::CompileResult mixed_linear_result; + Check(ShaderRecompiler::TryRecompile(mixed_shader, options, + mixed_linear_result, &error), + error.c_str()); + Check(SpirvHasDecorationValue(mixed_linear_result.spirv, 11u, 5287u), + "mixed linear input did not use BaryCoordNoPerspKHR"); + CheckSpirvBinaryValidates(mixed_linear_result.spirv); +} + void TestPsInputCountRegisterDecode() { HW::Context context; // NUM_INTERP is 3 while bit 14 is an independent control flag that must be @@ -11623,6 +11750,7 @@ int main() { TestLowerProgramResetsAnalysisState(); TestNewShaderRecompilerNativeBindingPlan(); TestNewShaderRecompilerStageInputInfo(); + TestCustomVintrpMovLowering(); TestGraphicsCreateInterpolantMapping(); TestNewShaderRecompilerPixelPipelineEntry(); TestComputeLdsAllocationIdentity();