mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 19:01:23 +02:00
Co-authored-by: Lydacious <81742991+lydacious@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
177 lines
9.3 KiB
C++
177 lines
9.3 KiB
C++
// Tests for gpu::pipeline_cache (runtime/gpu/pipeline_cache_header.h): when
|
|
// a saved pipelines.bin was made by another GPU, driver or OS version (the
|
|
// driver would set it aside, and the first drive stutters while pipelines
|
|
// compile again), and the log line that says so. From the repo root:
|
|
// clang++ -std=c++20 -O2 -UNDEBUG -Iruntime tests/pipeline_cache_header_test.cpp -o build/pipeline_cache_header_test
|
|
// build/pipeline_cache_header_test
|
|
// The two real headers are the first 32 bytes of the pipelines.bin MoltenVK
|
|
// 1.4.2 wrote on an M1 Pro under macOS 26.5 and under macOS 27.0.1.
|
|
#include <gpu/pipeline_cache_header.h>
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace gpu::pipeline_cache;
|
|
|
|
static int g_failures = 0;
|
|
#define CHECK(cond, ...) do { if (!(cond)) { g_failures++; printf(" FAIL %s:%d: %s: ", __FILE__, __LINE__, #cond); printf(__VA_ARGS__); printf("\n"); } } while (0)
|
|
|
|
static const std::vector<uint8_t> kMac265 = {
|
|
0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6b, 0x10, 0x00, 0x00, 0x07, 0x02, 0x05, 0x1a,
|
|
0x00, 0x00, 0x28, 0xa2, 0x1a, 0x05, 0x02, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
|
|
static const std::vector<uint8_t> kMac270 = {
|
|
0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6b, 0x10, 0x00, 0x00, 0x07, 0x02, 0x00, 0x1b,
|
|
0x00, 0x00, 0x28, 0xa2, 0x1b, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
// The device as MoltenVK reports it on each (vkGetPhysicalDeviceProperties).
|
|
static Maker Mac(uint32_t deviceID)
|
|
{
|
|
Maker m{ kAppleVendor, deviceID, {} };
|
|
m.uuid = { 0x00, 0x00, 0x28, 0xa2, uint8_t(deviceID >> 24), uint8_t(deviceID >> 16), uint8_t(deviceID >> 8),
|
|
uint8_t(deviceID), 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
|
|
return m;
|
|
}
|
|
|
|
// A version-one header written least significant byte first, plus `extra` bytes of cache.
|
|
static std::vector<uint8_t> Header(const Maker& m, uint32_t size = 32, uint32_t version = 1, size_t extra = 0)
|
|
{
|
|
std::vector<uint8_t> out;
|
|
for (uint32_t v : { size, version, m.vendorID, m.deviceID })
|
|
for (int i = 0; i < 4; i++)
|
|
out.push_back(uint8_t(v >> (8 * i)));
|
|
out.insert(out.end(), m.uuid.begin(), m.uuid.end());
|
|
out.resize(out.size() + extra, 0xAB);
|
|
return out;
|
|
}
|
|
|
|
static HeaderCheck Check(const std::vector<uint8_t>& data, const Maker& device)
|
|
{
|
|
return CheckHeader(data.data(), data.size(), device);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
const Maker mac265 = Mac(0x1a050207), mac270 = Mac(0x1b000207);
|
|
|
|
// The real headers parse as the device that wrote them.
|
|
HeaderCheck c = Check(kMac270, mac270);
|
|
CHECK(c.verdict == Verdict::Usable, "27.0's own cache on 27.0");
|
|
CHECK(c.file.vendorID == 0x106B && c.file.deviceID == 0x1b000207 && c.file.uuid == mac270.uuid, "fields read LSB first");
|
|
CHECK(LogLine(c, mac270).empty(), "nothing to say about a usable cache");
|
|
CHECK(Check(kMac265, mac265).verdict == Verdict::Usable, "26.5's own cache on 26.5");
|
|
CHECK(Header(mac270) == kMac270 && Header(mac265) == kMac265, "the test's writer matches MoltenVK's bytes");
|
|
|
|
// The macOS update: 26.5's cache on 27.0, as the 27.0.1 upgrade saw it.
|
|
std::vector<uint8_t> upgraded = kMac265;
|
|
upgraded.resize(2563 * 1024 + 100, 0x5A);
|
|
c = Check(upgraded, mac270);
|
|
CHECK(c.verdict == Verdict::OtherMaker, "26.5's cache on 27.0");
|
|
CHECK(!c.vendorDiffers && c.deviceDiffers && c.uuidDiffers, "the device ID and UUID carry the OS");
|
|
std::string line = LogLine(c, mac270);
|
|
CHECK(line == "[renderer] pipeline cache from another GPU, driver or OS version: rebuilding shaders, expect a brief "
|
|
"stutter on first use (OS 26.5 -> 27.0, device ID 1a050207 -> 1b000207, cache UUID "
|
|
"000028a21a0502070000000100000000 -> 000028a21b0002070000000100000000; 2563 KB set aside)",
|
|
"%s", line.c_str());
|
|
// And back (a second Mac, or a downgrade).
|
|
line = LogLine(Check(kMac270, mac265), mac265);
|
|
CHECK(line.find("(OS 27.0 -> 26.5, device ID 1b000207 -> 1a050207, ") != std::string::npos, "%s", line.c_str());
|
|
CHECK(line.find("; 0 KB set aside)") != std::string::npos, "a header-only file: %s", line.c_str());
|
|
|
|
// A new MoltenVK on the same macOS (a release that bundles another): the UUID alone.
|
|
Maker newMvk = mac270;
|
|
newMvk.uuid[3] = 0xa3;
|
|
c = Check(kMac270, newMvk);
|
|
CHECK(c.verdict == Verdict::OtherMaker && !c.vendorDiffers && !c.deviceDiffers && c.uuidDiffers, "MoltenVK update");
|
|
line = LogLine(c, newMvk);
|
|
CHECK(line.find("(cache UUID 000028a21b0002070000000100000000 -> 000028a31b0002070000000100000000; 0 KB set aside)")
|
|
!= std::string::npos && line.find("(OS ") == std::string::npos && line.find("device ID") == std::string::npos,
|
|
"%s", line.c_str());
|
|
|
|
// Mesa on the Steam Machine: a SteamOS update changes the UUID (its build ID) only.
|
|
Maker radv{ 0x1002, 0x7480, {} };
|
|
for (int i = 0; i < 16; i++)
|
|
radv.uuid[i] = uint8_t(0x10 + i);
|
|
Maker radvUpdated = radv;
|
|
radvUpdated.uuid[15] ^= 0xFF;
|
|
c = Check(Header(radv, 32, 1, 4096), radvUpdated);
|
|
CHECK(c.verdict == Verdict::OtherMaker && c.uuidDiffers && !c.deviceDiffers, "Mesa update");
|
|
CHECK(LogLine(c, radvUpdated).find("(cache UUID 101112131415161718191a1b1c1d1e1f -> 101112131415161718191a1b1c1d1ee0; 4 KB set aside)")
|
|
!= std::string::npos, "%s", LogLine(c, radvUpdated).c_str());
|
|
CHECK(Check(Header(radv, 32, 1, 4096), radv).verdict == Verdict::Usable, "Mesa, same build");
|
|
|
|
// Another GPU: the vendor and device IDs, and no OS reading (it's not one Apple GPU's ID against another).
|
|
c = Check(Header(radv), mac270);
|
|
CHECK(c.verdict == Verdict::OtherMaker && c.vendorDiffers && c.deviceDiffers && c.uuidDiffers, "AMD cache on an Apple GPU");
|
|
line = LogLine(c, mac270);
|
|
CHECK(line.find("(vendor 1002 -> 106b, device ID 00007480 -> 1b000207, cache UUID ") != std::string::npos
|
|
&& line.find("(OS ") == std::string::npos, "%s", line.c_str());
|
|
// Another Apple GPU on the same macOS: the low half differs, the OS doesn't.
|
|
const Maker m3 = Mac(0x1b000209);
|
|
c = Check(kMac270, m3);
|
|
CHECK(c.verdict == Verdict::OtherMaker && c.deviceDiffers, "M3 against an M1 cache");
|
|
line = LogLine(c, m3);
|
|
CHECK(line.find("(device ID 1b000207 -> 1b000209, ") != std::string::npos && line.find("(OS ") == std::string::npos, "%s",
|
|
line.c_str());
|
|
// An OS update that also moves the GPU family word: still read as the OS.
|
|
const Maker m270b = Mac(0x1b000208);
|
|
line = LogLine(Check(kMac265, m270b), m270b);
|
|
CHECK(line.find("(OS 26.5 -> 27.0, device ID 1a050207 -> 1b000208, ") != std::string::npos, "%s", line.c_str());
|
|
// Intel and AMD Macs report PCI IDs under MoltenVK: no OS reading for them.
|
|
const Maker intel{ 0x8086, 0x1b000207, mac270.uuid }, intelOld{ 0x8086, 0x1a050207, mac265.uuid };
|
|
line = LogLine(Check(Header(intelOld), intel), intel);
|
|
CHECK(line.find("(device ID 1a050207 -> 1b000207, ") != std::string::npos && line.find("(OS ") == std::string::npos, "%s",
|
|
line.c_str());
|
|
|
|
// Nothing, and files that aren't a version-one header.
|
|
c = CheckHeader(nullptr, 0, mac270);
|
|
CHECK(c.verdict == Verdict::Empty && LogLine(c, mac270).empty(), "no file");
|
|
c = CheckHeader(kMac270.data(), 31, mac270);
|
|
CHECK(c.verdict == Verdict::Unreadable, "31 bytes");
|
|
line = LogLine(c, mac270);
|
|
CHECK(line == "[renderer] pipeline cache file not readable: rebuilding shaders, expect a brief stutter on first use "
|
|
"(31 bytes, shorter than its header)", "%s", line.c_str());
|
|
CHECK(CheckHeader(kMac270.data(), 1, mac270).verdict == Verdict::Unreadable, "1 byte");
|
|
c = Check(Header(mac270, 40, 1, 8), mac270);
|
|
CHECK(c.verdict == Verdict::Unreadable && c.headerSize == 40, "a longer header (Mesa compares headerSize too)");
|
|
line = LogLine(c, mac270);
|
|
CHECK(line.find("(header size 40, version 1; 0 KB set aside)") != std::string::npos, "%s", line.c_str());
|
|
c = Check(Header(mac270, 32, 2, 2048), mac270);
|
|
CHECK(c.verdict == Verdict::Unreadable && c.headerVersion == 2, "another header version");
|
|
CHECK(LogLine(c, mac270).find("(header size 32, version 2; 2 KB set aside)") != std::string::npos, "%s", LogLine(c, mac270).c_str());
|
|
std::vector<uint8_t> bigEndian = kMac270; // a writer that ignored the byte order: unreadable, not another device
|
|
std::swap(bigEndian[0], bigEndian[3]);
|
|
std::swap(bigEndian[4], bigEndian[7]);
|
|
CHECK(Check(bigEndian, mac270).verdict == Verdict::Unreadable, "big-endian header fields");
|
|
|
|
// Each field alone decides.
|
|
for (int field = 0; field < 3; field++)
|
|
{
|
|
Maker other = mac270;
|
|
if (field == 0)
|
|
other.vendorID ^= 1;
|
|
else if (field == 1)
|
|
other.deviceID ^= 1;
|
|
else
|
|
other.uuid[15] ^= 1;
|
|
c = Check(kMac270, other);
|
|
CHECK(c.verdict == Verdict::OtherMaker && c.vendorDiffers == (field == 0) && c.deviceDiffers == (field == 1)
|
|
&& c.uuidDiffers == (field == 2), "field %d", field);
|
|
}
|
|
for (size_t i = 0; i < 16; i++)
|
|
{
|
|
Maker other = mac270;
|
|
other.uuid[i] ^= 0x80;
|
|
CHECK(Check(kMac270, other).verdict == Verdict::OtherMaker, "UUID byte %zu", i);
|
|
}
|
|
|
|
if (g_failures)
|
|
{
|
|
printf("pipeline_cache_header_test: %d FAILED\n", g_failures);
|
|
return 1;
|
|
}
|
|
printf("pipeline_cache_header_test: all passed\n");
|
|
return 0;
|
|
}
|