shader: share indirect buffer array access after an empty root

Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
nmzik
2026-10-14 21:17:44 +02:00
parent b24ce3440d
commit 1039c54f5d
4 changed files with 54 additions and 22 deletions
@@ -400,7 +400,7 @@ uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryIn
Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem);
bool UsesSharedIndirectBufferAccess(const EmitterState& state, uint32_t resource);
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
void EmitMemoryOffsets(EmitterState& state);
@@ -309,20 +309,24 @@ auto EmitBufferAccess(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInf
state.program.info.buffers[mem.resource].indirect_root != mem.resource) {
return access(mem.resource);
}
const auto& buffer = state.program.info.buffers[mem.resource];
const auto root_resource = mem.resource;
const auto& buffer = state.program.info.buffers[root_resource];
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto selected = ctx.Arg(handle, 0);
const auto shared = UsesSharedIndirectBufferAccess(state, mem.resource);
const auto representative = SharedIndirectBufferResource(state, root_resource);
const auto shared = representative != UINT32_MAX;
const auto empty_root = shared && buffer.packed_stride == 0u;
const auto selected_access = [&]() {
if (shared) {
const auto root_slot = buffer.descriptor_index;
const auto child_slot = state.program.info.buffers[buffer.indirect_resources[1]].descriptor_index;
auto slot = EmitAddU32(state, selected, ConstantU32(state, child_slot - 1u));
if (child_slot != root_slot + 1u) {
if (!empty_root && child_slot != root_slot + 1u) {
slot = Select(state, TypeU32(state),
Binary(state, spv::OpIEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
ConstantU32(state, root_slot), slot);
}
mem.resource = representative;
return emit(mem, PrepareMemoryResourceAccess(state, mem, slot));
}
return EmitIndexSwitch(state, selected, buffer.indirect_resources.size(),
@@ -332,12 +336,19 @@ auto EmitBufferAccess(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInf
if constexpr (!has_result) {
if (!shared) return selected_access();
}
const auto valid = Binary(state, spv::OpULessThan, TypeBool(state), selected,
auto valid = Binary(state, spv::OpULessThan, TypeBool(state), selected,
ConstantU32(state, buffer.indirect_resources.size()));
if constexpr (has_result) {
return EmitValueOrDefaultIfCondition(state, valid, TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), selected_access);
ConstantU32CompositeZero(state, components), [&]() {
if (!empty_root) return selected_access();
return EmitValueOrDefaultIfCondition(state,
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
TypeU32Composite(state, components), access(root_resource), selected_access);
});
} else {
if (empty_root) valid = AndCondition(state, valid,
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)));
EmitIfCondition(state, valid, selected_access);
}
}
@@ -108,24 +108,28 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
return state.program.info.buffers[mem.resource].descriptor_format;
}
bool UsesSharedIndirectBufferAccess(const EmitterState& state, uint32_t resource) {
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource) {
const auto& buffers = state.program.info.buffers;
const auto& root = buffers[resource];
if (root.indirect_root != resource || root.descriptor_index == UINT32_MAX ||
root.packed_stride == 0u || root.indirect_resources.size() < 2u) {
return false;
root.indirect_resources.size() < 2u) {
return UINT32_MAX;
}
const auto first = root.packed_stride == 0u ? 1u : 0u;
const auto representative = root.indirect_resources[first];
const auto& shared = buffers[representative];
if (shared.packed_stride == 0u) return UINT32_MAX;
uint32_t next_child_slot = UINT32_MAX;
for (uint32_t ordinal = 0; ordinal < root.indirect_resources.size(); ++ordinal) {
for (uint32_t ordinal = first; ordinal < root.indirect_resources.size(); ++ordinal) {
const auto& candidate = buffers[root.indirect_resources[ordinal]];
if (candidate.packed_stride != root.packed_stride ||
candidate.descriptor_format != root.descriptor_format ||
candidate.descriptor_swizzle != root.descriptor_swizzle ||
if (candidate.packed_stride != shared.packed_stride ||
candidate.descriptor_format != shared.descriptor_format ||
candidate.descriptor_swizzle != shared.descriptor_swizzle ||
candidate.descriptor_index == UINT32_MAX ||
(ordinal > 1u && candidate.descriptor_index != next_child_slot)) return false;
(ordinal > 1u && candidate.descriptor_index != next_child_slot)) return UINT32_MAX;
if (ordinal != 0u) next_child_slot = candidate.descriptor_index + 1u;
}
return true;
return representative;
}
void EmitMemoryOffsets(EmitterState& state) {
@@ -133,7 +137,7 @@ void EmitMemoryOffsets(EmitterState& state) {
state.memory_byte_offsets.resize(count);
std::vector<bool> dynamic_offsets(count);
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
if (!UsesSharedIndirectBufferAccess(state, resource)) continue;
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
for (const auto candidate: state.program.info.buffers[resource].indirect_resources) {
dynamic_offsets[state.program.info.buffers[candidate].descriptor_index] = true;
}