Add per-title vulkan pipeline cache (#1118)

This commit is contained in:
jbm11208 2025-06-23 14:51:25 -04:00 committed by GitHub
parent 680cbb559d
commit e24f8da113
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 145 additions and 21 deletions

View File

@ -10,6 +10,9 @@
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h"
#include "core/loader/loader.h"
#include "video_core/pica/shader_setup.h"
#include "video_core/renderer_vulkan/pica_to_vk.h" #include "video_core/renderer_vulkan/pica_to_vk.h"
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h" #include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_instance.h"
@ -125,58 +128,102 @@ PipelineCache::~PipelineCache() {
SaveDiskCache(); SaveDiskCache();
} }
void PipelineCache::LoadDiskCache() { void PipelineCache::LoadDiskCache(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);
}
auto load_cache = [this, &cache_info, &callback](bool allow_fallback) {
const vk::Device device = instance.GetDevice();
try {
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) {
// Fall back to empty cache
cache_info.initialDataSize = 0;
cache_info.pInitialData = nullptr;
try {
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::Complete, 0, 0);
}
};
// Try to load existing pipeline cache if disk cache is enabled and directories exist
if (!Settings::values.use_disk_shader_cache || !EnsureDirectories()) { if (!Settings::values.use_disk_shader_cache || !EnsureDirectories()) {
load_cache(false);
return; return;
} }
// Try to load existing pipeline cache for this game/device combination
const auto cache_dir = GetPipelineCacheDir(); const auto cache_dir = GetPipelineCacheDir();
const u32 vendor_id = instance.GetVendorID(); const u32 vendor_id = instance.GetVendorID();
const u32 device_id = instance.GetDeviceID(); const u32 device_id = instance.GetDeviceID();
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id); const u64 program_id = GetProgramID();
const auto cache_file_path =
fmt::format("{}{:016X}-{:X}{:X}.bin", cache_dir, program_id, vendor_id, device_id);
vk::PipelineCacheCreateInfo cache_info{};
std::vector<u8> cache_data; std::vector<u8> cache_data;
SCOPE_EXIT({
const vk::Device device = instance.GetDevice();
pipeline_cache = device.createPipelineCacheUnique(cache_info);
});
FileUtil::IOFile cache_file{cache_file_path, "rb"}; FileUtil::IOFile cache_file{cache_file_path, "rb"};
if (!cache_file.IsOpen()) { if (!cache_file.IsOpen()) {
LOG_INFO(Render_Vulkan, "No pipeline cache found for device"); LOG_INFO(Render_Vulkan, "No pipeline cache found for title_id={:016X}", program_id);
load_cache(false);
return; return;
} }
const u64 cache_file_size = cache_file.GetSize(); const u64 cache_file_size = cache_file.GetSize();
cache_data.resize(cache_file_size); cache_data.resize(cache_file_size);
if (cache_file.ReadBytes(cache_data.data(), cache_file_size) != cache_file_size) { if (cache_file.ReadBytes(cache_data.data(), cache_file_size) != cache_file_size) {
LOG_ERROR(Render_Vulkan, "Error during pipeline cache read"); LOG_ERROR(Render_Vulkan, "Error reading pipeline cache");
load_cache(false);
return; return;
} }
if (!IsCacheValid(cache_data)) { if (!IsCacheValid(cache_data)) {
LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, removing"); LOG_WARNING(Render_Vulkan, "Pipeline cache invalid, removing");
cache_file.Close(); cache_file.Close();
FileUtil::Delete(cache_file_path); FileUtil::Delete(cache_file_path);
load_cache(false);
return; return;
} }
LOG_INFO(Render_Vulkan, "Loading pipeline cache with size {} KB", cache_file_size / 1024); LOG_INFO(Render_Vulkan, "Loading pipeline cache for title_id={:016X} with size {} KB",
program_id, cache_file_size / 1024);
cache_info.initialDataSize = cache_file_size; cache_info.initialDataSize = cache_file_size;
cache_info.pInitialData = cache_data.data(); cache_info.pInitialData = cache_data.data();
load_cache(true);
} }
void PipelineCache::SaveDiskCache() { void PipelineCache::SaveDiskCache() {
if (!Settings::values.use_disk_shader_cache || !EnsureDirectories() || !pipeline_cache) { // Save Vulkan pipeline cache
if (!Settings::values.use_disk_shader_cache || !pipeline_cache) {
return; return;
} }
const auto cache_dir = GetPipelineCacheDir(); const auto cache_dir = GetPipelineCacheDir();
const u32 vendor_id = instance.GetVendorID(); const u32 vendor_id = instance.GetVendorID();
const u32 device_id = instance.GetDeviceID(); const u32 device_id = instance.GetDeviceID();
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id); const u64 program_id = GetProgramID();
// Include both device info and program id in cache path to handle both GPU changes and
// different games
const auto cache_file_path =
fmt::format("{}{:016X}-{:X}{:X}.bin", cache_dir, program_id, vendor_id, device_id);
FileUtil::IOFile cache_file{cache_file_path, "wb"}; FileUtil::IOFile cache_file{cache_file_path, "wb"};
if (!cache_file.IsOpen()) { if (!cache_file.IsOpen()) {
@ -513,7 +560,49 @@ bool PipelineCache::EnsureDirectories() const {
} }
std::string PipelineCache::GetPipelineCacheDir() const { std::string PipelineCache::GetPipelineCacheDir() const {
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP; return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP + "pipeline" +
DIR_SEP;
}
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);
}
// 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);
LoadDiskCache(stop_loading, nullptr);
if (callback) {
callback(VideoCore::LoadCallbackStage::Complete, 0, 0);
}
} }
} // namespace Vulkan } // namespace Vulkan

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -7,6 +7,7 @@
#include <bitset> #include <bitset>
#include <tsl/robin_map.h> #include <tsl/robin_map.h>
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h" #include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/shader/generator/pica_fs_config.h" #include "video_core/shader/generator/pica_fs_config.h"
@ -58,7 +59,8 @@ public:
} }
/// Loads the pipeline cache stored to disk /// Loads the pipeline cache stored to disk
void LoadDiskCache(); void LoadDiskCache(const std::atomic_bool& stop_loading = std::atomic_bool{false},
const VideoCore::DiskResourceLoadCallback& callback = {});
/// Stores the generated pipeline cache to disk /// Stores the generated pipeline cache to disk
void SaveDiskCache(); void SaveDiskCache();
@ -82,6 +84,20 @@ public:
/// Binds a fragment shader generated from PICA state /// Binds a fragment shader generated from PICA state
void UseFragmentShader(const Pica::RegsInternal& regs, const Pica::Shader::UserConfig& user); 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;
}
void SetProgramID(u64 program_id) {
current_program_id = program_id;
}
private: private:
/// Builds the rasterizer pipeline layout /// Builds the rasterizer pipeline layout
void BuildLayout(); void BuildLayout();
@ -89,7 +105,7 @@ private:
/// Returns true when the disk data can be used by the current driver /// Returns true when the disk data can be used by the current driver
bool IsCacheValid(std::span<const u8> cache_data) const; bool IsCacheValid(std::span<const u8> cache_data) const;
/// Create shader disk cache directories. Returns true on success. /// Create pipeline cache directories. Returns true on success.
bool EnsureDirectories() const; bool EnsureDirectories() const;
/// Returns the pipeline cache storage dir /// Returns the pipeline cache storage dir
@ -110,7 +126,6 @@ private:
GraphicsPipeline* current_pipeline{}; GraphicsPipeline* current_pipeline{};
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>> tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
graphics_pipelines; graphics_pipelines;
std::array<DescriptorHeap, NumDescriptorHeaps> descriptor_heaps; std::array<DescriptorHeap, NumDescriptorHeaps> descriptor_heaps;
std::array<vk::DescriptorSet, NumRasterizerSets> bound_descriptor_sets{}; std::array<vk::DescriptorSet, NumRasterizerSets> bound_descriptor_sets{};
std::array<u32, NumDynamicOffsets> offsets{}; std::array<u32, NumDynamicOffsets> offsets{};
@ -122,6 +137,8 @@ private:
std::unordered_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders; std::unordered_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
std::unordered_map<Pica::Shader::FSConfig, Shader> fragment_shaders; std::unordered_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
Shader trivial_vertex_shader; Shader trivial_vertex_shader;
u64 current_program_id{0};
}; };
} // namespace Vulkan } // namespace Vulkan

