diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index 0de8780d2..dbd956348 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -302,9 +302,9 @@ uint32_t EmitBufferByteAddress(EmitterState& state, const IR::Instruction& inst, return EmitBufferAddressFromParts(state, inst, index, offset, soffset); } -bool IsFlatMemoryKind(IR::ResourceKind kind) { - return kind == IR::ResourceKind::Flat || kind == IR::ResourceKind::Global || - kind == IR::ResourceKind::Scratch; +bool IsAddressMemoryKind(IR::ResourceKind kind) { + return kind == IR::ResourceKind::ScalarAddress || kind == IR::ResourceKind::Flat || + kind == IR::ResourceKind::Global || kind == IR::ResourceKind::Scratch; } uint32_t EmitRelativeAddress(EmitterState& state, const IR::Instruction& inst, uint32_t first_src, @@ -423,7 +423,7 @@ StorageBufferBindingForMemory(EmitterState& state, const IR::MemoryInfo& mem, ui uint32_t EmitStorageBufferIndex(EmitterState& state, const IR::MemoryInfo& mem, uint32_t index, uint32_t use_pc) { - if (IsFlatMemoryKind(mem.kind)) { + if (IsAddressMemoryKind(mem.kind)) { return index; } const auto binding = StorageBufferBindingForMemory(state, mem, use_pc); @@ -432,7 +432,7 @@ uint32_t EmitStorageBufferIndex(EmitterState& state, const IR::MemoryInfo& mem, uint32_t EmitStorageBufferObjectPointer(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc) { - if (IsFlatMemoryKind(mem.kind)) { + if (IsAddressMemoryKind(mem.kind)) { if (state.address_memory_variable == 0) { ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory, mem.resource, "address memory binding was not emitted"); @@ -467,7 +467,7 @@ uint32_t EmitStorageBufferElementInBounds(EmitterState& state, const IR::MemoryI uint32_t EmitStorageBufferElementPointer(EmitterState& state, const IR::MemoryInfo& mem, uint32_t index, uint32_t use_pc) { index = EmitStorageBufferIndex(state, mem, index, use_pc); - if (IsFlatMemoryKind(mem.kind)) { + if (IsAddressMemoryKind(mem.kind)) { if (state.address_memory_variable == 0) { ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory, mem.resource, "address memory binding was not emitted"); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp index 24c5fef0f..07b763dc1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp @@ -102,8 +102,12 @@ uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t ad uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, uint32_t low, uint32_t high) { - auto& state = ctx.state; - const auto immediate = static_cast(mem.offset); + auto& state = ctx.state; + auto immediate = static_cast(mem.offset); + if (mem.kind == IR::ResourceKind::ScalarAddress) { + immediate = static_cast(static_cast(immediate) & ~3u); + low = Binary(state, OpBitwiseAnd, state.uint_type, low, ConstantU32(state, ~3u)); + } const auto immediate_low = ConstantU32(state, static_cast(immediate)); const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u); const auto base = state.program.info.addresses[mem.resource].specialized_base; @@ -137,11 +141,7 @@ uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const I } uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) { - if (mem.kind == IR::ResourceKind::Buffer || mem.kind == IR::ResourceKind::ScalarBuffer) { - if (mem.kind == IR::ResourceKind::ScalarBuffer) { - return Binary(ctx.state, OpIAdd, ctx.state.uint_type, ctx.Arg(inst, 1), - ConstantU32(ctx.state, mem.offset)); - } + if (mem.kind == IR::ResourceKind::Buffer) { return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)); } diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 556f0de29..fc87dbf21 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -59,6 +59,7 @@ struct Operand { enum class ResourceKind { None, ScalarBuffer, + ScalarAddress, Buffer, Flat, Global, diff --git a/src/graphics/shader/recompiler/ir/ShaderIRLog.cpp b/src/graphics/shader/recompiler/ir/ShaderIRLog.cpp index 1be377192..573d6b172 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIRLog.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIRLog.cpp @@ -74,6 +74,7 @@ std::string OperandToString(const Operand& operand) { std::string ResourceKindToString(ResourceKind kind) { switch (kind) { case ResourceKind::ScalarBuffer: return "scalar_buffer"; + case ResourceKind::ScalarAddress: return "scalar_address"; case ResourceKind::Buffer: return "buffer"; case ResourceKind::Flat: return "flat"; case ResourceKind::Global: return "global"; diff --git a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp index e9aa4221a..5ac06ed70 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp +++ b/src/graphics/shader/recompiler/ir/ShaderIRMemory.cpp @@ -75,14 +75,16 @@ Opcode LoweredImageOpcode(Decoder::Opcode opcode) { } } -bool LowerScalarBufferLoadDword(const Decoder::Instruction& decoded, BasicBlock& block, Opcode op, +bool LowerScalarMemoryLoadDword(const Decoder::Instruction& decoded, BasicBlock& block, Opcode op, std::string* error) { for (uint32_t i = 0; i < decoded.data_dwords; i++) { Instruction inst; inst.pc = decoded.pc; inst.op = op; inst.src_count = 1; - inst.memory = OffsetMemoryInfo(decoded, ResourceKind::ScalarBuffer, i); + const auto kind = + op == Opcode::SLoadDword ? ResourceKind::ScalarAddress : ResourceKind::ScalarBuffer; + inst.memory = OffsetMemoryInfo(decoded, kind, i); // Raw s_load uses a two-SGPR pointer base; s_buffer_load uses a four-SGPR // descriptor resource index. inst.memory.resource = op == Opcode::SLoadDword ? RawScalarLoadBase(decoded.src0) @@ -511,13 +513,13 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo case Decoder::Opcode::SLoadDwordx4: case Decoder::Opcode::SLoadDwordx8: case Decoder::Opcode::SLoadDwordx16: - return LowerScalarBufferLoadDword(decoded, block, Opcode::SLoadDword, error); + return LowerScalarMemoryLoadDword(decoded, block, Opcode::SLoadDword, error); case Decoder::Opcode::SBufferLoadDword: case Decoder::Opcode::SBufferLoadDwordx2: case Decoder::Opcode::SBufferLoadDwordx4: case Decoder::Opcode::SBufferLoadDwordx8: case Decoder::Opcode::SBufferLoadDwordx16: - return LowerScalarBufferLoadDword(decoded, block, Opcode::SBufferLoadDword, error); + return LowerScalarMemoryLoadDword(decoded, block, Opcode::SBufferLoadDword, error); case Decoder::Opcode::BufferLoadUbyte: case Decoder::Opcode::BufferLoadSbyte: case Decoder::Opcode::BufferLoadUshort: diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index c72d8cd5f..caee76313 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -559,7 +559,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, auto base = (static_cast(value.dwords[0]) | static_cast(value.dwords[1]) << 32u) & AddressMask; - if (address.kind == ResourceKind::ScalarBuffer) { + if (address.kind == ResourceKind::ScalarAddress) { base &= ~uint64_t {3}; } const auto before = static_cast(-static_cast(address.min_offset)); diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index 250376d05..8e235c4de 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -724,7 +724,7 @@ private: uint32_t AddAddress(uint32_t source, bool unbased, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) { auto immediate = static_cast(memory.offset); - if (memory.kind == ResourceKind::ScalarBuffer) { + if (memory.kind == ResourceKind::ScalarAddress) { immediate = static_cast(static_cast(immediate) & ~3u); } const auto min_offset = unbased ? 0 : std::min(immediate, 0); diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 3f8060baa..05c9d4329 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -50,13 +50,17 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) { } bool IsRawRead(const ValueProgram& values, const Inst& inst) { - if (inst.GetOpcode() != ValueOpcode::LoadAddressU32 && - inst.GetOpcode() != ValueOpcode::ReadConstBuffer) { + const auto op = inst.GetOpcode(); + if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) { return false; } const auto index = inst.Flags().index; - return index < values.memory_info.size() && - values.memory_info[index].kind == ResourceKind::ScalarBuffer; + if (index >= values.memory_info.size()) { + return false; + } + const auto kind = values.memory_info[index].kind; + return (op == ValueOpcode::LoadAddressU32 && kind == ResourceKind::ScalarAddress) || + (op == ValueOpcode::ReadConstBuffer && kind == ResourceKind::ScalarBuffer); } bool IsDescriptorHandle(ValueOpcode opcode) { @@ -262,6 +266,22 @@ public: m_values.dynamic_reads.clear(); for (auto* block: m_values.blocks) { for (auto& inst: *block) { + const auto op = inst.GetOpcode(); + if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) { + const auto flags = inst.Flags(); + if (flags.index < m_values.memory_info.size()) { + const auto kind = m_values.memory_info[flags.index].kind; + const bool crosswired = (op == ValueOpcode::LoadAddressU32 && + kind == ResourceKind::ScalarBuffer) || + (op == ValueOpcode::ReadConstBuffer && + kind == ResourceKind::ScalarAddress); + if (crosswired) { + return Fail(flags.pc, error, + fmt::format("{} has incompatible scalar memory metadata", + ValueOpcodeName(op))); + } + } + } if (IsDescriptorHandle(inst.GetOpcode())) { for (size_t index = 0; index < inst.NumArgs(); index++) { if (!Collect(inst.Arg(index), 0, error)) { diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 0fed5c5b5..43ddff146 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -171,14 +171,14 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0, const auto pointer_address = fixture->Address(fixture->UserData(9), fixture->UserData(10), 0x10b0); MemoryInfo pointer_word; - pointer_word.kind = ResourceKind::ScalarBuffer; + pointer_word.kind = ResourceKind::ScalarAddress; const auto pointer = fixture->Emit(ValueOpcode::LoadAddressU32, {pointer_address, Value(0u), Value(0u), Value(true)}, fixture->AddMemory(pointer_word, 0x10b0)); const auto address = fixture->Address(pointer, Value(0u), 0x10c0); MemoryInfo descriptor_word; - descriptor_word.kind = ResourceKind::ScalarBuffer; + descriptor_word.kind = ResourceKind::ScalarAddress; material_words[0] = fixture->Emit(ValueOpcode::LoadAddressU32, {address, Value(0u), Value(0u), Value(true)}, @@ -642,7 +642,7 @@ void TestSrtFlatteningAndRuntimeMemoization() { const auto base = fixture.Address(fixture.UserData(0), fixture.UserData(1), 4); MemoryInfo scalar; - scalar.kind = ResourceKind::ScalarBuffer; + scalar.kind = ResourceKind::ScalarAddress; scalar.offset = 4; const auto read0 = fixture.Emit(ValueOpcode::LoadAddressU32, {base, Value(0u), Value(0u), Value(true)}, @@ -710,7 +710,7 @@ void TestDynamicSrtReadRemainsExplicit() { const auto base = fixture.Address(fixture.UserData(0), fixture.UserData(1), 4); MemoryInfo scalar; - scalar.kind = ResourceKind::ScalarBuffer; + scalar.kind = ResourceKind::ScalarAddress; const auto read = fixture.Emit(ValueOpcode::LoadAddressU32, {base, fixture.UserData(2), Value(0u), Value(true)}, diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index ee8bca54d..a9f30c5ee 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -94,7 +94,7 @@ Value RawRead(Fixture &fixture, Value address, Value offset, uint32_t memory, void TestImmediateFlatteningAndGvn() { Fixture fixture; - const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer, 0x20); + const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 0x20); const auto first = RawRead( fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory); const auto second = RawRead( @@ -119,9 +119,53 @@ void TestImmediateFlatteningAndGvn() { "flattened SRT did not evaluate its canonical read once"); } +void TestRawScalarComponentAlignment() { + Fixture fixture; + const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 2); + const auto read = RawRead( + fixture, Address(fixture, Value(0x1003u), Value(0u)), Value(2u), memory); + fixture.Emit(ValueOpcode::GetBufferResource, + {read, Value(0u), Value(16u), Value(0u)}); + fixture.Plan(); + + TestMemory memory_image{{{0x1000u, 0x12345678u}}}; + SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; + std::vector flat; + std::string error; + Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str()); + Check(flat == std::vector{0x12345678u} && + memory_image.reads == 1, + "raw scalar base, immediate, and offset were not aligned independently"); +} + +void TestScalarMemoryDomainMismatchFails() { + Fixture raw; + const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer); + RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), + raw_memory); + std::string error; + Check(!BuildSrtPlan(raw.program, &error) && + error.find("incompatible scalar memory metadata") != + std::string::npos, + "raw scalar load accepted descriptor-buffer metadata"); + + Fixture buffer; + const auto buffer_memory = buffer.AddMemory(ResourceKind::ScalarAddress); + const auto resource = buffer.Emit(ValueOpcode::GetBufferResource, + {Value(0x1000u), Value(0u), Value(16u), + Value(0u)}); + buffer.EmitMemory(ValueOpcode::ReadConstBuffer, + {resource, Value(0u)}, buffer_memory); + error.clear(); + Check(!BuildSrtPlan(buffer.program, &error) && + error.find("incompatible scalar memory metadata") != + std::string::npos, + "descriptor scalar load accepted raw-address metadata"); +} + void TestDynamicReadRemainsTyped() { Fixture fixture; - const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer); + const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress); const auto offset = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast(2))}); const auto read = RawRead( @@ -137,7 +181,7 @@ void TestDynamicReadRemainsTyped() { void TestNestedSrtWalk() { Fixture fixture; - const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer); + const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress); const auto pointer = RawRead( fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory); const auto value = @@ -242,7 +286,7 @@ void TestInvariantAndDivergentPhi() { void TestControlDependentStandaloneLoadStaysTyped() { Fixture fixture(3); - const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer); + const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress); auto &base = fixture.BlockAt(2).AppendNewInst(ValueOpcode::Phi); base.SetFlags(Type::U32); base.AddPhiOperand(&fixture.BlockAt(0), Value(0x1000u)); @@ -390,6 +434,8 @@ void DbgExit(int) { std::abort(); } int main() { try { TestImmediateFlatteningAndGvn(); + TestRawScalarComponentAlignment(); + TestScalarMemoryDomainMismatchFails(); TestDynamicReadRemainsTyped(); TestNestedSrtWalk(); TestShaderBaseAndUserData(); diff --git a/tests/ShaderStageRuntimeTests.cpp b/tests/ShaderStageRuntimeTests.cpp index 05154f4a2..82b278ef0 100644 --- a/tests/ShaderStageRuntimeTests.cpp +++ b/tests/ShaderStageRuntimeTests.cpp @@ -42,7 +42,7 @@ SrtProgram(uint64_t address) { auto &value_block = AddValueBlock(program); MemoryInfo memory; - memory.kind = ResourceKind::ScalarBuffer; + memory.kind = ResourceKind::ScalarAddress; memory.planning_only = true; program.values->memory_info.push_back(memory); const auto low = Value(static_cast(address)); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index ef111eb8a..64d611787 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -3582,6 +3582,132 @@ void TestNewShaderRecompilerMemoryFamilyLowering() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerScalarMemoryBindingDomains() { + const auto count_live_memory_ops = + [](const ShaderRecompiler::IR::Program &program, + ShaderRecompiler::IR::ValueOpcode opcode, + ShaderRecompiler::IR::ResourceKind kind) { + size_t count = 0; + for (const auto *block : program.values->blocks) { + for (const auto &inst : *block) { + if (inst.GetOpcode() != opcode) { + continue; + } + const auto index = + inst.Flags().index; + Check(index < program.values->memory_info.size(), + "typed scalar memory operation has invalid metadata"); + const auto &memory = program.values->memory_info[index]; + Check(memory.kind == kind && memory.resource == 0u && + !memory.planning_only, + "typed scalar memory operation has the wrong live domain"); + count++; + } + } + return count; + }; + + const uint32_t raw_shader[] = { + EncodeSmem0(0x00, 12, 4), + 2u, // s_load_dword s12, s[8:9], s0 offset:2 + EncodeVop1(0x01, 0, 12), + EncodeExp0(0x00, 0x1), + EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + }; + std::array raw_user_data{}; + raw_user_data[0] = 2u; + raw_user_data[8] = 0x1003u; + + ShaderRecompiler::CompileOptions raw_options; + raw_options.stage = ShaderType::Pixel; + raw_options.user_data = raw_user_data.data(); + raw_options.user_data_count = static_cast(raw_user_data.size()); + + ShaderRecompiler::CompileResult raw; + std::string error; + Check(ShaderRecompiler::TryRecompile(raw_shader, raw_options, raw, &error), + error.c_str()); + const auto *address_binding = ShaderRecompiler::IR::FindBinding( + raw.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory); + Check(raw.program.info.addresses.size() == 1u && + raw.program.info.addresses[0].kind == + ShaderRecompiler::IR::ResourceKind::ScalarAddress && + raw.program.info.addresses[0].read && + !raw.program.info.addresses[0].written && + raw.program.info.buffers.empty() && address_binding != nullptr && + address_binding->resources == std::vector{0u} && + ShaderRecompiler::IR::FindBinding( + raw.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::Buffers) == nullptr && + ShaderRecompiler::IR::FindBinding( + raw.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) == + nullptr && + raw.program.bindings.buffer_offset_count == 0u, + "raw scalar load did not use only the address-memory domain"); + Check(count_live_memory_ops( + raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32, + ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 1u, + "raw scalar load did not remain a live typed address operation"); + Check(raw.resources.addresses.size() == 1u && + raw.resources.addresses[0].guest_base == 0x1000u && + raw.resources.addresses[0].binding_base == 0x1000u && + raw.program.info.addresses[0].specialized_base == 0u, + "raw scalar address specialization was incorrect"); + Check(SpirvContainsOpcode(raw.spirv, 199), + "raw scalar SOFFSET alignment was not emitted"); + CheckSpirvBinaryValidates(raw.spirv); + + const uint32_t buffer_shader[] = { + EncodeSmem0(0x08, 12, 4), + 0u, // s_buffer_load_dword s12, s[8:11], s0 + EncodeVop1(0x01, 0, 12), + EncodeExp0(0x00, 0x1), + EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + }; + std::array buffer_user_data{}; + buffer_user_data[8] = 0x2000u; + buffer_user_data[10] = 4u; + + ShaderRecompiler::CompileOptions buffer_options; + buffer_options.stage = ShaderType::Pixel; + buffer_options.user_data = buffer_user_data.data(); + buffer_options.user_data_count = + static_cast(buffer_user_data.size()); + + ShaderRecompiler::CompileResult buffer; + error.clear(); + Check(ShaderRecompiler::TryRecompile(buffer_shader, buffer_options, buffer, + &error), + error.c_str()); + const auto *buffer_binding = ShaderRecompiler::IR::FindBinding( + buffer.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::Buffers); + Check(buffer.program.info.buffers.size() == 1u && + buffer.program.info.buffers[0].scalar && + buffer.program.info.addresses.empty() && buffer_binding != nullptr && + buffer_binding->resources == std::vector{0u} && + ShaderRecompiler::IR::FindBinding( + buffer.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory) == + nullptr && + ShaderRecompiler::IR::FindBinding( + buffer.program.bindings, + ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) == + nullptr && + buffer.program.bindings.buffer_offset_count == 1u, + "descriptor scalar load did not use only the buffer domain"); + Check(count_live_memory_ops( + buffer.program, + ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer, + ShaderRecompiler::IR::ResourceKind::ScalarBuffer) == 1u, + "descriptor scalar load did not remain a live typed buffer operation"); + CheckSpirvBinaryValidates(buffer.spirv); +} + void TestNewShaderRecompilerImageQueryLowering() { const uint32_t shader[] = { EncodeMimg0(0x60, 0x3), @@ -9014,6 +9140,7 @@ int main() { TestNormalizedImageContracts(); TestNativeSubgroupPolicy(); TestNewShaderRecompilerSMovB32(); + TestNewShaderRecompilerScalarMemoryBindingDomains(); // Opcode semantics and optimized direct SPIR-V are exercised by // ShaderRecompilerComputeTests. The pre-SSA register-IR shape checks above // remain as historical decoder fixtures only.