shader: add BVH stack operations

This commit is contained in:
nmzik committed 2026-10-29 18:37:29 +01:00
1 parent 6ff6e7551d
commit bfd4c34b88
18 files changed
+419 -3

No files matched your search

+129
View File
@@ -1,3 +1,132 @@
TestCase TrinityStack(u32 wave_size, u32 capacity, bool storage = false) {
TestCase test;
static constexpr const char* names[2][3] = {
{"TrinityStack32Capacity8", "TrinityStack32Capacity16", "TrinityStack32Capacity31"},
{"TrinityStack64Capacity8", "TrinityStack64Capacity16", "TrinityStack64Capacity31"}};
test.name = storage ? (wave_size == 32 ? "TrinityStackStorage32" : "TrinityStackStorage64")
: names[wave_size == 64][capacity == 8 ? 0 : capacity == 16 ? 1 : 2];
constexpr u32 Invalid = 0xffffffffu, Terminal = 0xfffffffeu;
constexpr u32 Parent = 1u << 29, Overflow = 1u << 30, Return = 1u << 31;
const auto state = [](u32 count, u32 parent, u32 ring, u32 flags = 0) {
return count | (parent << 5) | (ring << 10) | flags;
};
test.initial.resize(wave_size * 32);
struct Output { u32 control, node; std::array<u32, 8> lds; };
std::vector<Output> outputs(wave_size);
for (u32 lane = 0; lane < wave_size; ++lane) {
const u32 which = lane % 16;
const u32 base = which == 15 ? 0x3fff : (lane / 32) * capacity * 32 + lane % 32;
auto* input = test.initial.data() + lane * 32;
auto& out = outputs[lane];
input[0] = base << 15;
input[1] = Invalid;
std::fill(input + 2, input + 10, Invalid);
input[10] = base * 4;
for (u32 i = 0; i < 8; ++i) out.lds[i] = 100 + i * 8;
if (capacity == 8) out.lds[7] = 88;
out.node = Terminal;
out.control = 0;
const auto children = [&] {
for (u32 i = 0; i < 8; ++i) input[2 + i] = (i + 1) * 8;
};
switch (which) {
case 0: break;
case 1: input[0] |= Overflow; out.control = Overflow; out.node = Invalid; break;
case 2:
children(); out.control = state(7, 7, 7); out.node = 8;
out.lds = {64, 56, 48, 40, 32, 24, 16, 8}; break;
case 3:
input[2] = 8; input[4] = 24; input[7] = 48; input[9] = 64;
out.control = state(3, 3, 3); out.node = 8;
out.lds[0] = 64; out.lds[1] = 48; out.lds[2] = 24; out.lds[3] = 8; break;
case 4:
case 14:
input[0] |= state(capacity, capacity, 0, which == 14 ? Parent : 0);
input[2] = 8; out.control = state(capacity - 1, capacity - 1, 0, Overflow);
out.node = out.lds[0] = 8; break;
case 5:
input[0] |= state(1, 1, 0); out.control = state(0, 0, capacity - 1); out.node = 88; break;
case 6:
input[0] |= state(1, 0, 1, Parent);
out.control = state(0, 31, 0, Return); out.node = 100; break;
case 7:
input[2] = 14; out.control = Parent; out.node = out.lds[0] = 14; break;
case 8:
input[0] |= state(3, 1, 3, Parent);
out.lds[2] = 14;
out.control = state(0, 0, 2, Parent | Overflow); out.node = 14; break;
case 9:
children(); input[1] = 24; out.control = state(4, 4, 4); out.node = 32;
out.lds[0] = 64; out.lds[1] = 56; out.lds[2] = 48; out.lds[3] = 40; out.lds[4] = 32; break;
case 10:
children(); input[1] = 96; input[0] |= state(1, 1, 1);
out.node = 100; break;
case 11:
children(); input[9] = 0xfffffffbu; out.control = state(3, 3, 3); out.node = 8;
out.lds[0] = 32; out.lds[1] = 24; out.lds[2] = 16; out.lds[3] = 8; break;
case 12: children(); input[9] = 0xfffffff9u; break;
case 13:
children(); input[5] = 0xfffffffdu; out.control = state(3, 3, 3); out.node = 40;
out.lds[0] = 64; out.lds[1] = 56; out.lds[2] = 48; out.lds[3] = 40; break;
case 15:
input[0] |= state(1, 1, 1); out.node = 0; out.lds.fill(0); break;
}
out.control |= base << 15;
if ((lane % 32) >= 16) {
out.control = input[0]; out.node = input[2];
for (u32 i = 0; i < 8; ++i) out.lds[i] = 100 + i * 8;
if (capacity == 8) out.lds[7] = 88;
if (which == 8) out.lds[2] = 14;
if (which == 15) out.lds.fill(0);
}
}
test.expected = test.initial;
for (u32 lane = 0; lane < wave_size; ++lane) {
test.expected[lane * 32 + 20] = outputs[lane].control;
test.expected[lane * 32 + 21] = outputs[lane].node;
std::copy(outputs[lane].lds.begin(), outputs[lane].lds.end(), test.expected.begin() + lane * 32 + 24);
}
auto& code = test.code;
code.push_back(EncodeVop2(0x1a, 1, InlineU32(7), 0));
code.insert(code.end(), {EncodeMubuf0(0x0d), EncodeMubuf1(8, 0, 1),
EncodeMubuf0(0x0e, 8), EncodeMubuf1(16, 0, 1),
EncodeMubuf0(0x0e, 24), EncodeMubuf1(20, 0, 1),
EncodeMubuf0(0x0c, 40), EncodeMubuf1(2, 0, 1)});
for (u32 i = 0; i < 8; ++i) {
AppendVMovU32(&code, 3, 100 + i * 8);
code.insert(code.end(), {EncodeDs0(0x0d, i * 128), EncodeDs1(0, 3, 2)});
}
AppendVMovU32(&code, 3, 88);
code.insert(code.end(), {EncodeDs0(0x0d, (capacity - 1) * 128), EncodeDs1(0, 3, 2)});
// Lane eight starts with a nested instance on top of its stack.
AppendSMovLiteral(&code, 126, 0x01000100u);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x01000100u);
AppendVMovU32(&code, 3, 14);
code.insert(code.end(), {EncodeDs0(0x0d, 256), EncodeDs1(0, 3, 2)});
AppendSMovLiteral(&code, 126, 0x0000ffffu);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x0000ffffu);
code.insert(code.end(), {EncodeDs0(0xe1, capacity), EncodeDs1Ex(16, 16, 9, 8)});
code.push_back(EncodeSop1(0x04, 126, 193));
code.push_back(EncodeVop2(0x1a, 3, InlineU32(5), 0));
AppendStoreVgprAtLaneDwordOffset(&code, 8, 3, 20);
AppendStoreVgprAtLaneDwordOffset(&code, 16, 3, 21);
for (u32 i = 0; i < 8; ++i) {
code.insert(code.end(), {EncodeDs0(0x36, i * 128), EncodeDs1(24, 0, 2)});
AppendStoreVgprAtLaneDwordOffset(&code, 24, 3, 24 + i);
}
AppendEnd(&code);
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.lds_size_dwords = wave_size * capacity;
test.compute_info.lds_storage = storage;
test.has_compute_info = true;
test.decoded_counts = {{"DS_BVH_STACK_PUSH8_POP1_RTN_B32", 1}};
test.forbidden_spirv = {"OpControlBarrier", "OpMemoryBarrier", "get_bda_pointer"};
return test;
}
TestCase FlatStackApertures(u32 wave_size) {
TestCase test;
test.name = wave_size == 32 ? "FlatStackApertures32" : "FlatStackApertures64";
+41
View File
@@ -1,4 +1,45 @@
void TestTrinityStackInstruction() {
namespace D = ShaderRecompiler::Decoder;
const std::array code{0xdb840008u, 0x01010a09u,
0xdb840010u, 0x02010a09u, EncodeSopp(0x01)};
D::Program decoded;
D::DecodeProgram(code, decoded);
const auto& first = decoded.instructions.front();
Check(first.opcode == D::Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 &&
first.src_count == 3 && first.offset == 8 && first.dst.reg == 1 &&
first.src0.reg == 9 && first.src1.reg == 10 && first.src2.reg == 1,
"Trinity stack operands or capacity were not decoded");
ShaderComputeInputInfo compute{};
compute.lds_size_dwords = 1024;
auto options = MakeCompileOptions(ShaderType::Compute);
options.input_info.compute = &compute;
const auto result = RecompileForTest(code, options);
Check(CountSourceOccurrences(result.ir_dump, " = BvhStack ") == 2 &&
result.program.info.uses_lds && result.program.info.bvh_stack &&
!result.program.info.shared_int64_atomics,
"unused stack results discarded LDS writes or selected 64-bit atomics");
Check(SpirvInstructionOpcodeCount(result.spirv, 54u) == 2 &&
SpirvInstructionOpcodeCount(result.spirv, 246u) == 1,
"Trinity stack calls duplicated the function or unrolled its eight pushes");
CheckSpirvBinaryValidates(result.spirv);
compute.lds_storage = true;
CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv);
options = MakeCompileOptions(ShaderType::Pixel);
CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv);
for (const auto words : {std::array{0xdb860008u, 0x01010a09u},
std::array{0xdb840007u, 0x01010a09u},
std::array{0xdb840020u, 0x01010a09u},
std::array{0xdb840008u, 0x01f90a09u},
std::array{0xdb840008u, 0x09010a09u}}) {
const std::array invalid{words[0], words[1], EncodeSopp(0x01)};
D::DecodeProgram(invalid, decoded);
Check(decoded.instructions[0].opcode == D::Opcode::UNSUPPORTED,
"unsupported stack encoding was silently accepted");
}
}
void TestRayTracingInstructions() {
TestTrinityStackInstruction();
namespace D = ShaderRecompiler::Decoder;
const std::array code{
0xf1989f01u, 0x00081014u,
+8
View File
@@ -46807,6 +46807,10 @@ std::vector<TestCase> MakeCases() {
AddCase(ScratchIsPrivatePerInvocation);
cases.push_back(FlatStackApertures(32));
cases.push_back(FlatStackApertures(64));
for (const u32 wave : {32u, 64u}) {
for (const u32 capacity : {8u, 16u, 31u}) cases.push_back(TrinityStack(wave, capacity));
cases.push_back(TrinityStack(wave, 8, true));
}
cases.push_back(BvhIntersections(true, true, 1));
cases.push_back(BvhIntersections(true, true, 0, true));
for (bool barycentrics : {false, true}) {
@@ -53346,6 +53350,10 @@ int main(int argc, char **argv) {
#endif
if (argc == 2 && std::strcmp(argv[1], "--ray-tracing-only") == 0) {
VulkanHarness vulkan;
for (const u32 wave : {32u, 64u}) {
for (const u32 capacity : {8u, 16u, 31u}) RunCase(&vulkan, TrinityStack(wave, capacity));
RunCase(&vulkan, TrinityStack(wave, 8, true));
}
RunCase(&vulkan, FlatStackApertures(32));
RunCase(&vulkan, FlatStackApertures(64));
RunCase(&vulkan, BvhIntersections(true, true, 1));