Skip to content

Commit

Permalink
Merge pull request #4449 from stenzek/vulkan-pipeline-cache
Browse files Browse the repository at this point in the history
Vulkan: Implement pipeline UID cache
  • Loading branch information
stenzek committed Nov 28, 2016
2 parents 6812945 + 8d48319 commit da87580
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 53 deletions.
114 changes: 98 additions & 16 deletions Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
Expand Up @@ -159,12 +159,8 @@ GetVulkanColorBlendState(const BlendState& state,
return vk_state;
}

VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
VkPipeline ObjectCache::CreatePipeline(const PipelineInfo& info)
{
auto iter = m_pipeline_objects.find(info);
if (iter != m_pipeline_objects.end())
return iter->second;

// Declare descriptors for empty vertex buffers/attributes
static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
Expand Down Expand Up @@ -278,16 +274,34 @@ VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
-1 // int32_t basePipelineIndex
};

VkPipeline pipeline = VK_NULL_HANDLE;
VkPipeline pipeline;
VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
&pipeline_info, nullptr, &pipeline);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
return VK_NULL_HANDLE;
}

m_pipeline_objects.emplace(info, pipeline);
return pipeline;
}

VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
{
return GetPipelineWithCacheResult(info).first;
}

std::pair<VkPipeline, bool> ObjectCache::GetPipelineWithCacheResult(const PipelineInfo& info)
{
auto iter = m_pipeline_objects.find(info);
if (iter != m_pipeline_objects.end())
return {iter->second, true};

VkPipeline pipeline = CreatePipeline(info);
m_pipeline_objects.emplace(info, pipeline);
return {pipeline, false};
}

std::string ObjectCache::GetDiskCacheFileName(const char* type)
{
return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
Expand Down Expand Up @@ -330,6 +344,13 @@ bool ObjectCache::CreatePipelineCache(bool load_from_disk)
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);
disk_data.clear();
}

VkPipelineCacheCreateInfo info = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
nullptr, // const void* pNext
Expand All @@ -355,6 +376,76 @@ bool ObjectCache::CreatePipelineCache(bool load_from_disk)
return false;
}

// 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)
// TODO: Remove the #if here when GCC 5 is a minimum build requirement.
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
static_assert(std::has_trivial_copy_constructor<VK_PIPELINE_CACHE_HEADER>::value,
"VK_PIPELINE_CACHE_HEADER must be trivially copyable");
#else
static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
"VK_PIPELINE_CACHE_HEADER must be trivially copyable");
#endif

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()
{
for (const auto& it : m_pipeline_objects)
Expand All @@ -368,15 +459,6 @@ void ObjectCache::DestroyPipelineCache()
m_pipeline_cache = VK_NULL_HANDLE;
}

void ObjectCache::ClearPipelineCache()
{
// Reallocate the pipeline cache object, so it starts fresh and we don't
// save old pipelines to disk. This is for major changes, e.g. MSAA mode change.
DestroyPipelineCache();
if (!CreatePipelineCache(false))
PanicAlert("Failed to re-create pipeline cache");
}

void ObjectCache::SavePipelineCache()
{
size_t data_size;
Expand Down
19 changes: 13 additions & 6 deletions Source/Core/VideoBackends/Vulkan/ObjectCache.h
Expand Up @@ -111,12 +111,17 @@ class ObjectCache
// Perform at startup, create descriptor layouts, compiles all static shaders.
bool Initialize();

// Find a pipeline by the specified description, if not found, attempts to create it
// Creates a pipeline for the specified description. The resulting pipeline, if successful
// is not stored anywhere, this is left up to the caller.
VkPipeline CreatePipeline(const PipelineInfo& info);

// Find a pipeline by the specified description, if not found, attempts to create it.
VkPipeline GetPipeline(const PipelineInfo& info);

// Wipes out the pipeline cache, use when MSAA modes change, for example
// Also destroys the data that would be stored in the disk cache.
void ClearPipelineCache();
// Find a pipeline by the specified description, if not found, attempts to create it. If this
// resulted in a pipeline being created, the second field of the return value will be false,
// otherwise for a cache hit it will be true.
std::pair<VkPipeline, bool> GetPipelineWithCacheResult(const PipelineInfo& info);

// Saves the pipeline cache to disk. Call when shutting down.
void SavePipelineCache();
Expand All @@ -133,8 +138,12 @@ class ObjectCache
VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; }
// Gets the filename of the specified type of cache object (e.g. vertex shader, pipeline).
std::string GetDiskCacheFileName(const char* type);

private:
bool CreatePipelineCache(bool load_from_disk);
bool ValidatePipelineCache(const u8* data, size_t data_length);
void DestroyPipelineCache();
void LoadShaderCaches();
void DestroyShaderCaches();
Expand All @@ -148,8 +157,6 @@ class ObjectCache
void DestroySharedShaders();
void DestroySamplers();

std::string GetDiskCacheFileName(const char* type);

std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SETS> m_descriptor_set_layouts = {};

VkPipelineLayout m_standard_pipeline_layout = VK_NULL_HANDLE;
Expand Down
5 changes: 4 additions & 1 deletion Source/Core/VideoBackends/Vulkan/Renderer.cpp
Expand Up @@ -116,6 +116,9 @@ bool Renderer::Initialize()
m_bounding_box->GetGPUBufferSize());
}

// Ensure all pipelines previously used by the game have been created.
StateTracker::GetInstance()->LoadPipelineUIDCache();

// Various initialization routines will have executed commands on the command buffer.
// Execute what we have done before beginning the first frame.
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
Expand Down Expand Up @@ -1134,8 +1137,8 @@ void Renderer::CheckForConfigChanges()
g_command_buffer_mgr->WaitForGPUIdle();
RecompileShaders();
FramebufferManager::GetInstance()->RecompileShaders();
g_object_cache->ClearPipelineCache();
g_object_cache->RecompileSharedShaders();
StateTracker::GetInstance()->LoadPipelineUIDCache();
}

// For vsync, we need to change the present mode, which means recreating the swap chain.
Expand Down

0 comments on commit da87580

Please sign in to comment.