video_core/vulkan: Added VkPipelineCache to store Vulkan pipelines

As an optional feature which can be enabled in the advanced graphics configuration, all pipelines that get built at the initial shader loading are stored in a VkPipelineCache object and are dumped to the disk.

These vendor specific pipeline cache files are located at `/shader/GAME_ID/vulkan_pipelines.bin`. This feature was mainly added because of an issue with the AMD driver (see yuzu-emu#8507) causing invalidation of the cache files the driver builds automatically.
This commit is contained in:
Wollnashorn 2022-12-30 14:50:07 +01:00
parent e82e3e06be
commit 16809c1fa7
15 changed files with 253 additions and 67 deletions

View file

@ -24,13 +24,15 @@ using Shader::ImageBufferDescriptor;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descriptor_pool,
ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_,
DescriptorPool& descriptor_pool,
UpdateDescriptorQueue& update_descriptor_queue_,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_,
vk::ShaderModule spv_module_)
: device{device_}, update_descriptor_queue{update_descriptor_queue_}, info{info_},
: device{device_}, pipeline_cache(pipeline_cache_),
update_descriptor_queue{update_descriptor_queue_}, info{info_},
spv_module(std::move(spv_module_)) {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
@ -56,23 +58,27 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript
if (device.IsKhrPipelineExecutablePropertiesEnabled()) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
}
pipeline = device.GetLogical().CreateComputePipeline({
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *spv_module,
.pName = "main",
.pSpecializationInfo = nullptr,
pipeline = device.GetLogical().CreateComputePipeline(
{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext =
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *spv_module,
.pName = "main",
.pSpecializationInfo = nullptr,
},
.layout = *pipeline_layout,
.basePipelineHandle = 0,
.basePipelineIndex = 0,
},
.layout = *pipeline_layout,
.basePipelineHandle = 0,
.basePipelineIndex = 0,
});
*pipeline_cache);
if (pipeline_statistics) {
pipeline_statistics->Collect(*pipeline);
}

View file

