video_core: Fixes to vulkan disk cache (#1748)

This commit is contained in:
PabloMK7 2026-02-17 14:22:48 +01:00 committed by GitHub
parent 5d4aef81fe
commit c43f24e489
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 310 additions and 267 deletions

View File

@ -1254,20 +1254,19 @@ static std::size_t pread(int fd, void* buf, std::size_t count, uint64_t offset)
#define pread ::pread
#endif
std::size_t IOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
std::size_t IOFile::ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) {
if (!IsOpen()) {
m_good = false;
return std::numeric_limits<std::size_t>::max();
}
if (length == 0) {
if (byte_count == 0) {
return 0;
}
DEBUG_ASSERT(data != nullptr);
return pread(fileno(m_file), data, data_size * length, offset);
return pread(fileno(m_file), data, byte_count, offset);
}
std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
@ -1315,19 +1314,19 @@ struct CryptoIOFileImpl {
std::size_t res = f.IOFile::ReadImpl(data, length, data_size);
if (res != std::numeric_limits<std::size_t>::max() && res != 0) {
d.ProcessData(reinterpret_cast<CryptoPP::byte*>(data),
reinterpret_cast<CryptoPP::byte*>(data), length * data_size);
reinterpret_cast<CryptoPP::byte*>(data), res * data_size);
e.Seek(f.IOFile::Tell());
}
return res;
}
std::size_t ReadAtImpl(CryptoIOFile& f, void* data, std::size_t length, std::size_t data_size,
std::size_t ReadAtImpl(CryptoIOFile& f, void* data, std::size_t byte_count,
std::size_t offset) {
std::size_t res = f.IOFile::ReadAtImpl(data, length, data_size, offset);
std::size_t res = f.IOFile::ReadAtImpl(data, byte_count, offset);
if (res != std::numeric_limits<std::size_t>::max() && res != 0) {
d.Seek(offset);
d.ProcessData(reinterpret_cast<CryptoPP::byte*>(data),
reinterpret_cast<CryptoPP::byte*>(data), length * data_size);
reinterpret_cast<CryptoPP::byte*>(data), res);
e.Seek(f.IOFile::Tell());
}
return res;
@ -1378,9 +1377,8 @@ std::size_t CryptoIOFile::ReadImpl(void* data, std::size_t length, std::size_t d
return impl->ReadImpl(*this, data, length, data_size);
}
std::size_t CryptoIOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
return impl->ReadAtImpl(*this, data, length, data_size, offset);
std::size_t CryptoIOFile::ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) {
return impl->ReadAtImpl(*this, data, byte_count, offset);
}
std::size_t CryptoIOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {

View File

@ -302,6 +302,7 @@ public:
virtual bool Close();
/// Returns the amount of T items read
template <typename T>
std::size_t ReadArray(T* data, std::size_t length) {
static_assert(std::is_trivially_copyable_v<T>,
@ -314,16 +315,18 @@ public:
return items_read;
}
/// Returns the amount of bytes read
template <typename T>
std::size_t ReadAtArray(T* data, std::size_t length, std::size_t offset) {
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
std::size_t items_read = ReadAtImpl(data, length, sizeof(T), offset);
if (items_read != length)
const size_t bytes = length * sizeof(T);
std::size_t size_read = ReadAtImpl(data, bytes, offset);
if (size_read != bytes)
m_good = false;
return items_read;
return size_read;
}
template <typename T>
@ -466,8 +469,7 @@ protected:
virtual bool Open();
virtual std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
virtual std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset);
virtual std::size_t ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset);
virtual std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
virtual bool SeekImpl(s64 off, int origin);
@ -520,8 +522,7 @@ private:
std::unique_ptr<CryptoIOFileImpl> impl;
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) override;
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) override;
std::size_t ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) override;
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size) override;
bool SeekImpl(s64 off, int origin) override;

View File

