mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 05:09:34 -06:00
Move most backend functionality to VideoCommon
This commit is contained in:
@ -19,7 +19,7 @@
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ShaderCompiler.h"
|
||||
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
||||
#include "VideoBackends/Vulkan/Util.h"
|
||||
#include "VideoBackends/Vulkan/VKTexture.h"
|
||||
#include "VideoBackends/Vulkan/VertexFormat.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
@ -28,16 +28,16 @@ namespace Vulkan
|
||||
{
|
||||
std::unique_ptr<ObjectCache> g_object_cache;
|
||||
|
||||
ObjectCache::ObjectCache()
|
||||
{
|
||||
}
|
||||
ObjectCache::ObjectCache() = default;
|
||||
|
||||
ObjectCache::~ObjectCache()
|
||||
{
|
||||
DestroyPipelineCache();
|
||||
DestroySamplers();
|
||||
DestroyPipelineLayouts();
|
||||
DestroyDescriptorSetLayouts();
|
||||
DestroyRenderPassCache();
|
||||
m_dummy_texture.reset();
|
||||
}
|
||||
|
||||
bool ObjectCache::Initialize()
|
||||
@ -48,44 +48,37 @@ bool ObjectCache::Initialize()
|
||||
if (!CreatePipelineLayouts())
|
||||
return false;
|
||||
|
||||
if (!CreateUtilityShaderVertexFormat())
|
||||
return false;
|
||||
|
||||
if (!CreateStaticSamplers())
|
||||
return false;
|
||||
|
||||
m_texture_upload_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, INITIAL_TEXTURE_UPLOAD_BUFFER_SIZE,
|
||||
MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE);
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, TEXTURE_UPLOAD_BUFFER_SIZE);
|
||||
if (!m_texture_upload_buffer)
|
||||
{
|
||||
PanicAlert("Failed to create texture upload buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_utility_shader_vertex_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1024 * 1024, 4 * 1024 * 1024);
|
||||
m_utility_shader_uniform_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 1024, 4 * 1024 * 1024);
|
||||
if (!m_utility_shader_vertex_buffer || !m_utility_shader_uniform_buffer)
|
||||
return false;
|
||||
|
||||
m_dummy_texture = Texture2D::Create(1, 1, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_SAMPLE_COUNT_1_BIT,
|
||||
VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
|
||||
m_dummy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
VkClearColorValue clear_color = {};
|
||||
VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
m_dummy_texture->GetImage(), m_dummy_texture->GetLayout(), &clear_color, 1,
|
||||
&clear_range);
|
||||
m_dummy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
if (g_ActiveConfig.bShaderCache)
|
||||
{
|
||||
if (!LoadPipelineCache())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CreatePipelineCache())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectCache::Shutdown()
|
||||
{
|
||||
if (g_ActiveConfig.bShaderCache && m_pipeline_cache != VK_NULL_HANDLE)
|
||||
SavePipelineCache();
|
||||
}
|
||||
|
||||
void ObjectCache::ClearSamplerCache()
|
||||
{
|
||||
for (const auto& it : m_sampler_cache)
|
||||
@ -115,13 +108,9 @@ void ObjectCache::DestroySamplers()
|
||||
|
||||
bool ObjectCache::CreateDescriptorSetLayouts()
|
||||
{
|
||||
static const VkDescriptorSetLayoutBinding single_ubo_set_bindings[] = {
|
||||
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT};
|
||||
|
||||
// The geometry shader buffer must be last in this binding set, as we don't include it
|
||||
// if geometry shaders are not supported by the device. See the decrement below.
|
||||
static const VkDescriptorSetLayoutBinding per_stage_ubo_set_bindings[] = {
|
||||
static const VkDescriptorSetLayoutBinding standard_ubo_bindings[] = {
|
||||
{UBO_DESCRIPTOR_SET_BINDING_PS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{UBO_DESCRIPTOR_SET_BINDING_VS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
|
||||
@ -129,45 +118,56 @@ bool ObjectCache::CreateDescriptorSetLayouts()
|
||||
{UBO_DESCRIPTOR_SET_BINDING_GS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
|
||||
VK_SHADER_STAGE_GEOMETRY_BIT}};
|
||||
|
||||
static const VkDescriptorSetLayoutBinding sampler_set_bindings[] = {
|
||||
static const VkDescriptorSetLayoutBinding standard_sampler_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<u32>(NUM_PIXEL_SHADER_SAMPLERS),
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT}};
|
||||
|
||||
static const VkDescriptorSetLayoutBinding ssbo_set_bindings[] = {
|
||||
static const VkDescriptorSetLayoutBinding standard_ssbo_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}};
|
||||
|
||||
static const VkDescriptorSetLayoutBinding texel_buffer_set_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
static const VkDescriptorSetLayoutBinding utility_ubo_bindings[] = {
|
||||
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT};
|
||||
|
||||
// Utility samplers aren't dynamically indexed.
|
||||
static const VkDescriptorSetLayoutBinding utility_sampler_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{6, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{7, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
{8, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
};
|
||||
|
||||
static const VkDescriptorSetLayoutBinding compute_set_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{5, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{6, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{3, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{5, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo create_infos[NUM_DESCRIPTOR_SET_LAYOUTS] = {
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(single_ubo_set_bindings)), single_ubo_set_bindings},
|
||||
static_cast<u32>(ArraySize(standard_ubo_bindings)), standard_ubo_bindings},
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(per_stage_ubo_set_bindings)), per_stage_ubo_set_bindings},
|
||||
static_cast<u32>(ArraySize(standard_sampler_bindings)), standard_sampler_bindings},
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(sampler_set_bindings)), sampler_set_bindings},
|
||||
static_cast<u32>(ArraySize(standard_ssbo_bindings)), standard_ssbo_bindings},
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(ssbo_set_bindings)), ssbo_set_bindings},
|
||||
static_cast<u32>(ArraySize(utility_ubo_bindings)), utility_ubo_bindings},
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(texel_buffer_set_bindings)), texel_buffer_set_bindings},
|
||||
static_cast<u32>(ArraySize(utility_sampler_bindings)), utility_sampler_bindings},
|
||||
{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(compute_set_bindings)), compute_set_bindings}};
|
||||
|
||||
// Don't set the GS bit if geometry shaders aren't available.
|
||||
if (!g_vulkan_context->SupportsGeometryShaders())
|
||||
create_infos[DESCRIPTOR_SET_LAYOUT_PER_STAGE_UNIFORM_BUFFERS].bindingCount--;
|
||||
if (!g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
||||
create_infos[DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS].bindingCount--;
|
||||
|
||||
for (size_t i = 0; i < NUM_DESCRIPTOR_SET_LAYOUTS; i++)
|
||||
{
|
||||
@ -199,22 +199,15 @@ bool ObjectCache::CreatePipelineLayouts()
|
||||
// Descriptor sets for each pipeline layout.
|
||||
// In the standard set, the SSBO must be the last descriptor, as we do not include it
|
||||
// when fragment stores and atomics are not supported by the device.
|
||||
VkDescriptorSetLayout standard_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PER_STAGE_UNIFORM_BUFFERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_SHADER_STORAGE_BUFFERS]};
|
||||
VkDescriptorSetLayout texture_conversion_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PER_STAGE_UNIFORM_BUFFERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_TEXEL_BUFFERS]};
|
||||
VkDescriptorSetLayout utility_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_SINGLE_UNIFORM_BUFFER],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS]};
|
||||
VkDescriptorSetLayout compute_sets[] = {m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_COMPUTE]};
|
||||
VkPushConstantRange push_constant_range = {
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, PUSH_CONSTANT_BUFFER_SIZE};
|
||||
VkPushConstantRange compute_push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, 0,
|
||||
PUSH_CONSTANT_BUFFER_SIZE};
|
||||
const VkDescriptorSetLayout standard_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SAMPLERS],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS]};
|
||||
const VkDescriptorSetLayout utility_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UTILITY_UNIFORM_BUFFER],
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UTILITY_SAMPLERS]};
|
||||
const VkDescriptorSetLayout compute_sets[] = {
|
||||
m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_COMPUTE]};
|
||||
|
||||
// Info for each pipeline layout
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_info[NUM_PIPELINE_LAYOUTS] = {
|
||||
@ -222,25 +215,16 @@ bool ObjectCache::CreatePipelineLayouts()
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(standard_sets)), standard_sets, 0, nullptr},
|
||||
|
||||
// Push Constant
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(standard_sets)), standard_sets, 1, &push_constant_range},
|
||||
|
||||
// Texture Conversion
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(texture_conversion_sets)), texture_conversion_sets, 1,
|
||||
&push_constant_range},
|
||||
|
||||
// Texture Conversion
|
||||
// Utility
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(utility_sets)), utility_sets, 0, nullptr},
|
||||
|
||||
// Compute
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(compute_sets)), compute_sets, 1, &compute_push_constant_range}};
|
||||
static_cast<u32>(ArraySize(compute_sets)), compute_sets, 0, nullptr}};
|
||||
|
||||
// If bounding box is unsupported, don't bother with the SSBO descriptor set.
|
||||
if (!g_vulkan_context->SupportsBoundingBox())
|
||||
if (!g_ActiveConfig.backend_info.bSupportsBBox)
|
||||
pipeline_layout_info[PIPELINE_LAYOUT_STANDARD].setLayoutCount--;
|
||||
|
||||
for (size_t i = 0; i < NUM_PIPELINE_LAYOUTS; i++)
|
||||
@ -265,30 +249,6 @@ void ObjectCache::DestroyPipelineLayouts()
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectCache::CreateUtilityShaderVertexFormat()
|
||||
{
|
||||
PortableVertexDeclaration vtx_decl = {};
|
||||
vtx_decl.position.enable = true;
|
||||
vtx_decl.position.type = VAR_FLOAT;
|
||||
vtx_decl.position.components = 4;
|
||||
vtx_decl.position.integer = false;
|
||||
vtx_decl.position.offset = offsetof(UtilityShaderVertex, Position);
|
||||
vtx_decl.texcoords[0].enable = true;
|
||||
vtx_decl.texcoords[0].type = VAR_FLOAT;
|
||||
vtx_decl.texcoords[0].components = 4;
|
||||
vtx_decl.texcoords[0].integer = false;
|
||||
vtx_decl.texcoords[0].offset = offsetof(UtilityShaderVertex, TexCoord);
|
||||
vtx_decl.colors[0].enable = true;
|
||||
vtx_decl.colors[0].type = VAR_UNSIGNED_BYTE;
|
||||
vtx_decl.colors[0].components = 4;
|
||||
vtx_decl.colors[0].integer = false;
|
||||
vtx_decl.colors[0].offset = offsetof(UtilityShaderVertex, Color);
|
||||
vtx_decl.stride = sizeof(UtilityShaderVertex);
|
||||
|
||||
m_utility_shader_vertex_format = std::make_unique<VertexFormat>(vtx_decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectCache::CreateStaticSamplers()
|
||||
{
|
||||
VkSamplerCreateInfo create_info = {
|
||||
@ -472,4 +432,199 @@ void ObjectCache::DestroyRenderPassCache()
|
||||
vkDestroyRenderPass(g_vulkan_context->GetDevice(), it.second, nullptr);
|
||||
m_render_pass_cache.clear();
|
||||
}
|
||||
|
||||
class PipelineCacheReadCallback : public LinearDiskCacheReader<u32, u8>
|
||||
{
|
||||
public:
|
||||
PipelineCacheReadCallback(std::vector<u8>* data) : m_data(data) {}
|
||||
void Read(const u32& key, const u8* value, u32 value_size) override
|
||||
{
|
||||
m_data->resize(value_size);
|
||||
if (value_size > 0)
|
||||
memcpy(m_data->data(), value, value_size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8>* m_data;
|
||||
};
|
||||
|
||||
class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader<u32, u8>
|
||||
{
|
||||
public:
|
||||
void Read(const u32& key, const u8* value, u32 value_size) override {}
|
||||
};
|
||||
|
||||
bool ObjectCache::CreatePipelineCache()
|
||||
{
|
||||
// Vulkan pipeline caches can be shared between games for shader compile time reduction.
|
||||
// This assumes that drivers don't create all pipelines in the cache on load time, only
|
||||
// when a lookup occurs that matches a pipeline (or pipeline data) in the cache.
|
||||
m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
|
||||
|
||||
VkPipelineCacheCreateInfo info = {
|
||||
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
|
||||
nullptr, // const void* pNext
|
||||
0, // VkPipelineCacheCreateFlags flags
|
||||
0, // size_t initialDataSize
|
||||
nullptr // const void* pInitialData
|
||||
};
|
||||
|
||||
VkResult res =
|
||||
vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
|
||||
if (res == VK_SUCCESS)
|
||||
return true;
|
||||
|
||||
LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ObjectCache::LoadPipelineCache()
|
||||
{
|
||||
// We have to keep the pipeline cache file name around since when we save it
|
||||
// we delete the old one, by which time the game's unique ID is already cleared.
|
||||
m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
|
||||
|
||||
std::vector<u8> disk_data;
|
||||
LinearDiskCache<u32, u8> disk_cache;
|
||||
PipelineCacheReadCallback read_callback(&disk_data);
|
||||
if (disk_cache.OpenAndRead(m_pipeline_cache_filename, read_callback) != 1)
|
||||
disk_data.clear();
|
||||
|
||||
if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
|
||||
{
|
||||
// Don't use this data. In fact, we should delete it to prevent it from being used next time.
|
||||
File::Delete(m_pipeline_cache_filename);
|
||||
return CreatePipelineCache();
|
||||
}
|
||||
|
||||
VkPipelineCacheCreateInfo info = {
|
||||
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
|
||||
nullptr, // const void* pNext
|
||||
0, // VkPipelineCacheCreateFlags flags
|
||||
disk_data.size(), // size_t initialDataSize
|
||||
disk_data.data() // const void* pInitialData
|
||||
};
|
||||
|
||||
VkResult res =
|
||||
vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
|
||||
if (res == VK_SUCCESS)
|
||||
return true;
|
||||
|
||||
// Failed to create pipeline cache, try with it empty.
|
||||
LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed, trying empty cache: ");
|
||||
return CreatePipelineCache();
|
||||
}
|
||||
|
||||
// Based on Vulkan 1.0 specification,
|
||||
// Table 9.1. Layout for pipeline cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE
|
||||
// NOTE: This data is assumed to be in little-endian format.
|
||||
#pragma pack(push, 4)
|
||||
struct VK_PIPELINE_CACHE_HEADER
|
||||
{
|
||||
u32 header_length;
|
||||
u32 header_version;
|
||||
u32 vendor_id;
|
||||
u32 device_id;
|
||||
u8 uuid[VK_UUID_SIZE];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
|
||||
"VK_PIPELINE_CACHE_HEADER must be trivially copyable");
|
||||
|
||||
bool ObjectCache::ValidatePipelineCache(const u8* data, size_t data_length)
|
||||
{
|
||||
if (data_length < sizeof(VK_PIPELINE_CACHE_HEADER))
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header");
|
||||
return false;
|
||||
}
|
||||
|
||||
VK_PIPELINE_CACHE_HEADER header;
|
||||
std::memcpy(&header, data, sizeof(header));
|
||||
if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header length");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.vendor_id != g_vulkan_context->GetDeviceProperties().vendorID)
|
||||
{
|
||||
ERROR_LOG(VIDEO,
|
||||
"Pipeline cache failed validation: Incorrect vendor ID (file: 0x%X, device: 0x%X)",
|
||||
header.vendor_id, g_vulkan_context->GetDeviceProperties().vendorID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.device_id != g_vulkan_context->GetDeviceProperties().deviceID)
|
||||
{
|
||||
ERROR_LOG(VIDEO,
|
||||
"Pipeline cache failed validation: Incorrect device ID (file: 0x%X, device: 0x%X)",
|
||||
header.device_id, g_vulkan_context->GetDeviceProperties().deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID,
|
||||
VK_UUID_SIZE) != 0)
|
||||
{
|
||||
ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect UUID");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectCache::DestroyPipelineCache()
|
||||
{
|
||||
vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr);
|
||||
m_pipeline_cache = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void ObjectCache::SavePipelineCache()
|
||||
{
|
||||
size_t data_size;
|
||||
VkResult res =
|
||||
vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, nullptr);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> data(data_size);
|
||||
res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size,
|
||||
data.data());
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the old cache and re-create.
|
||||
File::Delete(m_pipeline_cache_filename);
|
||||
|
||||
// We write a single key of 1, with the entire pipeline cache data.
|
||||
// Not ideal, but our disk cache class does not support just writing a single blob
|
||||
// of data without specifying a key.
|
||||
LinearDiskCache<u32, u8> disk_cache;
|
||||
PipelineCacheReadIgnoreCallback callback;
|
||||
disk_cache.OpenAndRead(m_pipeline_cache_filename, callback);
|
||||
disk_cache.Append(1, data.data(), static_cast<u32>(data.size()));
|
||||
disk_cache.Close();
|
||||
}
|
||||
|
||||
void ObjectCache::ReloadPipelineCache()
|
||||
{
|
||||
SavePipelineCache();
|
||||
|
||||
if (g_ActiveConfig.bShaderCache)
|
||||
LoadPipelineCache();
|
||||
else
|
||||
CreatePipelineCache();
|
||||
}
|
||||
} // namespace Vulkan
|
||||
|
Reference in New Issue
Block a user