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/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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user