From e68b9b8d37fde3b28b0c34729796a9ec7157ced1 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 27 Sep 2026 21:28:16 +0400 Subject: [PATCH] shader: fix scalar memory loads --- .../backend/spirv/spirvEmitterMemory.cpp | 9 +++- tests/ShaderRecompilerComputeTests.cpp | 47 +++++++++++++++++++ tests/shaderCfgTests.cpp | 13 +++-- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index 736d5514b..7b9314a97 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -146,7 +146,12 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem ctx.Fail(inst, "has no address base pair"); return ConstantDeviceAddress(state, 0); } - const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), ctx.Arg(*handle, 1)); + auto base_low = ctx.Arg(*handle, 0); + if (mem.kind == IR::ResourceKind::ScalarAddress) { + base_low = Binary(state, spv::OpBitwiseAnd, TypeU32(state), base_low, + ConstantU32(state, ~3u)); + } + const auto base = DeviceAddressFromWords(state, base_low, ctx.Arg(*handle, 1)); address = Binary(state, spv::OpIAdd, TypeScalarU64(state), base, Unary(state, spv::OpUConvert, TypeScalarU64(state), low)); } @@ -1198,6 +1203,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) { value = LoadWideBuffer(ctx, inst, buffer_components); else if (shared_components > 1u) value = LoadWideShared(ctx, inst, shared_components); + else if (mem.kind == IR::ResourceKind::ScalarAddress) + value = LoadBdaDword(ctx, GuestAddress(ctx, inst, mem)); else if (address_info.access == IR::AddressAccess::Read && mem.kind != IR::ResourceKind::Scratch) value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1), diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index a3f901792..edede023c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -25603,6 +25603,52 @@ TestCase ScalarLoadAlignsComponentsAndMasksAddress() { {O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase ScalarLoadAlignsDynamicBase() { + using O = ShaderOpcode; + constexpr uint64_t GuestBase = 0x10000; + std::vector code; + AppendSMovLiteral(&code, 8, GuestBase); + AppendSMovLiteral(&code, 9, 0); + code.push_back(EncodeSop2(0x00, 8, 8, 4)); // group ID supplies each base low-bit pattern + AppendSMovLiteral(&code, 10, 7); + code.push_back(EncodeSmem0(0x01, 12, 4)); + code.push_back(EncodeSmem1(7, 10)); // all three address components align independently + code.push_back(EncodeVop1(0x01, 0, 12)); + code.push_back(EncodeVop1(0x01, 1, 13)); + code.push_back(EncodeVop1(0x01, 2, 4)); + code.push_back(EncodeVop2(0x1a, 3, InlineU32(3), 2)); + AppendBufferStoreDword(&code, 0, 3); + code.push_back(EncodeVop2(0x25, 3, InlineU32(4), 3)); + AppendBufferStoreDword(&code, 1, 3); + AppendEnd(&code); + + TestCase test; + test.name = "ScalarLoadAlignsDynamicBase"; + test.code = std::move(code); + test.initial.resize(20); + test.initial[18] = 0x12345678; + test.initial[19] = 0x9abcdef0; + test.expected = test.initial; + for (u32 group = 0; group < 4; ++group) { + test.expected[group * 2] = test.initial[18]; + test.expected[group * 2 + 1] = test.initial[19]; + } + test.bda_mappings = {{GuestBase, 64}}; + test.required_spirv = {"get_bda_pointer"}; + test.forbidden_spirv = {"flattened_srt"}; + test.opcodes = {O::S_MOV_B32, O::S_ADD_U32, O::S_LOAD_DWORDX2, O::V_MOV_B32, + O::V_LSHLREV_B32, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.compute_info.wave_size = 32; + test.compute_info.threads_num[0] = 1; + test.compute_info.threads_num[1] = 1; + test.compute_info.threads_num[2] = 1; + test.compute_info.group_id[0] = true; + test.compute_info.workgroup_register = 4; + test.has_compute_info = true; + test.dispatch_x = 4; + return test; +} + TestCase FlatVirtualAddressRebasesGuestAllocation() { using O = ShaderOpcode; @@ -29551,6 +29597,7 @@ std::vector MakeCases() { AddCase(ScalarMemoryLoadVariants); AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset); AddCase(ScalarLoadAlignsComponentsAndMasksAddress); + AddCase(ScalarLoadAlignsDynamicBase); AddCase(BufferLoadStore); AddCase(BufferLoadDwordOffenIdxenUsesVaddrPlusOneOffset); AddCase(BufferStoreDwordOffenIdxenUsesVaddrPlusOneOffset); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 74220d56a..01aeee725 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4842,11 +4842,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() { }; const uint32_t raw_shader[] = { - EncodeSmem0(0x00, 12, 4), - 2u, // s_load_dword s12, s[8:9], s0 offset:2 + EncodeSmem0(0x01, 12, 4), + 2u, // s_load_dwordx2 s[12:13], s[8:9], s0 offset:2 EncodeVop1(0x01, 0, 12), - EncodeExp0(0x00, 0x1), - EncodeExp1(0, 0, 0, 0), + EncodeVop1(0x01, 1, 13), + EncodeExp0(0x00, 0x3), + EncodeExp1(0, 1, 0, 0), EncodeSopp(0x01), }; std::array raw_user_data{}; @@ -4879,8 +4880,10 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() { "raw scalar load did not use only the DMA domain"); Check(count_live_memory_ops( raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32, - ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 1u, + ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 2u, "raw scalar load did not remain a live typed address operation"); + Check(SpirvInstructionOpcodeCount(raw.spirv, 57) == 2u, + "aligned scalar DWORDs must each use one BDA lookup"); Check(SpirvContainsOpcode(raw.spirv, 199), "raw scalar SOFFSET alignment was not emitted"); CheckSpirvBinaryValidates(raw.spirv);