From 7b864fd6650a4097a71698e35ae47b2e3a6116e7 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 15 Dec 2022 09:00:09 +0100 Subject: [PATCH] Add vulkan pipeline cache --- .../ZVulkan/include/zvulkan/vulkanbuilders.h | 22 +++++++ .../ZVulkan/include/zvulkan/vulkanobjects.h | 45 +++++++++++++ libraries/ZVulkan/src/vulkanbuilders.cpp | 64 ++++++++++++++++++- .../vulkan/renderer/vk_renderpass.cpp | 38 +++++++++++ .../rendering/vulkan/renderer/vk_renderpass.h | 5 ++ 5 files changed, 172 insertions(+), 2 deletions(-) diff --git a/libraries/ZVulkan/include/zvulkan/vulkanbuilders.h b/libraries/ZVulkan/include/zvulkan/vulkanbuilders.h index add83ff37..667732a5d 100644 --- a/libraries/ZVulkan/include/zvulkan/vulkanbuilders.h +++ b/libraries/ZVulkan/include/zvulkan/vulkanbuilders.h @@ -236,6 +236,7 @@ class ComputePipelineBuilder public: ComputePipelineBuilder(); + ComputePipelineBuilder& Cache(VulkanPipelineCache* cache); ComputePipelineBuilder& Layout(VulkanPipelineLayout *layout); ComputePipelineBuilder& ComputeShader(VulkanShader *shader); ComputePipelineBuilder& DebugName(const char* name) { debugName = name; return *this; } @@ -245,6 +246,7 @@ public: private: VkComputePipelineCreateInfo pipelineInfo = {}; VkPipelineShaderStageCreateInfo stageInfo = {}; + VulkanPipelineCache* cache = nullptr; const char* debugName = nullptr; }; @@ -324,6 +326,7 @@ class GraphicsPipelineBuilder public: GraphicsPipelineBuilder(); + GraphicsPipelineBuilder& Cache(VulkanPipelineCache* cache); GraphicsPipelineBuilder& Subpass(int subpass); GraphicsPipelineBuilder& Layout(VulkanPipelineLayout *layout); GraphicsPipelineBuilder& RenderPass(VulkanRenderPass *renderPass); @@ -377,6 +380,7 @@ private: std::vector vertexInputAttributes; std::vector dynamicStates; + VulkanPipelineCache* cache = nullptr; const char* debugName = nullptr; }; @@ -399,6 +403,24 @@ private: const char* debugName = nullptr; }; +class PipelineCacheBuilder +{ +public: + PipelineCacheBuilder(); + + PipelineCacheBuilder& InitialData(const void* data, size_t size); + PipelineCacheBuilder& Flags(VkPipelineCacheCreateFlags flags); + + PipelineCacheBuilder& DebugName(const char* name) { debugName = name; return *this; } + + std::unique_ptr Create(VulkanDevice* device); + +private: + VkPipelineCacheCreateInfo pipelineCacheInfo = {}; + std::vector initData; + const char* debugName = nullptr; +}; + class RenderPassBuilder { public: diff --git a/libraries/ZVulkan/include/zvulkan/vulkanobjects.h b/libraries/ZVulkan/include/zvulkan/vulkanobjects.h index f4934d942..c7842f670 100644 --- a/libraries/ZVulkan/include/zvulkan/vulkanobjects.h +++ b/libraries/ZVulkan/include/zvulkan/vulkanobjects.h @@ -305,6 +305,24 @@ private: VulkanPipelineLayout &operator=(const VulkanPipelineLayout &) = delete; }; +class VulkanPipelineCache +{ +public: + VulkanPipelineCache(VulkanDevice* device, VkPipelineCache cache); + ~VulkanPipelineCache(); + + void SetDebugName(const char* name) { device->SetObjectName(name, (uint64_t)cache, VK_OBJECT_TYPE_PIPELINE_CACHE); } + + std::vector GetCacheData(); + + VulkanDevice* device; + VkPipelineCache cache; + +private: + VulkanPipelineCache(const VulkanPipelineCache&) = delete; + VulkanPipelineCache& operator=(const VulkanPipelineCache&) = delete; +}; + class VulkanRenderPass { public: @@ -1179,6 +1197,33 @@ inline VulkanPipelineLayout::~VulkanPipelineLayout() ///////////////////////////////////////////////////////////////////////////// +inline VulkanPipelineCache::VulkanPipelineCache(VulkanDevice* device, VkPipelineCache cache) : device(device), cache(cache) +{ +} + +inline VulkanPipelineCache::~VulkanPipelineCache() +{ + vkDestroyPipelineCache(device->device, cache, nullptr); +} + +inline std::vector VulkanPipelineCache::GetCacheData() +{ + size_t dataSize = 0; + VkResult result = vkGetPipelineCacheData(device->device, cache, &dataSize, nullptr); + CheckVulkanError(result, "Could not get cache data size"); + + std::vector buffer; + buffer.resize(dataSize); + result = vkGetPipelineCacheData(device->device, cache, &dataSize, buffer.data()); + if (result == VK_INCOMPLETE) + VulkanError("Could not get cache data (incomplete)"); + CheckVulkanError(result, "Could not get cache data"); + buffer.resize(dataSize); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// + inline VulkanRenderPass::VulkanRenderPass(VulkanDevice *device, VkRenderPass renderPass) : device(device), renderPass(renderPass) { } diff --git a/libraries/ZVulkan/src/vulkanbuilders.cpp b/libraries/ZVulkan/src/vulkanbuilders.cpp index 652cd6355..bfa138080 100644 --- a/libraries/ZVulkan/src/vulkanbuilders.cpp +++ b/libraries/ZVulkan/src/vulkanbuilders.cpp @@ -589,6 +589,12 @@ ComputePipelineBuilder::ComputePipelineBuilder() stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; } +ComputePipelineBuilder& ComputePipelineBuilder::Cache(VulkanPipelineCache* cache) +{ + this->cache = cache; + return *this; +} + ComputePipelineBuilder& ComputePipelineBuilder::Layout(VulkanPipelineLayout* layout) { pipelineInfo.layout = layout->layout; @@ -608,7 +614,7 @@ ComputePipelineBuilder& ComputePipelineBuilder::ComputeShader(VulkanShader* shad std::unique_ptr ComputePipelineBuilder::Create(VulkanDevice* device) { VkPipeline pipeline; - vkCreateComputePipelines(device->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); + vkCreateComputePipelines(device->device, cache ? cache->cache : VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); auto obj = std::make_unique(device, pipeline); if (debugName) obj->SetDebugName(debugName); @@ -871,6 +877,12 @@ GraphicsPipelineBuilder& GraphicsPipelineBuilder::RasterizationSamples(VkSampleC return *this; } +GraphicsPipelineBuilder& GraphicsPipelineBuilder::Cache(VulkanPipelineCache* cache) +{ + this->cache = cache; + return *this; +} + GraphicsPipelineBuilder& GraphicsPipelineBuilder::Subpass(int subpass) { pipelineInfo.subpass = subpass; @@ -1087,7 +1099,7 @@ GraphicsPipelineBuilder& GraphicsPipelineBuilder::AddDynamicState(VkDynamicState std::unique_ptr GraphicsPipelineBuilder::Create(VulkanDevice* device) { VkPipeline pipeline = 0; - VkResult result = vkCreateGraphicsPipelines(device->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); + VkResult result = vkCreateGraphicsPipelines(device->device, cache ? cache->cache : VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline); CheckVulkanError(result, "Could not create graphics pipeline"); auto obj = std::make_unique(device, pipeline); if (debugName) @@ -1135,6 +1147,54 @@ std::unique_ptr PipelineLayoutBuilder::Create(VulkanDevice ///////////////////////////////////////////////////////////////////////////// +PipelineCacheBuilder::PipelineCacheBuilder() +{ + pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; +} + +PipelineCacheBuilder& PipelineCacheBuilder::InitialData(const void* data, size_t size) +{ + initData.resize(size); + memcpy(initData.data(), data, size); + return *this; +} + +PipelineCacheBuilder& PipelineCacheBuilder::Flags(VkPipelineCacheCreateFlags flags) +{ + pipelineCacheInfo.flags = flags; + return *this; +} + +std::unique_ptr PipelineCacheBuilder::Create(VulkanDevice* device) +{ + pipelineCacheInfo.pInitialData = nullptr; + pipelineCacheInfo.initialDataSize = 0; + + // Check if the saved cache data is compatible with our device: + if (initData.size() >= sizeof(VkPipelineCacheHeaderVersionOne)) + { + VkPipelineCacheHeaderVersionOne* header = (VkPipelineCacheHeaderVersionOne*)initData.data(); + if (header->headerVersion == VK_PIPELINE_CACHE_HEADER_VERSION_ONE && + header->vendorID == device->PhysicalDevice.Properties.vendorID && + header->deviceID == device->PhysicalDevice.Properties.deviceID && + memcmp(header->pipelineCacheUUID, device->PhysicalDevice.Properties.pipelineCacheUUID, VK_UUID_SIZE) == 0) + { + pipelineCacheInfo.pInitialData = initData.data(); + pipelineCacheInfo.initialDataSize = initData.size(); + } + } + + VkPipelineCache pipelineCache; + VkResult result = vkCreatePipelineCache(device->device, &pipelineCacheInfo, nullptr, &pipelineCache); + CheckVulkanError(result, "Could not create pipeline cache"); + auto obj = std::make_unique(device, pipelineCache); + if (debugName) + obj->SetDebugName(debugName); + return obj; +} + +///////////////////////////////////////////////////////////////////////////// + RenderPassBuilder::RenderPassBuilder() { renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; diff --git a/src/common/rendering/vulkan/renderer/vk_renderpass.cpp b/src/common/rendering/vulkan/renderer/vk_renderpass.cpp index a5d1400cd..5ecb3f65e 100644 --- a/src/common/rendering/vulkan/renderer/vk_renderpass.cpp +++ b/src/common/rendering/vulkan/renderer/vk_renderpass.cpp @@ -34,13 +34,49 @@ #include "flatvertices.h" #include "hw_viewpointuniforms.h" #include "v_2ddrawer.h" +#include "i_specialpaths.h" VkRenderPassManager::VkRenderPassManager(VulkanRenderDevice* fb) : fb(fb) { + FString path = M_GetCachePath(true); + CreatePath(path); + CacheFilename = path + "/pipelinecache.zdpc"; + + PipelineCacheBuilder builder; + builder.DebugName("PipelineCache"); + + try + { + FileReader fr; + if (fr.OpenFile(CacheFilename)) + { + std::vector data; + data.resize(fr.GetLength()); + if (fr.Read(data.data(), data.size()) == data.size()) + { + builder.InitialData(data.data(), data.size()); + } + } + } + catch (...) + { + } + + PipelineCache = builder.Create(fb->device.get()); } VkRenderPassManager::~VkRenderPassManager() { + try + { + auto data = PipelineCache->GetCacheData(); + std::unique_ptr fw(FileWriter::Open(CacheFilename)); + if (fw) + fw->Write(data.data(), data.size()); + } + catch (...) + { + } } void VkRenderPassManager::RenderBuffersReset() @@ -206,6 +242,7 @@ VulkanPipeline *VkRenderPassSetup::GetPipeline(const VkPipelineKey &key) std::unique_ptr VkRenderPassSetup::CreatePipeline(const VkPipelineKey &key) { GraphicsPipelineBuilder builder; + builder.Cache(fb->GetRenderPassManager()->GetCache()); VkShaderProgram *program; if (key.SpecialEffect != EFF_NONE) @@ -332,6 +369,7 @@ void VkPPRenderPassSetup::CreatePipelineLayout(const VkPPRenderPassKey& key) void VkPPRenderPassSetup::CreatePipeline(const VkPPRenderPassKey& key) { GraphicsPipelineBuilder builder; + builder.Cache(fb->GetRenderPassManager()->GetCache()); builder.AddVertexShader(key.Shader->VertexShader.get()); builder.AddFragmentShader(key.Shader->FragmentShader.get()); diff --git a/src/common/rendering/vulkan/renderer/vk_renderpass.h b/src/common/rendering/vulkan/renderer/vk_renderpass.h index 1e99ae1ce..25fd7d186 100644 --- a/src/common/rendering/vulkan/renderer/vk_renderpass.h +++ b/src/common/rendering/vulkan/renderer/vk_renderpass.h @@ -135,6 +135,8 @@ public: VkPPRenderPassSetup* GetPPRenderPass(const VkPPRenderPassKey& key); + VulkanPipelineCache* GetCache() { return PipelineCache.get(); } + private: VulkanRenderDevice* fb = nullptr; @@ -143,4 +145,7 @@ private: std::vector VertexFormats; std::map> PPRenderPassSetup; + + FString CacheFilename; + std::unique_ptr PipelineCache; };