249 lines
14 KiB
C++
249 lines
14 KiB
C++
TestCase FlatStackApertures(u32 wave_size) {
|
|
TestCase test;
|
|
test.name = wave_size == 32 ? "FlatStackApertures32" : "FlatStackApertures64";
|
|
auto &code = test.code;
|
|
code = {EncodeSop1(0x04, 8, 237), // private base
|
|
EncodeSop1(0x0b, 11, InlineU32(1)), // S_BREV_B32 shared aperture, as in GTA.
|
|
EncodeVop2(0x1a, 20, InlineU32(2), 0),
|
|
EncodeVop2(0x1b, 22, InlineU32(1), 0),
|
|
EncodeVop2(0x1a, 22, InlineU32(31), 22)};
|
|
AppendVop3(&code, 0x159, 21, 9, 11, Vgpr(22)); // Clamp the stack address to the local apertures.
|
|
AppendVMovU32(&code, 30, 100);
|
|
code.push_back(EncodeVop2(0x25, 30, Vgpr(0), 30));
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 1), EncodeFlat1(0, 0x7d, 30, 20)});
|
|
AppendVMovU32(&code, 30, 200);
|
|
code.push_back(EncodeVop2(0x25, 30, Vgpr(0), 30));
|
|
code.insert(code.end(), {EncodeDs0(0x0d), EncodeDs1(0, 30, 20)});
|
|
AppendVMovU32(&code, 30, 300);
|
|
code.push_back(EncodeVop2(0x25, 30, Vgpr(0), 30));
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0), EncodeFlat1(0, 0x7d, 30, 20)});
|
|
// FLAT writes must alias both explicit memory instructions, per invocation.
|
|
code.insert(code.end(), {EncodeFlat0(0x0c, 1), EncodeFlat1(24, 0x7d, 0, 20),
|
|
EncodeDs0(0x36), EncodeDs1(25, 0, 20),
|
|
EncodeFlat0(0x0c, 0), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 24, 0, 0);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 25, 0, wave_size);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 2 * wave_size);
|
|
// Inactive lanes must preserve both stores and destination VGPRs.
|
|
AppendSMovLiteral(&code, 126, 0x55555555);
|
|
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x55555555);
|
|
code.push_back(EncodeVop2(0x25, 22, InlineU32(1), 22));
|
|
AppendVop3(&code, 0x159, 21, 9, 11, Vgpr(22));
|
|
AppendVMovU32(&code, 30, 400);
|
|
code.push_back(EncodeVop2(0x25, 30, Vgpr(0), 30));
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0), EncodeFlat1(0, 0x7d, 30, 20)});
|
|
code.insert(code.end(), {EncodeFlat0(0x0c, 0), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
code.push_back(EncodeSop1(0x04, 126, 193)); // -1 sign-extends across EXEC
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 3 * wave_size);
|
|
// Noncanonical addresses outside either aperture, aperture overflow, and
|
|
// offsets outside allocated local storage must not wrap into valid storage.
|
|
AppendVMovLiteral(&code, 21, 0x78000000);
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0), EncodeFlat1(0, 0x7d, 30, 20),
|
|
EncodeFlat0(0x0c, 0), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 4 * wave_size);
|
|
code.push_back(EncodeVop1(0x01, 21, 11));
|
|
AppendVMovLiteral(&code, 20, 0xfffffffc);
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0, 4), EncodeFlat1(0, 0x7d, 30, 20),
|
|
EncodeFlat0(0x0c, 0, 4), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 5 * wave_size);
|
|
AppendVMovU32(&code, 20, wave_size * 4);
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0), EncodeFlat1(0, 0x7d, 30, 20),
|
|
EncodeFlat0(0x0c, 0), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 6 * wave_size);
|
|
AppendVMovU32(&code, 20, 1);
|
|
code.insert(code.end(), {EncodeFlat0(0x1c, 0), EncodeFlat1(0, 0x7d, 30, 20),
|
|
EncodeFlat0(0x0c, 0), EncodeFlat1(26, 0x7d, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 26, 0, 9 * wave_size);
|
|
code.push_back(EncodeVop2(0x1a, 20, InlineU32(2), 0));
|
|
code.insert(code.end(), {EncodeFlat0(0x0c, 1), EncodeFlat1(24, 0x7d, 0, 20),
|
|
EncodeDs0(0x36), EncodeDs1(25, 0, 20)});
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 24, 0, 7 * wave_size);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 25, 0, 8 * wave_size);
|
|
AppendEnd(&code);
|
|
test.expected.resize(10 * wave_size);
|
|
for (u32 lane = 0; lane < wave_size; ++lane) {
|
|
const bool shared = lane & 1;
|
|
test.expected[lane] = (shared ? 100 : 300) + lane;
|
|
test.expected[wave_size + lane] = (shared ? 300 : 200) + lane;
|
|
test.expected[2 * wave_size + lane] = 300 + lane;
|
|
test.expected[3 * wave_size + lane] = (shared ? 300 : 400) + lane;
|
|
test.expected[7 * wave_size + lane] = (shared ? 100 : 400) + lane;
|
|
test.expected[8 * wave_size + lane] = (shared ? 300 : 200) + lane;
|
|
}
|
|
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.scratch_size_dwords = wave_size;
|
|
test.compute_info.lds_size_dwords = wave_size;
|
|
test.has_compute_info = true;
|
|
test.decoded_counts = {{"S_BREV_B32", 1}};
|
|
test.ir_counts = {{" = BitReverse32 ", 0}};
|
|
test.forbidden_spirv = {"get_bda_pointer"};
|
|
return test;
|
|
}
|
|
|
|
TestCase BvhIntersections(bool barycentrics, bool sorted, u32 grow = 0, bool wide = false) {
|
|
TestCase test;
|
|
test.name = barycentrics ? (sorted ? "BvhBarycentricSorted" : "BvhBarycentricUnsorted")
|
|
: (sorted ? "BvhTriangleIdSorted" : "BvhTriangleIdUnsorted");
|
|
if (grow != 0) test.name = "BvhBoxExitGrowth";
|
|
if (wide) test.name = "Bvh64HighNodePointers";
|
|
constexpr u32 NodeStart = 1024, OutputStart = 512, LaneCount = 32;
|
|
constexpr uint64_t GuestBase = 0x10000;
|
|
const u32 node_base = wide ? 1u << 29 : 0;
|
|
const auto bits = [](float value) { return std::bit_cast<u32>(value); };
|
|
constexpr u32 Infinity = 0x7f800000, Invalid = UINT32_MAX, Flag = 0x04040404;
|
|
test.initial.resize(NodeStart + 258 * 16);
|
|
auto &data = test.initial;
|
|
// Pack five float3 vertices and the triangle flag into a 64-byte node.
|
|
const std::array<float, 15> vertices{-1, -1, 2, 1, -1, 2, 0, 1, 2, 2, 1, 2, 0, 3, 2};
|
|
for (u32 i = 0; i < vertices.size(); ++i) data[NodeStart + i] = bits(vertices[i]);
|
|
data[NodeStart + 15] = Flag;
|
|
constexpr std::array<u32, 4> children{0x88, 0x90, 0x98, 0xa0};
|
|
constexpr std::array<float, 4> z{5, 1, 3, 7};
|
|
constexpr std::array<u32, 4> half_z{0x4500, 0x3c00, 0x4200, 0x4700};
|
|
constexpr std::array<u32, 4> half_far{0x4600, 0x4000, 0x4400, 0x4800};
|
|
for (u32 child = 0; child < 4; ++child) {
|
|
data[NodeStart + 16 + child] = data[NodeStart + 32 + child] = children[child];
|
|
const auto fp16 = NodeStart + 20 + child * 3;
|
|
data[fp16] = 0xbc00bc00; // min.xy = -1,-1
|
|
data[fp16 + 1] = 0x3c000000 | half_z[child];
|
|
data[fp16 + 2] = (half_far[child] << 16) | 0x3c00;
|
|
const auto fp32 = NodeStart + 36 + child * 6;
|
|
const std::array<float, 6> bounds{-1, -1, z[child], 1, 1, z[child] + 1};
|
|
for (u32 i = 0; i < 6; ++i) data[fp32 + i] = bits(bounds[i]);
|
|
}
|
|
// A grazing ray enters one ULP after exiting on another axis.
|
|
for (u32 child = 0; child < 4; ++child) {
|
|
data[NodeStart + 64 + child] = children[child];
|
|
const std::array<float, 6> bounds = child == 0
|
|
? std::array<float, 6>{0, 0, std::nextafter(1.0f, 2.0f), 1, 2, 2}
|
|
: child == 1 ? std::array<float, 6>{-1, 1, -1, -0.0f, 2, 2}
|
|
: std::array<float, 6>{1, 1, 1, -1, -1, -1};
|
|
for (u32 i = 0; i < 6; ++i) data[NodeStart + 68 + child * 6 + i] = bits(bounds[i]);
|
|
}
|
|
// An FP32 node straddles two guest pages with a 64-byte gap in physical backing.
|
|
std::copy_n(data.begin() + NodeStart + 32, 16, data.begin() + NodeStart + 255 * 16);
|
|
std::copy_n(data.begin() + NodeStart + 48, 16, data.begin() + NodeStart + 257 * 16);
|
|
const std::array<std::array<float, 2>, 4> origins{{{0, 0}, {1, 0}, {.5f, 1.5f}, {-.25f, 1}}};
|
|
std::array<std::array<u32, 4>, LaneCount> expected{};
|
|
for (u32 lane = 0; lane < LaneCount; ++lane) {
|
|
const auto kind = lane % 8;
|
|
const auto input = lane * 16;
|
|
data[input] = kind < 4 ? kind : kind == 4 ? 12 : kind == 5 ? 21 : kind == 6 ? 6 : 0x1000;
|
|
data[input + 1] = bits(6);
|
|
data[input + 2] = bits(kind < 4 ? origins[kind][0] : 0);
|
|
data[input + 3] = bits(kind < 4 ? origins[kind][1] : 0);
|
|
data[input + 7] = bits(1);
|
|
data[input + 8] = data[input + 9] = Infinity;
|
|
data[input + 10] = bits(1);
|
|
if (kind < 4) {
|
|
const float denominator = kind == 3 ? -2 : -4;
|
|
const float i = kind == 0 || kind == 3 ? -1 : -2;
|
|
const float j = kind == 3 ? -.5f : -1;
|
|
expected[lane] = {bits(2 * denominator), bits(denominator),
|
|
barycentrics ? bits(i) : Flag + kind,
|
|
barycentrics ? bits(j) : 1};
|
|
} else if (kind < 6) {
|
|
expected[lane] = sorted ? std::array<u32, 4>{children[1], children[2], children[0], Invalid}
|
|
: std::array<u32, 4>{children[0], children[1], children[2], Invalid};
|
|
} else {
|
|
expected[lane] = kind == 6 ? std::array<u32, 4>{Invalid, Invalid, Invalid, Invalid}
|
|
: std::array<u32, 4>{Infinity, bits(1), Invalid, 0};
|
|
}
|
|
if (lane >= 8 && lane < 12) {
|
|
data[input + 2] = 0x7fc00000; // invalid ray origin
|
|
expected[lane] = {Infinity, bits(1), Invalid, 0};
|
|
} else if (lane == 12 || lane == 13) {
|
|
data[input + 1] = bits(1); // nearest face equals the exclusive extent
|
|
expected[lane] = {Invalid, Invalid, Invalid, Invalid};
|
|
} else if (lane >= 16 && lane < 20) {
|
|
const u32 axis = lane & 1;
|
|
data[input + 2 + axis] = bits(origins[kind][axis] - 4);
|
|
data[input + 5 + axis] = bits(2);
|
|
data[input + 8 + axis] = bits(.5f);
|
|
// Dominant X/Y projection scales all intersection numerators and the
|
|
// denominator by four; the distance and barycentrics remain unchanged.
|
|
for (u32 i = 0; i < (barycentrics ? 4u : 2u); ++i)
|
|
expected[lane][i] = bits(std::bit_cast<float>(expected[lane][i]) * 4);
|
|
} else if (lane == 22) {
|
|
data[input] = 37; // node 4, FP32
|
|
data[input + 1] = bits(10);
|
|
for (u32 i = 5; i <= 10; ++i) data[input + i] = bits(1);
|
|
expected[lane] = {grow ? children[0] : Invalid, Invalid, Invalid, Invalid};
|
|
} else if (lane >= 24 && lane < 28) {
|
|
data[input + 4] = bits(4);
|
|
data[input + 7] = data[input + 10] = bits(-1);
|
|
for (u32 i = 0; i < (barycentrics ? 4u : 2u); ++i)
|
|
expected[lane][i] = bits(-std::bit_cast<float>(expected[lane][i]));
|
|
} else if (lane == 28) {
|
|
data[input] = 0;
|
|
data[input + 3] = bits(-1); // horizontal triangle edge: top-left rule rejects
|
|
expected[lane] = {Infinity, bits(1), barycentrics ? bits(-2) : Flag,
|
|
barycentrics ? bits(-2) : 0};
|
|
} else if (lane == 29) {
|
|
data[input] = (255 << 3) | 5;
|
|
}
|
|
if (wide) {
|
|
// Keep the same small backing while placing nodes beyond the 32-bit pointer range.
|
|
std::copy_backward(data.begin() + input + 1, data.begin() + input + 11,
|
|
data.begin() + input + 12);
|
|
data[input + 1] = 1;
|
|
if (lane == 14 || lane == 15) {
|
|
data[input] = lane == 14 ? 0 : 5;
|
|
// Neither an oversized index nor a shifted-address wrap may read node zero.
|
|
data[input + 1] = lane == 14 ? 0x00001000 : 0x20000000;
|
|
expected[lane] = lane == 14 ? std::array<u32, 4>{Infinity, bits(1), Invalid, 0}
|
|
: std::array<u32, 4>{Invalid, Invalid, Invalid, Invalid};
|
|
}
|
|
}
|
|
}
|
|
test.expected = test.initial;
|
|
for (u32 lane = 0; lane < LaneCount; ++lane) {
|
|
std::copy(expected[lane].begin(), expected[lane].end(),
|
|
test.expected.begin() + OutputStart + lane * 4);
|
|
auto bounded = expected[lane];
|
|
if (lane % 8 == 5) bounded = {Invalid, Invalid, Invalid, Invalid};
|
|
if (lane >= 20) bounded = {0x12345678, 0x12345679, 0x1234567a, 0x1234567b};
|
|
std::copy(bounded.begin(), bounded.end(),
|
|
test.expected.begin() + OutputStart + LaneCount * 4 + lane * 4);
|
|
}
|
|
auto &code = test.code;
|
|
// The resource is a GPU value, not a materialized host image descriptor.
|
|
AppendSMovLiteral(&code, 8, GuestBase >> 8);
|
|
AppendSMovLiteral(&code, 9, (sorted ? 0x80000000 : 0) | (grow << 23));
|
|
AppendSMovLiteral(&code, 10, node_base + 256);
|
|
AppendSMovLiteral(&code, 11, barycentrics ? 0x81000000 : 0x80000000);
|
|
code.push_back(EncodeVop2(0x1a, 1, InlineU32(6), 0));
|
|
for (u32 offset : {0u, 4u, 8u}) {
|
|
code.insert(code.end(), {EncodeMubuf0(offset == 8 && !wide ? 0x0f : 0x0e, offset * 4),
|
|
EncodeMubuf1(4 + offset, 0, 1)});
|
|
}
|
|
code.insert(code.end(), {EncodeMimg0(wide ? 0xe7 : 0xe6, 15, 0, false, 0, true),
|
|
EncodeMimg1(20, 4, 2)});
|
|
code.push_back(EncodeVop2(0x1a, 2, InlineU32(2), 0));
|
|
for (u32 i = 0; i < 4; ++i) AppendStoreVgprAtLaneDwordOffset(&code, 20 + i, 2, OutputStart + i);
|
|
// NSA addressing, the FP32 second-block bound, and inactive destination lanes.
|
|
AppendSMovLiteral(&code, 10, node_base + 2);
|
|
for (u32 i = 0; i < 4; ++i) AppendVMovLiteral(&code, 24 + i, 0x12345678 + i);
|
|
AppendSMovLiteral(&code, 126, 0x000fffff);
|
|
code.insert(code.end(), {EncodeMimg0(wide ? 0xe7 : 0xe6, 15, 3, false, 0, true),
|
|
EncodeMimg1(24, 4, 2), 0x08070605, 0x0c0b0a09,
|
|
wide ? 0x000f0e0du : 0x00000e0du});
|
|
code.push_back(EncodeSop1(0x04, 126, 193));
|
|
for (u32 i = 0; i < 4; ++i)
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 24 + i, 2, OutputStart + LaneCount * 4 + i);
|
|
AppendEnd(&code);
|
|
const auto nodes_address = GuestBase + (uint64_t{node_base} << 6);
|
|
test.bda_mappings = {{nodes_address, NodeStart * sizeof(u32)},
|
|
{nodes_address + 0x4000, (NodeStart + 257 * 16) * sizeof(u32)}};
|
|
test.compute_info.wave_size = LaneCount;
|
|
test.compute_info.threads_num[0] = LaneCount;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
test.required_spirv = {"get_bda_pointer"};
|
|
return test;
|
|
}
|