From e24f8da1131deefd0dbcc0c47c3e842d646014d6 Mon Sep 17 00:00:00 2001 From: jbm11208 <81182113+jbm11208@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:51:25 -0400 Subject: [PATCH] Add per-title vulkan pipeline cache (#1118) --- .../renderer_vulkan/vk_pipeline_cache.cpp | 121 +++++++++++++++--- .../renderer_vulkan/vk_pipeline_cache.h | 25 +++- .../renderer_vulkan/vk_rasterizer.cpp | 17 ++- .../renderer_vulkan/vk_rasterizer.h | 3 + 4 files changed, 145 insertions(+), 21 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7df88d13f..1ff478984 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -10,6 +10,9 @@ #include "common/microprofile.h" #include "common/scope_exit.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/vk_descriptor_update_queue.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -125,58 +128,102 @@ PipelineCache::~PipelineCache() { 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()) { + load_cache(false); return; } + // Try to load existing pipeline cache for this game/device combination const auto cache_dir = GetPipelineCacheDir(); const u32 vendor_id = instance.GetVendorID(); 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 cache_data; - - SCOPE_EXIT({ - const vk::Device device = instance.GetDevice(); - pipeline_cache = device.createPipelineCacheUnique(cache_info); - }); - FileUtil::IOFile cache_file{cache_file_path, "rb"}; + 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; } const u64 cache_file_size = cache_file.GetSize(); cache_data.resize(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; } if (!IsCacheValid(cache_data)) { - LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, removing"); + LOG_WARNING(Render_Vulkan, "Pipeline cache invalid, removing"); cache_file.Close(); FileUtil::Delete(cache_file_path); + load_cache(false); 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.pInitialData = cache_data.data(); + load_cache(true); } 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; } const auto cache_dir = GetPipelineCacheDir(); const u32 vendor_id = instance.GetVendorID(); 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"}; if (!cache_file.IsOpen()) { @@ -513,7 +560,49 @@ bool PipelineCache::EnsureDirectories() 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 diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 6bb85d5e4..650df5680 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,6 +7,7 @@ #include #include +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" #include "video_core/shader/generator/pica_fs_config.h" @@ -58,7 +59,8 @@ public: } /// 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 void SaveDiskCache(); @@ -82,6 +84,20 @@ 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; + } + + void SetProgramID(u64 program_id) { + current_program_id = program_id; + } + private: /// Builds the rasterizer pipeline layout void BuildLayout(); @@ -89,7 +105,7 @@ private: /// Returns true when the disk data can be used by the current driver bool IsCacheValid(std::span cache_data) const; - /// Create shader disk cache directories. Returns true on success. + /// Create pipeline cache directories. Returns true on success. bool EnsureDirectories() const; /// Returns the pipeline cache storage dir @@ -110,7 +126,6 @@ private: GraphicsPipeline* current_pipeline{}; tsl::robin_map, Common::IdentityHash> graphics_pipelines; - std::array descriptor_heaps; std::array bound_descriptor_sets{}; std::array offsets{}; @@ -122,6 +137,8 @@ private: std::unordered_map fixed_geometry_shaders; std::unordered_map fragment_shaders; Shader trivial_vertex_shader; + + u64 current_program_id{0}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 3319b34b8..e9df994f7 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -8,6 +8,8 @@ #include "common/math_util.h" #include "common/microprofile.h" #include "common/settings.h" +#include "core/core.h" +#include "core/loader/loader.h" #include "core/memory.h" #include "video_core/pica/pica_core.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" @@ -143,7 +145,15 @@ void RasterizerVulkan::TickFrame() { void RasterizerVulkan::LoadDefaultDiskResources( 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() { @@ -973,4 +983,9 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) { 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 diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 1944a73f2..1770f6613 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -61,6 +61,9 @@ public: u32 pixel_stride, ScreenInfo& screen_info); bool AccelerateDrawBatch(bool is_indexed) override; + /// Switches the disk resources to the specified title + void SwitchDiskResources(u64 title_id) override; + private: /// Syncs pipeline state from PICA registers void SyncDrawState();