diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 309e6748b..46d602444 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -118,7 +118,7 @@ struct EmitterState { uint32_t bda_pointer_function = 0; uint32_t indirect_format_function = 0; uint32_t indirect_format_geometry_function = 0; - std::map, uint32_t> indirect_buffer_load_functions; + std::map, uint32_t> indirect_buffer_load_functions; uint32_t bvh_intersect_function = 0; uint32_t gds_variable = 0; uint32_t gds_length = 0; @@ -540,7 +540,6 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits); void EmitProgram(EmitterState& state); void DefineGetBdaPointer(EmitterState& state); -void DefineIndirectBufferFormats(ValueEmitContext& ctx); void DefineIndirectBufferLoads(ValueEmitContext& ctx); void DefineBvhIntersect(EmitterState& state); uint32_t GetBdaPointer(EmitterState& state, uint32_t address); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index a781a0885..a15fae156 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -1026,53 +1026,50 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b return ConstructU32Composite(state, components, values); } -uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) { +uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) { auto& state = ctx.state; - return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() { - const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true); - const auto geometry = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, - state.indirect_format_geometry_function, buffer.format); - const auto field = [&](uint32_t index) { - const auto value = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index); - return value; - }; - const auto bytes = field(0); - const auto component = Binary( - state, spv::OpUMod, TypeU32(state), - Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1)); - const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3)); - const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address, - Unary(state, spv::OpNot, TypeU64(state), alignment)); - const auto address = Binary( - state, spv::OpIAdd, TypeU64(state), base, - Unary(state, spv::OpUConvert, TypeU64(state), - Binary(state, spv::OpIMul, TypeU32(state), component, field(2)))); - const auto memory = AndCondition( - state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)), - Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector, - ConstantU32(state, 4))); - const auto raw = EmitValueOrZeroIfCondition( - state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)), - [&]() { - // Format alignment and component spacing keep every selected component within one DWORD. - const auto word = LoadBdaDword( - ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address, - ConstantU64(state, ~uint64_t {3}))); - const auto byte = Binary( - state, spv::OpBitwiseAnd, TypeU32(state), - Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3)); - return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word, - Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, - ConstantU32(state, 3))); - }); - const auto result = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result, - state.indirect_format_function, buffer.format, buffer.selector, - component, raw); - return result; - }); + const auto geometry = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, + state.indirect_format_geometry_function, buffer.format); + const auto field = [&](uint32_t index) { + const auto value = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index); + return value; + }; + const auto bytes = field(0); + const auto component = Binary( + state, spv::OpUMod, TypeU32(state), + Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1)); + const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3)); + const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address, + Unary(state, spv::OpNot, TypeU64(state), alignment)); + const auto address = Binary( + state, spv::OpIAdd, TypeU64(state), base, + Unary(state, spv::OpUConvert, TypeU64(state), + Binary(state, spv::OpIMul, TypeU32(state), component, field(2)))); + const auto memory = AndCondition( + state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)), + Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector, + ConstantU32(state, 4))); + const auto raw = EmitValueOrZeroIfCondition( + state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)), + [&]() { + // Format alignment and component spacing keep every selected component within one DWORD. + const auto word = LoadBdaDword( + ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address, + ConstantU64(state, ~uint64_t {3}))); + const auto byte = Binary( + state, spv::OpBitwiseAnd, TypeU32(state), + Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3)); + return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word, + Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, + ConstantU32(state, 3))); + }); + const auto result = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result, + state.indirect_format_function, buffer.format, buffer.selector, + component, raw); + return result; } uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, @@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) { auto& state = ctx.state; const auto mem = ctx.Memory(inst); - if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) { - return LoadIndirectFormattedX(ctx, inst); - } return EmitValueOrDefaultIfCondition( state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components), ConstantU32CompositeZero(state, components), [&]() { if (mem.kind == IR::ResourceKind::IndirectBuffer) { const auto function = state.indirect_buffer_load_functions.at( - {mem.data_bits, components, uint32_t(mem.coherent)}); + {mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)}); const auto args = IndirectBufferArgs(ctx, inst); const auto result = state.builder.AllocateId(); state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components), @@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo }); } -} // namespace - -uint32_t GetBdaPointer(EmitterState& state, uint32_t address) { - const auto result = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result, - state.bda_pointer_function, address); - return result; -} - -void DefineIndirectBufferLoads(ValueEmitContext& ctx) { - auto& state = ctx.state; - for (const auto* block: state.program.blocks) { - for (const auto& inst: *block) { - const auto components = IR::BufferComponentCount(inst.GetOpcode()); - if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue; - const auto& memory = ctx.Memory(inst); - if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted || - !memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue; - auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace( - std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u); - if (!inserted) continue; - const auto function = entry->second = state.builder.AllocateId(); - const auto type = TypeU32(state); - const auto result_type = TypeU32Composite(state, components); - const auto signature = state.builder.Type( - spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type); - const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" + - std::to_string(components) + (memory.coherent ? "_coherent" : ""); - state.builder.AddName(function, name.c_str()); - state.builder.AddFunction(spv::OpFunction, result_type, function, - spv::FunctionControlMaskNone, signature); - std::array args; - for (auto& arg: args) { - arg = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpFunctionParameter, type, arg); - } - EmitLabel(state, state.builder.AllocateId()); - const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false), - memory.data_bits, components, memory.coherent); - state.builder.AddFunction(spv::OpReturnValue, result); - state.builder.AddFunction(spv::OpFunctionEnd); - } - } -} - void DefineIndirectBufferFormats(ValueEmitContext& ctx) { auto& state = ctx.state; - const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) { - return std::ranges::any_of(*block, [&](const IR::Inst& inst) { - if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false; - const auto& memory = ctx.Memory(inst); - return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted; - }); - }); - if (!needed) return; - const auto type = TypeU32(state); constexpr auto format_count = static_cast(Prospero::BufferFormat::k32_32_32_32Float) + 1u; @@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) { state.builder.AddFunction(spv::OpFunctionEnd); } +} // namespace + +uint32_t GetBdaPointer(EmitterState& state, uint32_t address) { + const auto result = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result, + state.bda_pointer_function, address); + return result; +} + +void DefineIndirectBufferLoads(ValueEmitContext& ctx) { + auto& state = ctx.state; + for (const auto* block: state.program.blocks) { + for (const auto& inst: *block) { + const auto components = IR::BufferComponentCount(inst.GetOpcode()); + if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue; + const auto& memory = ctx.Memory(inst); + if (memory.kind != IR::ResourceKind::IndirectBuffer || + !memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue; + auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace( + std::array {memory.data_bits, components, uint32_t(memory.coherent), + uint32_t(memory.formatted)}, 0u); + if (!inserted) continue; + if (memory.formatted && state.indirect_format_function == 0u) { + DefineIndirectBufferFormats(ctx); + } + const auto function = entry->second = state.builder.AllocateId(); + const auto type = TypeU32(state); + const auto result_type = TypeU32Composite(state, components); + const auto signature = state.builder.Type( + spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type); + const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" + + std::to_string(components) + (memory.coherent ? "_coherent" : "") + + (memory.formatted ? "_formatted" : ""); + state.builder.AddName(function, name.c_str()); + state.builder.AddFunction(spv::OpFunction, result_type, function, + spv::FunctionControlMaskNone, signature); + std::array args; + for (auto& arg: args) { + arg = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFunctionParameter, type, arg); + } + EmitLabel(state, state.builder.AllocateId()); + const auto buffer = PrepareIndirectBuffer(state, args, memory.formatted); + const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer) + : LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent); + state.builder.AddFunction(spv::OpReturnValue, result); + state.builder.AddFunction(spv::OpFunctionEnd); + } + } +} + void DefineGetBdaPointer(EmitterState& state) { if (!state.program.info.uses_dma) { return; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp index 965fdf3e6..d35e769e2 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp @@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) { } DefineGetBdaPointer(state); DefineIndirectBufferLoads(ctx); - DefineIndirectBufferFormats(ctx); DefineBvhIntersect(state); if (program.info.uses_swizzle) { ctx.scratch_u32_variable = state.builder.AllocateId(); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d554f02ab..ec4cc87cc 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -33965,10 +33965,12 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() { return test; } -TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { +TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u, bool formatted = false) { const bool scalar = width == 1u; const bool xyz = width == 3u; using O = ShaderOpcode; + const u32 descriptor_format = formatted + ? (u32(Prospero::BufferFormat::k32UInt) << 12u) | 0x204u : 0x5204u; constexpr uint64_t GuestBase = 0x0000000110000000ull; struct DescriptorCase { u32 stride, records, mode, soffset; @@ -33994,7 +33996,8 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { // OFFEN byte offset 5 aligns to DWORD; swizzled offsets use eight-index groups. constexpr u32 scalar_expected[] = {2, 2, 2, 2, 2, 0, 3, 0, 10, 10, 0}; TestCase test; - test.name = scalar ? "BufferLoadDwordGpuSelectedDescriptors" + test.name = formatted ? "BufferLoadFormatXLaneAddressing" + : scalar ? "BufferLoadDwordGpuSelectedDescriptors" : xyz ? "BufferLoadDwordx3GpuSelectedDescriptors" : "BufferLoadsGpuSelectedDescriptors"; test.initial.resize(2048); @@ -34013,7 +34016,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { const std::array descriptor{ static_cast(GuestBase + data_offset), (input.stride << 16u) | (input.swizzle ? 1u << 31u : 0u) | 1u, - input.records, (input.bound ? 0x5204u : 0x204u) | (input.mode << 28u)}; + input.records, (input.bound ? descriptor_format : 0x204u) | (input.mode << 28u)}; std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130 + i * 30); for (u32 word = 0; word < 32; ++word) { test.initial[data_offset / 4 + word] = i * 100 + word + 1; @@ -34030,7 +34033,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { test.code.push_back(EncodeSmem1(520, 20)); AppendSMovLiteral(&test.code, 22, cases[selected].soffset); AppendVMovU32(&test.code, 21, scalar ? 5u : 1u); - test.code.push_back(EncodeMubuf0(scalar ? 0x0c : xyz ? 0x0f : 0x0e, 0, !scalar, scalar)); + test.code.push_back(EncodeMubuf0(formatted ? 0x00 : scalar ? 0x0c : xyz ? 0x0f : 0x0e, 0, !scalar, scalar)); test.code.push_back(EncodeMubuf1(0, 2, 21, 22)); if (!scalar) { test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0d, xyz ? 12 : 16, true, false)); @@ -34046,7 +34049,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { constexpr u32 payload = 6144u, output = 8192u, canary = 0xa5a5a5a5u; const u32 row = std::size(cases); const std::array descriptor{u32(GuestBase + payload), (32u << 16u) | 1u, - wave, 0x5204u | (1u << 23u)}; + wave, descriptor_format | (1u << 23u)}; std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130u + row * 30u); test.initial[64u + row] = row; for (u32 lane = 0; lane < wave; ++lane) @@ -34065,7 +34068,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u); test.code.insert(test.code.end(), {EncodeVop2(0x1a, 24, InlineU32(5), 25), EncodeVop2(0x25, 24, 255, 24), output}); - const u32 opcode = scalar ? 0x0cu : xyz ? 0x0fu : 0x0eu; + const u32 opcode = formatted ? 0x00u : scalar ? 0x0cu : xyz ? 0x0fu : 0x0eu; for (u32 component = width; component < 4u; ++component) AppendVMovLiteral(&test.code, 4u + component, canary); AppendVMovU32(&test.code, 21, 0u); @@ -34079,10 +34082,10 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { AppendVMovU32(&test.code, 22, 0u); AppendSMovLiteral(&test.code, 126, 0x55555555u); AppendSMovLiteral(&test.code, 127, wave == 64u ? 0xaaaaaaaau : 0u); - test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 4u, true, true, true), EncodeMubuf1(4, 2, 21)}); + test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 4u, true, true, !formatted), EncodeMubuf1(4, 2, 21)}); AppendSMovLiteral(&test.code, 126, 0u); AppendSMovLiteral(&test.code, 127, 0u); - test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 0u, true, true, true), EncodeMubuf1(4, 2, 21)}); + test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 0u, true, true, !formatted), EncodeMubuf1(4, 2, 21)}); AppendSMovLiteral(&test.code, 126, UINT32_MAX); AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u); test.code.insert(test.code.end(), {EncodeMubuf0(0x1e, 16u, false, true), EncodeMubuf1(4, 12, 24)}); @@ -34100,7 +34103,9 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - if (scalar) { + if (formatted) { + test.opcodes.push_back(O::BUFFER_LOAD_FORMAT_X); + } else if (scalar) { test.decoded_counts = {{"BUFFER_LOAD_DWORD ", 2u * std::size(cases) + 4u}}; } else if (xyz) { test.opcodes.push_back(O::BUFFER_LOAD_DWORDX3); @@ -34108,7 +34113,8 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) { test.opcodes.insert(test.opcodes.end(), {O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2}); } test.opcodes.insert(test.opcodes.end(), {O::V_LSHLREV_B32, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORDX4}); - test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer", "Volatile"}; + test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer"}; + if (!formatted) test.required_spirv.push_back("Volatile"); return test; } @@ -34325,6 +34331,8 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) { using namespace ShaderRecompiler::IR; for (const u32 wave : {32u, 64u}) { for (const auto &test : {BufferLoadsGpuSelectedDescriptors(1u, wave), + BufferLoadsGpuSelectedDescriptors(1u, wave, true), + BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_FORMAT_X), BufferLoadsGpuSelectedDescriptors(3u, wave), BufferLoadsGpuSelectedDescriptors(4u, wave), BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_UBYTE), @@ -34337,9 +34345,10 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) { const auto components = BufferComponentCount(inst.GetOpcode()); if (components == 0u || inst.GetOpcode() == ValueOpcode::ReadConstBuffer) continue; const auto &memory = compiled.program.memory_info[inst.Flags().index]; - if (memory.kind != ResourceKind::IndirectBuffer || memory.formatted) continue; + if (memory.kind != ResourceKind::IndirectBuffer) continue; const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" + - std::to_string(components) + (memory.coherent ? "_coherent" : ""); + std::to_string(components) + (memory.coherent ? "_coherent" : "") + + (memory.formatted ? "_formatted" : ""); auto &profile = profiles[name]; profile.components = components; ++profile.uses; @@ -34367,16 +34376,16 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) { } offset += words.size(); } - Require(test.name, "raw load function scope", functions.size() == profiles.size() && inline_reads == 0u, - "a raw load profile was unused, missing or still emitted inline"); + Require(test.name, "indirect load function scope", functions.size() == profiles.size() && inline_reads == 0u, + "an indirect load profile was unused, missing or still emitted inline"); for (const auto &[name, profile] : profiles) Require(test.name, name.c_str(), profile.uses != 0u && profile.definitions == 1u && profile.calls == profile.uses * (wave == 64u && vulkan.SubgroupSize() == 32u ? 2u : 1u) && profile.reads == profile.components, - "raw load helpers duplicated definitions, calls or component reads"); + "indirect load helpers duplicated definitions, calls or component reads"); auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size(), true); vulkan.Dispatch(test, compiled, buffer); - CompareWords(test, "shared raw loads, lane addressing, EXEC and bounds", test.expected, + CompareWords(test, "shared indirect loads, lane addressing, EXEC and bounds", test.expected, vulkan.ReadBuffer(test.name, buffer, test.expected.size())); vulkan.DestroyBuffer(&buffer); } @@ -41296,8 +41305,7 @@ void CheckBoundedFormattedBufferCopy(VulkanHarness &vulkan) { offset += words.size(); } Require(test.name, "one shared formatted conversion", - conversion_definitions == 1u && - conversion_calls == (wave > vulkan.SubgroupSize() ? 2u : 1u), + conversion_definitions == 1u && conversion_calls == 1u, "formatted loads must call one conversion function across virtual halves"); Require(test.name, "one shared pure format geometry", geometry_definitions == 1u && geometry_calls == conversion_calls &&