mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
shader: bound GPU-selected record descriptors by scalar table geometry
This commit is contained in:
+179
-39
@@ -4393,13 +4393,26 @@ void TestBoundedFormattedBufferStore() {
|
||||
"finite store accepted an unproved induction or indexed byte offset");
|
||||
}
|
||||
|
||||
void TestBoundedBufferRecords() {
|
||||
std::unique_ptr<Fixture> MakeBufferRecordFixture(uint32_t step = 1u, bool bypass = false,
|
||||
bool gpu_selected = false, uint32_t bias = 0u,
|
||||
bool split_offsets = false) {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
namespace Prospero = Libs::Graphics::Prospero;
|
||||
using Libs::Graphics::DstSel;
|
||||
const auto make = [](uint32_t step = 1u, bool bypass = false) {
|
||||
auto fixture = std::make_unique<Fixture>();
|
||||
auto& f = *fixture;
|
||||
auto fixture = std::make_unique<Fixture>();
|
||||
auto& f = *fixture;
|
||||
std::array<Value, 4> table_words;
|
||||
for (uint32_t word = 0; word < 4u; ++word) table_words[word] = f.UserData(word);
|
||||
const auto table = f.Buffer(table_words);
|
||||
const auto lane = f.Emit(ValueOpcode::LaneId);
|
||||
Value key;
|
||||
if (gpu_selected) {
|
||||
std::array<Value, 4> selector_words;
|
||||
for (uint32_t word = 0; word < 4u; ++word) selector_words[word] = f.UserData(4u + word);
|
||||
const auto group = f.Emit(ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)), Value(0u)});
|
||||
key = f.Emit(ValueOpcode::ReadConstBuffer,
|
||||
{f.Buffer(selector_words), f.Emit(ValueOpcode::IMul32, {group, Value(12u)})},
|
||||
f.AddMemory({.kind = ResourceKind::ScalarBuffer}, 0x1bcu));
|
||||
} else {
|
||||
auto* entry = f.block;
|
||||
auto* header = f.AddBlock();
|
||||
auto* body = f.AddBlock();
|
||||
@@ -4417,46 +4430,52 @@ void TestBoundedBufferRecords() {
|
||||
header->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = body, .false_block = bypass ? latch : exit};
|
||||
exit->terminator.kind = CFG::TerminatorKind::Return;
|
||||
std::array<Value, 4> table_words;
|
||||
for (uint32_t word = 0; word < 4u; ++word) table_words[word] = f.UserData(word);
|
||||
const auto table = f.Buffer(table_words);
|
||||
const auto count = f.Emit(ValueOpcode::LoadAddressU32,
|
||||
{f.Address(f.UserData(4u), f.UserData(5u)), Value(0u), Value(0u), Value(true)},
|
||||
f.AddMemory({.kind = ResourceKind::ScalarAddress}, 0x424u));
|
||||
const auto lane = f.Emit(ValueOpcode::LaneId);
|
||||
auto& phi = header->AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32));
|
||||
phi.AddPhiOperand(entry, Value(0u));
|
||||
phi.AddPhiOperand(latch, f.Emit(ValueOpcode::IAdd32, {Value(&phi), Value(step)}, 0, latch));
|
||||
header->condition = f.Emit(ValueOpcode::SLessThan32, {Value(&phi), count}, 0, header);
|
||||
f.block = body;
|
||||
const auto offset = f.Emit(ValueOpcode::IMul32, {Value(&phi), Value(192u)});
|
||||
for (const bool formatted : {true, false}) {
|
||||
std::array<Value, 4> words;
|
||||
for (uint32_t word = 0; word < 4u; ++word)
|
||||
words[word] = f.Emit(ValueOpcode::ReadConstBuffer, {table, offset},
|
||||
f.AddMemory({.kind = ResourceKind::ScalarBuffer,
|
||||
.offset = (formatted ? 160u : 176u) + word * 4u,
|
||||
.component_index = word, .component_count = 4u}, 0x1808u));
|
||||
for (uint32_t use = 0; use < (formatted ? 112u : 16u); ++use) {
|
||||
const auto handle = f.Buffer(words);
|
||||
if (formatted) {
|
||||
const auto loaded = f.Emit(ValueOpcode::LoadBufferU32x2,
|
||||
{handle, lane, Value(0u), Value(0u), Value(true)},
|
||||
f.AddMemory({.kind = ResourceKind::Buffer, .data_dwords = 2u, .data_bits = 16u,
|
||||
.component_count = 4u, .formatted = true, .idxen = true}, 0x1914u));
|
||||
f.Emit(ValueOpcode::ReferenceU32,
|
||||
{f.Emit(ValueOpcode::CompositeExtractU32x2, {loaded, Value(0u)})});
|
||||
} else {
|
||||
f.Emit(ValueOpcode::StoreBufferU8,
|
||||
{handle, lane, Value(0u), Value(0u), Value(uint8_t{42u}), Value(true)},
|
||||
f.AddMemory({.kind = ResourceKind::Buffer, .data_bits = 8u, .idxen = true}, 0x190cu));
|
||||
}
|
||||
key = Value(&phi);
|
||||
}
|
||||
auto offset = f.Emit(ValueOpcode::IMul32, {key, Value(192u)});
|
||||
if (bias != 0u) offset = f.Emit(ValueOpcode::IAdd32, {offset, Value(bias)});
|
||||
for (const bool formatted : {true, false}) {
|
||||
std::array<Value, 4> words;
|
||||
for (uint32_t word = 0; word < 4u; ++word) {
|
||||
const auto dynamic = split_offsets
|
||||
? f.Emit(ValueOpcode::IAdd32, {offset, Value(word * 4u)}) : offset;
|
||||
words[word] = f.Emit(ValueOpcode::ReadConstBuffer, {table, dynamic},
|
||||
f.AddMemory({.kind = ResourceKind::ScalarBuffer,
|
||||
.offset = (formatted ? 160u : 176u) + (split_offsets ? 0u : word * 4u),
|
||||
.component_index = word, .component_count = 4u}, 0x1808u));
|
||||
}
|
||||
for (uint32_t use = 0; use < (gpu_selected ? 2u : formatted ? 112u : 16u); ++use) {
|
||||
const auto handle = f.Buffer(words);
|
||||
if (formatted) {
|
||||
const auto loaded = f.Emit(ValueOpcode::LoadBufferU32x2,
|
||||
{handle, lane, Value(0u), Value(0u), Value(true)},
|
||||
f.AddMemory({.kind = ResourceKind::Buffer, .data_dwords = 2u, .data_bits = 16u,
|
||||
.component_count = 4u, .formatted = true, .idxen = true}, 0x1914u));
|
||||
f.Emit(ValueOpcode::ReferenceU32,
|
||||
{f.Emit(ValueOpcode::CompositeExtractU32x2, {loaded, Value(0u)})});
|
||||
} else {
|
||||
f.Emit(ValueOpcode::StoreBufferU8,
|
||||
{handle, lane, Value(0u), Value(0u), Value(uint8_t{42u}), Value(true)},
|
||||
f.AddMemory({.kind = ResourceKind::Buffer, .data_bits = 8u, .idxen = true}, 0x190cu));
|
||||
}
|
||||
}
|
||||
f.PlanAndTrack();
|
||||
return fixture;
|
||||
};
|
||||
auto fixture = make();
|
||||
}
|
||||
f.PlanAndTrack();
|
||||
return fixture;
|
||||
}
|
||||
|
||||
void TestBoundedBufferRecords() {
|
||||
namespace Prospero = Libs::Graphics::Prospero;
|
||||
using Libs::Graphics::DstSel;
|
||||
auto fixture = MakeBufferRecordFixture();
|
||||
const auto plan = ExtractResourcePlan(fixture->program);
|
||||
Check(plan.info.buffers.size() == 2u, "record descriptors did not share their resource roots");
|
||||
uint32_t selections = 0u;
|
||||
@@ -4557,9 +4576,129 @@ void TestBoundedBufferRecords() {
|
||||
}
|
||||
memory.words[0] = MaxIndirectDescriptorProbes + 1u;
|
||||
Check(!materialize(), "record table accepted an unbounded candidate count");
|
||||
for (const auto [step, bypass] : {std::pair{2u, false}, std::pair{1u, true}})
|
||||
CheckFatal([&] { make(step, bypass); }, "not a valid runtime value",
|
||||
"record table accepted an unproved loop bound");
|
||||
for (const auto [step, bypass] : {std::pair{2u, false}, std::pair{1u, true}}) {
|
||||
const auto unbounded = MakeBufferRecordFixture(step, bypass);
|
||||
for (const auto& buffer : unbounded->program.info.buffers) {
|
||||
const auto& source = unbounded->program.descriptor_sources[buffer.source];
|
||||
if (source.indirect_descriptor)
|
||||
Check(source.indirect_descriptor->table_scalar && source.indirect_descriptor->key_count.IsEmpty(),
|
||||
"unproved loop did not use the bounded descriptor table");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestGpuSelectedBufferRecords() {
|
||||
namespace Prospero = Libs::Graphics::Prospero;
|
||||
using Libs::Graphics::DstSel;
|
||||
for (const uint32_t bias : {0u, 7u}) {
|
||||
auto fixture = MakeBufferRecordFixture(1u, false, true, bias);
|
||||
const auto plan = ExtractResourcePlan(fixture->program);
|
||||
std::vector<uint32_t> roots;
|
||||
std::vector<const Inst*> selections(plan.info.buffers.size());
|
||||
for (uint32_t resource = 0; resource < plan.info.buffers.size(); ++resource) {
|
||||
const auto& source = plan.descriptor_sources[plan.info.buffers[resource].source];
|
||||
if (!source.indirect_descriptor) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
Check(indirect.table_scalar && indirect.table_stride == 192u &&
|
||||
indirect.table_offset == bias && indirect.key_count.IsEmpty() && !indirect.selector,
|
||||
"GPU record selector became a CPU key-list read or lost its affine geometry");
|
||||
roots.push_back(resource);
|
||||
}
|
||||
Check(roots.size() == 2u, "GPU record accesses lost their two descriptor roots");
|
||||
uint32_t selection_count = 0;
|
||||
for (const auto* block : fixture->program.blocks)
|
||||
for (const auto& inst : *block)
|
||||
if (inst.GetOpcode() == ValueOpcode::GetBufferResourceIndex) {
|
||||
selections[inst.Flags<uint32_t>()] = &inst;
|
||||
++selection_count;
|
||||
}
|
||||
Check(selection_count == 2u, "GPU descriptor uses duplicated mapping searches");
|
||||
const auto evaluate_key = [&](auto&& self, Value value, uint32_t key) -> uint32_t {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) return value.U32();
|
||||
const auto& inst = *value.Instruction();
|
||||
if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) return key;
|
||||
const auto a = self(self, inst.Arg(0), key);
|
||||
const auto b = self(self, inst.Arg(1), key);
|
||||
switch (inst.GetOpcode()) {
|
||||
case ValueOpcode::IMul32: return a * b;
|
||||
case ValueOpcode::IAdd32: return a + b;
|
||||
case ValueOpcode::ShiftRightLogical32: return a >> b;
|
||||
default: throw std::runtime_error("unexpected GPU descriptor key expression");
|
||||
}
|
||||
};
|
||||
LinearTestMemory memory;
|
||||
constexpr uint32_t table_offset = 256u;
|
||||
memory.fail_address = memory.base + 0x2000u;
|
||||
const auto aligned_bias = bias & ~3u;
|
||||
for (uint32_t slot = 0; slot < 4u; ++slot) {
|
||||
for (uint32_t member = 0; member < 2u; ++member) {
|
||||
const auto first = (table_offset + aligned_bias + 160u + member * 16u + slot * 64u) / 4u;
|
||||
memory.words[first] = 0x100000u + member * 0x10000u + slot * 0x1000u;
|
||||
memory.words[first + 1u] = (member == 0u ? 8u : 3u) << 16u;
|
||||
memory.words[first + 2u] = 16u;
|
||||
memory.words[first + 3u] = member == 0u
|
||||
? (static_cast<uint32_t>(Prospero::BufferFormat::k16_16_16_16Float) << 12u) |
|
||||
DstSel(4, 5, 6, 1) : 0x5204u;
|
||||
}
|
||||
}
|
||||
// SMEM aligns the base and dynamic offset independently; its V# format is ignored.
|
||||
std::array<uint32_t, 8> user_data{uint32_t(memory.base) + table_offset + 3u, 0u, 0u, 0u,
|
||||
uint32_t(memory.fail_address), 12u << 16u, 129600u, 0x5204u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
for (const uint32_t size : {0u, 384u + aligned_bias, 0u, 384u + aligned_bias}) {
|
||||
user_data[2] = size;
|
||||
memory.reads = 0u;
|
||||
Check(materialize() && memory.reads == (size == 0u ? 0u : 8u) &&
|
||||
snapshot.buffers.size() == plan.info.buffers.size() + (size == 0u ? 0u : 8u),
|
||||
"affine table refresh read GPU selector bytes, unrelated fields or stale candidates");
|
||||
}
|
||||
for (uint32_t member = 0; member < 2u; ++member) {
|
||||
const auto root = roots[member];
|
||||
const auto mapping = specialization.buffers[root].indirect_mapping_offset;
|
||||
Check(specialization.buffers[root].indirect_search_iterations == 0u &&
|
||||
snapshot.flattened_srt[mapping] == 4u,
|
||||
"affine descriptor table lost its compact dense mapping");
|
||||
// These keys land at byte offsets 0,64,128,192 after U32 multiplication by192.
|
||||
for (const auto [key, slot] : {std::pair{0u, 0u}, std::pair{44739243u, 1u},
|
||||
std::pair{22369622u, 2u}, std::pair{1u, 3u}}) {
|
||||
const auto index = evaluate_key(evaluate_key, selections[root]->Arg(0), key) >> 2u;
|
||||
Check(index == slot && snapshot.flattened_srt[mapping + 1u + index] == slot + 1u,
|
||||
"wrapped GPU record key selected the wrong physical descriptor");
|
||||
const auto candidate = plan.info.buffers.size() + member * 4u + slot;
|
||||
Check(snapshot.buffers[candidate].dwords[0] == 0x100000u + member * 0x10000u + slot * 0x1000u,
|
||||
"affine table read a stride192 row instead of a reachable stride64 member");
|
||||
Check(std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair{memory.base + table_offset + aligned_bias + 160u + member * 16u + slot * 64u,
|
||||
uint64_t{16u}}) != snapshot.specialization_reads.end(),
|
||||
"affine descriptor read escaped writable-alias capture");
|
||||
}
|
||||
}
|
||||
// The trailing V# has only three readable DWORDs; its missing format word is zero.
|
||||
const auto tail = (table_offset + aligned_bias + 416u) / 4u;
|
||||
memory.words[tail] = 0x300000u;
|
||||
memory.words[tail + 1u] = 8u << 16u;
|
||||
memory.words[tail + 2u] = 16u;
|
||||
memory.words[tail + 3u] = 0x5204u;
|
||||
user_data[2] = 428u + aligned_bias;
|
||||
memory.reads = 0u;
|
||||
Check(materialize() && memory.reads == 9u &&
|
||||
snapshot.buffers.size() == plan.info.buffers.size() + 8u &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair{memory.base + table_offset + aligned_bias + 416u, uint64_t{12u}}) !=
|
||||
snapshot.specialization_reads.end(),
|
||||
"partial descriptor did not zero-fill and capture only in-bounds DWORDs");
|
||||
user_data[2] = aligned_bias + 160u + MaxIndirectDescriptorProbes * 64u + 4u;
|
||||
memory.reads = 0u;
|
||||
Check(!materialize() && memory.reads == 0u,
|
||||
"oversized affine table was read before its candidate cap was checked");
|
||||
}
|
||||
CheckFatal([] { MakeBufferRecordFixture(1u, false, true, 0u, true); },
|
||||
"not a valid runtime value", "split dynamic descriptor offsets lost independent U32 wrapping");
|
||||
}
|
||||
|
||||
void TestFiniteBufferSelection() {
|
||||
@@ -6058,6 +6197,7 @@ int main() {
|
||||
Run("writable descriptor phi", TestWritableDescriptorPhi);
|
||||
Run("bounded formatted buffer store", TestBoundedFormattedBufferStore);
|
||||
Run("bounded buffer records", TestBoundedBufferRecords);
|
||||
Run("GPU-selected buffer records", TestGpuSelectedBufferRecords);
|
||||
Run("finite buffer selection", TestFiniteBufferSelection);
|
||||
Run("packed scalar buffer selection", TestPackedScalarBufferSelection);
|
||||
Run("conditional sampler phi", TestConditionalSamplerPhi);
|
||||
|
||||
@@ -36766,6 +36766,105 @@ void CheckBoundedRecordBufferAccess(VulkanHarness &vulkan) {
|
||||
std::printf("[compute] %-32s ok\n", "BoundedRecordBufferAccess");
|
||||
}
|
||||
|
||||
void CheckGpuSelectedRecordBufferAccess(VulkanHarness &vulkan) {
|
||||
const u32 alignment = vulkan.RuntimeContext().GetPhysicalDeviceProperties()
|
||||
.limits.minStorageBufferOffsetAlignment;
|
||||
const u32 slot = (4096u + alignment - 1u) / alignment * alignment;
|
||||
constexpr u32 selectors = 256u, table = 512u;
|
||||
// Multiplication by 192 wraps: these keys reach congruence slots 1,4,0,3,2,1,OOB,7,5.
|
||||
constexpr std::array keys{0u, 1u, 0x15555555u, 0x15555556u, 0x2aaaaaabu,
|
||||
0x40000000u, UINT32_MAX, 2u, 0x2aaaaaacu};
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
for (const u32 table_bytes : {0u, 536u, 544u}) {
|
||||
TestCase test;
|
||||
test.name = wave == 32u ? "GpuSelectedRecordBufferAccess32" : "GpuSelectedRecordBufferAccess64";
|
||||
test.initial.assign((slot * 13u + keys.size() * wave * 8u) / 4u, 0xdeadbeefu);
|
||||
test.has_user_data = test.has_compute_info = true;
|
||||
const std::array<u32, 8> descriptors{selectors, 12u << 16u, keys.size(), 0x5204u,
|
||||
table, 0u, table_bytes, 0x5204u};
|
||||
std::copy(descriptors.begin(), descriptors.end(), test.user_data.begin());
|
||||
const std::array<u32, 4> output{slot * 13u, 8u << 16u, u32(keys.size()) * wave, 0x5204u};
|
||||
std::copy(output.begin(), output.end(), test.user_data.begin() + 16u);
|
||||
test.compute_info.wave_size = wave;
|
||||
test.compute_info.threads_num[0] = wave;
|
||||
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1u;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.compute_info.group_id[0] = true;
|
||||
test.compute_info.workgroup_register = 24u;
|
||||
test.compute_info.lds_size_dwords = 0;
|
||||
test.dispatch_x = keys.size();
|
||||
auto &code = test.code;
|
||||
code.insert(code.end(), {EncodeSop2(0x26, 32, 24, InlineU32(12)),
|
||||
EncodeSmem0(0x08, 32, 0), EncodeSmem1(0u, 32), EncodeSopp(0x0c, 0),
|
||||
EncodeSop2(0x26, 33, 32, 255), 192u,
|
||||
EncodeSop2(0x02, 33, 33, 255), 96u,
|
||||
EncodeSmem0(0x0a, 8, 2), EncodeSmem1(160u, 33),
|
||||
EncodeSmem0(0x0a, 12, 2), EncodeSmem1(176u, 33), EncodeSopp(0x0c, 0),
|
||||
EncodeMubuf0(0x83, 0, true, false), EncodeMubuf1(4, 2, 0),
|
||||
EncodeSopp(0x0c, 0), EncodeSop2(0x1e, 34, 24, InlineU32(std::countr_zero(wave))),
|
||||
EncodeVop2(0x25, 20, 34, 0),
|
||||
EncodeMubuf0(0x1d, 0, true, false), EncodeMubuf1(4, 4, 20)});
|
||||
AppendVMovLiteral(&code, 10, 0xa5c39b72u);
|
||||
AppendSMovLiteral(&code, 126, 0x55555555u);
|
||||
AppendSMovLiteral(&code, 127, wave == 64u ? 0x55555555u : 0u);
|
||||
code.insert(code.end(), {EncodeMubuf0(0x19, 0, true, false, true), EncodeMubuf1(10, 3, 20)});
|
||||
AppendEnd(&code);
|
||||
for (u32 row = 0; row < 6u; ++row) {
|
||||
const std::array<u32, 8> record{slot * (row + 1u), 8u << 16u, 1u,
|
||||
static_cast<u32>(Prospero::BufferFormat::k16_16_16_16Float) << 12u | DstSel(4, 5, 6, 7),
|
||||
slot * (row + 7u), 1u << 16u, u32(keys.size()) * wave, 0x5204u};
|
||||
std::copy(record.begin(), record.end(), test.initial.begin() + (table + 192u + row * 64u) / 4u);
|
||||
test.initial[slot * (row + 1u) / 4u] = ((0x3800u + row) << 16u) | (0x3c00u + row);
|
||||
test.initial[slot * (row + 1u) / 4u + 1u] = ((0xc000u + row) << 16u) | (0x4000u + row);
|
||||
}
|
||||
// Compile against poison selectors. A separate GPU dispatch writes the actual keys.
|
||||
auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||
for (const auto &descriptor : compiled.resources.buffers) {
|
||||
ShaderBufferResource resource{};
|
||||
std::copy_n(descriptor.dwords.begin(), 4u, resource.fields);
|
||||
test.storage_buffer_ranges.emplace_back(resource.Base48(), std::max(4u, u32(resource.GetSize())));
|
||||
}
|
||||
TestCase producer;
|
||||
producer.name = "GpuRecordSelectorProducer";
|
||||
producer.initial = test.initial;
|
||||
producer.has_compute_info = true;
|
||||
producer.compute_info.threads_num[0] = producer.compute_info.threads_num[1] =
|
||||
producer.compute_info.threads_num[2] = 1u;
|
||||
producer.compute_info.wave_size = 32u;
|
||||
producer.compute_info.lds_size_dwords = 0;
|
||||
for (u32 group = 0; group < keys.size(); ++group) {
|
||||
AppendVMovLiteral(&producer.code, 1, keys[group]);
|
||||
AppendVMovLiteral(&producer.code, 2, selectors + group * 12u);
|
||||
AppendBufferStoreDword(&producer.code, 1, 2);
|
||||
}
|
||||
AppendEnd(&producer.code);
|
||||
const auto producer_shader = CompileCase(producer, vulkan.SubgroupSize());
|
||||
test.expected = test.initial;
|
||||
auto bytes = std::as_writable_bytes(std::span(test.expected));
|
||||
for (u32 group = 0; group < keys.size(); ++group) {
|
||||
test.expected[selectors / 4u + group * 3u] = keys[group];
|
||||
const u32 dynamic = keys[group] * 192u + 96u;
|
||||
const u32 row = dynamic / 64u;
|
||||
for (u32 lane = 0; lane < wave; ++lane) {
|
||||
const u32 out = slot * 13u / 4u + (group * wave + lane) * 2u;
|
||||
const bool source = uint64_t{dynamic} + 160u + 16u <= table_bytes && lane == 0u;
|
||||
test.expected[out] = source ? test.initial[slot * (row + 1u) / 4u] : 0u;
|
||||
test.expected[out + 1u] = source ? test.initial[slot * (row + 1u) / 4u + 1u] : 0u;
|
||||
if (uint64_t{dynamic} + 176u + 16u <= table_bytes && (lane & 1u) == 0u)
|
||||
bytes[slot * (row + 7u) + group * wave + lane] = std::byte{0xc3};
|
||||
}
|
||||
}
|
||||
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||
vulkan.Dispatch(producer, producer_shader, buffer);
|
||||
vulkan.Dispatch(test, compiled, buffer);
|
||||
CompareWords(test, "GPU selectors, wrapped offsets, partial descriptors, OOB and masked byte stores",
|
||||
test.expected, vulkan.ReadBuffer(test.name, buffer, test.expected.size()));
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
}
|
||||
}
|
||||
std::printf("[compute] %-32s ok\n", "GpuSelectedRecordBufferAccess");
|
||||
}
|
||||
|
||||
void CheckFiniteBufferSelection(VulkanHarness &vulkan) {
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
TestCase test;
|
||||
@@ -45574,6 +45673,7 @@ int main(int argc, char **argv) {
|
||||
CheckFiniteBufferSelection(vulkan);
|
||||
CheckIndirectBufferStore(vulkan);
|
||||
CheckBoundedRecordBufferAccess(vulkan);
|
||||
CheckGpuSelectedRecordBufferAccess(vulkan);
|
||||
CheckRuntimeBufferRecords(vulkan);
|
||||
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
|
||||
RunCase(&vulkan, BufferLoadDwordGpuSelectedDescriptors());
|
||||
@@ -46278,6 +46378,7 @@ int main(int argc, char **argv) {
|
||||
CheckBoundedFormattedBufferCopy(vulkan);
|
||||
CheckIndirectBufferStore(vulkan);
|
||||
CheckBoundedRecordBufferAccess(vulkan);
|
||||
CheckGpuSelectedRecordBufferAccess(vulkan);
|
||||
CheckFiniteBufferSelection(vulkan);
|
||||
CheckRuntimeBufferRecords(vulkan);
|
||||
CheckComputeThreadDimensions(vulkan);
|
||||
|
||||
Reference in New Issue
Block a user