shader: share formatted indirect buffer load emission

This commit is contained in:
nmzik
2026-10-29 05:21:55 +01:00
parent a0b99a8c21
commit 5e837a9bea
4 changed files with 122 additions and 125 deletions
@@ -118,7 +118,7 @@ struct EmitterState {
uint32_t bda_pointer_function = 0; uint32_t bda_pointer_function = 0;
uint32_t indirect_format_function = 0; uint32_t indirect_format_function = 0;
uint32_t indirect_format_geometry_function = 0; uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions; std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
uint32_t bvh_intersect_function = 0; uint32_t bvh_intersect_function = 0;
uint32_t gds_variable = 0; uint32_t gds_variable = 0;
uint32_t gds_length = 0; uint32_t gds_length = 0;
@@ -540,7 +540,6 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state); void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state); void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
void DefineIndirectBufferLoads(ValueEmitContext& ctx); void DefineIndirectBufferLoads(ValueEmitContext& ctx);
void DefineBvhIntersect(EmitterState& state); void DefineBvhIntersect(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address); uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
@@ -1026,10 +1026,8 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
return ConstructU32Composite(state, components, values); 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; 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(); const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format); state.indirect_format_geometry_function, buffer.format);
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
state.indirect_format_function, buffer.format, buffer.selector, state.indirect_format_function, buffer.format, buffer.selector,
component, raw); component, raw);
return result; return result;
});
} }
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, 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) { uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state; auto& state = ctx.state;
const auto mem = ctx.Memory(inst); const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
return LoadIndirectFormattedX(ctx, inst);
}
return EmitValueOrDefaultIfCondition( return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components), state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() { ConstantU32CompositeZero(state, components), [&]() {
if (mem.kind == IR::ResourceKind::IndirectBuffer) { if (mem.kind == IR::ResourceKind::IndirectBuffer) {
const auto function = state.indirect_buffer_load_functions.at( 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 args = IndirectBufferArgs(ctx, inst);
const auto result = state.builder.AllocateId(); const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components), 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<uint32_t, 8> 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) { void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
auto& state = ctx.state; 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); const auto type = TypeU32(state);
constexpr auto format_count = constexpr auto format_count =
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u; static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
state.builder.AddFunction(spv::OpFunctionEnd); 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<uint32_t, 8> 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) { void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) { if (!state.program.info.uses_dma) {
return; return;
@@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) {
} }
DefineGetBdaPointer(state); DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx); DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state); DefineBvhIntersect(state);
if (program.info.uses_swizzle) { if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId(); ctx.scratch_u32_variable = state.builder.AllocateId();
+26 -18
View File
@@ -33965,10 +33965,12 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() {
return test; 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 scalar = width == 1u;
const bool xyz = width == 3u; const bool xyz = width == 3u;
using O = ShaderOpcode; using O = ShaderOpcode;
const u32 descriptor_format = formatted
? (u32(Prospero::BufferFormat::k32UInt) << 12u) | 0x204u : 0x5204u;
constexpr uint64_t GuestBase = 0x0000000110000000ull; constexpr uint64_t GuestBase = 0x0000000110000000ull;
struct DescriptorCase { struct DescriptorCase {
u32 stride, records, mode, soffset; 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. // 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}; constexpr u32 scalar_expected[] = {2, 2, 2, 2, 2, 0, 3, 0, 10, 10, 0};
TestCase test; TestCase test;
test.name = scalar ? "BufferLoadDwordGpuSelectedDescriptors" test.name = formatted ? "BufferLoadFormatXLaneAddressing"
: scalar ? "BufferLoadDwordGpuSelectedDescriptors"
: xyz ? "BufferLoadDwordx3GpuSelectedDescriptors" : xyz ? "BufferLoadDwordx3GpuSelectedDescriptors"
: "BufferLoadsGpuSelectedDescriptors"; : "BufferLoadsGpuSelectedDescriptors";
test.initial.resize(2048); test.initial.resize(2048);
@@ -34013,7 +34016,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
const std::array<u32, 4> descriptor{ const std::array<u32, 4> descriptor{
static_cast<u32>(GuestBase + data_offset), static_cast<u32>(GuestBase + data_offset),
(input.stride << 16u) | (input.swizzle ? 1u << 31u : 0u) | 1u, (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); std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130 + i * 30);
for (u32 word = 0; word < 32; ++word) { for (u32 word = 0; word < 32; ++word) {
test.initial[data_offset / 4 + word] = i * 100 + word + 1; 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)); test.code.push_back(EncodeSmem1(520, 20));
AppendSMovLiteral(&test.code, 22, cases[selected].soffset); AppendSMovLiteral(&test.code, 22, cases[selected].soffset);
AppendVMovU32(&test.code, 21, scalar ? 5u : 1u); 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)); test.code.push_back(EncodeMubuf1(0, 2, 21, 22));
if (!scalar) { if (!scalar) {
test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0d, xyz ? 12 : 16, true, false)); 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; constexpr u32 payload = 6144u, output = 8192u, canary = 0xa5a5a5a5u;
const u32 row = std::size(cases); const u32 row = std::size(cases);
const std::array<u32, 4> descriptor{u32(GuestBase + payload), (32u << 16u) | 1u, const std::array<u32, 4> 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); std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130u + row * 30u);
test.initial[64u + row] = row; test.initial[64u + row] = row;
for (u32 lane = 0; lane < wave; ++lane) 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); AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u);
test.code.insert(test.code.end(), {EncodeVop2(0x1a, 24, InlineU32(5), 25), test.code.insert(test.code.end(), {EncodeVop2(0x1a, 24, InlineU32(5), 25),
EncodeVop2(0x25, 24, 255, 24), output}); 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) for (u32 component = width; component < 4u; ++component)
AppendVMovLiteral(&test.code, 4u + component, canary); AppendVMovLiteral(&test.code, 4u + component, canary);
AppendVMovU32(&test.code, 21, 0u); AppendVMovU32(&test.code, 21, 0u);
@@ -34079,10 +34082,10 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
AppendVMovU32(&test.code, 22, 0u); AppendVMovU32(&test.code, 22, 0u);
AppendSMovLiteral(&test.code, 126, 0x55555555u); AppendSMovLiteral(&test.code, 126, 0x55555555u);
AppendSMovLiteral(&test.code, 127, wave == 64u ? 0xaaaaaaaau : 0u); 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, 126, 0u);
AppendSMovLiteral(&test.code, 127, 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, 126, UINT32_MAX);
AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u); AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u);
test.code.insert(test.code.end(), {EncodeMubuf0(0x1e, 16u, false, true), EncodeMubuf1(4, 12, 24)}); 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, 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::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}; 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}}; test.decoded_counts = {{"BUFFER_LOAD_DWORD ", 2u * std::size(cases) + 4u}};
} else if (xyz) { } else if (xyz) {
test.opcodes.push_back(O::BUFFER_LOAD_DWORDX3); 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::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.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; return test;
} }
@@ -34325,6 +34331,8 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
using namespace ShaderRecompiler::IR; using namespace ShaderRecompiler::IR;
for (const u32 wave : {32u, 64u}) { for (const u32 wave : {32u, 64u}) {
for (const auto &test : {BufferLoadsGpuSelectedDescriptors(1u, wave), for (const auto &test : {BufferLoadsGpuSelectedDescriptors(1u, wave),
BufferLoadsGpuSelectedDescriptors(1u, wave, true),
BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_FORMAT_X),
BufferLoadsGpuSelectedDescriptors(3u, wave), BufferLoadsGpuSelectedDescriptors(3u, wave),
BufferLoadsGpuSelectedDescriptors(4u, wave), BufferLoadsGpuSelectedDescriptors(4u, wave),
BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_UBYTE), BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_UBYTE),
@@ -34337,9 +34345,10 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
const auto components = BufferComponentCount(inst.GetOpcode()); const auto components = BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == ValueOpcode::ReadConstBuffer) continue; if (components == 0u || inst.GetOpcode() == ValueOpcode::ReadConstBuffer) continue;
const auto &memory = compiled.program.memory_info[inst.Flags<MemoryFlags>().index]; const auto &memory = compiled.program.memory_info[inst.Flags<MemoryFlags>().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" + 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]; auto &profile = profiles[name];
profile.components = components; profile.components = components;
++profile.uses; ++profile.uses;
@@ -34367,16 +34376,16 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
} }
offset += words.size(); offset += words.size();
} }
Require(test.name, "raw load function scope", functions.size() == profiles.size() && inline_reads == 0u, Require(test.name, "indirect load function scope", functions.size() == profiles.size() && inline_reads == 0u,
"a raw load profile was unused, missing or still emitted inline"); "an indirect load profile was unused, missing or still emitted inline");
for (const auto &[name, profile] : profiles) for (const auto &[name, profile] : profiles)
Require(test.name, name.c_str(), profile.uses != 0u && profile.definitions == 1u && Require(test.name, name.c_str(), profile.uses != 0u && profile.definitions == 1u &&
profile.calls == profile.uses * (wave == 64u && vulkan.SubgroupSize() == 32u ? 2u : 1u) && profile.calls == profile.uses * (wave == 64u && vulkan.SubgroupSize() == 32u ? 2u : 1u) &&
profile.reads == profile.components, 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); auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size(), true);
vulkan.Dispatch(test, compiled, buffer); 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.ReadBuffer(test.name, buffer, test.expected.size()));
vulkan.DestroyBuffer(&buffer); vulkan.DestroyBuffer(&buffer);
} }
@@ -41296,8 +41305,7 @@ void CheckBoundedFormattedBufferCopy(VulkanHarness &vulkan) {
offset += words.size(); offset += words.size();
} }
Require(test.name, "one shared formatted conversion", Require(test.name, "one shared formatted conversion",
conversion_definitions == 1u && conversion_definitions == 1u && conversion_calls == 1u,
conversion_calls == (wave > vulkan.SubgroupSize() ? 2u : 1u),
"formatted loads must call one conversion function across virtual halves"); "formatted loads must call one conversion function across virtual halves");
Require(test.name, "one shared pure format geometry", Require(test.name, "one shared pure format geometry",
geometry_definitions == 1u && geometry_calls == conversion_calls && geometry_definitions == 1u && geometry_calls == conversion_calls &&