diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index b493c3e70..36071c826 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -85,6 +85,7 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) { case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicXorU32: case IR::Opcode::AtomicFMinF32: + case IR::Opcode::AtomicFMaxF32: 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 677a6e545..5c2bf2198 100644 --- a/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/decompiler/MemoryOps.cpp @@ -58,6 +58,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = { {0x3au, Opcode::BufferAtomicOr, 1, 32}, {0x3bu, Opcode::BufferAtomicXor, 1, 32}, {0x3fu, Opcode::BufferAtomicFMin, 1, 32}, + {0x40u, Opcode::BufferAtomicFMax, 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 fa5aa2e9e..da29067a3 100644 --- a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.cpp @@ -836,6 +836,7 @@ std::string OpcodeToString(Opcode opcode) { case Opcode::BufferAtomicOr: return "buffer_atomic_or"; case Opcode::BufferAtomicXor: return "buffer_atomic_xor"; case Opcode::BufferAtomicFMin: return "buffer_atomic_fmin"; + case Opcode::BufferAtomicFMax: return "buffer_atomic_fmax"; case Opcode::FlatLoadUbyte: return "flat_load_ubyte"; case Opcode::FlatLoadSbyte: return "flat_load_sbyte"; case Opcode::FlatLoadUshort: return "flat_load_ushort"; @@ -1127,6 +1128,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::BufferAtomicOr: case Opcode::BufferAtomicXor: case Opcode::BufferAtomicFMin: + case Opcode::BufferAtomicFMax: 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 74cc80e21..1da5b489a 100644 --- a/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/decompiler/ShaderDecoder.h @@ -435,6 +435,7 @@ enum class Opcode { BufferAtomicOr, BufferAtomicXor, BufferAtomicFMin, + BufferAtomicFMax, FlatLoadUbyte, FlatLoadSbyte, FlatLoadUshort, diff --git a/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp b/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp index e91ff179e..e94e1c9a2 100644 --- a/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/emitter/SpirvEmitter.cpp @@ -132,7 +132,8 @@ bool IsAtomic(IR::Opcode op) { case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicXorU32: - case IR::Opcode::AtomicFMinF32: return true; + case IR::Opcode::AtomicFMinF32: + case IR::Opcode::AtomicFMaxF32: return true; default: return false; } } @@ -142,6 +143,9 @@ uint32_t BufferAddressOperandCount(const IR::Instruction& inst) { } bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) { + if (static_cast(inst.op) >= static_cast(IR::Opcode::Count)) { + return Fail(error, "IR instruction opcode is out of range"); + } if (inst.src_count > std::size(inst.src)) { return Fail(error, "instruction source count exceeds fixed IR storage"); } @@ -201,6 +205,12 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error inst.src_count != 2)) { return Fail(error, "storage image instruction has an invalid resource class"); } + const auto float_atomic = + inst.op == IR::Opcode::AtomicFMinF32 || inst.op == IR::Opcode::AtomicFMaxF32; + if (float_atomic && + (kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) { + return Fail(error, "floating-point atomic instruction must target a buffer"); + } if (IsAtomic(inst.op) && !((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) || ((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) && @@ -489,6 +499,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc AllocateBlockLabels(state, program); AllocateDispatcherState(state, program); EmitFunction(state, program); + if (state.unsupported_ir_instruction) { + char pc[9] {}; + std::snprintf(pc, sizeof(pc), "%08x", state.unsupported_ir_pc); + return Fail(error, "SPIR-V emitter has no handler for IR opcode " + + std::string(IR::OpcodeName(state.unsupported_ir_opcode)) + + " at pc 0x" + pc); + } ReportReserveExceeded(program, "registers", state.registers.size()); ReportReserveExceeded(program, "inputs", state.inputs.size()); diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp index 9adb9d45f..58df9b67d 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterControlFlow.cpp @@ -778,6 +778,9 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) { case IR::Opcode::AtomicFMinF32: EmitGuardedByExec(state, [&]() { EmitAtomicFMinF32(state, inst); }); break; + case IR::Opcode::AtomicFMaxF32: + EmitGuardedByExec(state, [&]() { EmitAtomicFMaxF32(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; @@ -854,7 +857,13 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) { } EmitGuardedByExec(state, [&]() { EmitExport(state, inst); }); break; - default: break; + default: + if (!state.unsupported_ir_instruction) { + state.unsupported_ir_instruction = true; + state.unsupported_ir_opcode = inst.op; + state.unsupported_ir_pc = inst.pc; + } + break; } } diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h index d90560995..3e23e30cf 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterInternal.h @@ -414,6 +414,9 @@ struct EmitterState { bool needs_image_gather_extended = false; bool needs_function_lds = false; bool needs_pixel_valid_mask = false; + bool unsupported_ir_instruction = false; + IR::Opcode unsupported_ir_opcode = IR::Opcode::Count; + uint32_t unsupported_ir_pc = 0; std::vector registers; std::vector inputs; std::vector outputs; @@ -1012,6 +1015,7 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state); void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode); void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst); +void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst); void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst); diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp index 0bb25aea5..11493570c 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterMemory.cpp @@ -1184,7 +1184,8 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op namespace { -uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits) { +uint32_t EmitF32BitsOrderedCompare(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits, + uint32_t comparison_opcode) { struct ClassifiedBits { uint32_t nan = 0; uint32_t zero = 0; @@ -1219,14 +1220,13 @@ uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint 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); + const auto comparison = state.builder.AllocateId(); + state.builder.AddFunction({comparison_opcode, state.bool_type, comparison, lhs.key, rhs.key}); + return EmitLogicalAndBool(state, ordered_nonzero, comparison); } -} // namespace - -void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) { +void EmitAtomicFMinMaxF32(EmitterState& state, const IR::Instruction& inst, + uint32_t comparison_opcode) { const auto index = EmitMemoryDwordIndex(state, inst, inst.memory, 1, AddressSourceCount(inst, 1)); const auto in_bounds = EmitStorageBufferElementInBounds(state, inst.memory, index, inst.pc); @@ -1235,15 +1235,26 @@ void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) { 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); + const auto replace = + EmitF32BitsOrderedCompare(state, src_u32, old_u32, comparison_opcode); state.builder.AddFunction( - {OpSelect, state.uint_type, replace_old, less, src_u32, old_u32}); + {OpSelect, state.uint_type, replace_old, replace, src_u32, old_u32}); return replace_old; }); }); EmitStoreU32(state, inst.dst, old); } +} // namespace + +void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) { + EmitAtomicFMinMaxF32(state, inst, OpULessThan); +} + +void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst) { + EmitAtomicFMinMaxF32(state, inst, OpUGreaterThan); +} + 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 3e7b706d4..244206c9d 100644 --- a/src/graphics/shader/recompiler/ir/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/ResourceTracking.cpp @@ -31,7 +31,8 @@ bool IsAtomic(Opcode op) { case Opcode::AtomicAndU32: case Opcode::AtomicOrU32: case Opcode::AtomicXorU32: - case Opcode::AtomicFMinF32: return true; + case Opcode::AtomicFMinF32: + case Opcode::AtomicFMaxF32: return true; default: return false; } } diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.cpp b/src/graphics/shader/recompiler/ir/ShaderIR.cpp index cfcd0ee09..c888f7c91 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIR.cpp @@ -975,7 +975,12 @@ bool LowerControlInstruction(const Decoder::Instruction& decoded, BasicBlock& bl return LowerControlMarker(decoded, block, Opcode::TtraceData, true, error); case Decoder::Opcode::SInstPrefetch: return LowerControlMarker(decoded, block, Opcode::InstPrefetch, true, error); - default: return false; + default: + if (error != nullptr) { + *error = fmt::format("control opcode has no specialized IR lowering: {}", + Decoder::OpcodeToString(decoded.opcode)); + } + return false; } } @@ -1064,6 +1069,18 @@ bool IsTerminatorOpcode(Decoder::Opcode opcode) { bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error); +bool IsMemoryFamily(Decoder::Family family) { + switch (family) { + case Decoder::Family::SMEM: + case Decoder::Family::MUBUF: + case Decoder::Family::MTBUF: + case Decoder::Family::FLAT: + case Decoder::Family::DS: + case Decoder::Family::MIMG: return true; + default: return false; + } +} + bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block, std::string* error) { switch (inst.opcode) { @@ -1117,6 +1134,15 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block if (IsMemoryOpcode(inst.opcode)) { return LowerMemoryInstruction(inst, block, error); } + // Memory instructions require specialized lowering to populate MemoryInfo. Never let a + // newly decoded memory-family opcode fall through to generic register-only IR. + if (IsMemoryFamily(inst.family)) { + if (error != nullptr) { + *error = fmt::format("decoded memory-family opcode has no specialized IR lowering: {}", + Decoder::OpcodeToString(inst.opcode)); + } + return false; + } if (ScalarShiftLeftAddAmount(inst.opcode) != 0) { return LowerScalarShiftLeftAdd(inst, block, error); } diff --git a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp index 0d463cca2..e9aa4221a 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp @@ -571,6 +571,8 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error); case Decoder::Opcode::BufferAtomicFMin: return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMinF32, error); + case Decoder::Opcode::BufferAtomicFMax: + return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMaxF32, error); case Decoder::Opcode::FlatLoadUbyte: case Decoder::Opcode::FlatLoadSbyte: case Decoder::Opcode::FlatLoadUshort: @@ -675,7 +677,12 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo case Decoder::Opcode::ImageAtomicAnd: case Decoder::Opcode::ImageAtomicOr: case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error); - default: return false; + default: + if (error != nullptr) { + *error = fmt::format("memory opcode has no specialized IR lowering: {}", + Decoder::OpcodeToString(decoded.opcode)); + } + return false; } } @@ -732,6 +739,7 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) { case Decoder::Opcode::BufferAtomicOr: case Decoder::Opcode::BufferAtomicXor: case Decoder::Opcode::BufferAtomicFMin: + case Decoder::Opcode::BufferAtomicFMax: 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 0c5c04083..485d714f8 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.cpp @@ -353,6 +353,7 @@ constexpr LowerMap LOWER_OPS[] = { {Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32}, {Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32}, {Decoder::Opcode::BufferAtomicFMin, Opcode::AtomicFMinF32}, + {Decoder::Opcode::BufferAtomicFMax, Opcode::AtomicFMaxF32}, {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 dc29594c7..4996873a6 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc +++ b/src/graphics/shader/recompiler/ir/ShaderIROpcodes.inc @@ -308,6 +308,7 @@ IR_OPCODE(AtomicAndU32, General) IR_OPCODE(AtomicOrU32, General) IR_OPCODE(AtomicXorU32, General) IR_OPCODE(AtomicFMinF32, General) +IR_OPCODE(AtomicFMaxF32, General) IR_OPCODE(FlatLoadUbyte, General) IR_OPCODE(FlatLoadSbyte, General) IR_OPCODE(FlatLoadUshort, General) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 2c85ee908..079920623 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9051,6 +9051,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, const std::set& case Opcode::BufferAtomicOr: case Opcode::BufferAtomicXor: case Opcode::BufferAtomicFMin: + case Opcode::BufferAtomicFMax: case Opcode::FlatLoadUbyte: case Opcode::FlatLoadSbyte: case Opcode::FlatLoadUshort: @@ -14017,6 +14018,105 @@ TestCase BufferAtomicFMinContendedWorkgroup() { return test; } +TestCase BufferAtomicFMaxExactRawGlcModes() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 3, 0x40000000u); // 2.0 + code.push_back(0xe100000cu); + code.push_back(0x80010300u); // exact failing buffer_atomic_fmax v3, offset 12, GLC=0 + AppendStoreVgpr(&code, 3, 4); + AppendVMovLiteral(&code, 0, 0x40800000u); // 4.0 + code.push_back(0xe100400cu); + code.push_back(0x80010000u); // same address with GLC=1 + AppendStoreVgpr(&code, 0, 5); + AppendEnd(&code); + + TestCase test; + test.name = "BufferAtomicFMaxExactRawGlcModes"; + test.code = code; + test.initial = {0, 0, 0, 0x3f800000u, 0, 0}; // memory[3] = 1.0 + test.expected = {0, 0, 0, 0x40800000u, 0x40000000u, 0x40000000u}; + test.opcodes = {O::VMovB32, O::BufferAtomicFMax, 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 BufferAtomicFMaxSpecialValues() { + using O = ShaderOpcode; + + const u32 values[] = { + 0x40000000u, // 2.0, finite update + 0x40000000u, // 2.0, finite no-update + 0x7f800000u, // incoming +infinity + 0xff800000u, // incoming -infinity + 0x3f800000u, // finite against old +infinity + 0x3f800000u, // finite against old -infinity + 0x3f800000u, // finite against old quiet NaN + 0x7fcabcdeu, // incoming quiet NaN + 0x3f800000u, // finite against old signaling NaN + 0x7faabcdeu, // incoming signaling NaN + 0x00000000u, // +0.0 against old -0.0 + 0x80000000u, // -0.0 against old +0.0 + 0x80000000u, // -0.0 against a negative denorm + 0x80000001u, // negative denorm against -0.0 + 0x00000001u, // positive denorm against +0.0 + 0x00000000u, // +0.0 against a positive 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, 0x40, 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 {"BufferAtomicFMaxSpecialValues", + code, + {0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u, 0x7f800000u, + 0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u, + 0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u, 0x00000000u, + 0x00000001u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0x40000000u, 0x40800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, + 0x3f800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u, + 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000001u, + 0x00000001u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u, + 0x7f800000u, 0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, + 0x3f800000u, 0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u, + 0x00000000u, 0x00000001u}, + {O::VMovB32, O::BufferAtomicFMax, O::BufferStoreDword, O::SEndpgm}}; +} + +TestCase BufferAtomicFMaxContendedWorkgroup() { + 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, 0x40, 1, 20); + AppendEnd(&code); + + TestCase test; + test.name = "BufferAtomicFMaxContendedWorkgroup"; + test.code = code; + test.initial = {0xc2c80000u}; // -100.0 + test.expected = {0x427c0000u}; // max(-100.0, 0.0 .. 63.0) + test.opcodes = {O::VCvtF32U32, O::VMovB32, O::BufferAtomicFMax, 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); } @@ -15370,6 +15470,9 @@ std::vector MakeCases() { AddCase(BufferAtomicFMinExactRawGlcModes); AddCase(BufferAtomicFMinSpecialValues); AddCase(BufferAtomicFMinContendedWorkgroup); + AddCase(BufferAtomicFMaxExactRawGlcModes); + AddCase(BufferAtomicFMaxSpecialValues); + AddCase(BufferAtomicFMaxContendedWorkgroup); AddCase(ImageLoadVariants); AddCase(ImageLoadR32UintUsesIntegerSampledImage); AddCase(ImageLoad1DUsesScalarCoordinate); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 87a252e77..992d984e6 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -2959,6 +2959,15 @@ void TestNewShaderRecompilerIrLookupMissFailsExplicitly() { Check(Common::ContainsStr(error, "no IR lowering"), "missing decoder-to-IR mapping did not report an explicit error"); Check(ir.blocks.empty(), "missing decoder-to-IR mapping emitted a fallback IR block"); + + decoded.instructions.front().family = ShaderRecompiler::Decoder::Family::MUBUF; + decoded.instructions.front().opcode = ShaderRecompiler::Decoder::Opcode::VAddNcU32; + error.clear(); + Check(!ShaderRecompiler::IR::LowerProgram(decoded, cfg, ShaderType::Compute, 64u, ir, &error), + "memory-family opcode bypassed specialized memory lowering"); + Check(Common::ContainsStr(error, "memory-family opcode") && + Common::ContainsStr(error, "specialized IR lowering"), + "memory-family lowering bypass did not report an explicit error"); } void TestNewShaderRecompilerMemoryFamilyLowering() { @@ -4790,6 +4799,8 @@ void TestNewShaderRecompilerAtomicLowering() { EncodeMubuf1(6, 0, 1), // buffer_atomic_xor 0xe0fc0000u, 0x80010000u, // exact buffer_atomic_fmin v0, s[4:7], 0 (GLC=0) + 0xe100000cu, + 0x80010300u, // exact failing buffer_atomic_fmax v3, s[4:7], 12 (GLC=0) EncodeDs0(0x00), EncodeDs1(0, 2, 1), // ds_add_u32 EncodeDs0(0x01), @@ -4846,6 +4857,8 @@ void TestNewShaderRecompilerAtomicLowering() { "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, "buffer_atomic_fmax"), + "new decoder did not decode buffer atomic float max"); 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"), @@ -4880,6 +4893,8 @@ void TestNewShaderRecompilerAtomicLowering() { "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, "AtomicFMaxF32 null, v3"), + "buffer atomic float max 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"), @@ -6712,6 +6727,35 @@ void TestNewShaderRecompilerNativeBindingPlan() { rejected_spirv == rejected_before && rejected_error.find("atomic") != std::string::npos, "malformed atomic instruction reached a fatal descriptor path"); + auto malformed_float_atomic = result.program; + atomic_inst = FindBufferInstruction(&malformed_float_atomic); + Check(atomic_inst != nullptr, "native validation fixture lacks a float atomic candidate"); + atomic_inst->op = ShaderRecompiler::IR::Opcode::AtomicFMaxF32; + atomic_inst->memory.kind = ShaderRecompiler::IR::ResourceKind::Lds; + atomic_inst->memory.resource = 0; + atomic_inst->src_count = 2; + rejected_spirv = rejected_before; + rejected_error.clear(); + Check(!ShaderRecompiler::Spirv::EmitProgram(malformed_float_atomic, result.resources, nullptr, + nullptr, nullptr, rejected_spirv, + &rejected_error) && + rejected_spirv == rejected_before && + rejected_error.find("floating-point atomic") != std::string::npos, + "buffer-only floating-point atomic accepted a non-buffer resource"); + + auto invalid_ir_opcode = result.program; + Check(!invalid_ir_opcode.blocks.empty() && !invalid_ir_opcode.blocks.front().instructions.empty(), + "native validation fixture lacks an invalid-opcode candidate"); + invalid_ir_opcode.blocks.front().instructions.front().op = + ShaderRecompiler::IR::Opcode::Count; + rejected_spirv = rejected_before; + rejected_error.clear(); + Check(!ShaderRecompiler::Spirv::EmitProgram(invalid_ir_opcode, result.resources, nullptr, nullptr, + nullptr, rejected_spirv, &rejected_error) && + rejected_spirv == rejected_before && + rejected_error.find("opcode is out of range") != std::string::npos, + "out-of-range IR opcode was not rejected transactionally before emission"); + auto overwide_atomic = result.program; atomic_inst = FindBufferInstruction(&overwide_atomic); Check(atomic_inst != nullptr, "native validation fixture lacks an overwide atomic candidate");