diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 4290c4a85d02..e032bead1541 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -2277,6 +2277,71 @@ void verifyInt64AtomicCapabilities() { require(declares(compile(imageAtomic, spv::CapabilityInt64ImageEXT), spv::CapabilityInt64ImageEXT), "64-bit atomics: an image_atomic_swap on a 32_32 image does not declare Int64ImageEXT"); } +void verifySpecializedBufferAtomicBindings() { + using namespace ShaderRecompiler; + const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + const std::array capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; + alignas(256) static std::array buffer{}; + const auto bufferAddress = static_cast(reinterpret_cast(buffer.data())); + const std::array userData{ + 0u, 0u, 0u, 0u, + static_cast(bufferAddress), static_cast((bufferAddress >> 32u) & 0xffffu), 256u, 0x31016facu, + 0u, 0u, 0u, 0u, + 0u, 0u, 0u, 0u}; + const std::vector code{0xe0f05000u, 0x80012803u, 0xbf810000u}; + const auto compile = [&](std::span supported) { + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x58800u, code, 0, {}}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32; + request.target.bdaAbiVersion = BdaAbi::Version; + request.target.supportedCapabilities = supported; + request.target.supportedExtensions = extensions; + request.target.fragmentShaderBarycentricEnabled = false; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + return Recompile(request); + }; + const auto result = compile(capabilities); + std::size_t tables = 0; + std::size_t faults = 0; + std::size_t atomicElements = 0; + for (const auto& binding : result.bindings) { + if (binding.role == DescriptorRole::BdaPagetable) ++tables; + if (binding.role == DescriptorRole::FaultBuffer) ++faults; + atomicElements += static_cast(std::count(binding.bufferAtomic.begin(), binding.bufferAtomic.end(), true)); + } + require(tables == 0, "specialized buffer atomic: a buffer_atomic_inc through a V# the program specializes binds the address tables"); + require(faults == 1 && result.bdaAbiVersion == BdaAbi::Version, "specialized buffer atomic: the program has no fault buffer for its unaligned atomic"); + require(atomicElements == 1, "specialized buffer atomic: the V# element is not reported as atomic"); + const std::vector& words = result.spirv; + std::uint32_t faultFunction = 0; + std::size_t faultCalls = 0; + bool physical = false; + bool int64 = false; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + const auto opcode = words[cursor] & 0xffffu; + require(count != 0 && count <= words.size() - cursor, "specialized buffer atomic: truncated SPIR-V instruction"); + if (opcode == spv::OpCapability && words[cursor + 1] == spv::CapabilityPhysicalStorageBufferAddresses) physical = true; + if (opcode == spv::OpCapability && words[cursor + 1] == spv::CapabilityInt64) int64 = true; + if (opcode == spv::OpMemoryModel && words[cursor + 1] != spv::AddressingModelLogical) physical = true; + if (opcode == spv::OpName && std::string_view(reinterpret_cast(&words[cursor + 2])) == "record_bda_fault") faultFunction = words[cursor + 1]; + if (opcode == spv::OpFunctionCall && faultFunction != 0 && words[cursor + 3] == faultFunction) ++faultCalls; + cursor += count; + } + require(!physical, "specialized buffer atomic: the module addresses physical storage without an address access"); + require(int64, "specialized buffer atomic: the fault address is 64-bit and the module does not declare Int64"); + require(faultFunction != 0 && faultCalls == 1, "specialized buffer atomic: an unaligned atomic does not record exactly one fault"); + const std::array shaderOnly{spv::CapabilityShader}; + expectFailure([&] { static_cast(compile(shaderOnly)); }, "buffer atomic fault addresses require shaderInt64", "specialized buffer atomic: the program compiled without shaderInt64"); +} + void verifyCoherentBufferAccesses() { using namespace ShaderRecompiler; alignas(256) static std::array input{}; @@ -3303,6 +3368,7 @@ int main(int argc, char** argv) { verifyComputedTexelOffsets(); verifyShaderClockScopes(); verifyInt64AtomicCapabilities(); + verifySpecializedBufferAtomicBindings(); verifyCoherentBufferAccesses(); verifyUnnormalizedSamplers(); verifyUnusedUnnormalizedSampler(); diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 6d5e2d6f88e9..675f7d561f8f 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1187,7 +1187,7 @@ class Tracker { if (resource == std::numeric_limits::max()) fail("buffer resource limit exceeded"); AddMemoryPatch(memoryIndex, resource, 0u, false); memory.gpuDescriptor = false; - m_info.usesDma = m_info.usesDma || access == BufferAccess::Atomic; + m_info.usesFaultBuffer = m_info.usesFaultBuffer || access == BufferAccess::Atomic; return true; } memory.gpuDescriptor = true; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp index 2218a608ae9a..8c65294c3b90 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp @@ -601,6 +601,11 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa auto& state = context.state; if (bits != 32u && bits != 64u) context.Fail(instruction, "unsupported runtime buffer atomic width"); if (context.Memory(instruction).gpuDescriptor && (state.bdaAtomicPointerFunction == 0u || state.bdaNoteWriteFunction == 0u)) context.Fail(instruction, "BDA atomic functions are missing"); + if (!context.Memory(instruction).gpuDescriptor) { + if (state.program.Resources().stage == IrShaderStage::TessellationControl) context.Fail(instruction, "BDA fault termination requires a barrier-safe tessellation-control execution protocol"); + if (std::ranges::find(state.supportedCapabilities, spv::CapabilityInt64) == state.supportedCapabilities.end()) context.Fail(instruction, "buffer atomic fault addresses require shaderInt64"); + state.module.EmitCapability(spv::CapabilityInt64); + } const auto type = bits == 64u ? TypeU64(state) : TypeU32(state); const auto scalar = bits == 64u ? TypeScalarU64(state) : TypeU32(state); const auto zero = bits == 64u ? ConstantU64(state, 0u) : ConstantU32(state, 0u); @@ -613,7 +618,11 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa const auto byte = EmitAddU32(state, access.guest, storage.byteOffset); const auto unaligned = nonzero(state, EmitAndConstant(state, byte, bits / 8u - 1u)); const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), resource.base, Unary(state, spv::OpUConvert, TypeScalarU64(state), access.guest)); - faultIf(context, instruction, unaligned, guest, bits / 8u, BdaAbi::FaultReason::Unaligned); + EmitIfCondition(state, unaligned, [&] { + const auto high = Unary(state, spv::OpUConvert, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeScalarU64(state), guest, BdaConstant(state, 32u))); + RecordBdaFaultWords(state, Unary(state, spv::OpUConvert, TypeU32(state), guest), high, ConstantU32(state, bits / 8u), BdaInstructionPc(state, instruction), BdaAbi::FaultReason::Unaligned); + }); + StopBdaInvocationIf(state, unaligned); const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte, ConstantU32(state, bits == 64u ? 3u : 2u)); return EmitValueOrDefaultIfCondition(state, both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), EmitMemoryElementInBounds(state, storage, index)), type, zero, [&] { const auto pointer = EmitStorageBufferElementPointer(state, storage, index, bits == 64u ? TypeStorageBufferU64ElementPointer(state) : TypeStorageBufferElementPointer(state));