From aac66a1b61a0e4c1c2b85d89bc818a8b521bcffa Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 13 Nov 2016 18:39:06 +1000 Subject: Vulkan: Implement a pipeline UID cache This stores enough information to recreate the pipeline, including the shader UIDs, blend/depth/rasterization state, primitive and vertex format. --- Source/Core/VideoBackends/Vulkan/ObjectCache.cpp | 28 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'Source/Core/VideoBackends/Vulkan/ObjectCache.cpp') diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index 61e685874a..ecfc73c76e 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -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 @@ -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 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(), -- cgit v1.2.3 From 9604b336c8213c43dff90fab67df8eaa31cdbbb5 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 13 Nov 2016 18:41:36 +1000 Subject: Vulkan: Don't destroy the device's pipeline cache on MSAA mode change The user could switch back again, and this would mean this data would be lost. Disk space is cheap, and it's not going to be much. --- Source/Core/VideoBackends/Vulkan/ObjectCache.cpp | 9 --------- 1 file changed, 9 deletions(-) (limited to 'Source/Core/VideoBackends/Vulkan/ObjectCache.cpp') diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index ecfc73c76e..d7d505b793 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -382,15 +382,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; -- cgit v1.2.3 From 8d48319414d7e3f348e4ef38e976121e9a1fce6a Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 13 Nov 2016 18:50:10 +1000 Subject: Vulkan: Validate the pipeline cache before using it This ensures that if a user changes adapters or vendors we're not passing invalid data to the driver. --- Source/Core/VideoBackends/Vulkan/ObjectCache.cpp | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) (limited to 'Source/Core/VideoBackends/Vulkan/ObjectCache.cpp') diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index d7d505b793..761402e478 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -344,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 @@ -369,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::value, + "VK_PIPELINE_CACHE_HEADER must be trivially copyable"); +#else +static_assert(std::is_trivially_copyable::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) -- cgit v1.2.3