From 2d5023fda232a6a6c09fd1f0b0a0e713db6a440e Mon Sep 17 00:00:00 2001 From: Brandon Strong Date: Wed, 2 Sep 2026 18:37:55 -0700 Subject: [PATCH] shader: support IMAGE_ATOMIC_SWAP (#456) --- .../backend/spirv/spirvEmitterImage.cpp | 1 + .../recompiler/frontend/decode/ImageOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Memory.cpp | 4 ++- .../recompiler/ir/opcodes/ValueOpcodes.cpp | 1 + .../recompiler/ir/opcodes/ValueOpcodes.inc | 1 + tests/ShaderRecompilerComputeTests.cpp | 35 +++++++++++++++++++ 8 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index fe08970fe..5451b2e19 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -576,6 +576,7 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) { switch (opcode) { + case IR::ValueOpcode::ImageAtomicSwap32: return OpAtomicExchange; case IR::ValueOpcode::ImageAtomicIAdd32: return OpAtomicIAdd; case IR::ValueOpcode::ImageAtomicUMin32: return OpAtomicUMin; case IR::ValueOpcode::ImageAtomicUMax32: return OpAtomicUMax; diff --git a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp index 6a8b02f58..9380203cc 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp @@ -202,6 +202,7 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = { }; constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = { + {0x0fu, "image_atomic_swap", Opcode::IMAGE_ATOMIC_SWAP}, {0x11u, "image_atomic_add", Opcode::IMAGE_ATOMIC_ADD}, {0x15u, "image_atomic_umin", Opcode::IMAGE_ATOMIC_UMIN}, {0x17u, "image_atomic_umax", Opcode::IMAGE_ATOMIC_UMAX}, diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 8ffdc374d..2af892d4d 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -535,6 +535,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::IMAGE_SAMPLE: case Opcode::IMAGE_STORE: case Opcode::IMAGE_STORE_MIP: + case Opcode::IMAGE_ATOMIC_SWAP: case Opcode::IMAGE_ATOMIC_ADD: case Opcode::IMAGE_ATOMIC_UMIN: case Opcode::IMAGE_ATOMIC_UMAX: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index aef47bfa2..788aa7470 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -538,6 +538,7 @@ enum class Opcode { IMAGE_LOAD_MIP, IMAGE_STORE, IMAGE_STORE_MIP, + IMAGE_ATOMIC_SWAP, IMAGE_ATOMIC_ADD, IMAGE_ATOMIC_UMIN, IMAGE_ATOMIC_UMAX, diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index e4d2686d2..9eb792b76 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -223,7 +223,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in if (decoded.family == Decoder::Family::MIMG) { const bool store_or_atomic = decoded.opcode == Decoder::Opcode::IMAGE_STORE || decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP || - (decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_ADD && + (decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_SWAP && decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR); if (store_or_atomic) { return index == 0u ? decoded.dst : decoded.src0; @@ -981,6 +981,8 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) { case Decoder::Opcode::DS_WRXCHG_RTN_B32: return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicSwap32, true); + case Decoder::Opcode::IMAGE_ATOMIC_SWAP: + return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSwap32); case Decoder::Opcode::IMAGE_ATOMIC_ADD: return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicIAdd32); case Decoder::Opcode::IMAGE_ATOMIC_UMIN: diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp index 877f194c0..aaa0a5998 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp @@ -204,6 +204,7 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) { return {ImageAccess::Read, ImageResourceClass::Sampled, true}; case ValueOpcode::ImageWrite: return {ImageAccess::Write, ImageResourceClass::Storage, false}; + case ValueOpcode::ImageAtomicSwap32: case ValueOpcode::ImageAtomicIAdd32: case ValueOpcode::ImageAtomicUMin32: case ValueOpcode::ImageAtomicUMax32: diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index 4e540cabc..da2bb3115 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -268,6 +268,7 @@ VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1) VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1) VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress) +VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicUMin32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f0280d80f..954aa1ce5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -12206,6 +12206,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::IMAGE_LOAD_MIP: case Opcode::IMAGE_STORE: case Opcode::IMAGE_STORE_MIP: + case Opcode::IMAGE_ATOMIC_SWAP: case Opcode::IMAGE_ATOMIC_ADD: case Opcode::IMAGE_ATOMIC_UMIN: case Opcode::IMAGE_ATOMIC_AND: @@ -20897,6 +20898,39 @@ TestCase ImageStoreAndAtomicShareTypedBinding() { return test; } +TestCase ImageAtomicSwapReturnsPreviousTexel() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 0, 5); + code.push_back(EncodeMimg0(0x08, 0x1)); + code.push_back(EncodeMimg1(0, 20)); + AppendVMovU32(&code, 0, 3); + code.push_back(EncodeMimg0(0x0f, 0x1, 0, true)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + std::vector expected_image(16, 0); + expected_image[1 * 4 + 2] = 3; + + TestCase test; + test.name = "ImageAtomicSwapReturnsPreviousTexel"; + test.code = std::move(code); + test.expected = {5}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_SWAP, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt); + test.has_user_data = true; + test.storage_image_r32ui = std::vector(16, 0); + test.expected_storage_image_r32ui = std::move(expected_image); + test.required_spirv = {"OpAtomicExchange", "OpImageTexelPointer"}; + return test; +} + TestCase ImageStoreAndAtomicUseSeparateBindings() { using O = ShaderOpcode; @@ -21566,6 +21600,7 @@ std::vector MakeCases() { AddCase(ImageStorePackedUintHonorsSparseDmask); AddCase(ComputeTgSizeSgprUsesWaveMetadata); AddCase(ImageStoreAndAtomicShareTypedBinding); + AddCase(ImageAtomicSwapReturnsPreviousTexel); AddCase(ImageStoreAndAtomicUseSeparateBindings); AddCase(ImageAtomicVariants); AddCase(ImageAtomicGlc0DoesNotReturnOldValue);