@ -357,8 +357,7 @@ std::size_t Z3DSWriteIOFile::ReadImpl(void* data, std::size_t length, std::size_
return 0;
}
std::size_t Z3DSWriteIOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
std::size_t Z3DSWriteIOFile::ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) {
// Stubbed
UNIMPLEMENTED();
return 0;
@ -619,12 +618,12 @@ bool Z3DSReadIOFile::Open() {
}
std::size_t Z3DSReadIOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) {
return impl->Read(data, length * data_size);
size_t res = impl->Read(data, length * data_size);
return res == std::numeric_limits<size_t>::max() ? res : (res / data_size);
}
std::size_t Z3DSReadIOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
return impl->ReadAt(data, length * data_size, offset);
std::size_t Z3DSReadIOFile::ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) {
return impl->ReadAt(data, byte_count, offset);
}
std::size_t Z3DSReadIOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {

View File

@ -183,8 +183,7 @@ private:
bool Open() override;
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) override;
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) override;
std::size_t ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) override;
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size) override;
bool SeekImpl(s64 off, int origin) override;
@ -250,8 +249,7 @@ private:
bool Open() override;
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) override;
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) override;
std::size_t ReadAtImpl(void* data, std::size_t byte_count, std::size_t offset) override;
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size) override;
bool SeekImpl(s64 off, int origin) override;

View File

@ -168,7 +168,6 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
ASSERT(partition < 8);
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
LOG_ERROR(Service_FS, "{}", ncch_offset);
file->Seek(ncch_offset, SEEK_SET);
file->ReadBytes(&ncch_header, sizeof(NCCH_Header));
}

View File

