From 803a1312757343641c875896cb8ec409401e5b53 Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:51:47 +0200 Subject: [PATCH] fix(shader): store unaligned 32-bit BDA writes byte by byte EmitBdaWrite, used for buffer stores through GPU-selected descriptors, recorded an Unaligned fault and dropped every 32-bit store whose address is not 4-byte aligned. EmitBdaStore and the BDA reads already take the byte path for unaligned addresses, and raw buffer accesses use the exact bytes, as RDNA does in unaligned mode. The unaligned case now stores the four bytes one by one like the 8- and 16-bit stores of the same function. Sword of the Sea hit this about once per frame from one compute store, so four bytes were lost and a fault was reported each time. The BDA store execution table now also runs through EmitBdaWrite; its unaligned dword cases failed before this change. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp | 9 ++++++--- core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp | 12 ++++++++++++ core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp | 1 + .../recompiler/SpirvBackend/src/SpirvBdaRead.cpp | 7 +++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp index 5fc40da4e..2804e7e81 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace { @@ -345,15 +346,16 @@ void RunBdaStoreExecutionTests(const Context& context) { constexpr std::uint32_t writable = Abi::Read | Abi::Write; const Abi::Header header{Abi::Version, 2, sizeof(Abi::Range), 0}; std::array ranges{}; + bool runtime = false; const auto run = [&](const char* name, std::uint64_t address, std::uint32_t bits, bool coherent, const std::array& expected, std::initializer_list writtenAddresses, Abi::FaultReason reason = {}, std::uint64_t faultAddress = 0, std::uint32_t faultBytes = 0) { - const std::string label = std::string("BDA store ") + name + (coherent ? " coherent: " : ": "); + const std::string label = std::string(runtime ? "BDA runtime write " : "BDA store ") + name + (coherent ? " coherent: " : ": "); std::memset(first.Bytes().data(), 0xcd, first.Bytes().size()); std::memset(second.Bytes().data(), 0xcd, second.Bytes().size()); std::memcpy(table.Bytes().data(), &header, sizeof(header)); std::memcpy(table.Bytes().data() + sizeof(header), ranges.data(), sizeof(ranges)); std::memset(fault.Bytes().data(), 0, fault.Bytes().size()); std::memset(output.Bytes().data(), 0, output.Bytes().size()); - Pipeline pipeline(context, MakeBdaStoreTestShader(address, 0xa1b2c3d4u, bits, coherent), {&table, &fault, &output}); + Pipeline pipeline(context, runtime ? MakeBdaWriteTestShader(address, 0xa1b2c3d4u, bits, coherent) : MakeBdaStoreTestShader(address, 0xa1b2c3d4u, bits, coherent), {&table, &fault, &output}); pipeline.Run(1); std::array actual{}; std::memcpy(actual.data(), first.Bytes().data(), 2); @@ -387,7 +389,8 @@ void RunBdaStoreExecutionTests(const Context& context) { std::memcpy(&completed, output.Bytes().data(), sizeof(completed)); Require(completed == 1, label + "store did not finish its per-byte accesses"); }; - for (const bool coherent : {false, true}) { + for (const auto& [coherent, writes] : std::initializer_list>{{false, false}, {false, true}, {true, false}, {true, true}}) { + runtime = writes; ranges = {{{guest, guest + 2, first.DeviceAddress(), writable, 0}, {guest + 2, guest + 5, second.DeviceAddress(), writable, 0}}}; run("byte", guest + 1, 8, coherent, {0xcd, 0xd4, 0xcd, 0xcd, 0xcd}, {guest + 1}); run("split short", guest + 1, 16, coherent, {0xcd, 0xd4, 0xc3, 0xcd, 0xcd}, {guest + 1, guest + 2}); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp index 529457a60..60a2e8785 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp @@ -121,3 +121,15 @@ std::vector MakeBdaStoreTestShader(std::uint64_t address, std::ui #endif return shader; } + +std::vector MakeBdaWriteTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent) { + using namespace ShaderRecompiler; + auto shader = MakeShader(address, coherent, true, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) { + EmitBdaWrite(ctx, instruction, base, ConstantU32(ctx.state, value), bits); + return std::vector{ConstantU32(ctx.state, 1u)}; + }, 0u, true); +#if ANYPS5_ENABLE_SPIRV_TOOLS + shader = ValidateAndOptimizeSpirv(shader, VK_API_VERSION_1_1, shader[1], false, false); +#endif + return shader; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp index abfefe977..62a3adcb1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp @@ -10,6 +10,7 @@ std::vector MakeBdaDwordReadTestShader(std::uint64_t address, std std::vector MakeBdaSpanReadTestShader(std::uint64_t address, std::uint32_t offset, std::uint32_t extracted, bool coherent, bool stops); std::vector MakeBdaDwordWriteTestShader(std::uint64_t address, std::uint32_t dwords, const std::uint32_t* values); std::vector MakeBdaStoreTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent); +std::vector MakeBdaWriteTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent); void RunBdaStoreExecutionTests(const AgcDriver::Graphics::Context& context); void RunBdaExecutionTests(const AgcDriver::Graphics::Context& context); void RunBdaContractTests(); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index f07e159ee..5f9f55301 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -358,7 +358,7 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); const auto instruction = BdaInstructionPc(state, inst); if (bits != 8u && bits != 16u && bits != 32u) ctx.Fail(inst, "unsupported BDA write width"); - if (bits != 32u) { + const auto storeBytes = [&] { EmitBdaOverflowCheck(state, address, bits / 8u, instruction); for (std::uint32_t byte = 0u; byte < bits / 8u; ++byte) { const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), address, BdaConstant(state, byte)); @@ -373,10 +373,13 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, guest); }); } + }; + if (bits != 32u) { + storeBytes(); return; } const auto unaligned = BdaAddressUnaligned(state, address); - EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, 4u), instruction, BdaAbi::FaultReason::Unaligned); }); + EmitIfCondition(state, unaligned, storeBytes); EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, BdaAccessMask(state, inst)); });