mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-06 23:19:57 +01:00
Add per-title vulkan pipeline cache (#1118)
This commit is contained in:
parent
680cbb559d
commit
e24f8da113
@ -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<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"};
|
||||
|
||||
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
|
||||
|
||||
@ -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 <bitset>
|
||||
#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_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<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;
|
||||
|
||||
/// Returns the pipeline cache storage dir
|
||||
@ -110,7 +126,6 @@ private:
|
||||
GraphicsPipeline* current_pipeline{};
|
||||
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
|
||||
graphics_pipelines;
|
||||
|
||||
std::array<DescriptorHeap, NumDescriptorHeaps> descriptor_heaps;
|
||||
std::array<vk::DescriptorSet, NumRasterizerSets> bound_descriptor_sets{};
|
||||
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::FSConfig, Shader> fragment_shaders;
|
||||
Shader trivial_vertex_shader;
|
||||
|
||||
u64 current_program_id{0};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user