shader: keep GPU-derived scalar resource reads on the GPU

Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
Co-authored-by: TheCruZ <thecruzwtf@gmail.com>
This commit is contained in:
nmzik
2026-10-04 03:18:09 +04:00
parent f18679e0cd
commit b4d32394b6
7 changed files with 169 additions and 21 deletions
@@ -222,7 +222,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
Fail(program, "buffer operation has invalid memory metadata");
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind == IR::ResourceKind::IndirectBuffer) {
if (memory.kind == IR::ResourceKind::IndirectBuffer &&
inst.GetOpcode() != IR::ValueOpcode::ReadConstBuffer) {
requirements.subgroup_local_invocation_id = true;
}
if (memory.kind == IR::ResourceKind::Buffer) {
@@ -809,6 +809,35 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
});
}
uint32_t LoadIndirectScalarBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto word1 = ctx.Arg(handle, 1);
const auto stride = EmitBitFieldUExtract(state, word1, ConstantU32(state, 16),
ConstantU32(state, 14));
const auto size = Binary(
state, spv::OpIMul, TypeScalarU64(state),
Unary(state, spv::OpUConvert, TypeScalarU64(state),
EmitUMax32(state, stride, ConstantU32(state, 1))),
Unary(state, spv::OpUConvert, TypeScalarU64(state), ctx.Arg(handle, 2)));
const auto offset = Binary(
state, spv::OpIAdd, TypeScalarU64(state),
Unary(state, spv::OpUConvert, TypeScalarU64(state),
Binary(state, spv::OpBitwiseAnd, TypeU32(state), ctx.Arg(inst, 1),
ConstantU32(state, ~3u))),
ConstantDeviceAddress(state, ctx.Memory(inst).offset & ~3u));
const auto end = Binary(state, spv::OpIAdd, TypeScalarU64(state), offset,
ConstantDeviceAddress(state, sizeof(uint32_t)));
return EmitValueOrZeroIfCondition(
state, Binary(state, spv::OpULessThanEqual, TypeBool(state), end, size), [&]() {
const auto base = DeviceAddressFromWords(
state, Binary(state, spv::OpBitwiseAnd, TypeU32(state), ctx.Arg(handle, 0),
ConstantU32(state, ~3u)),
Binary(state, spv::OpBitwiseAnd, TypeU32(state), word1, ConstantU32(state, 0xffffu)));
return LoadBdaDword(ctx, Binary(state, spv::OpIAdd, TypeScalarU64(state), base, offset));
});
}
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
@@ -1249,6 +1278,10 @@ uint32_t EmitReadConst(ValueEmitContext& ctx, const IR::Inst& inst) {
void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto mem = ctx.Memory(inst);
if (mem.planning_only) return;
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
ctx.Define(inst, LoadIndirectScalarBuffer(ctx, inst));
return;
}
auto& state = ctx.state;
mem.kind = IR::ResourceKind::ScalarBuffer;
auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), ctx.Arg(inst, 1),
@@ -399,7 +399,8 @@ void ValidateProgram(const Program& program, bool require_ssa) {
ValueOpcodeName(inst.GetOpcode())));
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind != ResourceKind::ScalarBuffer) {
if (memory.kind != ResourceKind::ScalarBuffer &&
memory.kind != ResourceKind::IndirectBuffer) {
return Fail(fmt::format("{} has an invalid scalar-memory resource kind",
ValueOpcodeName(inst.GetOpcode())));
}
@@ -457,7 +458,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
}
if (memory.kind == ResourceKind::IndirectBuffer &&
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) {
return Fail("indirect buffer requires a raw DWORD x2/x3/x4 load");
return Fail("indirect buffer requires a scalar or raw DWORD x2/x3/x4 load");
}
if (buffer_components > 1u &&
(!vector_buffer || memory.data_bits != 32u ||
+2 -2
View File
@@ -74,8 +74,8 @@ struct MemoryInfo {
[[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const {
return !formatted && !typed && data_bits == 32u &&
(opcode == ValueOpcode::LoadBufferU32x2 || opcode == ValueOpcode::LoadBufferU32x3 ||
opcode == ValueOpcode::LoadBufferU32x4);
(opcode == ValueOpcode::ReadConstBuffer || opcode == ValueOpcode::LoadBufferU32x2 ||
opcode == ValueOpcode::LoadBufferU32x3 || opcode == ValueOpcode::LoadBufferU32x4);
}
bool operator==(const MemoryInfo& other) const = default;
@@ -736,12 +736,14 @@ private:
for (auto* read: m_scalar_reads) {
const auto flags = read->Flags<MemoryFlags>();
auto& memory = m_program.memory_info[flags.index];
memory.planning_only = true;
const auto* handle = read->Arg(0).Resolve().TryInstruction();
auto resolved = std::ranges::find_if(m_resolved_handles, [&](const auto& entry) {
return entry.handle == handle && entry.pc == flags.pc;
});
EXIT_IF(resolved == m_resolved_handles.end());
uint32_t bad_dword = 0;
if (!ValidateSource(resolved->source, bad_dword)) continue;
memory.planning_only = true;
uint32_t slot = 0;
for (; slot < m_program.srt_reads.size(); ++slot) {
const auto* other = m_program.srt_reads[slot].value.Resolve().TryInstruction();
@@ -2071,10 +2073,12 @@ private:
source = indirect->source;
} else if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc,
memory.resource * 4u, handle, source)) {
if (memory.kind != ResourceKind::Buffer || !memory.SupportsIndirectBufferLoad(op)) {
if (memory.kind != (op == ValueOpcode::ReadConstBuffer ? ResourceKind::ScalarBuffer
: ResourceKind::Buffer) ||
!memory.SupportsIndirectBufferLoad(op)) {
Fail(flags.pc,
"buffer descriptor is not a valid runtime value; GPU-selected access "
"requires a raw DWORD x2/x3/x4 load");
"requires a scalar or raw DWORD x2/x3/x4 load");
}
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
m_info.uses_dma = true;