@ -144,24 +144,59 @@ void PipelineCache::BuildLayout() {
}
PipelineCache::~PipelineCache() {
SaveDiskCache();
workers.WaitForRequests();
SaveDriverPipelineDiskCache();
}
void PipelineCache::LoadPipelineDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
void PipelineCache::LoadCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
LoadDriverPipelineDiskCache(stop_loading, callback);
LoadDiskCache(stop_loading, callback);
}
void PipelineCache::SwitchCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
if (GetProgramID() == title_id) {
LOG_DEBUG(Render_Vulkan,
"Skipping pipeline cache switch - already using cache for title_id={:016X}",
title_id);
return;
}
// Make sure we have a valid pipeline cache before switching
if (!driver_pipeline_cache) {
vk::PipelineCacheCreateInfo cache_info{};
try {
driver_pipeline_cache = instance.GetDevice().createPipelineCacheUnique(cache_info);
} catch (const vk::SystemError& err) {
LOG_ERROR(Render_Vulkan, "Failed to create pipeline cache: {}", err.what());
return;
}
}
LOG_INFO(Render_Vulkan, "Switching pipeline cache to title_id={:016X}", title_id);
// Save current driver cache, update program ID and load the new driver cache
SaveDriverPipelineDiskCache();
SetProgramID(title_id);
LoadDriverPipelineDiskCache(stop_loading, nullptr);
// Switch the disk shader cache after driver cache is switched
SwitchDiskCache(title_id, stop_loading, callback);
}
void PipelineCache::LoadDriverPipelineDiskCache(
const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) {
vk::PipelineCacheCreateInfo cache_info{};
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 0, 1, "Driver Pipeline Cache");
}
auto load_cache = [this, &cache_info](bool allow_fallback) {
auto load_cache = [this, &cache_info, &callback](bool allow_fallback) {
const vk::Device device = instance.GetDevice();
try {
pipeline_cache = device.createPipelineCacheUnique(cache_info);
driver_pipeline_cache = device.createPipelineCacheUnique(cache_info);
} catch (const vk::SystemError& err) {
LOG_ERROR(Render_Vulkan, "Failed to create pipeline cache: {}", err.what());
if (allow_fallback) {
@ -169,13 +204,16 @@ void PipelineCache::LoadPipelineDiskCache(const std::atomic_bool& stop_loading,
cache_info.initialDataSize = 0;
cache_info.pInitialData = nullptr;
try {
pipeline_cache = device.createPipelineCacheUnique(cache_info);
driver_pipeline_cache = device.createPipelineCacheUnique(cache_info);
} catch (const vk::SystemError& err) {
LOG_ERROR(Render_Vulkan, "Failed to create fallback pipeline cache: {}",
err.what());
}
}
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 1, 1, "Driver Pipeline Cache");
}
};
// Try to load existing pipeline cache if disk cache is enabled and directories exist
@ -226,19 +264,9 @@ void PipelineCache::LoadPipelineDiskCache(const std::atomic_bool& stop_loading,
load_cache(true);
}
void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
disk_caches.clear();
curr_disk_cache =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, GetProgramID()));
curr_disk_cache->Init(stop_loading, callback);
}
void PipelineCache::SaveDiskCache() {
void PipelineCache::SaveDriverPipelineDiskCache() {
// Save Vulkan pipeline cache
if (!Settings::values.use_disk_shader_cache || !pipeline_cache) {
if (!Settings::values.use_disk_shader_cache || !driver_pipeline_cache) {
return;
}
@ -258,13 +286,81 @@ void PipelineCache::SaveDiskCache() {
}
const vk::Device device = instance.GetDevice();
const auto cache_data = device.getPipelineCacheData(*pipeline_cache);
const auto cache_data = device.getPipelineCacheData(*driver_pipeline_cache);
if (cache_file.WriteBytes(cache_data.data(), cache_data.size()) != cache_data.size()) {
LOG_ERROR(Render_Vulkan, "Error during pipeline cache write");
return;
}
}
void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
disk_caches.clear();
curr_disk_cache =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, GetProgramID()));
curr_disk_cache->Init(stop_loading, callback);
}
void PipelineCache::SwitchDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
// NOTE: curr_disk_cache can be null if emulation restarted without calling
// LoadDefaultDiskResources
// Check if the current cache is for the specified TID.
if (curr_disk_cache && curr_disk_cache->GetProgramID() == title_id) {
return;
}
// Search for an existing manager
size_t new_pos = 0;
for (new_pos = 0; new_pos < disk_caches.size(); new_pos++) {
if (disk_caches[new_pos]->GetProgramID() == title_id) {
break;
}
}
// Manager does not exist, create it and append to the end
if (new_pos >= disk_caches.size()) {
new_pos = disk_caches.size();
auto& new_manager =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, title_id));
new_manager->Init(stop_loading, callback);
}
auto is_applet = [](u64 tid) {
constexpr u32 APPLET_TID_HIGH = 0x00040030;
return static_cast<u32>(tid >> 32) == APPLET_TID_HIGH;
};
bool prev_applet = curr_disk_cache ? is_applet(curr_disk_cache->GetProgramID()) : false;
bool new_applet = is_applet(disk_caches[new_pos]->GetProgramID());
curr_disk_cache = disk_caches[new_pos];
if (prev_applet) {
// If we came from an applet, clean up all other applets
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache ||
!is_applet((*it)->GetProgramID())) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
if (!new_applet) {
// If we are going into a non-applet, clean up everything
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
}
bool PipelineCache::BindPipeline(PipelineInfo& info, bool wait_built) {
MICROPROFILE_SCOPE(Vulkan_Bind);
@ -551,112 +647,4 @@ std::string PipelineCache::GetTransferableDir() const {
return GetVulkanDir() + DIR_SEP + "transferable";
}
void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
if (!Settings::values.use_disk_shader_cache || GetProgramID() == title_id) {
LOG_DEBUG(Render_Vulkan,
"Skipping pipeline cache switch - already using cache for title_id={:016X}",
title_id);
return;
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Build, 0, 1, "Driver Pipeline Cache");
}
// Make sure we have a valid pipeline cache before switching
if (!pipeline_cache) {
vk::PipelineCacheCreateInfo cache_info{};
try {
pipeline_cache = instance.GetDevice().createPipelineCacheUnique(cache_info);
} catch (const vk::SystemError& err) {
LOG_ERROR(Render_Vulkan, "Failed to create pipeline cache: {}", err.what());
return;
}
}
LOG_INFO(Render_Vulkan, "Switching pipeline cache to title_id={:016X}", title_id);
// Save current cache before switching
SaveDiskCache();
// Update program ID and load the new pipeline cache
SetProgramID(title_id);
LoadPipelineDiskCache(stop_loading, nullptr);
SwitchDiskCache(title_id, stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
void PipelineCache::SwitchDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
// NOTE: curr_disk_cache can be null if emulation restarted without calling
// LoadDefaultDiskResources
// Check if the current cache is for the specified TID.
if (curr_disk_cache && curr_disk_cache->GetProgramID() == title_id) {
return;
}
// Search for an existing manager
size_t new_pos = 0;
for (new_pos = 0; new_pos < disk_caches.size(); new_pos++) {
if (disk_caches[new_pos]->GetProgramID() == title_id) {
break;
}
}
// Manager does not exist, create it and append to the end
if (new_pos >= disk_caches.size()) {
new_pos = disk_caches.size();
auto& new_manager =
disk_caches.emplace_back(std::make_shared<ShaderDiskCache>(*this, title_id));
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
new_manager->Init(stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
auto is_applet = [](u64 tid) {
constexpr u32 APPLET_TID_HIGH = 0x00040030;
return static_cast<u32>(tid >> 32) == APPLET_TID_HIGH;
};
bool prev_applet = curr_disk_cache ? is_applet(curr_disk_cache->GetProgramID()) : false;
bool new_applet = is_applet(disk_caches[new_pos]->GetProgramID());
curr_disk_cache = disk_caches[new_pos];
if (prev_applet) {
// If we came from an applet, clean up all other applets
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache ||
!is_applet((*it)->GetProgramID())) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
if (!new_applet) {
// If we are going into a non-applet, clean up everything
for (auto it = disk_caches.begin(); it != disk_caches.end();) {
if (it == disk_caches.begin() || *it == curr_disk_cache) {
it++;
continue;
}
it = disk_caches.erase(it);
}
}
}
} // namespace Vulkan

View File

@ -58,15 +58,13 @@ public:
offsets[binding] = offset;
}
/// Loads the pipeline cache stored to disk
void LoadPipelineDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Loads the driver pipeline cache and the disk shader cache
void LoadCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
void LoadDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Stores the generated pipeline cache to disk
void SaveDiskCache();
/// Switches the driver pipeline cache and the shader disk cache to the specified title
void SwitchCache(u64 title_id, const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Binds a pipeline using the provided information
bool BindPipeline(PipelineInfo& info, bool wait_built = false);
@ -90,11 +88,6 @@ public:
/// Binds a fragment shader generated from PICA state
void UseFragmentShader(const Pica::RegsInternal& regs, const Pica::Shader::UserConfig& user);
/// Switches the shader disk cache to the specified title
void SwitchPipelineCache(u64 title_id,
const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Gets the current program ID
u64 GetProgramID() const {
return current_program_id;
@ -111,6 +104,17 @@ public:
private:
friend ShaderDiskCache;
/// Loads the driver pipeline cache
void LoadDriverPipelineDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Stores the generated pipeline cache
void SaveDriverPipelineDiskCache();
/// Loads the shader disk cache
void LoadDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Switches the disk cache at runtime to use a different title ID
void SwitchDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
@ -140,7 +144,7 @@ private:
DescriptorUpdateQueue& update_queue;
Pica::Shader::Profile profile{};
vk::UniquePipelineCache pipeline_cache;
vk::UniquePipelineCache driver_pipeline_cache;
vk::UniquePipelineLayout pipeline_layout;
std::size_t num_worker_threads;
Common::ThreadWorker workers;

View File

@ -152,10 +152,13 @@ void RasterizerVulkan::LoadDefaultDiskResources(
program_id = 0;
}
if (callback) {
callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
pipeline_cache.SetProgramID(program_id);
pipeline_cache.SetAccurateMul(accurate_mul);
pipeline_cache.LoadPipelineDiskCache(stop_loading, callback);
pipeline_cache.LoadDiskCache(stop_loading, callback);
pipeline_cache.LoadCache(stop_loading, callback);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
@ -985,8 +988,17 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
void RasterizerVulkan::SwitchDiskResources(u64 title_id) {
std::atomic_bool stop_loading = false;
if (switch_disk_resources_callback) {
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
}
pipeline_cache.SetAccurateMul(accurate_mul);
pipeline_cache.SwitchPipelineCache(title_id, stop_loading, switch_disk_resources_callback);
pipeline_cache.SwitchCache(title_id, stop_loading, switch_disk_resources_callback);
if (switch_disk_resources_callback) {
switch_disk_resources_callback(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
}
}
} // namespace Vulkan

View File

@ -59,6 +59,9 @@ static VideoCore::DiskResourceLoadCallback MakeThrottledCallback(
void ShaderDiskCache::Init(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
if (!Settings::values.use_disk_shader_cache)
return;
auto new_callback = MakeThrottledCallback(callback);
if (!stop_loading && !InitVSCache(stop_loading, new_callback)) {
@ -248,7 +251,7 @@ GraphicsPipeline* ShaderDiskCache::GetPipeline(const PipelineInfo& info) {
parent.UseTrivialGeometryShader();
}
it.value() = std::make_unique<GraphicsPipeline>(
parent.instance, parent.renderpass_cache, info, *parent.pipeline_cache,
parent.instance, parent.renderpass_cache, info, *parent.driver_pipeline_cache,
*parent.pipeline_layout, parent.current_shaders, &parent.workers);
}
@ -339,86 +342,129 @@ ShaderDiskCache::CacheEntry ShaderDiskCache::CacheFile::ReadAt(size_t position)
}
size_t ShaderDiskCache::CacheFile::GetTotalEntries() {
if (biggest_entry_id != SIZE_MAX) {
return biggest_entry_id + 1;
if (!file.IsGood()) {
next_entry_id = SIZE_MAX;
return next_entry_id;
}
if (next_entry_id != SIZE_MAX) {
return next_entry_id;
}
const size_t file_size = file.GetSize();
if (file_size == 0) {
next_entry_id = 0;
return next_entry_id;
}
CacheEntry::CacheEntryFooter footer{};
if (file.ReadAtArray(&footer, 1, file_size - sizeof(footer)) == sizeof(footer) &&
footer.version == CacheEntry::CacheEntryFooter::ENTRY_VERSION) {
biggest_entry_id = footer.entry_id;
next_entry_id = footer.entry_id + 1;
} else {
return SIZE_MAX;
}
return biggest_entry_id + 1;
return next_entry_id;
}
bool ShaderDiskCache::CacheFile::Append(CacheEntryType type, u64 id, std::span<const u8> data,
void ShaderDiskCache::CacheFile::Append(CacheEntryType type, u64 id, std::span<const u8> data,
bool compress) {
std::scoped_lock lock(mutex);
std::span<const u8> data_final;
std::vector<u8> data_compress;
CacheEntry::CacheEntryHeader header{};
CacheEntry::CacheEntryFooter footer{};
constexpr u32 headers_size =
sizeof(CacheEntry::CacheEntryHeader) + sizeof(CacheEntry::CacheEntryFooter);
if (compress) {
data_compress = Common::Compression::CompressDataZSTDDefault(data);
data_final = data_compress;
header.zstd_compressed.Assign(true);
} else {
data_final = data;
if (curr_mode != CacheOpMode::APPEND) {
return;
}
header.entry_version = CacheEntry::CacheEntryHeader::ENTRY_VERSION;
footer.version.Assign(CacheEntry::CacheEntryFooter::ENTRY_VERSION);
header.entry_size = footer.entry_size = data_final.size() + headers_size;
footer.entry_id.Assign(biggest_entry_id++);
header.type = type;
header.id = id;
std::vector<u8> copy_data(data.begin(), data.end());
std::vector<u8> out_data(data_final.size() + headers_size);
memcpy(out_data.data(), &header, sizeof(header));
memcpy(out_data.data() + sizeof(header), data_final.data(), data_final.size());
memcpy(out_data.data() + sizeof(header) + data_final.size(), &footer, sizeof(footer));
append_worker.QueueWork([this, type, id, compress, data = std::move(copy_data)]() {
if (next_entry_id == SIZE_MAX || !file.IsGood()) {
return;
}
return file.WriteBytes(out_data.data(), out_data.size()) == out_data.size();
std::span<const u8> data_final;
std::vector<u8> data_compress;
CacheEntry::CacheEntryHeader header{};
CacheEntry::CacheEntryFooter footer{};
constexpr u32 headers_size =
sizeof(CacheEntry::CacheEntryHeader) + sizeof(CacheEntry::CacheEntryFooter);
if (compress) {
data_compress = Common::Compression::CompressDataZSTDDefault(data);
data_final = data_compress;
header.zstd_compressed.Assign(true);
} else {
data_final = data;
}
header.entry_version = CacheEntry::CacheEntryHeader::ENTRY_VERSION;
footer.version.Assign(CacheEntry::CacheEntryFooter::ENTRY_VERSION);
header.entry_size = footer.entry_size = data_final.size() + headers_size;
footer.entry_id.Assign(next_entry_id++);
header.type = type;
header.id = id;
std::vector<u8> out_data(data_final.size() + headers_size);
memcpy(out_data.data(), &header, sizeof(header));
memcpy(out_data.data() + sizeof(header), data_final.data(), data_final.size());
memcpy(out_data.data() + sizeof(header) + data_final.size(), &footer, sizeof(footer));
file.WriteBytes(out_data.data(), out_data.size());
if (file.IsGood()) {
file.Flush();
}
});
}
bool ShaderDiskCache::CacheFile::SwitchMode(CacheOpMode mode) {
if (curr_mode == mode) {
return true;
}
if (curr_mode == CacheOpMode::APPEND) {
append_worker.WaitForRequests();
}
switch (mode) {
case CacheOpMode::READ: {
next_entry_id = SIZE_MAX; // Force reading entries agains
file = FileUtil::IOFile(filepath, "rb");
bool is_open = file.IsOpen();
bool is_open = file.IsGood();
if (is_open) {
GetTotalEntries();
}
curr_mode = mode;
return is_open;
}
case CacheOpMode::APPEND: {
GetTotalEntries();
if (!SwitchMode(CacheOpMode::READ)) {
curr_mode = mode;
return false;
}
file.Close();
if (biggest_entry_id == SIZE_MAX) {
curr_mode = mode;
if (next_entry_id == SIZE_MAX) {
// Cannot append if getting total items fails
return false;
}
file = FileUtil::IOFile(filepath, "ab");
return file.IsOpen();
return file.IsGood();
}
case CacheOpMode::DELETE: {
biggest_entry_id = 0;
next_entry_id = SIZE_MAX;
file.Close();
FileUtil::Delete(filepath);
return true;
curr_mode = mode;
return FileUtil::Delete(filepath);
}
case CacheOpMode::RECREATE: {
SwitchMode(CacheOpMode::DELETE);
if (!SwitchMode(CacheOpMode::DELETE)) {
return false;
}
if (!FileUtil::CreateEmptyFile(filepath)) {
return false;
}
return SwitchMode(CacheOpMode::APPEND);
}
default:
@ -827,8 +873,7 @@ bool ShaderDiskCache::InitVSCache(const std::atomic_bool& stop_loading,
}
// Switch to append mode to receive new entries.
vs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
return true;
return vs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
}
bool ShaderDiskCache::InitFSCache(const std::atomic_bool& stop_loading,
@ -1045,8 +1090,7 @@ bool ShaderDiskCache::InitFSCache(const std::atomic_bool& stop_loading,
}
// Switch to append mode to receive new entries.
fs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
return true;
return fs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
}
bool ShaderDiskCache::InitGSCache(const std::atomic_bool& stop_loading,
@ -1272,8 +1316,7 @@ bool ShaderDiskCache::InitGSCache(const std::atomic_bool& stop_loading,
}
// Switch to append mode to receive new entries.
gs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
return true;
return gs_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
}
bool ShaderDiskCache::InitPLCache(const std::atomic_bool& stop_loading,
@ -1400,7 +1443,7 @@ bool ShaderDiskCache::InitPLCache(const std::atomic_bool& stop_loading,
auto [it_pl, _] = graphics_pipelines.try_emplace(pl_hash_opt);
it_pl.value() = std::make_unique<GraphicsPipeline>(
parent.instance, parent.renderpass_cache, info, *parent.pipeline_cache,
parent.instance, parent.renderpass_cache, info, *parent.driver_pipeline_cache,
*parent.pipeline_layout, shaders, &parent.workers);
it_pl.value()->TryBuild(false);
@ -1413,11 +1456,10 @@ bool ShaderDiskCache::InitPLCache(const std::atomic_bool& stop_loading,
}
// Switch to append mode to receive new entries.
pl_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
return true;
return pl_cache.SwitchMode(CacheFile::CacheOpMode::APPEND);
}
bool ShaderDiskCache::AppendVSConfigProgram(CacheFile& file,
void ShaderDiskCache::AppendVSConfigProgram(CacheFile& file,
const Pica::Shader::Generator::PicaVSConfig& config,
const Pica::ShaderSetup& setup, u64 config_id,
u64 spirv_id) {
@ -1430,7 +1472,6 @@ bool ShaderDiskCache::AppendVSConfigProgram(CacheFile& file,
Common::HashCombine(config.state.program_hash, config.state.swizzle_hash);
bool new_entry = known_vertex_programs.emplace(entry.program_entry_id).second;
bool prog_res = true;
if (new_entry) {
std::unique_ptr<VSProgramEntry> prog_entry = std::make_unique<VSProgramEntry>();
prog_entry->version = VSProgramEntry::EXPECTED_VERSION;
@ -1439,49 +1480,46 @@ bool ShaderDiskCache::AppendVSConfigProgram(CacheFile& file,
prog_entry->swizzle_len = setup.GetBiggestSwizzleSize();
prog_entry->swizzle_code = setup.GetSwizzleData();
prog_res = AppendVSProgram(file, *prog_entry, entry.program_entry_id);
AppendVSProgram(file, *prog_entry, entry.program_entry_id);
}
return AppendVSConfig(file, entry, config_id) && prog_res;
AppendVSConfig(file, entry, config_id);
}
bool ShaderDiskCache::AppendVSProgram(CacheFile& file, const VSProgramEntry& entry,
void ShaderDiskCache::AppendVSProgram(CacheFile& file, const VSProgramEntry& entry,
u64 program_id) {
return file.Append(CacheEntryType::VS_PROGRAM, program_id, entry, true);
file.Append(CacheEntryType::VS_PROGRAM, program_id, entry, true);
}
bool ShaderDiskCache::AppendVSConfig(CacheFile& file, const VSConfigEntry& entry, u64 config_id) {
return file.Append(CacheEntryType::VS_CONFIG, config_id, entry, true);
void ShaderDiskCache::AppendVSConfig(CacheFile& file, const VSConfigEntry& entry, u64 config_id) {
file.Append(CacheEntryType::VS_CONFIG, config_id, entry, true);
}
bool ShaderDiskCache::AppendVSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
return file.Append(CacheEntryType::VS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)},
true);
void ShaderDiskCache::AppendVSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
file.Append(CacheEntryType::VS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)}, true);
}
bool ShaderDiskCache::AppendFSConfig(CacheFile& file, const FSConfigEntry& entry, u64 config_id) {
return file.Append(CacheEntryType::FS_CONFIG, config_id, entry, true);
void ShaderDiskCache::AppendFSConfig(CacheFile& file, const FSConfigEntry& entry, u64 config_id) {
file.Append(CacheEntryType::FS_CONFIG, config_id, entry, true);
}
bool ShaderDiskCache::AppendFSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
return file.Append(CacheEntryType::FS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)},
true);
void ShaderDiskCache::AppendFSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
file.Append(CacheEntryType::FS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)}, true);
}
bool ShaderDiskCache::AppendGSConfig(CacheFile& file, const GSConfigEntry& entry, u64 config_id) {
return file.Append(CacheEntryType::GS_CONFIG, config_id, entry, true);
void ShaderDiskCache::AppendGSConfig(CacheFile& file, const GSConfigEntry& entry, u64 config_id) {
file.Append(CacheEntryType::GS_CONFIG, config_id, entry, true);
}
bool ShaderDiskCache::AppendGSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
return file.Append(CacheEntryType::GS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)},
true);
void ShaderDiskCache::AppendGSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id) {
file.Append(CacheEntryType::GS_SPIRV, program_id,
{reinterpret_cast<const u8*>(program.data()), program.size() * sizeof(u32)}, true);
}
bool ShaderDiskCache::AppendPLConfig(CacheFile& file, const PLConfigEntry& entry, u64 config_id) {
return file.Append(CacheEntryType::PL_CONFIG, config_id, entry, true);
void ShaderDiskCache::AppendPLConfig(CacheFile& file, const PLConfigEntry& entry, u64 config_id) {
file.Append(CacheEntryType::PL_CONFIG, config_id, entry, true);
}
} // namespace Vulkan

View File

@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/thread_worker.h"
#include "video_core/pica/shader_setup.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
@ -241,6 +242,7 @@ private:
class CacheFile {
public:
enum class CacheOpMode {
NONE = 0,
READ,
APPEND,
DELETE,
@ -249,6 +251,9 @@ private:
CacheFile() = default;
CacheFile(const std::string& _filepath) : filepath(_filepath) {}
~CacheFile() {
append_worker.WaitForRequests();
}
void SetFilePath(const std::string& path) {
filepath = path;
@ -268,24 +273,25 @@ private:
size_t GetTotalEntries();
template <typename T>
bool Append(CacheEntryType type, u64 id, const T& object, bool compress) {
void Append(CacheEntryType type, u64 id, const T& object, bool compress) {
static_assert(std::is_trivially_copyable_v<T>);
auto bytes = std::as_bytes(std::span{&object, 1});
auto u8_span =
std::span<const u8>(reinterpret_cast<const u8*>(bytes.data()), bytes.size());
return Append(type, id, u8_span, compress);
Append(type, id, u8_span, compress);
}
bool Append(CacheEntryType type, u64 id, std::span<const u8> data, bool compress);
void Append(CacheEntryType type, u64 id, std::span<const u8> data, bool compress);
bool SwitchMode(CacheOpMode mode);
private:
CacheOpMode curr_mode = CacheOpMode::NONE;
std::string filepath;
std::mutex mutex;
FileUtil::IOFile file{};
size_t biggest_entry_id = SIZE_MAX;
std::atomic<size_t> next_entry_id = SIZE_MAX;
Common::ThreadWorker append_worker{1, "Disk Shader Cache Append Worker"};
};
std::string GetVSFile(u64 title_id, bool is_temp) const;
@ -307,19 +313,19 @@ private:
bool InitPLCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
bool AppendVSConfigProgram(CacheFile& file, const Pica::Shader::Generator::PicaVSConfig& config,
void AppendVSConfigProgram(CacheFile& file, const Pica::Shader::Generator::PicaVSConfig& config,
const Pica::ShaderSetup& setup, u64 config_id, u64 program_id);
bool AppendVSProgram(CacheFile& file, const VSProgramEntry& entry, u64 program_id);
bool AppendVSConfig(CacheFile& file, const VSConfigEntry& entry, u64 config_id);
bool AppendVSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
void AppendVSProgram(CacheFile& file, const VSProgramEntry& entry, u64 program_id);
void AppendVSConfig(CacheFile& file, const VSConfigEntry& entry, u64 config_id);
void AppendVSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendFSConfig(CacheFile& file, const FSConfigEntry& entry, u64 config_id);
bool AppendFSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
void AppendFSConfig(CacheFile& file, const FSConfigEntry& entry, u64 config_id);
void AppendFSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendGSConfig(CacheFile& file, const GSConfigEntry& entry, u64 config_id);
bool AppendGSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
void AppendGSConfig(CacheFile& file, const GSConfigEntry& entry, u64 config_id);
void AppendGSSPIRV(CacheFile& file, std::span<const u32> program, u64 program_id);
bool AppendPLConfig(CacheFile& file, const PLConfigEntry& entry, u64 config_id);
void AppendPLConfig(CacheFile& file, const PLConfigEntry& entry, u64 config_id);
CacheFile vs_cache;
CacheFile fs_cache;