diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index f504bed10..534057e8d 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace { @@ -140,6 +141,74 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl return writable; } +uint32_t LoadVertexParameter(EmitterState& state, const InputBinding& input, uint32_t vertex, + uint32_t chan) { + const auto pointer = state.builder.AllocateId(); + const auto value = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpAccessChain, + TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer, + input.variable_id, ConstantU32(state, vertex), + ConstantU32(state, chan & 3u)); + state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer); + return value; +} + +// Interpolates a pixel input channel to f32. A half (0 low, 1 high) selects one f16 of a packed +// FP16_INTERP_MODE dword before blending; smooth inputs read by halves are always per-vertex. +uint32_t EmitPixelAttribute(EmitterState& state, const InputBinding& input, uint32_t attr, + uint32_t chan, std::optional half) { + const auto select = [&](uint32_t value) { + if (!half) { + return value; + } + const auto bits = state.builder.AllocateId(); + const auto unpacked = state.builder.AllocateId(); + const auto word = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); + state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked, + GlslStd450(state), GLSLstd450UnpackHalf2x16, bits); + state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), word, unpacked, *half); + return word; + }; + if (!input.per_vertex) { + const auto vector = state.builder.AllocateId(); + const auto component = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input.variable_id); + state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector, + chan & 3u); + return select(component); + } + if (PixelParameterIsFlat(state, attr)) { + const auto vertex = + state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u; + return select(LoadVertexParameter(state, input, vertex, chan)); + } + 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(spv::OpAccessChain, + TypePointer(state, spv::StorageClassInput, TypeF32(state)), + pointer, barycentric, ConstantU32(state, vertex)); + state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer); + state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, + select(LoadVertexParameter(state, input, vertex, chan)), weight); + if (vertex == 0u) { + sum = product; + } else { + const auto next = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product); + sum = next; + } + } + return sum; +} + uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) { const auto* input = InputBindingForParameter(state, attr); if (input == nullptr || input->variable_id == 0) { @@ -148,57 +217,9 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) { 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) { - const auto pointer = state.builder.AllocateId(); - const auto value = state.builder.AllocateId(); - state.builder.AddFunction( - spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer, - input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)); - state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer); - return value; - }; - if (input->per_vertex) { - if (PixelParameterIsFlat(state, attr)) { - const auto value = load_per_vertex( - state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u); - const auto bits = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); - return bits; - } - 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(spv::OpAccessChain, - TypePointer(state, spv::StorageClassInput, TypeF32(state)), - pointer, barycentric, ConstantU32(state, vertex)); - state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer); - state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex), - weight); - if (vertex == 0u) { - sum = product; - } else { - const auto next = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product); - sum = next; - } - } - const auto bits = state.builder.AllocateId(); - state.builder.AddFunction(spv::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(); - state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id); - state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector, - chan & 3u); - state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, component); + const auto value = EmitPixelAttribute(state, *input, attr, chan, std::nullopt); + const auto bits = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); return bits; } @@ -209,25 +230,16 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32 if (!input->per_vertex) { return EmitAttribute(ctx.state, attr, chan); } - const auto load_vertex = [&](uint32_t vertex) { - const auto pointer = state.builder.AllocateId(); - const auto value = state.builder.AllocateId(); - state.builder.AddFunction( - spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer, - input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)); - state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer); - return value; - }; - uint32_t selected_vertex = (mode + 1u) % 3u; if (mode == 2u && PixelParameterIsFlat(state, attr)) { selected_vertex = state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u; } - uint32_t value = load_vertex(selected_vertex); + uint32_t value = LoadVertexParameter(state, *input, selected_vertex, chan); if (!PixelParameterIsCustom(state, attr) && mode < 2u) { const auto delta = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0)); + state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, + LoadVertexParameter(state, *input, 0, chan)); value = delta; } const auto bits = state.builder.AllocateId(); @@ -853,6 +865,12 @@ uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { return EmitAttribute(ctx.state, inst.Arg(0).U32(), inst.Arg(1).U32()); } +uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst) { + const auto attr = inst.Arg(0).U32(); + return EmitPixelAttribute(ctx.state, *InputBindingForParameter(ctx.state, attr), attr, + inst.Arg(1).U32(), inst.Arg(2).U32()); +} + uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst) { return EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(), inst.Arg(2).U32()); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index 528f22dd5..0a9556283 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -236,6 +236,7 @@ uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst); +uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst); void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitGetShaderBase(ValueEmitContext& ctx); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 25d3a8bcd..63e92124a 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -713,6 +713,8 @@ enum class Opcode { V_INTERP_P1_F32, V_INTERP_P2_F32, V_INTERP_MOV_F32, + V_INTERP_P1LL_F16, + V_INTERP_P2_F16, S_NOP, S_WAITCNT, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 19f836a49..2e4433129 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -391,6 +391,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = { {0x377u, Opcode::V_PERMLANE16_B32}, {0x378u, Opcode::V_PERMLANEX16_B32}, {0x34bu, Opcode::V_FMA_F16}, + {0x342u, Opcode::V_INTERP_P1LL_F16}, + {0x35au, Opcode::V_INTERP_P2_F16}, {0x36du, Opcode::V_ADD3_U32}, {0x14fu, Opcode::V_ALIGNBYTE_B32}, }; @@ -1553,6 +1555,9 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi } switch (opcode) { case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u; + case Opcode::V_INTERP_P1LL_F16: + case Opcode::V_INTERP_P2_F16: + return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u; case Opcode::V_CNDMASK_B32: return (abs & ~0x3u) != 0u || op_sel != 0u || clamp != 0u || omod != 0u || (neg & ~0x3u) != 0u; @@ -1664,6 +1669,17 @@ bool IsVopcCompareExec(Opcode opcode) { } } +// VINTRP and VOP3 interpolation share one layout: src0 = I/J VGPR, src1 = attr, src2 = chan. +void SetInterpolationAttribute(Instruction& inst, uint32_t attr, uint32_t chan) { + inst.src1.kind = OperandKind::IntegerInlineConstant; + inst.src1.value = attr; + inst.src1.signed_val = static_cast(attr); + inst.src2.kind = OperandKind::IntegerInlineConstant; + inst.src2.value = chan; + inst.src2.signed_val = static_cast(chan); + inst.src_count = 3; +} + } // namespace void DecodeVop2(uint32_t pc, std::span code, uint32_t word_index, @@ -1830,6 +1846,15 @@ void DecodeVop3(uint32_t pc, std::span code, uint32_t word_index inst.src_count = 0; return; } + if (inst.opcode == Opcode::V_INTERP_P1LL_F16 || inst.opcode == Opcode::V_INTERP_P2_F16) { + // SRC0 packs {attr_word, attr_chan, attr}; attr_word selects the high f16 of the dword. + DecodeVectorGpr(vdst, inst.dst); + DecodeScalarSource(src1, pc, inst.src0); + SetInterpolationAttribute(inst, src0 & 0x3fu, (src0 >> 6u) & 0x3u); + inst.src1.op_sel = (src0 & 0x100u) != 0u; + inst.dst.sdwa_sel = (op_sel & 0x8u) != 0u ? 5u : 4u; + return; + } if (compare_exec) { inst.dst.kind = OperandKind::ExecLo; } else if (scalar_dst) { @@ -2004,13 +2029,7 @@ void DecodeVintrp(uint32_t pc, std::span code, uint32_t word_ind } else { DecodeVectorGpr(vsrc, inst.src0); } - inst.src1.kind = OperandKind::IntegerInlineConstant; - inst.src1.value = attr; - inst.src1.signed_val = static_cast(attr); - inst.src2.kind = OperandKind::IntegerInlineConstant; - inst.src2.value = chan; - inst.src2.signed_val = static_cast(chan); - inst.src_count = 3; + SetInterpolationAttribute(inst, attr, chan); } } // namespace Libs::Graphics::ShaderRecompiler::Decoder diff --git a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp index 4d3621096..b76ad1bda 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp @@ -73,14 +73,19 @@ void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32 } } -void Translator::V_INTERP_P1_F32() {} - void Translator::V_INTERP_P2_F32(const Decoder::Instruction& inst) { const auto value = ir.Emit(IR::ValueOpcode::GetAttribute, {IR::Value(inst.src1.value), IR::Value(inst.src2.value)}); WriteOperand(inst.dst, value); } +void Translator::V_INTERP_P2_F16(const Decoder::Instruction& inst) { + const auto value = ir.Emit(IR::ValueOpcode::GetAttributeF16, + {IR::Value(inst.src1.value), IR::Value(inst.src2.value), + IR::Value(uint32_t {inst.src1.op_sel})}); + WriteF16(inst.dst, IR::F32(value)); +} + void Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst) { if (inst.src0.value >= 3u) { EXIT("v_interp_mov_f32 mode %u is reserved at pc 0x%08x", inst.src0.value, inst.pc); @@ -108,8 +113,11 @@ void Translator::EXP(const Decoder::Instruction& inst) { void Translator::EmitInterpolation(const Decoder::Instruction& inst) { switch (inst.opcode) { - case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return; + // P2 interpolates the whole attribute, so the P1 halves have nothing to compute. + case Decoder::Opcode::V_INTERP_P1_F32: + case Decoder::Opcode::V_INTERP_P1LL_F16: return; case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return; + case Decoder::Opcode::V_INTERP_P2_F16: V_INTERP_P2_F16(inst); return; case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return; default: return FailMissingTranslation(inst); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 097463b14..50e37c030 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -240,8 +240,8 @@ private: void V_READLANE_B32(const Decoder::Instruction& inst); void V_WRITELANE_B32(const Decoder::Instruction& inst); void V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16); - void V_INTERP_P1_F32(); void V_INTERP_P2_F32(const Decoder::Instruction& inst); + void V_INTERP_P2_F16(const Decoder::Instruction& inst); void V_INTERP_MOV_F32(const Decoder::Instruction& inst); void EXP(const Decoder::Instruction& inst); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 81a27f2e5..455c9a8a2 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -6,6 +6,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { using O = Decoder::Opcode; switch (inst.opcode) { case O::V_NOP: return; + case O::V_INTERP_P1LL_F16: + case O::V_INTERP_P2_F16: return EmitInterpolation(inst); case O::V_ADD_I32: ADD_U32(inst, true, false); return; case O::V_ADDC_U32: ADD_U32(inst, true, true); return; case O::V_SUB_I32: SUB_U32(inst, true, false); return; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index c9a3408a0..27da7d59a 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -338,6 +338,7 @@ VALUE_OPCODE(ImageAtomicXor64, U64, ImageResource, ImageAddress, U64, U1) VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(GetAttribute, U32, U32, U32) +VALUE_OPCODE(GetAttributeF16, F32, U32, U32, U32) VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32) VALUE_OPCODE(SetAttribute, Void, U32x4, U1) diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 4de7f2b84..7d91d05ac 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -373,7 +373,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs } } switch (inst.GetOpcode()) { - case ValueOpcode::GetAttribute: { + case ValueOpcode::GetAttribute: + case ValueOpcode::GetAttributeF16: { if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) { return Fail("typed attribute reference is not constant"); @@ -395,6 +396,15 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs auto& components = inputs.components[inst.Arg(0).U32()]; components = std::max(components, static_cast(inst.Arg(1).U32() + 1u)); } + if (inst.GetOpcode() == ValueOpcode::GetAttributeF16) { + if (program.stage != ShaderType::Pixel || !inst.Arg(2).IsImmediate()) { + return Fail("f16 attribute reference is invalid"); + } + // FP16_INTERP_MODE packs two halves per dword, so smooth inputs blend unpacked + // vertices; flat inputs unpack the provoking vertex. + const auto input = inst.Arg(0).U32(); + inputs.per_vertex[input] |= !ShaderPixelParameterIsFlat(*input_info.pixel, input); + } break; } case ValueOpcode::GetInterpolationParameter: { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 09b0be1d9..c7b01daa7 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6832,6 +6832,50 @@ void TestCustomVintrpMovTranslation() { CheckSpirvBinaryValidates(mixed_linear_result.spirv); } +void TestVop3F16InterpolationTranslation() { + // v_interp_p1ll_f16 v21, v0, attr0.w high; v_interp_p2_f16 v14, v1, attr0.w, v21 high + // v_interp_p1ll_f16 v15, v0, attr0.z; v_interp_p2_f16 v14, v1, attr0.z, v15 (op_sel[3]) + const uint32_t shader[] = { + EncodeVop3Word0(0x342, 21), EncodeVop3Word1(0x1c0, 256 + 0, 0), + EncodeVop3Word0(0x35a, 14), EncodeVop3Word1(0x1c0, 256 + 1, 256 + 21), + EncodeVop3Word0(0x342, 15), EncodeVop3Word1(0x080, 256 + 0, 0), + EncodeVop3Word0(0x35a, 14, 8), EncodeVop3Word1(0x080, 256 + 1, 256 + 15), + EncodeExp0(0x00, 0x1), EncodeExp1(14, 0, 0, 0), + 0xbf810000u, + }; + ShaderPixelInputInfo ps_info{}; + ps_info.input_num = 1; + SetIdentityInterpolatorSettings(&ps_info); + auto options = MakeCompileOptions(ShaderType::Pixel); + options.input_info.pixel = &ps_info; + options.dump_ir = true; + + auto result = RecompileForTest(shader, options); + Check(result.ir_dump.find("GetAttributeF16 0x00000000, 0x00000003, 0x00000001") != + std::string::npos, + "v_interp_p2_f16 did not read the high f16 of attr0.w"); + Check(result.ir_dump.find("GetAttributeF16 0x00000000, 0x00000002, 0x00000000") != + std::string::npos, + "v_interp_p2_f16 did not read the low f16 of attr0.z"); + Check(result.decoded_dump.find("v14.sdwa(sel=5") != std::string::npos, + "v_interp_p2_f16 op_sel[3] did not write the high half of v14"); + Check(SpirvHasDecorationValueWithDecoration(result.spirv, 30u, 0u, 5285u), + "f16 interpolation input was not per-vertex"); + Check(SpirvExtInstCount(result.spirv, 62u) == 6u, + "f16 interpolation did not unpack each of the three vertices per read"); + Check(SpirvInstructionOpcodeCount(result.spirv, 133u) == 6u, + "f16 interpolation did not blend three barycentric weights per read"); + CheckSpirvBinaryValidates(result.spirv); + + ps_info.interpolator_settings[0] = 0x00000400u; + auto flat_result = RecompileForTest(shader, options); + Check(SpirvExtInstCount(flat_result.spirv, 62u) == 2u && + !SpirvContainsCapability(flat_result.spirv, 5284u) && + SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u), + "flat f16 interpolation did not unpack only the flat provoking value"); + CheckSpirvBinaryValidates(flat_result.spirv); +} + void TestPerspectiveCentroidInputs() { constexpr std::array cases{std::array{4u, UINT32_MAX, 0u}, std::array{5u, UINT32_MAX, 2u}, @@ -16828,6 +16872,7 @@ int main() { TestNewShaderRecompilerNativeBindingPlan(); TestNewShaderRecompilerStageInputInfo(); TestCustomVintrpMovTranslation(); + TestVop3F16InterpolationTranslation(); TestPerspectiveCentroidInputs(); TestPerspectiveSampleInputs(); TestGraphicsCreateInterpolantMapping();