Caches and saves: read where they lie, write what changed

Shader caches are walked where their entries lie in the mapped file,
each value handed to its reader from there - copied only when
misaligned for its type - with no allocation or read call per entry.
An entry is appended in one write. The Vulkan pipeline cache is made
from the stored blob in place, and stored again only when it changed,
atomically.

A raw memory card writes back only the blocks that changed, a run of
them at a time, keeping a block marked until it is written. A GCI save
is replaced whole or not at all, in two writes. The Gecko code handler
is read from its file straight into emulated memory, the DSP ROMs are
swapped out of the mapping, and system menu files go into the NAND from
the mapping.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 03:27:39 +01:00
1 parent 30a5010c5a
commit 54b8264a05
8 files changed
+268 -147

No files matched your search

+105 -50
View File
@@ -7,12 +7,16 @@
#include <cstring>
#include <memory>
#include <string>
#include <cstdint>
#include <type_traits>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Common/Version.h"
#include <streams/file_stream.h>
// On disk format:
// header{
// u32 'DCAC';
@@ -62,49 +66,33 @@ public:
// close any currently opened file
Close();
m_num_entries = 0;
// try opening for reading/writing
m_file.Open(filename, "r+b");
const u64 file_size = m_file.GetSize();
m_header.Init();
if (m_file.IsOpen() && ValidateHeader())
u64 valid_end = 0;
{
// good header, read some key/value pairs
K key;
std::unique_ptr<V[]> value = nullptr;
u32 value_size = 0;
u32 entry_number = 0;
u64 last_valid_value_start = m_file.Tell();
while (m_file.ReadArray(&value_size, 1))
// Entries are walked where they lie in the mapped file and values handed to the
// reader from there, copied only when misaligned for V or when the file is not mapped.
File::IOFile in(filename, "rb");
u64 size = 0;
const u8* map = in.GetMappedPtr(&size);
std::unique_ptr<u8[]> owned;
if (in && !map && (size = in.GetSize()) != 0)
{
const u64 next_extent = m_file.Tell() + sizeof(value_size) + value_size;
if (next_extent > file_size)
break;
value = std::make_unique_for_overwrite<V[]>(value_size);
// read key/value and pass to reader
if (m_file.ReadArray(&key, 1) && m_file.ReadArray(value.get(), value_size) &&
m_file.ReadArray(&entry_number, 1) && entry_number == m_num_entries + 1)
{
last_valid_value_start = m_file.Tell();
reader.Read(key, value.get(), value_size);
}
else
{
break;
}
m_num_entries++;
owned = std::make_unique_for_overwrite<u8[]>(size);
if (in.ReadBytes(owned.get(), size))
map = owned.get();
}
m_file.ClearError();
m_file.Seek(last_valid_value_start, File::SeekOrigin::Begin);
if (map && size >= sizeof(Header) && !std::memcmp(&m_header, map, sizeof(Header)))
valid_end = ReadEntries(map, size, reader);
}
return m_num_entries;
if (valid_end)
{
// appends go after the last whole entry
m_file.Open(filename, "r+b");
if (m_file.IsOpen() && m_file.Seek(valid_end, File::SeekOrigin::Begin))
return m_num_entries;
m_num_entries = 0;
}
// failed to open file for reading or bad header
@@ -115,6 +103,30 @@ public:
return 0;
}
// Replaces the file with a store of this one entry, atomically, unless it already holds
// exactly that.
static bool WriteSingle(const std::string& filename, const K& key, const V* value,
u32 value_size)
{
LinearDiskCache cache;
const size_t value_bytes = size_t(value_size) * sizeof(V);
const u32 entry_number = 1;
std::vector<u8> out(sizeof(Header) + sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32));
u8* p = out.data();
cache.m_header.Init();
std::memcpy(p, &cache.m_header, sizeof(Header));
p += sizeof(Header);
std::memcpy(p, &value_size, sizeof(u32));
std::memcpy(p + sizeof(u32), &key, sizeof(K));
if (value_bytes)
std::memcpy(p + sizeof(u32) + sizeof(K), value, value_bytes);
std::memcpy(p + sizeof(u32) + sizeof(K) + value_bytes, &entry_number, sizeof(u32));
if (filestream_matches_buf(filename.c_str(), out.data(), out.size()))
return true;
return filestream_write_file_atomic(filename.c_str(), out.data(), s64(out.size()));
}
void Sync() { m_file.Flush(); }
void Close()
{
@@ -122,28 +134,70 @@ public:
m_file.Close();
}
// Appends a key-value pair to the store.
// Appends a key-value pair to the store, in one write.
void Append(const K& key, const V* value, u32 value_size)
{
// TODO: Should do a check that we don't already have "key"? (I think each caller does that
// already.)
m_file.WriteArray(&value_size, 1);
m_file.WriteArray(&key, 1);
m_file.WriteArray(value, value_size);
const size_t value_bytes = size_t(value_size) * sizeof(V);
const size_t entry_bytes = sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32);
u8* out;
if (m_append_buffer.size() < entry_bytes)
m_append_buffer.resize(entry_bytes);
out = m_append_buffer.data();
m_num_entries++;
m_file.WriteArray(&m_num_entries, 1);
std::memcpy(out, &value_size, sizeof(u32));
std::memcpy(out + sizeof(u32), &key, sizeof(K));
if (value_bytes)
std::memcpy(out + sizeof(u32) + sizeof(K), value, value_bytes);
std::memcpy(out + sizeof(u32) + sizeof(K) + value_bytes, &m_num_entries, sizeof(u32));
m_file.WriteBytes(out, entry_bytes);
}
private:
void WriteHeader() { m_file.WriteArray(&m_header, 1); }
bool ValidateHeader()
// Hands each whole entry of [base, base + size) after the header to the reader; returns
// the offset where the last whole one ends.
u64 ReadEntries(const u8* base, u64 size, LinearDiskCacheReader<K, V>& reader)
{
char file_header[sizeof(Header)];
std::unique_ptr<V[]> aligned;
u32 aligned_capacity = 0;
u64 pos = sizeof(Header);
return (m_file.ReadArray(file_header, sizeof(Header)) &&
!memcmp((const char*)&m_header, file_header, sizeof(Header)));
for (;;)
{
u32 value_size;
u32 entry_number;
K key;
if (size - pos < sizeof(u32) + sizeof(K))
break;
std::memcpy(&value_size, base + pos, sizeof(u32));
const u64 value_bytes = u64(value_size) * sizeof(V);
if (size - pos - sizeof(u32) - sizeof(K) < value_bytes + sizeof(u32))
break;
std::memcpy(&key, base + pos + sizeof(u32), sizeof(K));
const u8* value = base + pos + sizeof(u32) + sizeof(K);
std::memcpy(&entry_number, value + value_bytes, sizeof(u32));
if (entry_number != m_num_entries + 1)
break;
if (reinterpret_cast<uintptr_t>(value) % alignof(V) != 0)
{
if (aligned_capacity < value_size)
{
aligned = std::make_unique_for_overwrite<V[]>(value_size);
aligned_capacity = value_size;
}
std::memcpy(aligned.get(), value, value_bytes);
value = reinterpret_cast<const u8*>(aligned.get());
}
reader.Read(key, reinterpret_cast<const V*>(value), value_size);
m_num_entries++;
pos += sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32);
}
return pos;
}
void WriteHeader() { m_file.WriteArray(&m_header, 1); }
struct Header
{
void Init()
@@ -162,6 +216,7 @@ private:
} m_header;
File::IOFile m_file;
std::vector<u8> m_append_buffer;
u32 m_num_entries = 0;
};
} // namespace Common
+16 -8
View File
@@ -12,6 +12,7 @@
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Core/AchievementManager.h"
#include "Core/Config/MainSettings.h"
@@ -19,6 +20,7 @@
#include "Core/Host.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/HW/Memmap.h"
#include "Core/System.h"
namespace Gecko
@@ -110,15 +112,16 @@ std::vector<GeckoCode> SetAndReturnActiveCodes(std::span<const GeckoCode> gcodes
// NOTE: Refer to "codehandleronly.s" from Gecko OS.
static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
{
std::string data;
if (!File::ReadFileToString(File::GetSysDirectory() + GECKO_CODE_HANDLER, data))
File::IOFile handler(File::GetSysDirectory() + GECKO_CODE_HANDLER, "rb");
if (!handler)
{
ERROR_LOG_FMT(ACTIONREPLAY,
"Could not enable cheats because " GECKO_CODE_HANDLER " was missing.");
return Installation::Failed;
}
if (data.size() > INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS - CODE_SIZE)
const u64 handler_size = handler.GetSize();
if (handler_size > INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS - CODE_SIZE)
{
ERROR_LOG_FMT(ACTIONREPLAY, GECKO_CODE_HANDLER " is too big. The file may be corrupt.");
return Installation::Failed;
@@ -130,12 +133,17 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
mmio_addr = 0xCD;
}
// Install code handler
for (u32 i = 0; i < data.size(); ++i)
PowerPC::MMU::HostWrite<u8>(guard, data[i], INSTALLER_BASE_ADDRESS + i);
// Install code handler, from the file straight into emulated memory
{
u8* const dest =
guard.GetSystem().GetMemory().GetPointerForRange(INSTALLER_BASE_ADDRESS, handler_size);
if (!dest || !handler.ReadBytes(dest, handler_size))
return Installation::Failed;
}
const u32 data_size = static_cast<u32>(handler_size);
// Patch the code handler to the current system type (Gamecube/Wii)
for (u32 h = 0; h < data.length(); h += 4)
for (u32 h = 0; h < data_size; h += 4)
{
// Patch MMIO address
if (PowerPC::MMU::HostRead<u32>(guard, INSTALLER_BASE_ADDRESS + h) ==
@@ -147,7 +155,7 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
}
const u32 codelist_base_address =
INSTALLER_BASE_ADDRESS + static_cast<u32>(data.size()) - CODE_SIZE;
INSTALLER_BASE_ADDRESS + data_size - CODE_SIZE;
const u32 codelist_end_address = INSTALLER_END_ADDRESS;
// Write a magic value to 'gameid' (codehandleronly does not actually read this).
+13 -6
View File
@@ -3,6 +3,7 @@
#include "Core/HW/DSPLLE/DSPLLE.h"
#include <cstring>
#include <string>
#include "Common/CallGate.h"
@@ -11,6 +12,7 @@
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/Thread.h"
#include "Core/Config/MainSettings.h"
@@ -85,20 +87,25 @@ void DSPLLE::DSPThread(void* data)
static bool LoadDSPRom(u16* rom, const std::string& filename, u32 size_in_bytes)
{
std::string bytes;
if (!File::ReadFileToString(filename, bytes))
File::IOFile file(filename, "rb");
if (!file)
return false;
if (bytes.size() != size_in_bytes)
if (file.GetSize() != size_in_bytes)
{
ERROR_LOG_FMT(DSPLLE, "{} has a wrong size ({}, expected {})", filename, bytes.size(),
ERROR_LOG_FMT(DSPLLE, "{} has a wrong size ({}, expected {})", filename, file.GetSize(),
size_in_bytes);
return false;
}
const u16* words = reinterpret_cast<const u16*>(bytes.c_str());
// swapped from the mapping where the file has one, else in place
const u8* map = file.GetMappedPtr();
if (map)
std::memcpy(rom, map, size_in_bytes);
else if (!file.ReadBytes(rom, size_in_bytes))
return false;
for (u32 i = 0; i < size_in_bytes / 2; ++i)
rom[i] = Common::swap16(words[i]);
rom[i] = Common::swap16(rom[i]);
return true;
}
@@ -22,6 +22,8 @@
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include <streams/file_stream.h>
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
@@ -683,30 +685,20 @@ void GCMemcardDirectory::WriteQueuedJobs()
continue;
}
File::IOFile gci(job.filename, "wb");
if (gci)
// replaced whole or left as it was: a save is never half written
RFILE* gci = filestream_open_atomic(job.filename.c_str());
if (!gci)
{
gci.WriteBytes(&job.header, Memcard::DENTRY_SIZE);
for (const Memcard::GCMBlock& block : job.blocks)
gci.WriteBytes(block.m_block.data(), Memcard::BLOCK_SIZE);
if (gci.IsGood())
{
Core::DisplayMessage("Wrote save contents to GCI Folder", 4000);
}
else
{
Core::DisplayMessage(fmt::format("Failed to write save contents to {}", job.filename),
10000);
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to save data to {}", job.filename);
}
}
else
{
Core::DisplayMessage(fmt::format("Failed to open file at {} for writing", job.filename),
10000);
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to open file at {} for writing", job.filename);
continue;
}
static_assert(sizeof(Memcard::GCMBlock) == Memcard::BLOCK_SIZE);
const s64 blocks_size = s64(job.blocks.size()) * Memcard::BLOCK_SIZE;
const bool ok =
filestream_write(gci, &job.header, Memcard::DENTRY_SIZE) == Memcard::DENTRY_SIZE &&
(!blocks_size || filestream_write(gci, job.blocks.data(), blocks_size) == blocks_size);
if (filestream_commit_atomic(gci, job.filename.c_str(), ok) != 0)
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to save data to {}", job.filename);
}
}
+55 -4
View File
@@ -5,6 +5,7 @@
#include <cstddef>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
@@ -44,11 +45,13 @@ MemoryCard::MemoryCard(std::string filename, ExpansionInterface::Slot card_slot,
// Measure size of the existing memcard file.
m_memory_card_size = (u32)file.GetSize();
m_nintendo_card_id = m_memory_card_size / SIZE_TO_Mb;
m_memcard_data = std::make_unique<u8[]>(m_memory_card_size);
memset(&m_memcard_data[0], 0xFF, m_memory_card_size);
m_memcard_data = std::make_unique_for_overwrite<u8[]>(m_memory_card_size);
INFO_LOG_FMT(EXPANSIONINTERFACE, "Reading memory card {}", m_filename);
file.ReadBytes(&m_memcard_data[0], m_memory_card_size);
size_t got = 0;
file.ReadArray(&m_memcard_data[0], m_memory_card_size, &got);
if (got < m_memory_card_size)
memset(&m_memcard_data[got], 0xFF, m_memory_card_size - got);
}
else
{
@@ -79,6 +82,7 @@ MemoryCard::MemoryCard(std::string filename, ExpansionInterface::Slot card_slot,
// it's safe to startup the flush thread (which reads them).
m_flush_buffer = std::make_unique<u8[]>(m_memory_card_size);
memcpy(&m_flush_buffer[0], &m_memcard_data[0], m_memory_card_size);
m_flush_dirty.assign((m_memory_card_size + Memcard::BLOCK_SIZE - 1) / Memcard::BLOCK_SIZE, 0);
m_flush_thread = sthread_create(Common::ThreadEntry<&MemoryCard::FlushThread>, this);
}
@@ -121,10 +125,54 @@ void MemoryCard::ApplyChanges()
memset(&m_flush_buffer[change->offset], 0xFF, change->length);
else
memcpy(&m_flush_buffer[change->offset], change->data, change->length);
if (change->length)
{
const u32 last = (change->offset + change->length - 1) / Memcard::BLOCK_SIZE;
for (u32 block = change->offset / Memcard::BLOCK_SIZE; block <= last; block++)
m_flush_dirty[block] = 1;
}
std::free(change);
}
}
bool MemoryCard::WriteChanges(File::IOFile& file, bool whole)
{
const u32 blocks = static_cast<u32>(m_flush_dirty.size());
u32 block = 0;
// a block stays marked until it is written
if (whole)
{
if (!file.WriteBytes(&m_flush_buffer[0], m_memory_card_size))
return false;
std::fill(m_flush_dirty.begin(), m_flush_dirty.end(), u8(0));
return true;
}
while (block < blocks)
{
u32 end;
if (!m_flush_dirty[block])
{
block++;
continue;
}
for (end = block; end < blocks && m_flush_dirty[end]; end++)
{
}
const u32 offset = block * Memcard::BLOCK_SIZE;
const u32 length = std::min(end * Memcard::BLOCK_SIZE, m_memory_card_size) - offset;
if (!file.Seek(offset, File::SeekOrigin::Begin) ||
!file.WriteBytes(&m_flush_buffer[offset], length))
{
return false;
}
std::fill(m_flush_dirty.begin() + block, m_flush_dirty.begin() + end, u8(0));
block = end;
}
return true;
}
void MemoryCard::FlushThread()
{
if (!Config::Get(Config::SESSION_SAVE_DATA_WRITABLE))
@@ -153,7 +201,9 @@ void MemoryCard::FlushThread()
// Opening the file is purposefully done each iteration to ensure the
// file doesn't disappear out from under us after the first check.
// Only the blocks that changed are written over a card of the right size.
File::IOFile file(m_filename, "r+b");
bool whole = !file || file.GetSize() != m_memory_card_size;
if (!file)
{
@@ -182,7 +232,8 @@ void MemoryCard::FlushThread()
}
ApplyChanges();
file.WriteBytes(&m_flush_buffer[0], m_memory_card_size);
if (!WriteChanges(file, whole))
m_dirty.Set();
if (do_exit)
return;
+7 -1
View File
@@ -5,12 +5,14 @@
#include <memory>
#include <string>
#include <vector>
#include <queues/mpsc_stack.h>
#include <rthreads/rthreads.h>
#include "Common/Event.h"
#include "Common/Flag.h"
#include "Common/IOFile.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/GCMemcard/GCMemcardBase.h"
@@ -44,8 +46,10 @@ private:
};
// CPU thread: hands a change to the flush thread.
void PostChange(u32 offset, u32 length, const u8* data);
// Flush thread: applies what was posted, oldest first.
// Flush thread: applies what was posted, oldest first, marking the blocks it touched.
void ApplyChanges();
// Flush thread: writes the marked blocks, a run of them at a time, or the whole card.
bool WriteChanges(File::IOFile& file, bool whole);
bool IsAddressInBounds(u32 address, u32 length) const
{
@@ -59,6 +63,8 @@ private:
// The flush thread's own copy, brought up to date from m_changes before each write.
std::unique_ptr<u8[]> m_flush_buffer;
mpsc_stack_t m_changes{};
// Flush thread: one flag per block, set where m_flush_buffer differs from the file.
std::vector<u8> m_flush_dirty;
sthread_t* m_flush_thread = nullptr;
Common::Flag m_flush_running{true};
Common::Flag m_dirty;
+13 -4
View File
@@ -271,14 +271,23 @@ static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_sou
if (fs->GetMetadata(IOS::SYSMENU_UID, IOS::SYSMENU_UID, nand_path).has_value())
continue;
// written into the NAND from the mapping where the host file has one
File::IOFile host_file{host_path, "rb"};
std::vector<u8> file_data(host_file.GetSize());
if (!host_file.ReadBytes(file_data.data(), file_data.size()))
return false;
u64 size = 0;
const u8* data = host_file.GetMappedPtr(&size);
std::vector<u8> file_data;
if (!data)
{
file_data.resize(host_file.GetSize());
if (!host_file.ReadBytes(file_data.data(), file_data.size()))
return false;
data = file_data.data();
size = file_data.size();
}
const auto nand_file =
fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, nand_path, public_modes);
if (!nand_file || !nand_file->Write(file_data.data(), file_data.size()))
if (!nand_file || !nand_file->Write(data, size))
return false;
}
}
@@ -505,27 +505,6 @@ void ObjectCache::DestroyRenderPassCache()
m_render_pass_cache.clear();
}
class PipelineCacheReadCallback : public Common::LinearDiskCacheReader<u32, u8>
{
public:
PipelineCacheReadCallback(std::vector<u8>* data) : m_data(data) {}
void Read(const u32& key, const u8* value, u32 value_size) override
{
m_data->resize(value_size);
if (value_size > 0)
memcpy(m_data->data(), value, value_size);
}
private:
std::vector<u8>* m_data;
};
class PipelineCacheReadIgnoreCallback : public Common::LinearDiskCacheReader<u32, u8>
{
public:
void Read(const u32& key, const u8* value, u32 value_size) override {}
};
bool ObjectCache::CreatePipelineCache()
{
// Vulkan pipeline caches can be shared between games for shader compile time reduction.
@@ -552,38 +531,60 @@ bool ObjectCache::CreatePipelineCache()
bool ObjectCache::LoadPipelineCache()
{
// The cache is made from the stored blob where it lies in the mapped file.
struct Reader final : Common::LinearDiskCacheReader<u32, u8>
{
explicit Reader(ObjectCache* cache_) : cache(cache_) {}
void Read(const u32& key, const u8* value, u32 value_size) override
{
if (++count != 1 || !value_size)
return;
if (!cache->ValidatePipelineCache(value, value_size))
{
invalid = true;
return;
}
VkPipelineCacheCreateInfo info = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
nullptr, // const void* pNext
0, // VkPipelineCacheCreateFlags flags
value_size, // size_t initialDataSize
value // const void* pInitialData
};
result = vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr,
&cache->m_pipeline_cache);
}
ObjectCache* cache;
VkResult result = VK_INCOMPLETE;
u32 count = 0;
bool invalid = false;
};
// We have to keep the pipeline cache file name around since when we save it
// we delete the old one, by which time the game's unique ID is already cleared.
m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
std::vector<u8> disk_data;
Common::LinearDiskCache<u32, u8> disk_cache;
PipelineCacheReadCallback read_callback(&disk_data);
if (disk_cache.OpenAndRead(m_pipeline_cache_filename, read_callback) != 1)
disk_data.clear();
Reader reader(this);
{
Common::LinearDiskCache<u32, u8> disk_cache;
disk_cache.OpenAndRead(m_pipeline_cache_filename, reader);
}
if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
if (reader.invalid)
{
// Don't use this data. In fact, we should delete it to prevent it from being used next time.
File::Delete(m_pipeline_cache_filename);
return CreatePipelineCache();
}
VkPipelineCacheCreateInfo info = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
nullptr, // const void* pNext
0, // VkPipelineCacheCreateFlags flags
disk_data.size(), // size_t initialDataSize
disk_data.data() // const void* pInitialData
};
VkResult res =
vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
if (res == VK_SUCCESS)
if (reader.count == 1 && reader.result == VK_SUCCESS)
return true;
// Failed to create pipeline cache, try with it empty.
LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed, trying empty cache: ");
if (reader.count > 1 && m_pipeline_cache != VK_NULL_HANDLE)
{
vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr);
m_pipeline_cache = VK_NULL_HANDLE;
}
if (reader.result != VK_INCOMPLETE && reader.result != VK_SUCCESS)
LOG_VULKAN_ERROR(reader.result, "vkCreatePipelineCache failed, trying empty cache: ");
return CreatePipelineCache();
}
@@ -677,17 +678,9 @@ void ObjectCache::SavePipelineCache()
return;
}
// Delete the old cache and re-create.
File::Delete(m_pipeline_cache_filename);
// We write a single key of 1, with the entire pipeline cache data.
// Not ideal, but our disk cache class does not support just writing a single blob
// of data without specifying a key.
Common::LinearDiskCache<u32, u8> disk_cache;
PipelineCacheReadIgnoreCallback callback;
disk_cache.OpenAndRead(m_pipeline_cache_filename, callback);
disk_cache.Append(1, data.data(), static_cast<u32>(data.size()));
disk_cache.Close();
// A single key of 1 with the whole pipeline cache, written only when it changed.
Common::LinearDiskCache<u32, u8>::WriteSingle(m_pipeline_cache_filename, 1, data.data(),
static_cast<u32>(data.size()));
}
void ObjectCache::ReloadPipelineCache()