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
+274 -17

No files matched your search

+1
View File
@@ -35,6 +35,7 @@ struct GraphicContext {
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool maintenance8_enabled = false;
bool robust_buffer_access_enabled = false;
bool conditional_rendering_enabled = false;
bool occlusion_queries_enabled = false;
bool occlusion_query_precise_enabled = false;
@@ -119,7 +119,8 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
const auto& [address, size, id] = source;
if (address == 0 || size == 0) {
return {context.GetBufferCache().GetBuffer(NULL_BUFFER_ID).Handle(), 0, 16};
// Null descriptors read zero and drop writes, with or without shader bounds checks.
return {nullptr, 0, vk::WholeSize};
}
const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment();
@@ -1017,7 +1018,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
case BindingKind::Buffers:
EXIT_IF(descriptors.buffers.size() != write.descriptorCount);
for (const auto& view: descriptors.buffers) {
EXIT_IF(view.buffer == nullptr);
EXIT_IF(view.range == 0);
}
write.pBufferInfo = descriptors.buffers.data();
break;
@@ -239,7 +239,8 @@ ShaderRecompiler::Spirv::Profile SpirvProfile(const GraphicContext& graphics) {
// signed-zero results; GLSL.std.450 alone does not order -0 below +0.
return {.maintenance8 = graphics.maintenance8_enabled,
.native_float_min_max = graphics.physical_device_properties.vendorID == 0x1002u,
.device_clock_period_ps = graphics.device_clock_period_ps};
.device_clock_period_ps = graphics.device_clock_period_ps,
.robust_buffer_access = graphics.robust_buffer_access_enabled};
}
std::filesystem::path ShaderStorePath() {
@@ -574,8 +574,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
vk::PhysicalDeviceVulkan11Properties properties11 {};
properties11.pNext = &subgroup_size_control;
vk::PhysicalDeviceRobustness2PropertiesEXT robustness2_properties {};
robustness2_properties.pNext = &properties11;
vk::PhysicalDeviceFloatControlsProperties float_controls {};
float_controls.pNext = &properties11;
float_controls.pNext = &robustness2_properties;
vk::PhysicalDeviceProperties2 properties2 {};
properties2.pNext = &float_controls;
@@ -605,6 +607,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
feedback_dynamic.attachmentFeedbackLoopDynamicState;
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
// A coarser alignment rounds the range up past the guest's last byte.
graphics.robust_buffer_access_enabled =
supported_robustness2.robustBufferAccess2 &&
robustness2_properties.robustStorageBufferAccessSizeAlignment == 1u;
// The device clock ticks at timestampPeriod nanoseconds.
if (shader_clock_extension && shader_clock.shaderDeviceClock) {
graphics.device_clock_period_ps = static_cast<uint32_t>(
@@ -703,9 +709,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
features13.subgroupSizeControl =
graphics.compute_subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s buffer bounds=%s\n",
features13.robustImageAccess == VK_TRUE ? "true" : "false",
robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false",
graphics.robust_buffer_access_enabled ? "robustBufferAccess2" : "shader checks");
vk::DeviceCreateInfo create_info {};
vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features {};
@@ -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,9 +561,12 @@ 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)), [&]() {
// 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);
});
}
@@ -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
+222 -2
View File
@@ -11741,6 +11741,35 @@ public:
std::printf("[gpu] %-32s ok\n", name);
}
// An empty binding is a null descriptor, so loads and stores without shader checks touch no memory.
void CheckNullStorageBufferDescriptor() {
constexpr const char *name = "NullStorageBufferDescriptor";
EnsureRuntimeContext();
RenderContext context(m_runtime_context);
auto &executor = context.GetRenderExecutor();
ShaderModule module;
auto &program = module.info;
program.stage = ShaderType::Compute;
program.info.buffers.push_back({.packed_stride = 1, .written = true});
program.info.buffers[0].descriptor_index = 0;
program.bindings.descriptor_counts[0] = 1;
program.bindings.descriptor_mask = 1;
ShaderRecompiler::IR::ResourceSnapshot snapshot;
snapshot.buffers.emplace_back().dword_count = 4;
ShaderStageRuntime runtime{&module, &snapshot};
PreparedBindings bindings;
executor.PrepareBindings(runtime, bindings);
std::array<PreparedBindings *, 1> stages{&bindings};
executor.FindBuffers(stages);
executor.RebindBuffers(bindings);
const auto &view = bindings.buffers[0];
Require(name, "null descriptor",
view.buffer == nullptr && view.offset == 0 && view.range == vk::WholeSize &&
bindings.shader_data[program.bindings.MemorySizeDword()] == 0,
"an empty storage binding was not a zero-length null descriptor");
std::printf("[host] %-32s ok\n", name);
}
void CheckZeroStrideBufferBinding() {
constexpr const char *name = "ZeroStrideBufferBinding";
constexpr uintptr_t base = 0x0000000204c00000ull;
@@ -18434,6 +18463,10 @@ public:
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
pipeline_info.stage = stage;
pipeline_info.layout = pipeline_layout;
// The device leaves out-of-range storage accesses to the shader; robust profiles drop the checks.
vk::PipelineRobustnessCreateInfoEXT robustness{};
robustness.storageBuffers = vk::PipelineRobustnessBufferBehaviorEXT::eRobustBufferAccess2;
if (test.profile->robust_buffer_access) pipeline_info.pNext = &robustness;
vk::Pipeline pipeline = nullptr;
RequireVk(test.name, "dispatch",
m_device.createComputePipelines(nullptr, 1, &pipeline_info,
@@ -22165,14 +22198,25 @@ private:
available_maintenance8.pNext = &available_min_lod;
vk::PhysicalDeviceShaderClockFeaturesKHR available_clock{};
available_clock.pNext = &available_maintenance8;
vk::PhysicalDevicePipelineRobustnessFeaturesEXT available_pipeline_robustness{};
available_pipeline_robustness.pNext = &available_clock;
vk::PhysicalDeviceRobustness2FeaturesEXT available_robustness2{};
available_robustness2.pNext = &available_clock;
available_robustness2.pNext = &available_pipeline_robustness;
vk::PhysicalDeviceFeatures2 available_features2{};
available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
available_features2.pNext = &available_robustness2;
m_physical_device.getFeatures2(&available_features2);
vk::PhysicalDeviceRobustness2PropertiesEXT robustness2_properties{};
vk::PhysicalDeviceProperties2 properties2{};
properties2.pNext = &robustness2_properties;
m_physical_device.getProperties2(&properties2);
Require("VulkanHarness", "dispatch", available_robustness2.nullDescriptor == true,
"null descriptors are not supported");
Require("VulkanHarness", "dispatch",
available_robustness2.robustBufferAccess2 == true &&
available_pipeline_robustness.pipelineRobustness == true &&
robustness2_properties.robustStorageBufferAccessSizeAlignment == 1u,
"byte-granular per-pipeline robustBufferAccess2 is not supported");
Require("VulkanHarness", "dispatch",
available_features.shaderStorageImageWriteWithoutFormat == true,
"shaderStorageImageWriteWithoutFormat is not supported");
@@ -22304,9 +22348,12 @@ private:
shader_clock.shaderDeviceClock = true;
shader_clock.pNext = &min_lod;
maintenance8.pNext = &shader_clock;
vk::PhysicalDevicePipelineRobustnessFeaturesEXT pipeline_robustness{};
pipeline_robustness.pipelineRobustness = true;
pipeline_robustness.pNext = &maintenance8;
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2{};
robustness2.nullDescriptor = true;
robustness2.pNext = &maintenance8;
robustness2.pNext = &pipeline_robustness;
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering{};
conditional_rendering.conditionalRendering = true;
conditional_rendering.pNext = &robustness2;
@@ -22333,6 +22380,7 @@ private:
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME,
VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
VK_KHR_SHADER_CLOCK_EXTENSION_NAME,
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME,
@@ -33952,6 +34000,162 @@ TestCase BufferSubwordStoresAtHostOffset(u32 bytes) {
return test;
}
constexpr ShaderProfile RobustBufferProfile{.maintenance8 = true, .robust_buffer_access = true};
// A 20-byte binding ends inside each access: dwords, bytes and stores past it read zero or drop,
// whether the shader checks the bounds or the pipeline does.
TestCase BufferTailAccesses(u32 wave_size, bool robust) {
using O = ShaderOpcode;
TestCase test;
constexpr const char *names[2][2] = {
{"CheckedBufferTailAccessesWave32", "CheckedBufferTailAccessesWave64"},
{"RobustBufferTailAccessesWave32", "RobustBufferTailAccessesWave64"}};
test.name = names[robust][wave_size == 64];
auto &code = test.code;
AppendVMovU32(&code, 20, 8);
code.insert(code.end(), {EncodeMubuf0(0x0eu), EncodeMubuf1(0, 0, 20),
EncodeSmem0(0x0au, 24), EncodeSmem1(8, 125)});
for (const auto [opcode, dst, offset] : {std::array{0x0au, 4u, 18u}, std::array{0x08u, 5u, 19u},
std::array{0x0au, 6u, 20u}}) {
AppendVMovU32(&code, 21, offset);
code.insert(code.end(), {EncodeMubuf0(opcode), EncodeMubuf1(dst, 0, 21)});
}
for (u32 i = 0; i < 4; ++i) AppendVMovLiteral(&code, 8 + i, 0x50000000u + i);
AppendVMovU32(&code, 22, 12);
code.insert(code.end(), {EncodeMubuf0(0x1eu), EncodeMubuf1(8, 0, 22)});
AppendVMovLiteral(&code, 12, 0x123456eeu);
for (const auto [opcode, offset] : {std::array{0x18u, 4u}, std::array{0x1au, 20u},
std::array{0x18u, 21u}}) {
AppendVMovU32(&code, 22, offset);
code.insert(code.end(), {EncodeMubuf0(opcode), EncodeMubuf1(12, 0, 22)});
}
for (u32 i = 0; i < 7; ++i) AppendStoreVgpr(&code, i, i);
for (u32 i = 0; i < 4; ++i) AppendStoreSgpr(&code, 24 + i, 7 + i);
AppendEnd(&code);
test.initial = {0x10101010u, 0x11111111u, 0x22222222u, 0x33333333u, 0x44332211u,
0xc5c5c5c5u, 0xc6c6c6c6u, 0xc7c7c7c7u};
test.initial.resize(8 + 16);
test.expected = test.initial;
test.expected[1] = 0x111111eeu;
test.expected[3] = 0x50000000u;
test.expected[4] = 0x50000001u;
const std::array<u32, 11> outputs{0x22222222u, 0x33333333u, 0x44332211u, 0, 0x4433u, 0x44u, 0,
0x22222222u, 0x33333333u, 0x44332211u, 0};
std::copy(outputs.begin(), outputs.end(), test.expected.begin() + 8);
test.user_data = MakeStructuredStorageBufferData(0, 20);
test.user_data[50] = 64;
test.user_data[51] = test.user_data[3];
test.has_user_data = true;
test.storage_buffer_ranges = {{0, 20}, {32, 64}};
// A full wave needs no lane compare, so OpULessThan comes only from bounds checks.
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.wave_size = static_cast<uint8_t>(wave_size);
test.has_compute_info = true;
// Robust pipelines bound these accesses themselves; the checked shader compares every one.
if (robust) {
test.profile = &RobustBufferProfile;
test.forbidden_spirv = {"OpULessThan"};
} else {
test.required_spirv = {"OpULessThan"};
}
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX4, O::S_BUFFER_LOAD_DWORDX4,
O::BUFFER_LOAD_USHORT, O::BUFFER_LOAD_UBYTE, O::BUFFER_STORE_DWORDX4,
O::BUFFER_STORE_BYTE, O::BUFFER_STORE_SHORT, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
return test;
}
// Robust pipelines leave out-of-range atomic results undefined, so atomics keep their checks.
TestCase RobustBufferAtomicOutOfRange() {
using O = ShaderOpcode;
TestCase test;
test.name = "RobustBufferAtomicOutOfRange";
auto &code = test.code;
AppendVMovU32(&code, 1, 5);
for (const auto [dst, offset] : {std::array{0u, 16u}, std::array{2u, 20u}}) {
AppendVMovU32(&code, 21, offset);
code.insert(code.end(), {EncodeVop1(0x01, dst, Vgpr(1)),
EncodeMubuf0(0x32u, 0, false, true, true), EncodeMubuf1(dst, 0, 21)});
}
AppendStoreVgpr(&code, 0, 0);
AppendStoreVgpr(&code, 2, 1);
AppendEnd(&code);
test.initial = {0x10u, 0x11u, 0x12u, 0x13u, 0x14u, 0xc5u};
test.initial.resize(8 + 16);
test.expected = test.initial;
test.expected[4] = 0x19u;
test.expected[8] = 0x14u;
test.expected[9] = 0;
test.user_data = MakeStructuredStorageBufferData(0, 20);
test.user_data[50] = 64;
test.user_data[51] = test.user_data[3];
test.has_user_data = true;
test.storage_buffer_ranges = {{0, 20}, {32, 64}};
// One wave32 thread needs no lane compare, so OpULessThan comes only from bounds checks.
test.compute_info.threads_num[0] = test.compute_info.threads_num[1] =
test.compute_info.threads_num[2] = 1;
test.compute_info.wave_size = 32;
test.has_compute_info = true;
test.profile = &RobustBufferProfile;
test.required_spirv = {"OpULessThan"};
test.opcodes = {O::V_MOV_B32, O::BUFFER_ATOMIC_ADD, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
// Guest offsets near 2^32 index past the end of every view: they read zero and drop stores
// instead of wrapping onto the bytes before the descriptor offset or onto the host prefix.
TestCase RobustBufferWrappedAddresses(u32 prefix) {
using O = ShaderOpcode;
TestCase test;
test.name = prefix == 0 ? "RobustBufferWrappedAddresses" : "RobustBufferWrappedAddressesOffset";
auto &code = test.code;
for (const auto [opcode, dst, offset] :
{std::array{0x0cu, 0u, 0xfffffffcu}, std::array{0x0au, 1u, 0xfffffffeu},
std::array{0x08u, 2u, 0xffffffffu}}) {
AppendVMovLiteral(&code, 20, offset);
code.insert(code.end(), {EncodeMubuf0(opcode), EncodeMubuf1(dst, 0, 20)});
}
// s32 holds a runtime offset, s30 one the recompiler folds.
AppendSMovLiteral(&code, 30, 0xfffffffcu);
code.insert(code.end(), {EncodeSmem0(0x08u, 24), EncodeSmem1(0, 32), EncodeSmem0(0x08u, 25),
EncodeSmem1(0, 30)});
AppendVMovLiteral(&code, 8, 0x5a5a5a5au);
for (const auto [opcode, offset] : {std::array{0x1cu, 0xfffffffcu},
std::array{0x1au, 0xfffffffeu},
std::array{0x18u, 0xffffffffu}}) {
AppendVMovLiteral(&code, 20, offset);
code.insert(code.end(), {EncodeMubuf0(opcode), EncodeMubuf1(8, 0, 20)});
}
for (u32 i = 0; i < 3; ++i) AppendStoreVgpr(&code, i, i);
for (u32 i = 0; i < 2; ++i) AppendStoreSgpr(&code, 24 + i, 3 + i);
AppendEnd(&code);
// Outputs fill dwords 0-4 and guest bytes start at dword 16, so a 32-bit wrap would land on
// dword 15 or 16.
test.initial.resize(24);
for (u32 i = 0; i < test.initial.size(); ++i) test.initial[i] = 0x01010101u * (i + 1);
test.expected = test.initial;
std::fill_n(test.expected.begin(), 5, 0u);
test.user_data = MakeStructuredStorageBufferData(0, 16);
test.user_data[32] = 0xfffffffcu;
test.user_data[50] = 32;
test.user_data[51] = test.user_data[3];
test.has_user_data = true;
test.storage_buffer_offsets = {prefix};
test.storage_buffer_ranges = {{64, 16 + prefix}, {0, 32}};
test.compute_info.threads_num[0] = test.compute_info.threads_num[1] =
test.compute_info.threads_num[2] = 1;
test.compute_info.wave_size = 32;
test.has_compute_info = true;
test.profile = &RobustBufferProfile;
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, O::BUFFER_LOAD_USHORT,
O::BUFFER_LOAD_UBYTE, O::S_BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD,
O::BUFFER_STORE_SHORT, O::BUFFER_STORE_BYTE, O::S_ENDPGM};
return test;
}
TestCase BufferLoadDwordx4SnapshotsOverlappingAddress() {
using O = ShaderOpcode;
@@ -46826,6 +47030,11 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferLoadDwordIdxenUsesDescriptorStride);
AddCase(BufferStoreDwordIdxenUsesDescriptorStride);
AddCase(BufferStoreDwordAppliesHostOffset);
for (const u32 wave : {32u, 64u}) {
for (const bool robust : {false, true}) cases.push_back(BufferTailAccesses(wave, robust));
}
for (const u32 prefix : {0u, 4u}) cases.push_back(RobustBufferWrappedAddresses(prefix));
AddCase(RobustBufferAtomicOutOfRange);
cases.push_back(BufferOffsetsUsePackedWords(false));
cases.push_back(BufferOffsetsUsePackedWords(true));
AddCase(BufferLoadVariants);
@@ -53785,6 +53994,16 @@ int main(int argc, char **argv) {
vulkan.CheckDescriptorHeapLargeSet();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--robust-buffer-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckNullStorageBufferDescriptor();
for (const u32 wave : {32u, 64u}) {
for (const bool robust : {false, true}) RunCase(&vulkan, BufferTailAccesses(wave, robust));
}
for (const u32 prefix : {0u, 4u}) RunCase(&vulkan, RobustBufferWrappedAddresses(prefix));
RunCase(&vulkan, RobustBufferAtomicOutOfRange());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--push-constant-bank-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckGraphicsPushConstantBank();
@@ -54432,6 +54651,7 @@ int main(int argc, char **argv) {
CheckShaderStore(vulkan);
vulkan.CheckOrderedDispatch();
vulkan.CheckZeroStrideBufferBinding();
vulkan.CheckNullStorageBufferDescriptor();
vulkan.CheckNullTextureDescriptors();
vulkan.CheckBdaPageTableUploads();
vulkan.CheckBdaSyncUploads();