shader: rely on robustBufferAccess2 for storage buffer bounds

This commit is contained in:
nmzik committed 2026-10-31 03:04:38 +01:00
1 parent ec746622c2
commit ee034be4b2
10 files changed
+276 -19

No files matched your search

@@ -15,6 +15,8 @@ struct Profile {
bool native_float_min_max = false; // NMin/NMax give the guest's NaN and signed-zero results.
// OpReadClockKHR Device tick in picoseconds; 0 without shaderDeviceClock.
uint32_t device_clock_period_ps = 0;
// Storage buffer elements past the descriptor range, to the byte, read zero and drop writes.
bool robust_buffer_access = false;
};
inline constexpr Profile DefaultProfile {};
@@ -449,6 +449,10 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& st
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index);
// EmitMemoryElementInBounds, or true for storage buffers the device bounds-checks; only
// loads and plain stores may use it.
uint32_t EmitStorageElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index);
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index);
@@ -380,7 +380,7 @@ uint32_t LoadWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR:
const MemoryResourceAccess& resource) {
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index),
ctx.state, EmitStorageElementInBounds(ctx.state, resource, index),
[&]() { return LoadWordInBounds(ctx, resource, index); });
}
@@ -399,7 +399,7 @@ uint32_t LoadSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state, EmitStorageElementInBounds(ctx.state, resource, index), [&]() {
return LoadSubwordInBounds(ctx, resource, address, index, bits, sign_extend);
});
}
@@ -561,11 +561,14 @@ void StoreSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR:
const auto index = Binary(
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
EmitIfCondition(
ctx.state, BufferIndexInBounds(ctx, inst, mem, resource,
EmitMemoryElementInBounds(ctx.state, resource, index)), [&]() {
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
});
// 32-bit views merge subwords in a compare-exchange loop, and out-of-range atomics return
// undefined values.
const auto in_bounds = resource.element_bits < 32u
? EmitStorageElementInBounds(ctx.state, resource, index)
: EmitMemoryElementInBounds(ctx.state, resource, index);
EmitIfCondition(ctx.state, BufferIndexInBounds(ctx, inst, mem, resource, in_bounds), [&]() {
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
});
}
void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem, uint32_t bits) {
@@ -632,7 +635,7 @@ void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
if (resource.object_pointer == 0u) return;
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
const auto in_bounds = BufferIndexInBounds(ctx, inst, mem, resource,
EmitMemoryElementInBounds(ctx.state, resource, index));
EmitStorageElementInBounds(ctx.state, resource, index));
EmitIfCondition(ctx.state, in_bounds, [&]() {
StoreWordInBounds(ctx, resource, index, data);
});
@@ -1604,10 +1607,18 @@ void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
}
const auto access = PrepareMemoryResourceAccess(state, mem);
// Scalar buffer addressing aligns the base and each offset independently.
const auto element = Binary(
auto element = Binary(
state, spv::OpIAdd, TypeU32(state), index,
Binary(state, spv::OpShiftRightLogical, TypeU32(state), access.byte_offset, ConstantU32(state, 2)));
const auto condition = EmitMemoryElementInBounds(state, access, element);
// The device computes byte offsets in 32 bits, so elements from 2^30 on would wrap back into
// the range; the host prefix adds at most 63.
constexpr uint32_t last_element = UINT32_MAX >> 2u;
if (state.profile.robust_buffer_access &&
(!offset.IsImmediate() || (offset.U32() >> 2u) + (mem.offset >> 2u) > last_element - 63u)) {
element = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(state, element,
ConstantU32(state, last_element));
}
const auto condition = EmitStorageElementInBounds(state, access, element);
ctx.Define(inst, EmitValueOrZeroIfCondition(state, condition, [&]() {
return EmitNative<spv::OpLoad, IR::Type::U32>(
state, EmitMemoryElementPointer(state, access, element));
@@ -341,6 +341,16 @@ uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAcce
return in_bounds;
}
uint32_t EmitStorageElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index) {
// The descriptor range ends where access.length does.
if (state.profile.robust_buffer_access && (access.kind == IR::ResourceKind::Buffer ||
access.kind == IR::ResourceKind::ScalarBuffer)) {
return ConstantBool(state, true);
}
return EmitMemoryElementInBounds(state, access, index);
}
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index) {
if (access.kind == IR::ResourceKind::Lds || access.kind == IR::ResourceKind::Scratch) {
@@ -174,7 +174,7 @@ KYTY_IO_FIELDS(ResourceSpecialization::Sampler, a, b, c)
KYTY_IO_FIELDS(ResourceSpecialization, a, b, c)
KYTY_IO_FIELDS(Bindings, a, b, c, d, e)
KYTY_IO_FIELDS(CompiledShaderInfo, a, b, c, d, e, f, g, h, i, j)
KYTY_IO_FIELDS(Spirv::Profile, a, b, c)
KYTY_IO_FIELDS(Spirv::Profile, a, b, c, d)
KYTY_IO_FIELDS(ShaderTessellationInputInfo, a, b, c, d, e, f, g, h, i)
KYTY_IO_FIELDS(RectListParameter, a, b, c)
#undef KYTY_IO_FIELDS