perf(agc): load the pipeline cache file only on the windowed device and save without re-reading an unchanged file

The headless device is replaced by the windowed one at window attach and then only
lingers for in-flight workers, but it parsed its own full copy of the cache file.
It now starts empty (it still merges what it compiles into the file). Load reuses
the file buffer as the initial data instead of copying it, and save() writes the
live data directly when the file still carries the header this object loaded or
wrote, skipping the read-back and the merge cache.
This commit is contained in:
AlxcBTW committed 2026-10-15 15:26:47 +02:00
1 parent 8dc92ae290
commit d65cc07a1b
4 files changed
+35 -11

No files matched your search

@@ -1839,7 +1839,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
Graphics::PrepareImportWatch(graphicsContext());
state->bufferPool = std::make_shared<Graphics::BufferPool>(graphicsContext());
state->emptyBuffer = std::make_unique<Graphics::Buffer>(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties);
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties, window != nullptr || compileOnly);
state->detiler = std::make_unique<Graphics::TextureDetiler>(graphicsContext());
state->textureCache = std::make_unique<Graphics::TextureCache>(graphicsContext());
state->colorTransfer = std::make_unique<Graphics::GpuColorTransfer>(graphicsContext());
@@ -18,7 +18,7 @@ class PipelineCache {
public:
static constexpr int SaveIntervalSeconds = 10;
PipelineCache(const Context& context, const VkPhysicalDeviceProperties& properties);
PipelineCache(const Context& context, const VkPhysicalDeviceProperties& properties, bool loadFromFile = true);
~PipelineCache();
PipelineCache(const PipelineCache&) = delete;
@@ -35,7 +35,7 @@ std::mutex& pathMutex(const std::filesystem::path& path) {
}
PipelineCache::PipelineCache(const Context& context, const VkPhysicalDeviceProperties& properties) : context(context), properties(properties) {
PipelineCache::PipelineCache(const Context& context, const VkPhysicalDeviceProperties& properties, bool loadFromFile) : context(context), properties(properties) {
static_assert(sizeof(FileHeader) == 24);
const auto directory = ShaderRecompiler::ShaderCacheDirectory();
if (!directory.empty()) {
@@ -52,7 +52,7 @@ PipelineCache::PipelineCache(const Context& context, const VkPhysicalDevicePrope
std::vector<std::byte> initialData;
if (!path.empty()) {
std::lock_guard pathLock(pathMutex(path));
load(initialData);
if (loadFromFile) load(initialData);
}
VkPipelineCacheCreateInfo info{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
info.initialDataSize = initialData.size();
@@ -145,7 +145,7 @@ std::optional<PipelineCache::FileHeader> PipelineCache::readHeader() const {
}
void PipelineCache::load(std::vector<std::byte>& initialData) {
const auto file = readFile();
auto file = readFile();
if (file.state == FileState::Missing) return;
if (file.state == FileState::Unreadable) {
std::fprintf(stderr, "[pipeline-cache] cannot read %s; starting empty\n", path.string().c_str());
@@ -155,8 +155,8 @@ void PipelineCache::load(std::vector<std::byte>& initialData) {
std::fprintf(stderr, "[pipeline-cache] ignoring %s: %s\n", path.string().c_str(), file.invalidReason);
return;
}
const auto data = file.Payload();
initialData.assign(data.begin(), data.end());
initialData = std::move(file.contents);
initialData.erase(initialData.begin(), initialData.begin() + sizeof(FileHeader));
loadedBytes = initialData.size();
settledBytes = initialData.size();
settledFile = file.header;
@@ -171,20 +171,22 @@ void PipelineCache::save(bool final) {
auto synchronizationLock = synchronization.LockForSnapshot();
if (getData(context.device, cache, &liveBytes, nullptr) != VK_SUCCESS || liveBytes == 0) return;
}
if (liveBytes == settledBytes && readHeader() == settledFile) return;
const auto onDisk = readHeader();
if (liveBytes == settledBytes && onDisk == settledFile) return;
const bool fileUnchanged = settledFile.has_value() && onDisk == settledFile;
const auto started = std::chrono::steady_clock::now();
const auto settle = [&](const std::optional<FileHeader>& file) {
settledBytes = liveBytes;
settledFile = file;
};
const auto disk = readFile();
const auto disk = fileUnchanged ? File{} : readFile();
if (disk.state == FileState::Unreadable) {
std::fprintf(stderr, "[pipeline-cache] cannot read %s; not saving over it\n", path.string().c_str());
settle(readHeader());
return;
}
if (disk.state == FileState::Invalid) std::fprintf(stderr, "[pipeline-cache] replacing %s: %s\n", path.string().c_str(), disk.invalidReason);
std::size_t floor = 0;
std::size_t floor = fileUnchanged ? std::max<std::size_t>(loadedBytes, settledFile->dataBytes) : 0;
VkPipelineCache merged = VK_NULL_HANDLE;
struct DestroyMerged {
const Context& context;
@@ -231,7 +233,7 @@ void PipelineCache::save(bool final) {
}
if (size < floor) {
std::fprintf(stderr, "[pipeline-cache] keeping %s: the merged cache has %.1f KiB, less than the %.1f KiB on disk or loaded at start\n", path.string().c_str(), static_cast<double>(size) / 1024.0, static_cast<double>(floor) / 1024.0);
settle(disk.header);
settle(fileUnchanged ? *settledFile : disk.header);
return;
}
const FileHeader header{FileMagic, FileFormat, size, ShaderRecompiler::HashBytes(std::span(file).subspan(sizeof(FileHeader)))};
@@ -7429,6 +7429,28 @@ int pipelineCacheFileTests(const Device& device) {
PipelineCache idle(context, properties);
}
Require(contents(path) == stored, "an object that added no pipelines rewrote the cache file");
{
PipelineCache unseeded(context, properties, false);
Require(!holds(unseeded.Handle(), 0), "a cache created without the file already held a pipeline of the file");
}
{
PipelineCache reloaded(context, properties);
Require(holds(reloaded.Handle(), 0) && holds(reloaded.Handle(), 1), "an unseeded cache dropped pipelines from the cache file when it saved");
}
std::filesystem::remove_all(directory);
{
PipelineCache writer(context, properties);
build(writer.Handle(), 0);
}
{
PipelineCache extender(context, properties);
Require(holds(extender.Handle(), 0), "the cache file lost a pipeline");
build(extender.Handle(), 1);
}
{
PipelineCache reloaded(context, properties);
Require(holds(reloaded.Handle(), 0) && holds(reloaded.Handle(), 1), "a cache that saved over an unchanged file lost pipelines");
}
{
std::ofstream corrupt(path, std::ios::binary | std::ios::trunc);
corrupt << "not a pipeline cache";