View File

@ -8,6 +8,8 @@
#include "common/math_util.h" #include "common/math_util.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h"
#include "core/loader/loader.h"
#include "core/memory.h" #include "core/memory.h"
#include "video_core/pica/pica_core.h" #include "video_core/pica/pica_core.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/renderer_vulkan.h"
@ -143,7 +145,15 @@ void RasterizerVulkan::TickFrame() {
void RasterizerVulkan::LoadDefaultDiskResources( void RasterizerVulkan::LoadDefaultDiskResources(
const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) {
pipeline_cache.LoadDiskCache();
u64 program_id;
if (Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id) !=
Loader::ResultStatus::Success) {
program_id = 0;
}
pipeline_cache.SetProgramID(program_id);
pipeline_cache.LoadDiskCache(stop_loading, callback);
} }
void RasterizerVulkan::SyncDrawState() { void RasterizerVulkan::SyncDrawState() {
@ -973,4 +983,9 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
uniform_buffer.Commit(used_bytes); uniform_buffer.Commit(used_bytes);
} }
void RasterizerVulkan::SwitchDiskResources(u64 title_id) {
std::atomic_bool stop_loading = false;
pipeline_cache.SwitchPipelineCache(title_id, stop_loading, switch_disk_resources_callback);
}
} // namespace Vulkan } // namespace Vulkan

View File

@ -61,6 +61,9 @@ public:
u32 pixel_stride, ScreenInfo& screen_info); u32 pixel_stride, ScreenInfo& screen_info);
bool AccelerateDrawBatch(bool is_indexed) override; bool AccelerateDrawBatch(bool is_indexed) override;
/// Switches the disk resources to the specified title
void SwitchDiskResources(u64 title_id) override;
private: private:
/// Syncs pipeline state from PICA registers /// Syncs pipeline state from PICA registers
void SyncDrawState(); void SyncDrawState();