From df44ad3facfc431e1f88e45bc9fe979741eebb12 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 5 Aug 2026 02:14:48 +0200 Subject: [PATCH] shader: implement BUFFER_ATOMIC_FMIN, unhandled IR opcode now fails --- .../shader/recompiler/ShaderRecompiler.cpp | 1 + .../recompiler/decompiler/MemoryOps.cpp | 1 + .../recompiler/decompiler/ShaderDecoder.cpp | 2 + .../recompiler/decompiler/ShaderDecoder.h | 1 + .../recompiler/emitter/SpirvEmitter.cpp | 3 +- .../emitter/spirvEmitterControlFlow.cpp | 3 + .../recompiler/emitter/spirvEmitterInternal.h | 7 +- .../recompiler/emitter/spirvEmitterMemory.cpp | 67 +++++++++++++- .../shader/recompiler/ir/ResourceTracking.cpp | 3 +- .../shader/recompiler/ir/ShaderIRMemory.cpp | 3 + .../shader/recompiler/ir/ShaderIROpcodes.cpp | 1 + .../shader/recompiler/ir/ShaderIROpcodes.inc | 1 + tests/ShaderRecompilerComputeTests.cpp | 91 +++++++++++++++++++ tests/shaderCfgTests.cpp | 8 ++ 14 files changed, 187 insertions(+), 5 deletions(-) diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index c94d3324..b493c3e7 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -84,6 +84,7 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) { case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicXorU32: + case IR::Opcode::AtomicFMinF32: case IR::Opcode::FlatLoadUbyte: case IR::Opcode::FlatLoadSbyte: case IR::Opcode::FlatLoadUshort: diff --git a/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp b/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp index 08317289..677a6e54 100644 --- a/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp @@ -57,6 +57,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = { {0x39u, Opcode::BufferAtomicAnd, 1, 32}, {0x3au, Opcode::BufferAtomicOr, 1, 32}, {0x3bu, Opcode::BufferAtomicXor, 1, 32}, + {0x3fu, Opcode::BufferAtomicFMin, 1, 32}, }; constexpr MemoryOpcodeInfo MTBUF_OPS[] = { diff --git a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp index fcbb3e27..fa5aa2e9 100644 --- a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp @@ -835,6 +835,7 @@ std::string OpcodeToString(Opcode opcode) { case Opcode::BufferAtomicAnd: return "buffer_atomic_and"; case Opcode::BufferAtomicOr: return "buffer_atomic_or"; case Opcode::BufferAtomicXor: return "buffer_atomic_xor"; + case Opcode::BufferAtomicFMin: return "buffer_atomic_fmin"; case Opcode::FlatLoadUbyte: return "flat_load_ubyte"; case Opcode::FlatLoadSbyte: return "flat_load_sbyte"; case Opcode::FlatLoadUshort: return "flat_load_ushort"; @@ -1125,6 +1126,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::BufferAtomicAnd: case Opcode::BufferAtomicOr: case Opcode::BufferAtomicXor: + case Opcode::BufferAtomicFMin: case Opcode::BufferLoadSbyte: case Opcode::BufferLoadSshort: case Opcode::FlatLoadUbyte: diff --git a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h index 72868db8..74cc80e2 100644 --- a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h @@ -434,6 +434,7 @@ enum class Opcode { BufferAtomicAnd, BufferAtomicOr, BufferAtomicXor, + BufferAtomicFMin, FlatLoadUbyte, FlatLoadSbyte, FlatLoadUshort, diff --git a/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp b/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp index 73237550..e91ff179 100644 --- a/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp @@ -131,7 +131,8 @@ bool IsAtomic(IR::Opcode op) { case IR::Opcode::AtomicUMaxU32: case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicOrU32: - case IR::Opcode::AtomicXorU32: return true; + case IR::Opcode::AtomicXorU32: + case IR::Opcode::AtomicFMinF32: return true; default: return false; } } diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp index aaf6def6..9adb9d45 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp @@ -775,6 +775,9 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) { case IR::Opcode::AtomicXorU32: EmitGuardedByExec(state, [&]() { EmitAtomicU32(state, inst, OpAtomicXor); }); break; + case IR::Opcode::AtomicFMinF32: + EmitGuardedByExec(state, [&]() { EmitAtomicFMinF32(state, inst); }); + break; case IR::Opcode::FlatLoadUbyte: EmitFlatLoadUbyte(state, inst); break; case IR::Opcode::FlatLoadSbyte: EmitFlatLoadSbyte(state, inst); break; case IR::Opcode::FlatLoadUshort: EmitFlatLoadUshort(state, inst); break; diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h index 0158dfec..d9056099 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h @@ -1011,6 +1011,8 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state); void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode); +void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst); + void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst); void EmitLoadSrtDword(EmitterState& state, const IR::Instruction& inst); @@ -1573,19 +1575,22 @@ uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn& } const auto then_label = state.builder.AllocateId(); + const auto then_exit = state.builder.AllocateId(); const auto else_label = state.builder.AllocateId(); const auto merge_label = state.builder.AllocateId(); state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone}); state.builder.AddFunction({OpBranchConditional, condition, then_label, else_label}); state.builder.AddFunction({OpLabel, then_label}); const auto then_value = fn(); + state.builder.AddFunction({OpBranch, then_exit}); + state.builder.AddFunction({OpLabel, then_exit}); state.builder.AddFunction({OpBranch, merge_label}); state.builder.AddFunction({OpLabel, else_label}); state.builder.AddFunction({OpBranch, merge_label}); state.builder.AddFunction({OpLabel, merge_label}); const auto value = state.builder.AllocateId(); state.builder.AddFunction( - {OpPhi, state.uint_type, value, then_value, then_label, ConstantU32(state, 0), else_label}); + {OpPhi, state.uint_type, value, then_value, then_exit, ConstantU32(state, 0), else_label}); return value; } diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp index b80a7a3d..0bb25aea 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp @@ -640,8 +640,8 @@ void EmitMemoryStoreU32(EmitterState& state, const IR::Instruction& inst, IR::Re } template -void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, - Fn&& desired_value) { +uint32_t EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, + Fn&& desired_value) { const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice; const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory : MemorySemanticsUniformMemory; @@ -675,6 +675,7 @@ void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind const auto semantics = MemorySemanticsAcquireRelease | memory; state.builder.AddFunction( {OpMemoryBarrier, ConstantU32(state, scope), ConstantU32(state, semantics)}); + return observed; } void EmitMemoryStoreSubDwordU32(EmitterState& state, const IR::Instruction& inst, @@ -1181,6 +1182,68 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op EmitStoreU32(state, inst.dst, old); } +namespace { + +uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits) { + struct ClassifiedBits { + uint32_t nan = 0; + uint32_t zero = 0; + uint32_t key = 0; + }; + const auto Classify = [&](uint32_t bits) { + ClassifiedBits cls; + const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu); + const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u); + const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu); + const auto exponent_max = + EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u); + const auto mantissa_nonzero = EmitCompareU32Constant(state, OpINotEqual, mantissa_bits, 0); + const auto negative = EmitCompareU32Constant(state, OpINotEqual, + EmitAndConstant(state, bits, 0x80000000u), 0); + const auto negative_key = state.builder.AllocateId(); + const auto positive_key = state.builder.AllocateId(); + // Map all non-NaN IEEE-754 encodings to monotonically increasing unsigned keys. + state.builder.AddFunction({OpNot, state.uint_type, negative_key, bits}); + state.builder.AddFunction( + {OpBitwiseXor, state.uint_type, positive_key, bits, ConstantU32(state, 0x80000000u)}); + cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero); + cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0); + cls.key = EmitSelectValueU32(state, negative, negative_key, positive_key); + return cls; + }; + + const auto lhs = Classify(lhs_bits); + const auto rhs = Classify(rhs_bits); + const auto any_nan = EmitLogicalOrBool(state, lhs.nan, rhs.nan); + // The key transform orders -0 below +0, while the floating comparison treats them as equal. + const auto both_zero = EmitLogicalAndBool(state, lhs.zero, rhs.zero); + const auto ordered_nonzero = + EmitLogicalNotBool(state, EmitLogicalOrBool(state, any_nan, both_zero)); + const auto less = state.builder.AllocateId(); + state.builder.AddFunction({OpULessThan, state.bool_type, less, lhs.key, rhs.key}); + return EmitLogicalAndBool(state, ordered_nonzero, less); +} + +} // namespace + +void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) { + const auto index = + EmitMemoryDwordIndex(state, inst, inst.memory, 1, AddressSourceCount(inst, 1)); + const auto in_bounds = EmitStorageBufferElementInBounds(state, inst.memory, index, inst.pc); + const auto src_u32 = EmitValueLoad(state, inst.src[0]); + const auto old = EmitValueOrZeroIfCondition(state, in_bounds, [&]() { + const auto pointer = EmitStorageBufferElementPointer(state, inst.memory, index, inst.pc); + return EmitAtomicUpdateU32(state, pointer, inst.memory.kind, [&](uint32_t old_u32) { + const auto replace_old = state.builder.AllocateId(); + const auto less = EmitF32BitsOrderedLessThan(state, src_u32, old_u32); + state.builder.AddFunction( + {OpSelect, state.uint_type, replace_old, less, src_u32, old_u32}); + return replace_old; + }); + }); + EmitStoreU32(state, inst.dst, old); +} + void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst) { if (state.address_memory_variable == 0) { ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory, diff --git a/src/graphics/shader/recompiler/ir/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/ResourceTracking.cpp index 3669036d..3e7b706d 100644 --- a/src/graphics/shader/recompiler/ir/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/ResourceTracking.cpp @@ -30,7 +30,8 @@ bool IsAtomic(Opcode op) { case Opcode::AtomicUMaxU32: case Opcode::AtomicAndU32: case Opcode::AtomicOrU32: - case Opcode::AtomicXorU32: return true; + case Opcode::AtomicXorU32: + case Opcode::AtomicFMinF32: return true; default: return false; } } diff --git a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp index 5ff9c2c2..0d463cca 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp @@ -569,6 +569,8 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo return LowerBufferAtomicDword(decoded, block, Opcode::AtomicOrU32, error); case Decoder::Opcode::BufferAtomicXor: return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error); + case Decoder::Opcode::BufferAtomicFMin: + return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMinF32, error); case Decoder::Opcode::FlatLoadUbyte: case Decoder::Opcode::FlatLoadSbyte: case Decoder::Opcode::FlatLoadUshort: @@ -729,6 +731,7 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) { case Decoder::Opcode::BufferAtomicAnd: case Decoder::Opcode::BufferAtomicOr: case Decoder::Opcode::BufferAtomicXor: + case Decoder::Opcode::BufferAtomicFMin: case Decoder::Opcode::FlatLoadUbyte: case Decoder::Opcode::FlatLoadSbyte: case Decoder::Opcode::FlatLoadUshort: diff --git a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp index 192a4554..0c5c0408 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp @@ -352,6 +352,7 @@ constexpr LowerMap LOWER_OPS[] = { {Decoder::Opcode::BufferAtomicAnd, Opcode::AtomicAndU32}, {Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32}, {Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32}, + {Decoder::Opcode::BufferAtomicFMin, Opcode::AtomicFMinF32}, {Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte}, {Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte}, {Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort}, diff --git a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc index 0b5b0eb5..dc29594c 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc +++ b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc @@ -307,6 +307,7 @@ IR_OPCODE(AtomicUMaxU32, General) IR_OPCODE(AtomicAndU32, General) IR_OPCODE(AtomicOrU32, General) IR_OPCODE(AtomicXorU32, General) +IR_OPCODE(AtomicFMinF32, General) IR_OPCODE(FlatLoadUbyte, General) IR_OPCODE(FlatLoadSbyte, General) IR_OPCODE(FlatLoadUshort, General) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 01a5a13f..2c85ee90 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9050,6 +9050,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, const std::set& case Opcode::BufferAtomicAnd: case Opcode::BufferAtomicOr: case Opcode::BufferAtomicXor: + case Opcode::BufferAtomicFMin: case Opcode::FlatLoadUbyte: case Opcode::FlatLoadSbyte: case Opcode::FlatLoadUshort: @@ -13929,6 +13930,93 @@ TestCase BufferAtomicGlc0DoesNotReturnOldValue() { {O::VMovB32, O::BufferAtomicAdd, O::BufferStoreDword, O::SEndpgm}}; } +TestCase BufferAtomicFMinExactRawGlcModes() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 0, 0x40000000u); // 2.0 + code.push_back(0xe0fc0000u); + code.push_back(0x80010000u); // exact failing buffer_atomic_fmin, GLC=0 + AppendStoreVgpr(&code, 0, 1); + AppendVMovLiteral(&code, 0, 0x3f800000u); // 1.0 + code.push_back(0xe0fc4000u); + code.push_back(0x80010000u); // same instruction with GLC=1 + AppendStoreVgpr(&code, 0, 2); + AppendEnd(&code); + + TestCase test; + test.name = "BufferAtomicFMinExactRawGlcModes"; + test.code = code; + test.initial = {0x40800000u, 0, 0}; // 4.0 + test.expected = {0x3f800000u, 0x40000000u, 0x40000000u}; + test.opcodes = {O::VMovB32, O::BufferAtomicFMin, O::BufferStoreDword, O::SEndpgm}; + const auto descriptor = + MakeStructuredStorageBufferData(0, static_cast(test.initial.size() * sizeof(u32))); + std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4); + test.user_data[50] = 1u << 20u; + test.has_user_data = true; + return test; +} + +TestCase BufferAtomicFMinSpecialValues() { + using O = ShaderOpcode; + + const u32 values[] = { + 0x40000000u, // 2.0 + 0x7f800000u, // +infinity + 0xff800000u, // -infinity + 0x3f800000u, // 1.0 + 0x7fc00000u, // quiet NaN + 0x00000000u, // +0.0 + 0x80000000u, // -0.0 + 0x00000000u, // +0.0 + 0x80000001u, // smallest negative denorm + }; + std::vector code; + for (u32 i = 0; i < static_cast(std::size(values)); i++) { + AppendVMovU32(&code, 20, i * 4u); + AppendVMovLiteral(&code, i, values[i]); + AppendBufferStoreOpcode(&code, 0x3f, i, 20, true); + } + for (u32 i = 0; i < static_cast(std::size(values)); i++) { + AppendStoreVgpr(&code, i, i + static_cast(std::size(values))); + } + AppendEnd(&code); + + return {"BufferAtomicFMinSpecialValues", + code, + {0x40800000u, 0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u, + 0x80000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0x40000000u, 0xbf800000u, 0xff800000u, 0x7fc00000u, 0x3f800000u, + 0x80000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x40800000u, + 0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u, 0x80000000u, + 0x00000000u, 0x00000001u, 0x00000000u}, + {O::VMovB32, O::BufferAtomicFMin, O::BufferStoreDword, O::SEndpgm}}; +} + +TestCase BufferAtomicFMinContendedWorkgroup() { + using O = ShaderOpcode; + + std::vector code; + code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x + AppendVMovU32(&code, 20, 0); + AppendBufferStoreOpcode(&code, 0x3f, 1, 20); + AppendEnd(&code); + + TestCase test; + test.name = "BufferAtomicFMinContendedWorkgroup"; + test.code = code; + test.initial = {0x42c80000u}; // 100.0 + test.expected = {0x00000000u}; // min(100.0, 0.0 .. 63.0) + test.opcodes = {O::VCvtF32U32, O::VMovB32, O::BufferAtomicFMin, O::SEndpgm}; + test.compute_info.threads_num[0] = 64; + test.compute_info.threads_num[1] = 1; + test.compute_info.threads_num[2] = 1; + test.compute_info.thread_ids_num = 1; + test.has_compute_info = true; + return test; +} + std::vector MakeRgbaImage(u32 width, u32 height, u32 value = 0) { return std::vector(static_cast(width) * height * 4u, value); } @@ -15279,6 +15367,9 @@ std::vector MakeCases() { AddCase(DsSwizzleInvalidSourceLaneZero); AddCase(BufferAtomicVariants); AddCase(BufferAtomicGlc0DoesNotReturnOldValue); + AddCase(BufferAtomicFMinExactRawGlcModes); + AddCase(BufferAtomicFMinSpecialValues); + AddCase(BufferAtomicFMinContendedWorkgroup); AddCase(ImageLoadVariants); AddCase(ImageLoadR32UintUsesIntegerSampledImage); AddCase(ImageLoad1DUsesScalarCoordinate); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 036288f9..87a252e7 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4788,6 +4788,8 @@ void TestNewShaderRecompilerAtomicLowering() { EncodeMubuf1(5, 0, 1), // buffer_atomic_or EncodeMubuf0(0x3b, 40, true, true), EncodeMubuf1(6, 0, 1), // buffer_atomic_xor + 0xe0fc0000u, + 0x80010000u, // exact buffer_atomic_fmin v0, s[4:7], 0 (GLC=0) EncodeDs0(0x00), EncodeDs1(0, 2, 1), // ds_add_u32 EncodeDs0(0x01), @@ -4842,6 +4844,8 @@ void TestNewShaderRecompilerAtomicLowering() { "new decoder did not decode buffer atomic signed max"); Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_xor"), "new decoder did not decode buffer atomic xor"); + Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_fmin"), + "new decoder did not decode buffer atomic float min"); Check(Common::ContainsStr(result.decoded_dump, "ds_add_u32"), "new decoder did not decode DS atomic add"); Check(Common::ContainsStr(result.decoded_dump, "ds_sub_u32"), @@ -4874,6 +4878,8 @@ void TestNewShaderRecompilerAtomicLowering() { "buffer atomic or did not lower to IR"); Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 v6"), "buffer atomic xor did not lower to IR"); + Check(Common::ContainsStr(result.ir_dump, "AtomicFMinF32 null, v0"), + "buffer atomic float min did not lower to IR without a GLC return"); Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 null, v2"), "DS no-return atomic add did not lower to IR"); Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 null, v10"), @@ -4917,6 +4923,8 @@ void TestNewShaderRecompilerAtomicLowering() { Check(SpirvContainsOpcode(result.spirv, 240), "SPIR-V binary does not contain OpAtomicAnd"); Check(SpirvContainsOpcode(result.spirv, 241), "SPIR-V binary does not contain OpAtomicOr"); Check(SpirvContainsOpcode(result.spirv, 242), "SPIR-V binary does not contain OpAtomicXor"); + Check(SpirvContainsOpcode(result.spirv, 230), + "SPIR-V binary does not contain OpAtomicCompareExchange for float min"); Check(SpirvContainsOpcode(result.spirv, 225), "buffer atomic SPIR-V binary does not contain OpMemoryBarrier"); CheckSpirvBinaryValidates(result.spirv);