Embedded fetch shader: Fix overlapping buffer loads (#133)

Fix overlapping buffer loads. Fixes many games
This commit is contained in:
nmzik
2026-07-30 05:08:38 +02:00
committed by GitHub
parent 85622befb8
commit d04938c88c
4 changed files with 442 additions and 199 deletions
@@ -1025,15 +1025,47 @@ void EmitDispatcherSwitch(EmitterState& state, const IR::Program& program) {
EmitDispatcherExit(state);
}
size_t BufferLoadGroupSize(const IR::BasicBlock& block, size_t first_index) {
const auto& first = block.instructions[first_index];
if (first.op != IR::Opcode::BufferLoadDword || first.memory.component_index != 0u ||
first.memory.component_count <= 1u) {
return 1u;
}
size_t count = 1u;
while (first_index + count < block.instructions.size() &&
count < first.memory.component_count) {
const auto& next = block.instructions[first_index + count];
if (next.op != IR::Opcode::BufferLoadDword || next.pc != first.pc ||
next.memory.component_index != count ||
next.memory.component_count != first.memory.component_count) {
break;
}
count++;
}
return count;
}
void EmitBlockInstructions(EmitterState& state, const IR::BasicBlock& block) {
for (size_t i = 0; i < block.instructions.size();) {
const auto count = BufferLoadGroupSize(block, i);
if (count > 1u) {
EmitBufferLoadDwordGroup(state, block.instructions.data() + i,
static_cast<uint32_t>(count));
} else {
EmitInstruction(state, block.instructions[i]);
}
i += count;
}
}
void EmitDispatcherBlocks(EmitterState& state, const IR::Program& program) {
for (const auto& block: program.blocks) {
if (block.id >= state.reachable_blocks.size() || !state.reachable_blocks[block.id]) {
continue;
}
state.builder.AddFunction({OpLabel, BlockLabel(state, block.id)});
for (const auto& inst: block.instructions) {
EmitInstruction(state, inst);
}
EmitBlockInstructions(state, block);
EmitDispatcherTerminator(state, block.terminator);
}
}
@@ -1083,9 +1115,7 @@ void EmitFunction(EmitterState& state, const IR::Program& program) {
continue;
}
state.builder.AddFunction({OpLabel, BlockLabel(state, block.id)});
for (const auto& inst: block.instructions) {
EmitInstruction(state, inst);
}
EmitBlockInstructions(state, block);
EmitTerminator(state, block.terminator);
}
@@ -3,11 +3,11 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/BufferFormat.h"
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
#include <algorithm>
#include <array>
@@ -990,11 +990,6 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
uint32_t UnpackTBufferFormat(EmitterState& state, const IR::Instruction& inst,
const Format::BufferFormatInfo& info);
bool EmitTypedTBufferLoad(EmitterState& state, const IR::Instruction& inst,
const Format::BufferFormatInfo& info);
bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst);
uint32_t FormattedBufferDwordStoreComponentCount(Prospero::BufferFormat format,
uint32_t opcode_components);
@@ -1020,6 +1015,9 @@ void EmitBufferLoadSshort(EmitterState& state, const IR::Instruction& inst);
void EmitBufferLoadDword(EmitterState& state, const IR::Instruction& inst);
void EmitBufferLoadDwordGroup(EmitterState& state, const IR::Instruction* instructions,
uint32_t count);
void EmitBufferStoreDword(EmitterState& state, const IR::Instruction& inst);
void EmitFlatLoadUbyte(EmitterState& state, const IR::Instruction& inst);
@@ -34,16 +34,15 @@ uint32_t EmitDppWriteActiveBool(EmitterState& state, const IR::Operand& dst) {
{OpShiftLeftLogical, state.uint_type, bank_bit, ConstantU32(state, 1), bank});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, row_bit, ConstantU32(state, 1), row});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, bank_hit,
ConstantU32(state, dst.dpp_bank_mask), bank_bit});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, bank_hit, ConstantU32(state, dst.dpp_bank_mask), bank_bit});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row_hit, ConstantU32(state, dst.dpp_row_mask), row_bit});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, bank_active, bank_hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, row_active, row_hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpLogicalAnd, state.bool_type, dpp_active, bank_active, row_active});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, dpp_active, bank_active, row_active});
uint32_t write_active = dpp_active;
if (!dst.dpp_bound_ctrl) {
const auto target = EmitDppTargetLane(state, dst.dpp_ctrl);
@@ -131,8 +130,7 @@ uint32_t EmitNotEqualZeroBool(EmitterState& state, uint32_t value) {
uint32_t EmitSelectU32Value(EmitterState& state, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, ret, condition, true_value, false_value});
state.builder.AddFunction({OpSelect, state.uint_type, ret, condition, true_value, false_value});
return ret;
}
@@ -212,12 +210,12 @@ bool IsStorageBufferMemoryKind(IR::ResourceKind kind) {
void EmitStorageBufferOffsets(EmitterState& state) {
for (uint32_t i = 0; i < state.program.bindings.buffer_offset_count; i++) {
const auto word = EmitShaderDataDwordLoad(
state, state.program.bindings.buffer_offset_dword + i / 4u);
const auto word =
EmitShaderDataDwordLoad(state, state.program.bindings.buffer_offset_dword + i / 4u);
const auto shift = ConstantU32(state, (i % 4u) * 8u + 2u);
state.storage_buffer_offsets[i] = EmitBinaryU32(
state, OpBitwiseAnd,
EmitBinaryU32(state, OpShiftRightLogical, word, shift), ConstantU32(state, 0x3fu));
state, OpBitwiseAnd, EmitBinaryU32(state, OpShiftRightLogical, word, shift),
ConstantU32(state, 0x3fu));
}
}
@@ -329,8 +327,7 @@ uint32_t EmitRelativeAddress(EmitterState& state, const IR::Instruction& inst, u
uint32_t EmitFlatVirtualAddress(EmitterState& state, const IR::Instruction& inst,
uint32_t first_src, uint32_t src_count) {
if (inst.memory.resource >= state.resources.addresses.size() ||
src_count < 2) {
if (inst.memory.resource >= state.resources.addresses.size() || src_count < 2) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
inst.memory.resource, "flat address snapshot is missing");
}
@@ -659,8 +656,7 @@ void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind
state.builder.AddFunction({OpBranch, preheader});
state.builder.AddFunction({OpLabel, preheader});
state.builder.AddFunction({OpAtomicLoad, state.uint_type, initial, pointer,
ConstantU32(state, scope),
ConstantU32(state, MemorySemanticsNone)});
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
state.builder.AddFunction({OpBranch, header});
state.builder.AddFunction({OpLabel, header});
state.builder.AddFunction(
@@ -793,8 +789,7 @@ uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
state.builder.AddFunction({opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
return ret;
}
@@ -961,18 +956,8 @@ uint32_t UnpackTBufferFormat(EmitterState& state, const IR::Instruction& inst,
return NormalizeFormatComponent(state, info, inst.memory.component_index, raw);
}
bool EmitTypedTBufferLoad(EmitterState& state, const IR::Instruction& inst,
const Format::BufferFormatInfo& info) {
if (!Format::CanUseTypedBufferLoad(info.format)) {
return false;
}
const auto value = EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
AddressSourceCount(inst, 0));
EmitStoreU32(state, inst.dst, value);
return true;
}
bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst) {
bool EmitFormattedBufferLoadValueU32(EmitterState& state, const IR::Instruction& inst,
uint32_t& value) {
if (!IsFormattedBufferComponent(inst)) {
return false;
}
@@ -984,18 +969,29 @@ bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst) {
const auto info = Format::GetFormatInfo(format);
if (inst.memory.component_index >= info.component_count) {
EmitStoreU32(state, inst.dst, ConstantU32(state, 0));
value = ConstantU32(state, 0);
return true;
}
if (EmitTypedTBufferLoad(state, inst, info)) {
if (Format::CanUseTypedBufferLoad(info.format)) {
value = EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
AddressSourceCount(inst, 0));
return true;
}
EmitStoreU32(state, inst.dst, UnpackTBufferFormat(state, inst, info));
value = UnpackTBufferFormat(state, inst, info);
return true;
}
uint32_t EmitBufferLoadDwordValueU32(EmitterState& state, const IR::Instruction& inst) {
uint32_t value = 0;
if (EmitFormattedBufferLoadValueU32(state, inst, value)) {
return value;
}
return EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
AddressSourceCount(inst, 0));
}
uint32_t FormattedBufferDwordStoreComponentCount(Prospero::BufferFormat format,
uint32_t opcode_components) {
switch (format) {
@@ -1262,11 +1258,27 @@ void EmitBufferLoadSshort(EmitterState& state, const IR::Instruction& inst) {
}
void EmitBufferLoadDword(EmitterState& state, const IR::Instruction& inst) {
EmitGuardedByExec(state, [&]() {
if (EmitFormattedBufferLoad(state, inst)) {
EmitGuardedByExec(
state, [&]() { EmitStoreU32(state, inst.dst, EmitBufferLoadDwordValueU32(state, inst)); });
}
void EmitBufferLoadDwordGroup(EmitterState& state, const IR::Instruction* instructions,
uint32_t count) {
if (instructions == nullptr || count == 0u) {
return;
}
EmitMemoryLoadU32(state, inst, IR::ResourceKind::Buffer, 0, AddressSourceCount(inst, 0));
EmitGuardedByExec(state, [&]() {
// RDNA VMEM captures every VADDR component before making overlapping VDATA writes
// visible. Keep the split IR components instruction-atomic by deferring all stores.
std::vector<uint32_t> values;
values.reserve(count);
for (uint32_t i = 0; i < count; i++) {
values.push_back(EmitBufferLoadDwordValueU32(state, instructions[i]));
}
for (uint32_t i = 0; i < count; i++) {
EmitStoreU32(state, instructions[i].dst, values[i]);
}
});
}
@@ -1482,8 +1494,7 @@ void EmitDsAppendConsume(EmitterState& state, const IR::Instruction& inst, uint3
const auto do_atomic = state.builder.AllocateId();
state.builder.AddFunction({OpIEqual, state.bool_type, first_lane, subid, exec.first_lane});
const auto first_active = EmitLogicalAndBool(state, first_lane, exec.any_active);
state.builder.AddFunction(
{OpLogicalAnd, state.bool_type, do_atomic, first_active, in_bounds});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, do_atomic, first_active, in_bounds});
const auto atomic_value = EmitValueOrZeroIfCondition(state, do_atomic, [&]() {
const auto pointer = gds ? EmitGdsElementPointer(state, address.index)
@@ -1575,14 +1586,13 @@ uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t c
const auto xored = state.builder.AllocateId();
const auto base = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 31)});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 31)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, masked, lane, ConstantU32(state, control & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseOr, state.uint_type, ored, masked, ConstantU32(state, (control >> 5u) & 0x1fu)});
state.builder.AddFunction({OpBitwiseXor, state.uint_type, xored, ored,
ConstantU32(state, (control >> 10u) & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseXor, state.uint_type, xored, ored, ConstantU32(state, (control >> 10u) & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, base, subid, ConstantU32(state, 0xffffffe0u)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, base, xored});
+205
View File
@@ -12129,6 +12129,84 @@ TestCase BufferLoadVariants() {
O::BufferLoadDwordx4, O::VMovB32, O::BufferStoreDword, O::SEndpgm}};
}
TestCase BufferLoadDwordx4SnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
code.push_back(EncodeMubuf0(0x0eu, 0, true, true));
code.push_back(EncodeMubuf1(21, 0, 21));
for (u32 i = 0; i < 4; i++) {
AppendStoreVgpr(&code, 21 + i, 4 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "BufferLoadDwordx4SnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
0, 0, 0, 0};
test.expected = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
test.opcodes = {O::VMovB32, O::BufferLoadDwordx4, O::BufferStoreDword,
O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(16, 2);
test.has_user_data = true;
return test;
}
TestCase BufferLoadDwordx2SnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
code.push_back(EncodeMubuf0(0x0du, 0, true, true));
code.push_back(EncodeMubuf1(21, 0, 21));
for (u32 i = 0; i < 2; i++) {
AppendStoreVgpr(&code, 21 + i, 2 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "BufferLoadDwordx2SnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x11111111u, 0x22222222u, 0, 0};
test.expected = {0x11111111u, 0x22222222u, 0x11111111u, 0x22222222u};
test.opcodes = {O::VMovB32, O::BufferLoadDwordx2, O::BufferStoreDword,
O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(8, 2);
test.has_user_data = true;
return test;
}
TestCase BufferLoadDwordx3SnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
code.push_back(EncodeMubuf0(0x0fu, 0, true, true));
code.push_back(EncodeMubuf1(21, 0, 21));
for (u32 i = 0; i < 3; i++) {
AppendStoreVgpr(&code, 21 + i, 3 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "BufferLoadDwordx3SnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0, 0, 0};
test.expected = {0x11111111u, 0x22222222u, 0x33333333u,
0x11111111u, 0x22222222u, 0x33333333u};
test.opcodes = {O::VMovB32, O::BufferLoadDwordx3, O::BufferStoreDword,
O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(12, 2);
test.has_user_data = true;
return test;
}
TestCase BufferStoreVariants() {
using O = ShaderOpcode;
@@ -12197,6 +12275,69 @@ TestCase BufferFormatVariants() {
return load;
}
TestCase BufferLoadFormatXyzwSnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
code.push_back(EncodeMubuf0(0x03u, 0, true, true));
code.push_back(EncodeMubuf1(21, 0, 21));
for (u32 i = 0; i < 4; i++) {
AppendStoreVgpr(&code, 21 + i, 4 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "BufferLoadFormatXyzwSnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
0, 0, 0, 0};
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
test.opcodes = {O::VMovB32, O::BufferLoadFormatXyzw, O::BufferStoreDword,
O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(
16, 2, false,
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
test.has_user_data = true;
return test;
}
TestCase BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 21, 0x11111111u);
AppendVMovLiteral(&code, 22, 0x22222222u);
AppendVMovLiteral(&code, 23, 0x33333333u);
AppendVMovLiteral(&code, 24, 0x44444444u);
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
code.push_back(EncodeMubuf0(0x03u, 0, true, true));
code.push_back(EncodeMubuf1(21, 0, 21));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
code.push_back(EncodeSMovB32(127, InlineU32(0)));
for (u32 i = 0; i < 4; i++) {
AppendStoreVgpr(&code, 21 + i, 4 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress";
test.code = std::move(code);
test.initial = {0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd,
0, 0, 0, 0};
test.expected = {0xaaaaaaaau, 0xbbbbbbbbu, 0xccccccccu, 0xddddddddu,
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
test.opcodes = {O::VMovB32, O::SMovB64, O::BufferLoadFormatXyzw,
O::SMovB32, O::BufferStoreDword, O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(
16, 2, false,
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
test.has_user_data = true;
return test;
}
TestCase BufferFormatStoreVariants() {
using O = ShaderOpcode;
@@ -12505,6 +12646,63 @@ TestCase TBufferLoadVariants() {
O::BufferStoreDword, O::SEndpgm}};
}
TestCase TBufferLoadFormatXyzwSnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
constexpr auto format =
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float);
code.push_back(
EncodeMtbuf0(0x03u, format & 0xfu, (format >> 4u) & 0x7u, 0, true, true));
code.push_back(EncodeMtbuf1(0x03u, 21, 0, 21));
for (u32 i = 0; i < 4; i++) {
AppendStoreVgpr(&code, 21 + i, 4 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "TBufferLoadFormatXyzwSnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
0, 0, 0, 0};
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
test.opcodes = {O::VMovB32, O::TBufferLoadFormatXyzw,
O::BufferStoreDword, O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(16, 2);
test.has_user_data = true;
return test;
}
TestCase TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 21, 0);
AppendVMovU32(&code, 22, 0);
constexpr auto format = BufferFormat(Prospero::BufferFormat::k8_8_8_8UInt);
code.push_back(
EncodeMtbuf0(0x03u, format & 0xfu, (format >> 4u) & 0x7u, 0, true, true));
code.push_back(EncodeMtbuf1(0x03u, 21, 0, 21));
for (u32 i = 0; i < 4; i++) {
AppendStoreVgpr(&code, 21 + i, 4 + i);
}
AppendEnd(&code);
TestCase test;
test.name = "TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress";
test.code = std::move(code);
test.initial = {0x44332211u, 0, 0, 0, 0, 0, 0, 0};
test.expected = {0x44332211u, 0, 0, 0, 0x11u, 0x22u, 0x33u, 0x44u};
test.opcodes = {O::VMovB32, O::TBufferLoadFormatXyzw,
O::BufferStoreDword, O::SEndpgm};
test.user_data = MakeStructuredStorageBufferData(4, 8);
test.has_user_data = true;
return test;
}
TestCase TBufferStoreFormatX8UintWritesOneByte() {
using O = ShaderOpcode;
@@ -14839,8 +15037,13 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferStoreDwordAppliesHostOffset);
AddCase(BufferOffsetsUsePackedLaneAndStorageFallback);
AddCase(BufferLoadVariants);
AddCase(BufferLoadDwordx2SnapshotsOverlappingAddress);
AddCase(BufferLoadDwordx3SnapshotsOverlappingAddress);
AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress);
AddCase(BufferStoreVariants);
AddCase(BufferFormatVariants);
AddCase(BufferLoadFormatXyzwSnapshotsOverlappingAddress);
AddCase(BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress);
AddCase(BufferFormatStoreVariants);
AddCase(BufferStoreFormatXResource16UintWritesHalfword);
AddCase(BufferLoadFormatXResource8UintZeroExtendsByte);
@@ -14854,6 +15057,8 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferStoreFormatXAddTidUsesLaneIndex);
AddCase(BufferStoreFormatXDropsOutOfRangeRecord);
AddCase(TBufferLoadVariants);
AddCase(TBufferLoadFormatXyzwSnapshotsOverlappingAddress);
AddCase(TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress);
AddCase(TBufferLoadFormatX8UintZeroExtendsByte);
AddCase(TBufferLoadFormatX8888UintExtractsFirstByte);
AddCase(TBufferLoadFormatXIdxenUsesDescriptorStride);