@ -28,7 +28,8 @@ class Scheduler;
class ComputePipeline {
public:
explicit ComputePipeline(const Device& device, DescriptorPool& descriptor_pool,
explicit ComputePipeline(const Device& device, vk::PipelineCache& pipeline_cache,
DescriptorPool& descriptor_pool,
UpdateDescriptorQueue& update_descriptor_queue,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
@ -46,6 +47,7 @@ public:
private:
const Device& device;
vk::PipelineCache& pipeline_cache;
UpdateDescriptorQueue& update_descriptor_queue;
Shader::Info info;

View file

@ -234,13 +234,14 @@ ConfigureFuncPtr ConfigureFunc(const std::array<vk::ShaderModule, NUM_STAGES>& m
GraphicsPipeline::GraphicsPipeline(
Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_,
VideoCore::ShaderNotify* shader_notify, const Device& device_, DescriptorPool& descriptor_pool,
vk::PipelineCache& pipeline_cache_, VideoCore::ShaderNotify* shader_notify,
const Device& device_, DescriptorPool& descriptor_pool,
UpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key_, std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos)
: key{key_}, device{device_}, texture_cache{texture_cache_},
buffer_cache{buffer_cache_}, scheduler{scheduler_},
: key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
pipeline_cache(pipeline_cache_), scheduler{scheduler_},
update_descriptor_queue{update_descriptor_queue_}, spv_modules{std::move(stages)} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
@ -897,27 +898,29 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
if (device.IsKhrPipelineExecutablePropertiesEnabled()) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
}
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stageCount = static_cast<u32>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_ci,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = &tessellation_ci,
.pViewportState = &viewport_ci,
.pRasterizationState = &rasterization_ci,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &color_blend_ci,
.pDynamicState = &dynamic_state_ci,
.layout = *pipeline_layout,
.renderPass = render_pass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
});
pipeline = device.GetLogical().CreateGraphicsPipeline(
{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = flags,
.stageCount = static_cast<u32>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_ci,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = &tessellation_ci,
.pViewportState = &viewport_ci,
.pRasterizationState = &rasterization_ci,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &color_blend_ci,
.pDynamicState = &dynamic_state_ci,
.layout = *pipeline_layout,
.renderPass = render_pass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
},
*pipeline_cache);
}
void GraphicsPipeline::Validate() {

View file

@ -70,16 +70,14 @@ class GraphicsPipeline {
static constexpr size_t NUM_STAGES = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage;
public:
explicit GraphicsPipeline(Scheduler& scheduler, BufferCache& buffer_cache,
TextureCache& texture_cache, VideoCore::ShaderNotify* shader_notify,
const Device& device, DescriptorPool& descriptor_pool,
UpdateDescriptorQueue& update_descriptor_queue,
Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics,
RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key,
std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
explicit GraphicsPipeline(
Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache,
vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify,
const Device& device, DescriptorPool& descriptor_pool,
UpdateDescriptorQueue& update_descriptor_queue, Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key, std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
@ -133,6 +131,7 @@ private:
const Device& device;
TextureCache& texture_cache;
BufferCache& buffer_cache;
vk::PipelineCache& pipeline_cache;
Scheduler& scheduler;
UpdateDescriptorQueue& update_descriptor_queue;

View file

@ -55,6 +55,7 @@ using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
constexpr u32 CACHE_VERSION = 10;
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
template <typename Container>
auto MakeSpan(Container& container) {
@ -284,6 +285,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_},
texture_cache{texture_cache_}, shader_notify{shader_notify_},
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
@ -362,7 +364,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
};
}
PipelineCache::~PipelineCache() = default;
PipelineCache::~PipelineCache() {
if (use_vulkan_pipeline_cache) {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache);
}
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
MICROPROFILE_SCOPE(Vulkan_PipelineCache);
@ -418,6 +424,11 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
}
pipeline_cache_filename = base_dir / "vulkan.bin";
if (use_vulkan_pipeline_cache) {
vulkan_pipeline_cache_filename = base_dir / "vulkan_pipelines.bin";
vulkan_pipeline_cache = LoadVulkanPipelineCache(vulkan_pipeline_cache_filename);
}
struct {
std::mutex mutex;
size_t total{};
@ -496,6 +507,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
workers.WaitForRequests(stop_loading);
if (use_vulkan_pipeline_cache) {
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache);
}
if (state.statistics) {
state.statistics->Report();
}
@ -616,10 +631,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
previous_stage = &program;
}
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<GraphicsPipeline>(scheduler, buffer_cache, texture_cache,
&shader_notify, device, descriptor_pool,
update_descriptor_queue, thread_worker, statistics,
render_pass_cache, key, std::move(modules), infos);
return std::make_unique<GraphicsPipeline>(
scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device,
descriptor_pool, update_descriptor_queue, thread_worker, statistics, render_pass_cache, key,
std::move(modules), infos);
} catch (const Shader::Exception& exception) {
LOG_ERROR(Render_Vulkan, "{}", exception.what());
@ -689,13 +704,99 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
spv_module.SetObjectNameEXT(name.c_str());
}
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<ComputePipeline>(device, descriptor_pool, update_descriptor_queue,
thread_worker, statistics, &shader_notify,
program.info, std::move(spv_module));
return std::make_unique<ComputePipeline>(device, vulkan_pipeline_cache, descriptor_pool,
update_descriptor_queue, thread_worker, statistics,
&shader_notify, program.info, std::move(spv_module));
} catch (const Shader::Exception& exception) {
LOG_ERROR(Render_Vulkan, "{}", exception.what());
return nullptr;
}
void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& filename,
const vk::PipelineCache& pipeline_cache) try {
std::ofstream file(filename, std::ios::binary);
file.exceptions(std::ifstream::failbit);
if (!file.is_open()) {
LOG_ERROR(Common_Filesystem, "Failed to open Vulkan pipeline cache file {}",
Common::FS::PathToUTF8String(filename));
return;
}
file.write(VULKAN_CACHE_MAGIC_NUMBER.data(), VULKAN_CACHE_MAGIC_NUMBER.size());
size_t cache_size = 0;
std::vector<char> cache_data;
if (pipeline_cache) {
pipeline_cache.Read(&cache_size, nullptr);
cache_data.resize(cache_size);
pipeline_cache.Read(&cache_size, cache_data.data());
}
file.write(cache_data.data(), cache_size);
LOG_INFO(Render_Vulkan, "Vulkan pipelines cached at: {}",
Common::FS::PathToUTF8String(filename));
} catch (const std::ios_base::failure& e) {
LOG_ERROR(Common_Filesystem, "{}", e.what());
if (!Common::FS::RemoveFile(filename)) {
LOG_ERROR(Common_Filesystem, "Failed to delete Vulkan pipeline cache file {}",
Common::FS::PathToUTF8String(filename));
}
}
vk::PipelineCache PipelineCache::LoadVulkanPipelineCache(const std::filesystem::path& filename) {
const auto create_pipeline_cache = [this](size_t data_size, const void* data) {
VkPipelineCacheCreateInfo pipeline_cache_ci = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.initialDataSize = data_size,
.pInitialData = data};
return device.GetLogical().CreatePipelineCache(pipeline_cache_ci);
};
try {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return create_pipeline_cache(0, nullptr);
}
file.exceptions(std::ifstream::failbit);
const auto end{file.tellg()};
file.seekg(0, std::ios::beg);
std::array<char, 8> magic_number;
file.read(magic_number.data(), magic_number.size());
if (magic_number != VULKAN_CACHE_MAGIC_NUMBER) {
file.close();
if (Common::FS::RemoveFile(filename)) {
if (magic_number != VULKAN_CACHE_MAGIC_NUMBER) {
LOG_ERROR(Common_Filesystem, "Invalid Vulkan pipeline cache file");
}
} else {
LOG_ERROR(Common_Filesystem,
"Invalid Vulkan pipeline cache file and failed to delete it in \"{}\"",
Common::FS::PathToUTF8String(filename));
}
return create_pipeline_cache(0, nullptr);
}
const size_t cache_size = static_cast<size_t>(end) - magic_number.size();
std::vector<char> cache_data(cache_size);
file.read(cache_data.data(), cache_size);
LOG_INFO(Render_Vulkan,
"Loaded Vulkan pipeline cache: ", Common::FS::PathToUTF8String(filename));
return create_pipeline_cache(cache_size, cache_data.data());
} catch (const std::ios_base::failure& e) {
LOG_ERROR(Common_Filesystem, "{}", e.what());
if (!Common::FS::RemoveFile(filename)) {
LOG_ERROR(Common_Filesystem, "Failed to delete Vulkan pipeline cache file {}",
Common::FS::PathToUTF8String(filename));
}
return create_pipeline_cache(0, nullptr);
}
}
} // namespace Vulkan

View file

@ -135,6 +135,11 @@ private:
PipelineStatistics* statistics,
bool build_in_parallel);
void SerializeVulkanPipelineCache(const std::filesystem::path& filename,
const vk::PipelineCache& pipeline_cache);
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename);
const Device& device;
Scheduler& scheduler;
DescriptorPool& descriptor_pool;
@ -144,6 +149,7 @@ private:
TextureCache& texture_cache;
VideoCore::ShaderNotify& shader_notify;
bool use_asynchronous_shaders{};
bool use_vulkan_pipeline_cache{};
GraphicsPipelineCacheKey graphics_key{};
GraphicsPipeline* current_pipeline{};
@ -158,6 +164,9 @@ private:
std::filesystem::path pipeline_cache_filename;
std::filesystem::path vulkan_pipeline_cache_filename;
vk::PipelineCache vulkan_pipeline_cache;
Common::ThreadWorker workers;
Common::ThreadWorker serialization_thread;
DynamicFeatures dynamic_features;