2026-07-22 18:09:06 +02:00
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-06-16 18:23:25 +00:00
|
|
|
|
|
|
|
|
// Regression test for issue #69: the engine feeds a shared Device::pipelineCache
|
|
|
|
|
// to every vkCreate*Pipelines call and persists it across runs. A blob written
|
|
|
|
|
// by a different GPU (or a corrupt/short file) must be rejected before it is
|
|
|
|
|
// handed to vkCreatePipelineCache, otherwise the driver ignores or rejects it.
|
|
|
|
|
// Device::PipelineCacheDataCompatible is that gate: pure logic over the standard
|
|
|
|
|
// 32-byte VkPipelineCacheHeaderVersionOne header (headerSize, headerVersion,
|
|
|
|
|
// vendorID, deviceID, pipelineCacheUUID) compared against Device::deviceProperties.
|
|
|
|
|
// It needs no GPU, so this test stamps synthetic device identities and headers
|
|
|
|
|
// and drives it directly, mirroring MemoryTypeFallback / UploadStrategy.
|
|
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include "vulkan/vulkan.h"
|
|
|
|
|
|
|
|
|
|
import Crafter.Graphics;
|
|
|
|
|
import std;
|
|
|
|
|
using namespace Crafter;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
int failures = 0;
|
|
|
|
|
|
|
|
|
|
void Check(bool ok, std::string_view what) {
|
|
|
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
|
|
|
|
if (!ok) ++failures;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stamp the device identity the validator compares against.
|
|
|
|
|
void SetDevice(std::uint32_t vendorID, std::uint32_t deviceID,
|
|
|
|
|
const std::array<std::uint8_t, VK_UUID_SIZE>& uuid) {
|
|
|
|
|
Device::deviceProperties = {};
|
|
|
|
|
Device::deviceProperties.vendorID = vendorID;
|
|
|
|
|
Device::deviceProperties.deviceID = deviceID;
|
|
|
|
|
std::memcpy(Device::deviceProperties.pipelineCacheUUID, uuid.data(), VK_UUID_SIZE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build a 32-byte VkPipelineCacheHeaderVersionOne blob, optionally with extra
|
|
|
|
|
// trailing payload bytes (the real cache body). Fields are written little-endian
|
|
|
|
|
// at fixed offsets, matching how the driver lays the header out on disk.
|
|
|
|
|
std::vector<std::byte> MakeHeader(std::uint32_t headerSize,
|
|
|
|
|
std::uint32_t headerVersion,
|
|
|
|
|
std::uint32_t vendorID,
|
|
|
|
|
std::uint32_t deviceID,
|
|
|
|
|
const std::array<std::uint8_t, VK_UUID_SIZE>& uuid,
|
|
|
|
|
std::size_t trailing = 0) {
|
|
|
|
|
std::vector<std::byte> blob(16 + VK_UUID_SIZE + trailing, std::byte{0xAB});
|
|
|
|
|
auto put = [&](std::size_t off, std::uint32_t v) {
|
|
|
|
|
std::memcpy(blob.data() + off, &v, sizeof(v));
|
|
|
|
|
};
|
|
|
|
|
put(0, headerSize);
|
|
|
|
|
put(4, headerVersion);
|
|
|
|
|
put(8, vendorID);
|
|
|
|
|
put(12, deviceID);
|
|
|
|
|
std::memcpy(blob.data() + 16, uuid.data(), VK_UUID_SIZE);
|
|
|
|
|
return blob;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr std::uint32_t kVendor = 0x10DE; // NVIDIA
|
|
|
|
|
constexpr std::uint32_t kDevice = 0x2204; // some GPU device id
|
|
|
|
|
constexpr std::array<std::uint8_t, VK_UUID_SIZE> kUuid = {
|
|
|
|
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
|
|
|
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
|
|
|
|
|
};
|
|
|
|
|
constexpr std::uint32_t kV1 = VK_PIPELINE_CACHE_HEADER_VERSION_ONE;
|
|
|
|
|
constexpr std::uint32_t kHeaderSize = 16 + VK_UUID_SIZE; // 32
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
SetDevice(kVendor, kDevice, kUuid);
|
|
|
|
|
|
|
|
|
|
// --- the happy path: a header written by this exact device --------------
|
|
|
|
|
{
|
|
|
|
|
auto blob = MakeHeader(kHeaderSize, kV1, kVendor, kDevice, kUuid);
|
|
|
|
|
Check(Device::PipelineCacheDataCompatible(blob),
|
|
|
|
|
"matching vendor/device/UUID header is accepted");
|
|
|
|
|
|
|
|
|
|
auto withBody = MakeHeader(kHeaderSize, kV1, kVendor, kDevice, kUuid, /*trailing*/ 4096);
|
|
|
|
|
Check(Device::PipelineCacheDataCompatible(withBody),
|
|
|
|
|
"matching header followed by a cache body is accepted");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- foreign / stale blobs must be rejected -----------------------------
|
|
|
|
|
{
|
|
|
|
|
auto otherVendor = MakeHeader(kHeaderSize, kV1, 0x1002 /*AMD*/, kDevice, kUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(otherVendor),
|
|
|
|
|
"different vendorID is rejected");
|
|
|
|
|
|
|
|
|
|
auto otherDevice = MakeHeader(kHeaderSize, kV1, kVendor, 0x9999, kUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(otherDevice),
|
|
|
|
|
"different deviceID is rejected");
|
|
|
|
|
|
|
|
|
|
std::array<std::uint8_t, VK_UUID_SIZE> otherUuid = kUuid;
|
|
|
|
|
otherUuid[15] ^= 0xFF; // a driver update bumps the UUID
|
|
|
|
|
auto staleUuid = MakeHeader(kHeaderSize, kV1, kVendor, kDevice, otherUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(staleUuid),
|
|
|
|
|
"different pipelineCacheUUID (e.g. driver update) is rejected");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- malformed headers --------------------------------------------------
|
|
|
|
|
{
|
|
|
|
|
auto badVersion = MakeHeader(kHeaderSize, 0xDEAD, kVendor, kDevice, kUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(badVersion),
|
|
|
|
|
"unknown headerVersion is rejected");
|
|
|
|
|
|
|
|
|
|
auto smallHeaderSize = MakeHeader(8, kV1, kVendor, kDevice, kUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(smallHeaderSize),
|
|
|
|
|
"headerSize smaller than the 32-byte header is rejected");
|
|
|
|
|
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible({}),
|
|
|
|
|
"empty blob (no file / cold start) is rejected");
|
|
|
|
|
|
|
|
|
|
std::vector<std::byte> truncated(20, std::byte{0});
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(truncated),
|
|
|
|
|
"blob too short to hold the header is rejected");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- identity follows the active device ---------------------------------
|
|
|
|
|
{
|
|
|
|
|
// Re-stamp as a different device; a blob valid for the old one is now
|
|
|
|
|
// foreign. Guards against the validator caching identity anywhere but
|
|
|
|
|
// Device::deviceProperties.
|
|
|
|
|
auto blob = MakeHeader(kHeaderSize, kV1, kVendor, kDevice, kUuid);
|
|
|
|
|
SetDevice(0x8086 /*Intel*/, 0x1234, kUuid);
|
|
|
|
|
Check(!Device::PipelineCacheDataCompatible(blob),
|
|
|
|
|
"a blob from the previous device is rejected after the device changes");
|
|
|
|
|
SetDevice(kVendor, kDevice, kUuid);
|
|
|
|
|
Check(Device::PipelineCacheDataCompatible(blob),
|
|
|
|
|
"...and accepted again once the matching device is restored");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (failures != 0) {
|
|
|
|
|
std::println("{} check(s) failed", failures);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
std::println("all checks passed");
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|