diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt index 744b471d73..f3d602afb6 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -7,6 +7,7 @@ set(SRCS PostProcessing.cpp RasterFont.cpp Renderer.cpp + ShaderCache.cpp ShaderCompiler.cpp StateTracker.cpp StagingBuffer.cpp diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp index be751f636e..dec6d9e952 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp @@ -435,8 +435,8 @@ void FramebufferManager::ReinterpretPixelData(int convtype) UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), - m_efb_load_render_pass, g_object_cache->GetScreenQuadVertexShader(), - g_object_cache->GetScreenQuadGeometryShader(), pixel_shader); + m_efb_load_render_pass, g_shader_cache->GetScreenQuadVertexShader(), + g_shader_cache->GetScreenQuadGeometryShader(), pixel_shader); RasterizationState rs_state = Util::GetNoCullRasterizationState(); rs_state.samples = m_efb_samples; @@ -510,8 +510,8 @@ Texture2D* FramebufferManager::ResolveEFBDepthTexture(const VkRect2D& region) // Draw using resolve shader to write the minimum depth of all samples to the resolve texture. UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), - m_depth_resolve_render_pass, g_object_cache->GetScreenQuadVertexShader(), - g_object_cache->GetScreenQuadGeometryShader(), m_ps_depth_resolve); + m_depth_resolve_render_pass, g_shader_cache->GetScreenQuadVertexShader(), + g_shader_cache->GetScreenQuadGeometryShader(), m_ps_depth_resolve); draw.BeginRenderPass(m_depth_resolve_framebuffer, region); draw.SetPSSampler(0, m_efb_depth_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(region.offset.x, region.offset.y, region.extent.width, @@ -641,7 +641,7 @@ bool FramebufferManager::CompileConversionShaders() } )"; - std::string header = g_object_cache->GetUtilityShaderHeader(); + std::string header = g_shader_cache->GetUtilityShaderHeader(); DestroyConversionShaders(); m_ps_rgb8_to_rgba6 = Util::CompileAndCreateFragmentShader(header + RGB8_TO_RGBA6_SHADER_SOURCE); @@ -698,7 +698,7 @@ bool FramebufferManager::PopulateColorReadbackTexture() UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), - m_copy_color_render_pass, g_object_cache->GetScreenQuadVertexShader(), + m_copy_color_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_color_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; @@ -781,7 +781,7 @@ bool FramebufferManager::PopulateDepthReadbackTexture() UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), - m_copy_depth_render_pass, g_object_cache->GetScreenQuadVertexShader(), + m_copy_depth_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_depth_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; @@ -956,10 +956,10 @@ bool FramebufferManager::CompileReadbackShaders() } )"; - source = g_object_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE; + source = g_shader_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE; m_copy_color_shader = Util::CompileAndCreateFragmentShader(source); - source = g_object_cache->GetUtilityShaderHeader() + COPY_DEPTH_SHADER_SOURCE; + source = g_shader_cache->GetUtilityShaderHeader() + COPY_DEPTH_SHADER_SOURCE; m_copy_depth_shader = Util::CompileAndCreateFragmentShader(source); return m_copy_color_shader != VK_NULL_HANDLE && m_copy_depth_shader != VK_NULL_HANDLE; @@ -1176,7 +1176,7 @@ void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, size_t pipeline_info.depth_stencil_state.compare_op = VK_COMPARE_OP_ALWAYS; } - VkPipeline pipeline = g_object_cache->GetPipeline(pipeline_info); + VkPipeline pipeline = g_shader_cache->GetPipeline(pipeline_info); if (pipeline == VK_NULL_HANDLE) { PanicAlert("Failed to get pipeline for EFB poke draw"); @@ -1309,7 +1309,7 @@ bool FramebufferManager::CompilePokeShaders() } )"; - std::string source = g_object_cache->GetUtilityShaderHeader(); + std::string source = g_shader_cache->GetUtilityShaderHeader(); if (m_poke_primitive_topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST) source += "#define USE_POINT_SIZE 1\n"; source += POKE_VERTEX_SHADER_SOURCE; @@ -1319,13 +1319,13 @@ bool FramebufferManager::CompilePokeShaders() if (g_vulkan_context->SupportsGeometryShaders()) { - source = g_object_cache->GetUtilityShaderHeader() + POKE_GEOMETRY_SHADER_SOURCE; + source = g_shader_cache->GetUtilityShaderHeader() + POKE_GEOMETRY_SHADER_SOURCE; m_poke_geometry_shader = Util::CompileAndCreateGeometryShader(source); if (m_poke_geometry_shader == VK_NULL_HANDLE) return false; } - source = g_object_cache->GetUtilityShaderHeader() + POKE_PIXEL_SHADER_SOURCE; + source = g_shader_cache->GetUtilityShaderHeader() + POKE_PIXEL_SHADER_SOURCE; m_poke_fragment_shader = Util::CompileAndCreateFragmentShader(source); if (m_poke_fragment_shader == VK_NULL_HANDLE) return false; diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index 7f7dc93ccf..4e856cb0d5 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -33,9 +33,6 @@ ObjectCache::ObjectCache() ObjectCache::~ObjectCache() { - DestroyPipelineCache(); - DestroyShaderCaches(); - DestroySharedShaders(); DestroySamplers(); DestroyPipelineLayouts(); DestroyDescriptorSetLayouts(); @@ -49,27 +46,12 @@ bool ObjectCache::Initialize() if (!CreatePipelineLayouts()) return false; - if (g_ActiveConfig.bShaderCache) - { - LoadShaderCaches(); - if (!LoadPipelineCache()) - return false; - } - else - { - if (!CreatePipelineCache()) - return false; - } - if (!CreateUtilityShaderVertexFormat()) return false; if (!CreateStaticSamplers()) return false; - if (!CompileSharedShaders()) - 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 = @@ -80,721 +62,6 @@ bool ObjectCache::Initialize() return true; } -static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology) -{ - return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || - topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || - topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY || - topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY; -} - -static VkPipelineRasterizationStateCreateInfo -GetVulkanRasterizationState(const RasterizationState& state) -{ - return { - VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkPipelineRasterizationStateCreateFlags flags - state.depth_clamp, // VkBool32 depthClampEnable - VK_FALSE, // VkBool32 rasterizerDiscardEnable - VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode - state.cull_mode, // VkCullModeFlags cullMode - VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace - VK_FALSE, // VkBool32 depthBiasEnable - 0.0f, // float depthBiasConstantFactor - 0.0f, // float depthBiasClamp - 0.0f, // float depthBiasSlopeFactor - 1.0f // float lineWidth - }; -} - -static VkPipelineMultisampleStateCreateInfo -GetVulkanMultisampleState(const RasterizationState& rs_state) -{ - return { - VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkPipelineMultisampleStateCreateFlags flags - rs_state.samples, // VkSampleCountFlagBits rasterizationSamples - rs_state.per_sample_shading, // VkBool32 sampleShadingEnable - 1.0f, // float minSampleShading - nullptr, // const VkSampleMask* pSampleMask; - VK_FALSE, // VkBool32 alphaToCoverageEnable - VK_FALSE // VkBool32 alphaToOneEnable - }; -} - -static VkPipelineDepthStencilStateCreateInfo -GetVulkanDepthStencilState(const DepthStencilState& state) -{ - return { - VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkPipelineDepthStencilStateCreateFlags flags - state.test_enable, // VkBool32 depthTestEnable - state.write_enable, // VkBool32 depthWriteEnable - state.compare_op, // VkCompareOp depthCompareOp - VK_FALSE, // VkBool32 depthBoundsTestEnable - VK_FALSE, // VkBool32 stencilTestEnable - {}, // VkStencilOpState front - {}, // VkStencilOpState back - 0.0f, // float minDepthBounds - 1.0f // float maxDepthBounds - }; -} - -static VkPipelineColorBlendAttachmentState GetVulkanAttachmentBlendState(const BlendingState& state) -{ - VkPipelineColorBlendAttachmentState vk_state = {}; - vk_state.blendEnable = static_cast(state.blendenable); - vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; - vk_state.alphaBlendOp = state.subtractAlpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; - - if (state.usedualsrc && g_vulkan_context->SupportsDualSourceBlend()) - { - static constexpr std::array src_factors = { - {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, - VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; - static constexpr std::array dst_factors = { - {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, - VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; - - vk_state.srcColorBlendFactor = src_factors[state.srcfactor]; - vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha]; - vk_state.dstColorBlendFactor = dst_factors[state.dstfactor]; - vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha]; - } - else - { - static constexpr std::array src_factors = { - {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, - VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; - - static constexpr std::array dst_factors = { - {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, - VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; - - vk_state.srcColorBlendFactor = src_factors[state.srcfactor]; - vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha]; - vk_state.dstColorBlendFactor = dst_factors[state.dstfactor]; - vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha]; - } - - if (state.colorupdate) - { - vk_state.colorWriteMask = - VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; - } - else - { - vk_state.colorWriteMask = 0; - } - - if (state.alphaupdate) - vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT; - - return vk_state; -} - -static VkPipelineColorBlendStateCreateInfo -GetVulkanColorBlendState(const BlendingState& state, - const VkPipelineColorBlendAttachmentState* attachments, - uint32_t num_attachments) -{ - static constexpr std::array vk_logic_ops = { - {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY, - VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR, - VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE, - VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}}; - - VkBool32 vk_logic_op_enable = static_cast(state.logicopenable); - if (vk_logic_op_enable && !g_vulkan_context->SupportsLogicOps()) - { - // At the time of writing, Adreno and Mali drivers didn't support logic ops. - // The "emulation" through blending path has been removed, so just disable it completely. - // These drivers don't support dual-source blend either, so issues are to be expected. - vk_logic_op_enable = VK_FALSE; - } - - VkLogicOp vk_logic_op = vk_logic_op_enable ? vk_logic_ops[state.logicmode] : VK_LOGIC_OP_CLEAR; - - VkPipelineColorBlendStateCreateInfo vk_state = { - VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkPipelineColorBlendStateCreateFlags flags - vk_logic_op_enable, // VkBool32 logicOpEnable - vk_logic_op, // VkLogicOp logicOp - num_attachments, // uint32_t attachmentCount - attachments, // const VkPipelineColorBlendAttachmentState* pAttachments - {1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4] - }; - - return vk_state; -} - -VkPipeline ObjectCache::CreatePipeline(const PipelineInfo& info) -{ - // 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 - nullptr, // const void* pNext - 0, // VkPipelineVertexInputStateCreateFlags flags - 0, // uint32_t vertexBindingDescriptionCount - nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions - 0, // uint32_t vertexAttributeDescriptionCount - nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions - }; - - // Vertex inputs - const VkPipelineVertexInputStateCreateInfo& vertex_input_state = - info.vertex_format ? info.vertex_format->GetVertexInputStateInfo() : empty_vertex_input_state; - - // Input assembly - VkPipelineInputAssemblyStateCreateInfo input_assembly_state = { - VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkPipelineInputAssemblyStateCreateFlags flags - info.primitive_topology, // VkPrimitiveTopology topology - VK_FALSE // VkBool32 primitiveRestartEnable - }; - - // See Vulkan spec, section 19: - // If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, - // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, - // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, - // primitiveRestartEnable must be VK_FALSE - if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart && - IsStripPrimitiveTopology(info.primitive_topology)) - { - input_assembly_state.primitiveRestartEnable = VK_TRUE; - } - - // Shaders to stages - VkPipelineShaderStageCreateInfo shader_stages[3]; - uint32_t num_shader_stages = 0; - if (info.vs != VK_NULL_HANDLE) - { - shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, - 0, - VK_SHADER_STAGE_VERTEX_BIT, - info.vs, - "main"}; - } - if (info.gs != VK_NULL_HANDLE) - { - shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, - 0, - VK_SHADER_STAGE_GEOMETRY_BIT, - info.gs, - "main"}; - } - if (info.ps != VK_NULL_HANDLE) - { - shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, - 0, - VK_SHADER_STAGE_FRAGMENT_BIT, - info.ps, - "main"}; - } - - // Fill in Vulkan descriptor structs from our state structures. - VkPipelineRasterizationStateCreateInfo rasterization_state = - GetVulkanRasterizationState(info.rasterization_state); - VkPipelineMultisampleStateCreateInfo multisample_state = - GetVulkanMultisampleState(info.rasterization_state); - VkPipelineDepthStencilStateCreateInfo depth_stencil_state = - GetVulkanDepthStencilState(info.depth_stencil_state); - VkPipelineColorBlendAttachmentState blend_attachment_state = - GetVulkanAttachmentBlendState(info.blend_state); - VkPipelineColorBlendStateCreateInfo blend_state = - GetVulkanColorBlendState(info.blend_state, &blend_attachment_state, 1); - - // This viewport isn't used, but needs to be specified anyway. - static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; - static const VkRect2D scissor = {{0, 0}, {1, 1}}; - static const VkPipelineViewportStateCreateInfo viewport_state = { - VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - nullptr, - 0, // VkPipelineViewportStateCreateFlags flags; - 1, // uint32_t viewportCount - &viewport, // const VkViewport* pViewports - 1, // uint32_t scissorCount - &scissor // const VkRect2D* pScissors - }; - - // Set viewport and scissor dynamic state so we can change it elsewhere. - static const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR}; - static const VkPipelineDynamicStateCreateInfo dynamic_state = { - VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr, - 0, // VkPipelineDynamicStateCreateFlags flags - static_cast(ArraySize(dynamic_states)), // uint32_t dynamicStateCount - dynamic_states // const VkDynamicState* pDynamicStates - }; - - // Combine to full pipeline info structure. - VkGraphicsPipelineCreateInfo pipeline_info = { - VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - nullptr, // VkStructureType sType - 0, // VkPipelineCreateFlags flags - num_shader_stages, // uint32_t stageCount - shader_stages, // const VkPipelineShaderStageCreateInfo* pStages - &vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState - &input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState - nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState - &viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState - &rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState - &multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState - &depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState - &blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState - &dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState - info.pipeline_layout, // VkPipelineLayout layout - info.render_pass, // VkRenderPass renderPass - 0, // uint32_t subpass - VK_NULL_HANDLE, // VkPipeline basePipelineHandle - -1 // int32_t basePipelineIndex - }; - - 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; - } - - 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}; -} - -VkPipeline ObjectCache::CreateComputePipeline(const ComputePipelineInfo& info) -{ - VkComputePipelineCreateInfo pipeline_info = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - nullptr, - 0, - {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, info.cs, - "main", nullptr}, - info.pipeline_layout, - VK_NULL_HANDLE, - -1}; - - VkPipeline pipeline; - VkResult res = vkCreateComputePipelines(g_vulkan_context->GetDevice(), VK_NULL_HANDLE, 1, - &pipeline_info, nullptr, &pipeline); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateComputePipelines failed: "); - return VK_NULL_HANDLE; - } - - return pipeline; -} - -VkPipeline ObjectCache::GetComputePipeline(const ComputePipelineInfo& info) -{ - auto iter = m_compute_pipeline_objects.find(info); - if (iter != m_compute_pipeline_objects.end()) - return iter->second; - - VkPipeline pipeline = CreateComputePipeline(info); - m_compute_pipeline_objects.emplace(info, pipeline); - return pipeline; -} - -void ObjectCache::ClearPipelineCache() -{ - for (const auto& it : m_pipeline_objects) - { - if (it.second != VK_NULL_HANDLE) - vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr); - } - m_pipeline_objects.clear(); - - for (const auto& it : m_compute_pipeline_objects) - { - if (it.second != VK_NULL_HANDLE) - vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr); - } - m_compute_pipeline_objects.clear(); -} - -class PipelineCacheReadCallback : public LinearDiskCacheReader -{ -public: - PipelineCacheReadCallback(std::vector* 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* m_data; -}; - -class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader -{ -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 disk_data; - LinearDiskCache 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) -// 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() -{ - ClearPipelineCache(); - 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 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 disk_cache; - PipelineCacheReadIgnoreCallback callback; - disk_cache.OpenAndRead(m_pipeline_cache_filename, callback); - disk_cache.Append(1, data.data(), static_cast(data.size())); - disk_cache.Close(); -} - -// Cache inserter that is called back when reading from the file -template -struct ShaderCacheReader : public LinearDiskCacheReader -{ - ShaderCacheReader(std::map& shader_map) : m_shader_map(shader_map) {} - void Read(const Uid& key, const u32* value, u32 value_size) override - { - // We don't insert null modules into the shader map since creation could succeed later on. - // e.g. we're generating bad code, but fix this in a later version, and for some reason - // the cache is not invalidated. - VkShaderModule module = Util::CreateShaderModule(value, value_size); - if (module == VK_NULL_HANDLE) - return; - - m_shader_map.emplace(key, module); - } - - std::map& m_shader_map; -}; - -void ObjectCache::LoadShaderCaches() -{ - ShaderCacheReader vs_reader(m_vs_cache.shader_map); - m_vs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "VS", true, true), - vs_reader); - - ShaderCacheReader ps_reader(m_ps_cache.shader_map); - m_ps_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "PS", true, true), - ps_reader); - - if (g_vulkan_context->SupportsGeometryShaders()) - { - ShaderCacheReader gs_reader(m_gs_cache.shader_map); - m_gs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "GS", true, true), - gs_reader); - } - - SETSTAT(stats.numPixelShadersCreated, static_cast(m_ps_cache.shader_map.size())); - SETSTAT(stats.numPixelShadersAlive, static_cast(m_ps_cache.shader_map.size())); - SETSTAT(stats.numVertexShadersCreated, static_cast(m_vs_cache.shader_map.size())); - SETSTAT(stats.numVertexShadersAlive, static_cast(m_vs_cache.shader_map.size())); -} - -template -static void DestroyShaderCache(T& cache) -{ - cache.disk_cache.Sync(); - cache.disk_cache.Close(); - for (const auto& it : cache.shader_map) - { - if (it.second != VK_NULL_HANDLE) - vkDestroyShaderModule(g_vulkan_context->GetDevice(), it.second, nullptr); - } - cache.shader_map.clear(); -} - -void ObjectCache::DestroyShaderCaches() -{ - DestroyShaderCache(m_vs_cache); - DestroyShaderCache(m_ps_cache); - - if (g_vulkan_context->SupportsGeometryShaders()) - DestroyShaderCache(m_gs_cache); - - SETSTAT(stats.numPixelShadersCreated, 0); - SETSTAT(stats.numPixelShadersAlive, 0); - SETSTAT(stats.numVertexShadersCreated, 0); - SETSTAT(stats.numVertexShadersAlive, 0); -} - -VkShaderModule ObjectCache::GetVertexShaderForUid(const VertexShaderUid& uid) -{ - auto it = m_vs_cache.shader_map.find(uid); - if (it != m_vs_cache.shader_map.end()) - return it->second; - - // Not in the cache, so compile the shader. - ShaderCompiler::SPIRVCodeVector spv; - VkShaderModule module = VK_NULL_HANDLE; - ShaderCode source_code = - GenerateVertexShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); - if (ShaderCompiler::CompileVertexShader(&spv, source_code.GetBuffer().c_str(), - source_code.GetBuffer().length())) - { - module = Util::CreateShaderModule(spv.data(), spv.size()); - - // Append to shader cache if it created successfully. - if (module != VK_NULL_HANDLE) - { - m_vs_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); - INCSTAT(stats.numVertexShadersCreated); - INCSTAT(stats.numVertexShadersAlive); - } - } - - // We still insert null entries to prevent further compilation attempts. - m_vs_cache.shader_map.emplace(uid, module); - return module; -} - -VkShaderModule ObjectCache::GetGeometryShaderForUid(const GeometryShaderUid& uid) -{ - _assert_(g_vulkan_context->SupportsGeometryShaders()); - auto it = m_gs_cache.shader_map.find(uid); - if (it != m_gs_cache.shader_map.end()) - return it->second; - - // Not in the cache, so compile the shader. - ShaderCompiler::SPIRVCodeVector spv; - VkShaderModule module = VK_NULL_HANDLE; - ShaderCode source_code = - GenerateGeometryShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); - if (ShaderCompiler::CompileGeometryShader(&spv, source_code.GetBuffer().c_str(), - source_code.GetBuffer().length())) - { - module = Util::CreateShaderModule(spv.data(), spv.size()); - - // Append to shader cache if it created successfully. - if (module != VK_NULL_HANDLE) - m_gs_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); - } - - // We still insert null entries to prevent further compilation attempts. - m_gs_cache.shader_map.emplace(uid, module); - return module; -} - -VkShaderModule ObjectCache::GetPixelShaderForUid(const PixelShaderUid& uid) -{ - auto it = m_ps_cache.shader_map.find(uid); - if (it != m_ps_cache.shader_map.end()) - return it->second; - - // Not in the cache, so compile the shader. - ShaderCompiler::SPIRVCodeVector spv; - VkShaderModule module = VK_NULL_HANDLE; - ShaderCode source_code = - GeneratePixelShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); - if (ShaderCompiler::CompileFragmentShader(&spv, source_code.GetBuffer().c_str(), - source_code.GetBuffer().length())) - { - module = Util::CreateShaderModule(spv.data(), spv.size()); - - // Append to shader cache if it created successfully. - if (module != VK_NULL_HANDLE) - { - m_ps_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); - INCSTAT(stats.numPixelShadersCreated); - INCSTAT(stats.numPixelShadersAlive); - } - } - - // We still insert null entries to prevent further compilation attempts. - m_ps_cache.shader_map.emplace(uid, module); - return module; -} - void ObjectCache::ClearSamplerCache() { for (const auto& it : m_sampler_cache) @@ -822,30 +89,6 @@ void ObjectCache::DestroySamplers() } } -void ObjectCache::RecompileSharedShaders() -{ - DestroySharedShaders(); - if (!CompileSharedShaders()) - PanicAlert("Failed to recompile shared shaders."); -} - -void ObjectCache::ReloadShaderAndPipelineCaches() -{ - SavePipelineCache(); - DestroyShaderCaches(); - DestroyPipelineCache(); - - if (g_ActiveConfig.bShaderCache) - { - LoadShaderCaches(); - LoadPipelineCache(); - } - else - { - CreatePipelineCache(); - } -} - bool ObjectCache::CreateDescriptorSetLayouts() { static const VkDescriptorSetLayoutBinding ubo_set_bindings[] = { @@ -1102,234 +345,4 @@ VkSampler ObjectCache::GetSampler(const SamplerState& info) m_sampler_cache.emplace(info, sampler); return sampler; } - -std::string ObjectCache::GetUtilityShaderHeader() const -{ - std::stringstream ss; - if (g_ActiveConfig.iMultisamples > 1) - { - ss << "#define MSAA_ENABLED 1" << std::endl; - ss << "#define MSAA_SAMPLES " << g_ActiveConfig.iMultisamples << std::endl; - if (g_ActiveConfig.bSSAA) - ss << "#define SSAA_ENABLED 1" << std::endl; - } - - u32 efb_layers = (g_ActiveConfig.iStereoMode != STEREO_OFF) ? 2 : 1; - ss << "#define EFB_LAYERS " << efb_layers << std::endl; - - return ss.str(); -} - -// Comparison operators for PipelineInfos -// Since these all boil down to POD types, we can just memcmp the entire thing for speed -// The is_trivially_copyable check fails on MSVC due to BitField. -// TODO: Can we work around this any way? -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && !defined(_MSC_VER) -static_assert(std::has_trivial_copy_constructor::value, - "PipelineInfo is trivially copyable"); -#elif !defined(_MSC_VER) -static_assert(std::is_trivially_copyable::value, - "PipelineInfo is trivially copyable"); -#endif - -std::size_t PipelineInfoHash::operator()(const PipelineInfo& key) const -{ - return static_cast(XXH64(&key, sizeof(key), 0)); -} - -bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; -} - -bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs) -{ - return !operator==(lhs, rhs); -} - -bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0; -} - -bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0; -} - -bool operator==(const SamplerState& lhs, const SamplerState& rhs) -{ - return lhs.bits == rhs.bits; -} - -bool operator!=(const SamplerState& lhs, const SamplerState& rhs) -{ - return !operator==(lhs, rhs); -} - -bool operator>(const SamplerState& lhs, const SamplerState& rhs) -{ - return lhs.bits > rhs.bits; -} - -bool operator<(const SamplerState& lhs, const SamplerState& rhs) -{ - return lhs.bits < rhs.bits; -} - -std::size_t ComputePipelineInfoHash::operator()(const ComputePipelineInfo& key) const -{ - return static_cast(XXH64(&key, sizeof(key), 0)); -} - -bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; -} - -bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) -{ - return !operator==(lhs, rhs); -} - -bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0; -} - -bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) -{ - return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0; -} - -bool ObjectCache::CompileSharedShaders() -{ - static const char PASSTHROUGH_VERTEX_SHADER_SOURCE[] = R"( - layout(location = 0) in vec4 ipos; - layout(location = 5) in vec4 icol0; - layout(location = 8) in vec3 itex0; - - layout(location = 0) out vec3 uv0; - layout(location = 1) out vec4 col0; - - void main() - { - gl_Position = ipos; - uv0 = itex0; - col0 = icol0; - } - )"; - - static const char PASSTHROUGH_GEOMETRY_SHADER_SOURCE[] = R"( - layout(triangles) in; - layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; - - layout(location = 0) in vec3 in_uv0[]; - layout(location = 1) in vec4 in_col0[]; - - layout(location = 0) out vec3 out_uv0; - layout(location = 1) out vec4 out_col0; - - void main() - { - for (int j = 0; j < EFB_LAYERS; j++) - { - for (int i = 0; i < 3; i++) - { - gl_Layer = j; - gl_Position = gl_in[i].gl_Position; - out_uv0 = vec3(in_uv0[i].xy, float(j)); - out_col0 = in_col0[i]; - EmitVertex(); - } - EndPrimitive(); - } - } - )"; - - static const char SCREEN_QUAD_VERTEX_SHADER_SOURCE[] = R"( - layout(location = 0) out vec3 uv0; - - void main() - { - /* - * id &1 &2 clamp(*2-1) - * 0 0,0 0,0 -1,-1 TL - * 1 1,0 1,0 1,-1 TR - * 2 0,2 0,1 -1,1 BL - * 3 1,2 1,1 1,1 BR - */ - vec2 rawpos = vec2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f)); - gl_Position = vec4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f); - uv0 = vec3(rawpos, 0.0f); - } - )"; - - static const char SCREEN_QUAD_GEOMETRY_SHADER_SOURCE[] = R"( - layout(triangles) in; - layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; - - layout(location = 0) in vec3 in_uv0[]; - - layout(location = 0) out vec3 out_uv0; - - void main() - { - for (int j = 0; j < EFB_LAYERS; j++) - { - for (int i = 0; i < 3; i++) - { - gl_Layer = j; - gl_Position = gl_in[i].gl_Position; - out_uv0 = vec3(in_uv0[i].xy, float(j)); - EmitVertex(); - } - EndPrimitive(); - } - } - )"; - - std::string header = GetUtilityShaderHeader(); - - m_screen_quad_vertex_shader = - Util::CompileAndCreateVertexShader(header + SCREEN_QUAD_VERTEX_SHADER_SOURCE); - m_passthrough_vertex_shader = - Util::CompileAndCreateVertexShader(header + PASSTHROUGH_VERTEX_SHADER_SOURCE); - if (m_screen_quad_vertex_shader == VK_NULL_HANDLE || - m_passthrough_vertex_shader == VK_NULL_HANDLE) - { - return false; - } - - if (g_ActiveConfig.iStereoMode != STEREO_OFF && g_vulkan_context->SupportsGeometryShaders()) - { - m_screen_quad_geometry_shader = - Util::CompileAndCreateGeometryShader(header + SCREEN_QUAD_GEOMETRY_SHADER_SOURCE); - m_passthrough_geometry_shader = - Util::CompileAndCreateGeometryShader(header + PASSTHROUGH_GEOMETRY_SHADER_SOURCE); - if (m_screen_quad_geometry_shader == VK_NULL_HANDLE || - m_passthrough_geometry_shader == VK_NULL_HANDLE) - { - return false; - } - } - - return true; -} - -void ObjectCache::DestroySharedShaders() -{ - auto DestroyShader = [this](VkShaderModule& shader) { - if (shader != VK_NULL_HANDLE) - { - vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr); - shader = VK_NULL_HANDLE; - } - }; - - DestroyShader(m_screen_quad_vertex_shader); - DestroyShader(m_passthrough_vertex_shader); - DestroyShader(m_screen_quad_geometry_shader); - DestroyShader(m_passthrough_geometry_shader); -} } diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h index 53f0485698..dd7b1ed739 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h @@ -27,52 +27,6 @@ class CommandBufferManager; class VertexFormat; class StreamBuffer; -struct PipelineInfo -{ - // These are packed in descending order of size, to avoid any padding so that the structure - // can be copied/compared as a single block of memory. 64-bit pointer size is assumed. - const VertexFormat* vertex_format; - VkPipelineLayout pipeline_layout; - VkShaderModule vs; - VkShaderModule gs; - VkShaderModule ps; - VkRenderPass render_pass; - BlendingState blend_state; - RasterizationState rasterization_state; - DepthStencilState depth_stencil_state; - VkPrimitiveTopology primitive_topology; -}; - -struct PipelineInfoHash -{ - std::size_t operator()(const PipelineInfo& key) const; -}; - -bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs); -bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs); -bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs); -bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs); -bool operator==(const SamplerState& lhs, const SamplerState& rhs); -bool operator!=(const SamplerState& lhs, const SamplerState& rhs); -bool operator>(const SamplerState& lhs, const SamplerState& rhs); -bool operator<(const SamplerState& lhs, const SamplerState& rhs); - -struct ComputePipelineInfo -{ - VkPipelineLayout pipeline_layout; - VkShaderModule cs; -}; - -struct ComputePipelineInfoHash -{ - std::size_t operator()(const ComputePipelineInfo& key) const; -}; - -bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); -bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); -bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); -bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); - class ObjectCache { public: @@ -103,14 +57,6 @@ public: return m_utility_shader_uniform_buffer.get(); } - // Get utility shader header based on current config. - std::string GetUtilityShaderHeader() const; - - // Accesses ShaderGen shader caches - VkShaderModule GetVertexShaderForUid(const VertexShaderUid& uid); - VkShaderModule GetGeometryShaderForUid(const GeometryShaderUid& uid); - VkShaderModule GetPixelShaderForUid(const PixelShaderUid& uid); - // Static samplers VkSampler GetPointSampler() const { return m_point_sampler; } VkSampler GetLinearSampler() const { return m_linear_sampler; } @@ -119,64 +65,17 @@ public: // Perform at startup, create descriptor layouts, compiles all static shaders. bool Initialize(); - // 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); - - // 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 GetPipelineWithCacheResult(const PipelineInfo& info); - - // Creates a compute pipeline, and does not track the handle. - VkPipeline CreateComputePipeline(const ComputePipelineInfo& info); - - // Find a pipeline by the specified description, if not found, attempts to create it - VkPipeline GetComputePipeline(const ComputePipelineInfo& info); - - // Clears our pipeline cache of all objects. This is necessary when recompiling shaders, - // as drivers are free to return the same pointer again, which means that we may end up using - // and old pipeline object if they are not cleared first. Some stutter may be experienced - // while our cache is rebuilt on use, but the pipeline cache object should mitigate this. - // NOTE: Ensure that none of these objects are in use before calling. - void ClearPipelineCache(); - - // Saves the pipeline cache to disk. Call when shutting down. - void SavePipelineCache(); - // Clear sampler cache, use when anisotropy mode changes // WARNING: Ensure none of the objects from here are in use when calling void ClearSamplerCache(); - // Recompile shared shaders, call when stereo mode changes. - void RecompileSharedShaders(); - - // Reload pipeline cache. This will destroy all pipelines. - void ReloadShaderAndPipelineCaches(); - - // Shared shader accessors - VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; } - 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; } private: - bool CreatePipelineCache(); - bool LoadPipelineCache(); - bool ValidatePipelineCache(const u8* data, size_t data_length); - void DestroyPipelineCache(); - void LoadShaderCaches(); - void DestroyShaderCaches(); bool CreateDescriptorSetLayouts(); void DestroyDescriptorSetLayouts(); bool CreatePipelineLayouts(); void DestroyPipelineLayouts(); bool CreateUtilityShaderVertexFormat(); bool CreateStaticSamplers(); - bool CompileSharedShaders(); - void DestroySharedShaders(); void DestroySamplers(); std::array m_descriptor_set_layouts = {}; @@ -186,32 +85,10 @@ private: std::unique_ptr m_utility_shader_vertex_buffer; std::unique_ptr m_utility_shader_uniform_buffer; - template - struct ShaderCache - { - std::map shader_map; - LinearDiskCache disk_cache; - }; - ShaderCache m_vs_cache; - ShaderCache m_gs_cache; - ShaderCache m_ps_cache; - - std::unordered_map m_pipeline_objects; - std::unordered_map - m_compute_pipeline_objects; - VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE; - std::string m_pipeline_cache_filename; - VkSampler m_point_sampler = VK_NULL_HANDLE; VkSampler m_linear_sampler = VK_NULL_HANDLE; std::map m_sampler_cache; - - // Utility/shared shaders - VkShaderModule m_screen_quad_vertex_shader = VK_NULL_HANDLE; - VkShaderModule m_passthrough_vertex_shader = VK_NULL_HANDLE; - VkShaderModule m_screen_quad_geometry_shader = VK_NULL_HANDLE; - VkShaderModule m_passthrough_geometry_shader = VK_NULL_HANDLE; }; extern std::unique_ptr g_object_cache; diff --git a/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp index 971de51180..991117567c 100644 --- a/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp +++ b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp @@ -10,6 +10,7 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/ShaderCache.h" #include "VideoBackends/Vulkan/Texture2D.h" #include "VideoBackends/Vulkan/Util.h" #include "VideoBackends/Vulkan/VulkanContext.h" @@ -43,12 +44,12 @@ void VulkanPostProcessing::BlitFromTexture(const TargetRectangle& dst, const Tar { // If the source layer is negative we simply copy all available layers. VkShaderModule geometry_shader = - src_layer < 0 ? g_object_cache->GetPassthroughGeometryShader() : VK_NULL_HANDLE; + src_layer < 0 ? g_shader_cache->GetPassthroughGeometryShader() : VK_NULL_HANDLE; VkShaderModule fragment_shader = m_fragment_shader != VK_NULL_HANDLE ? m_fragment_shader : m_default_fragment_shader; UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), render_pass, - g_object_cache->GetPassthroughVertexShader(), geometry_shader, + g_shader_cache->GetPassthroughVertexShader(), geometry_shader, fragment_shader); // Source is always bound. @@ -240,7 +241,7 @@ bool VulkanPostProcessing::RecompileShader() if (m_fragment_shader != VK_NULL_HANDLE) { g_command_buffer_mgr->WaitForGPUIdle(); - g_object_cache->ClearPipelineCache(); + g_shader_cache->ClearPipelineCache(); vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_fragment_shader, nullptr); m_fragment_shader = VK_NULL_HANDLE; } diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 599792d493..4e72d94a61 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -462,8 +462,8 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), FramebufferManager::GetInstance()->GetEFBLoadRenderPass(), - g_object_cache->GetPassthroughVertexShader(), - g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader); + g_shader_cache->GetPassthroughVertexShader(), + g_shader_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader); draw.SetRasterizationState(rs_state); draw.SetDepthStencilState(depth_state); @@ -1168,8 +1168,8 @@ void Renderer::CheckForConfigChanges() BindEFBToStateTracker(); RecompileShaders(); FramebufferManager::GetInstance()->RecompileShaders(); - g_object_cache->ReloadShaderAndPipelineCaches(); - g_object_cache->RecompileSharedShaders(); + g_shader_cache->ReloadShaderAndPipelineCaches(); + g_shader_cache->RecompileSharedShaders(); StateTracker::GetInstance()->InvalidateShaderPointers(); StateTracker::GetInstance()->ReloadPipelineUIDCache(); } @@ -1500,7 +1500,7 @@ bool Renderer::CompileShaders() )"; - std::string source = g_object_cache->GetUtilityShaderHeader() + CLEAR_FRAGMENT_SHADER_SOURCE; + std::string source = g_shader_cache->GetUtilityShaderHeader() + CLEAR_FRAGMENT_SHADER_SOURCE; m_clear_fragment_shader = Util::CompileAndCreateFragmentShader(source); return m_clear_fragment_shader != VK_NULL_HANDLE; diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp b/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp new file mode 100644 index 0000000000..79a77458b6 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp @@ -0,0 +1,1029 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/ShaderCache.h" + +#include +#include +#include +#include + +#include "Common/Assert.h" +#include "Common/CommonFuncs.h" +#include "Common/LinearDiskCache.h" +#include "Common/MsgHandler.h" + +#include "Core/ConfigManager.h" + +#include "VideoBackends/Vulkan/ShaderCompiler.h" +#include "VideoBackends/Vulkan/StreamBuffer.h" +#include "VideoBackends/Vulkan/Util.h" +#include "VideoBackends/Vulkan/VertexFormat.h" +#include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoCommon/Statistics.h" + +namespace Vulkan +{ +std::unique_ptr g_shader_cache; + +ShaderCache::ShaderCache() +{ +} + +ShaderCache::~ShaderCache() +{ + DestroyPipelineCache(); + DestroyShaderCaches(); + DestroySharedShaders(); +} + +bool ShaderCache::Initialize() +{ + if (g_ActiveConfig.bShaderCache) + { + LoadShaderCaches(); + if (!LoadPipelineCache()) + return false; + } + else + { + if (!CreatePipelineCache()) + return false; + } + + if (!CompileSharedShaders()) + return false; + + return true; +} + +static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology) +{ + return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || + topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || + topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY || + topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY; +} + +static VkPipelineRasterizationStateCreateInfo +GetVulkanRasterizationState(const RasterizationState& state) +{ + return { + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkPipelineRasterizationStateCreateFlags flags + state.depth_clamp, // VkBool32 depthClampEnable + VK_FALSE, // VkBool32 rasterizerDiscardEnable + VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode + state.cull_mode, // VkCullModeFlags cullMode + VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace + VK_FALSE, // VkBool32 depthBiasEnable + 0.0f, // float depthBiasConstantFactor + 0.0f, // float depthBiasClamp + 0.0f, // float depthBiasSlopeFactor + 1.0f // float lineWidth + }; +} + +static VkPipelineMultisampleStateCreateInfo +GetVulkanMultisampleState(const RasterizationState& rs_state) +{ + return { + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkPipelineMultisampleStateCreateFlags flags + rs_state.samples, // VkSampleCountFlagBits rasterizationSamples + rs_state.per_sample_shading, // VkBool32 sampleShadingEnable + 1.0f, // float minSampleShading + nullptr, // const VkSampleMask* pSampleMask; + VK_FALSE, // VkBool32 alphaToCoverageEnable + VK_FALSE // VkBool32 alphaToOneEnable + }; +} + +static VkPipelineDepthStencilStateCreateInfo +GetVulkanDepthStencilState(const DepthStencilState& state) +{ + return { + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkPipelineDepthStencilStateCreateFlags flags + state.test_enable, // VkBool32 depthTestEnable + state.write_enable, // VkBool32 depthWriteEnable + state.compare_op, // VkCompareOp depthCompareOp + VK_FALSE, // VkBool32 depthBoundsTestEnable + VK_FALSE, // VkBool32 stencilTestEnable + {}, // VkStencilOpState front + {}, // VkStencilOpState back + 0.0f, // float minDepthBounds + 1.0f // float maxDepthBounds + }; +} + +static VkPipelineColorBlendAttachmentState GetVulkanAttachmentBlendState(const BlendingState& state) +{ + VkPipelineColorBlendAttachmentState vk_state = {}; + vk_state.blendEnable = static_cast(state.blendenable); + vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; + vk_state.alphaBlendOp = state.subtractAlpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; + + if (state.usedualsrc && g_vulkan_context->SupportsDualSourceBlend()) + { + static constexpr std::array src_factors = { + {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, + VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; + static constexpr std::array dst_factors = { + {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, + VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; + + vk_state.srcColorBlendFactor = src_factors[state.srcfactor]; + vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha]; + vk_state.dstColorBlendFactor = dst_factors[state.dstfactor]; + vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha]; + } + else + { + static constexpr std::array src_factors = { + {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, + VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; + + static constexpr std::array dst_factors = { + {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, + VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; + + vk_state.srcColorBlendFactor = src_factors[state.srcfactor]; + vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha]; + vk_state.dstColorBlendFactor = dst_factors[state.dstfactor]; + vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha]; + } + + if (state.colorupdate) + { + vk_state.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; + } + else + { + vk_state.colorWriteMask = 0; + } + + if (state.alphaupdate) + vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT; + + return vk_state; +} + +static VkPipelineColorBlendStateCreateInfo +GetVulkanColorBlendState(const BlendingState& state, + const VkPipelineColorBlendAttachmentState* attachments, + uint32_t num_attachments) +{ + static constexpr std::array vk_logic_ops = { + {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY, + VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR, + VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE, + VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}}; + + VkBool32 vk_logic_op_enable = static_cast(state.logicopenable); + if (vk_logic_op_enable && !g_vulkan_context->SupportsLogicOps()) + { + // At the time of writing, Adreno and Mali drivers didn't support logic ops. + // The "emulation" through blending path has been removed, so just disable it completely. + // These drivers don't support dual-source blend either, so issues are to be expected. + vk_logic_op_enable = VK_FALSE; + } + + VkLogicOp vk_logic_op = vk_logic_op_enable ? vk_logic_ops[state.logicmode] : VK_LOGIC_OP_CLEAR; + + VkPipelineColorBlendStateCreateInfo vk_state = { + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkPipelineColorBlendStateCreateFlags flags + vk_logic_op_enable, // VkBool32 logicOpEnable + vk_logic_op, // VkLogicOp logicOp + num_attachments, // uint32_t attachmentCount + attachments, // const VkPipelineColorBlendAttachmentState* pAttachments + {1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4] + }; + + return vk_state; +} + +VkPipeline ShaderCache::CreatePipeline(const PipelineInfo& info) +{ + // 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 + nullptr, // const void* pNext + 0, // VkPipelineVertexInputStateCreateFlags flags + 0, // uint32_t vertexBindingDescriptionCount + nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions + 0, // uint32_t vertexAttributeDescriptionCount + nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions + }; + + // Vertex inputs + const VkPipelineVertexInputStateCreateInfo& vertex_input_state = + info.vertex_format ? info.vertex_format->GetVertexInputStateInfo() : empty_vertex_input_state; + + // Input assembly + VkPipelineInputAssemblyStateCreateInfo input_assembly_state = { + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkPipelineInputAssemblyStateCreateFlags flags + info.primitive_topology, // VkPrimitiveTopology topology + VK_FALSE // VkBool32 primitiveRestartEnable + }; + + // See Vulkan spec, section 19: + // If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, + // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, + // primitiveRestartEnable must be VK_FALSE + if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart && + IsStripPrimitiveTopology(info.primitive_topology)) + { + input_assembly_state.primitiveRestartEnable = VK_TRUE; + } + + // Shaders to stages + VkPipelineShaderStageCreateInfo shader_stages[3]; + uint32_t num_shader_stages = 0; + if (info.vs != VK_NULL_HANDLE) + { + shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_VERTEX_BIT, + info.vs, + "main"}; + } + if (info.gs != VK_NULL_HANDLE) + { + shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_GEOMETRY_BIT, + info.gs, + "main"}; + } + if (info.ps != VK_NULL_HANDLE) + { + shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_FRAGMENT_BIT, + info.ps, + "main"}; + } + + // Fill in Vulkan descriptor structs from our state structures. + VkPipelineRasterizationStateCreateInfo rasterization_state = + GetVulkanRasterizationState(info.rasterization_state); + VkPipelineMultisampleStateCreateInfo multisample_state = + GetVulkanMultisampleState(info.rasterization_state); + VkPipelineDepthStencilStateCreateInfo depth_stencil_state = + GetVulkanDepthStencilState(info.depth_stencil_state); + VkPipelineColorBlendAttachmentState blend_attachment_state = + GetVulkanAttachmentBlendState(info.blend_state); + VkPipelineColorBlendStateCreateInfo blend_state = + GetVulkanColorBlendState(info.blend_state, &blend_attachment_state, 1); + + // This viewport isn't used, but needs to be specified anyway. + static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; + static const VkRect2D scissor = {{0, 0}, {1, 1}}; + static const VkPipelineViewportStateCreateInfo viewport_state = { + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + nullptr, + 0, // VkPipelineViewportStateCreateFlags flags; + 1, // uint32_t viewportCount + &viewport, // const VkViewport* pViewports + 1, // uint32_t scissorCount + &scissor // const VkRect2D* pScissors + }; + + // Set viewport and scissor dynamic state so we can change it elsewhere. + static const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR}; + static const VkPipelineDynamicStateCreateInfo dynamic_state = { + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr, + 0, // VkPipelineDynamicStateCreateFlags flags + static_cast(ArraySize(dynamic_states)), // uint32_t dynamicStateCount + dynamic_states // const VkDynamicState* pDynamicStates + }; + + // Combine to full pipeline info structure. + VkGraphicsPipelineCreateInfo pipeline_info = { + VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + nullptr, // VkStructureType sType + 0, // VkPipelineCreateFlags flags + num_shader_stages, // uint32_t stageCount + shader_stages, // const VkPipelineShaderStageCreateInfo* pStages + &vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState + &input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState + nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState + &viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState + &rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState + &multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState + &depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState + &blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState + &dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState + info.pipeline_layout, // VkPipelineLayout layout + info.render_pass, // VkRenderPass renderPass + 0, // uint32_t subpass + VK_NULL_HANDLE, // VkPipeline basePipelineHandle + -1 // int32_t basePipelineIndex + }; + + 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; + } + + return pipeline; +} + +VkPipeline ShaderCache::GetPipeline(const PipelineInfo& info) +{ + return GetPipelineWithCacheResult(info).first; +} + +std::pair ShaderCache::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}; +} + +VkPipeline ShaderCache::CreateComputePipeline(const ComputePipelineInfo& info) +{ + VkComputePipelineCreateInfo pipeline_info = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + nullptr, + 0, + {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, info.cs, + "main", nullptr}, + info.pipeline_layout, + VK_NULL_HANDLE, + -1}; + + VkPipeline pipeline; + VkResult res = vkCreateComputePipelines(g_vulkan_context->GetDevice(), VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &pipeline); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateComputePipelines failed: "); + return VK_NULL_HANDLE; + } + + return pipeline; +} + +VkPipeline ShaderCache::GetComputePipeline(const ComputePipelineInfo& info) +{ + auto iter = m_compute_pipeline_objects.find(info); + if (iter != m_compute_pipeline_objects.end()) + return iter->second; + + VkPipeline pipeline = CreateComputePipeline(info); + m_compute_pipeline_objects.emplace(info, pipeline); + return pipeline; +} + +void ShaderCache::ClearPipelineCache() +{ + for (const auto& it : m_pipeline_objects) + { + if (it.second != VK_NULL_HANDLE) + vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr); + } + m_pipeline_objects.clear(); + + for (const auto& it : m_compute_pipeline_objects) + { + if (it.second != VK_NULL_HANDLE) + vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr); + } + m_compute_pipeline_objects.clear(); +} + +class PipelineCacheReadCallback : public LinearDiskCacheReader +{ +public: + PipelineCacheReadCallback(std::vector* 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* m_data; +}; + +class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader +{ +public: + void Read(const u32& key, const u8* value, u32 value_size) override {} +}; + +bool ShaderCache::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 ShaderCache::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 disk_data; + LinearDiskCache 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) +// 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 ShaderCache::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 ShaderCache::DestroyPipelineCache() +{ + ClearPipelineCache(); + vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr); + m_pipeline_cache = VK_NULL_HANDLE; +} + +void ShaderCache::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 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 disk_cache; + PipelineCacheReadIgnoreCallback callback; + disk_cache.OpenAndRead(m_pipeline_cache_filename, callback); + disk_cache.Append(1, data.data(), static_cast(data.size())); + disk_cache.Close(); +} + +// Cache inserter that is called back when reading from the file +template +struct ShaderCacheReader : public LinearDiskCacheReader +{ + ShaderCacheReader(std::map& shader_map) : m_shader_map(shader_map) {} + void Read(const Uid& key, const u32* value, u32 value_size) override + { + // We don't insert null modules into the shader map since creation could succeed later on. + // e.g. we're generating bad code, but fix this in a later version, and for some reason + // the cache is not invalidated. + VkShaderModule module = Util::CreateShaderModule(value, value_size); + if (module == VK_NULL_HANDLE) + return; + + m_shader_map.emplace(key, module); + } + + std::map& m_shader_map; +}; + +void ShaderCache::LoadShaderCaches() +{ + ShaderCacheReader vs_reader(m_vs_cache.shader_map); + m_vs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "VS", true, true), + vs_reader); + + ShaderCacheReader ps_reader(m_ps_cache.shader_map); + m_ps_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "PS", true, true), + ps_reader); + + if (g_vulkan_context->SupportsGeometryShaders()) + { + ShaderCacheReader gs_reader(m_gs_cache.shader_map); + m_gs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "GS", true, true), + gs_reader); + } + + SETSTAT(stats.numPixelShadersCreated, static_cast(m_ps_cache.shader_map.size())); + SETSTAT(stats.numPixelShadersAlive, static_cast(m_ps_cache.shader_map.size())); + SETSTAT(stats.numVertexShadersCreated, static_cast(m_vs_cache.shader_map.size())); + SETSTAT(stats.numVertexShadersAlive, static_cast(m_vs_cache.shader_map.size())); +} + +template +static void DestroyShaderCache(T& cache) +{ + cache.disk_cache.Sync(); + cache.disk_cache.Close(); + for (const auto& it : cache.shader_map) + { + if (it.second != VK_NULL_HANDLE) + vkDestroyShaderModule(g_vulkan_context->GetDevice(), it.second, nullptr); + } + cache.shader_map.clear(); +} + +void ShaderCache::DestroyShaderCaches() +{ + DestroyShaderCache(m_vs_cache); + DestroyShaderCache(m_ps_cache); + + if (g_vulkan_context->SupportsGeometryShaders()) + DestroyShaderCache(m_gs_cache); + + SETSTAT(stats.numPixelShadersCreated, 0); + SETSTAT(stats.numPixelShadersAlive, 0); + SETSTAT(stats.numVertexShadersCreated, 0); + SETSTAT(stats.numVertexShadersAlive, 0); +} + +VkShaderModule ShaderCache::GetVertexShaderForUid(const VertexShaderUid& uid) +{ + auto it = m_vs_cache.shader_map.find(uid); + if (it != m_vs_cache.shader_map.end()) + return it->second; + + // Not in the cache, so compile the shader. + ShaderCompiler::SPIRVCodeVector spv; + VkShaderModule module = VK_NULL_HANDLE; + ShaderCode source_code = + GenerateVertexShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); + if (ShaderCompiler::CompileVertexShader(&spv, source_code.GetBuffer().c_str(), + source_code.GetBuffer().length())) + { + module = Util::CreateShaderModule(spv.data(), spv.size()); + + // Append to shader cache if it created successfully. + if (module != VK_NULL_HANDLE) + { + m_vs_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); + INCSTAT(stats.numVertexShadersCreated); + INCSTAT(stats.numVertexShadersAlive); + } + } + + // We still insert null entries to prevent further compilation attempts. + m_vs_cache.shader_map.emplace(uid, module); + return module; +} + +VkShaderModule ShaderCache::GetGeometryShaderForUid(const GeometryShaderUid& uid) +{ + _assert_(g_vulkan_context->SupportsGeometryShaders()); + auto it = m_gs_cache.shader_map.find(uid); + if (it != m_gs_cache.shader_map.end()) + return it->second; + + // Not in the cache, so compile the shader. + ShaderCompiler::SPIRVCodeVector spv; + VkShaderModule module = VK_NULL_HANDLE; + ShaderCode source_code = + GenerateGeometryShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); + if (ShaderCompiler::CompileGeometryShader(&spv, source_code.GetBuffer().c_str(), + source_code.GetBuffer().length())) + { + module = Util::CreateShaderModule(spv.data(), spv.size()); + + // Append to shader cache if it created successfully. + if (module != VK_NULL_HANDLE) + m_gs_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); + } + + // We still insert null entries to prevent further compilation attempts. + m_gs_cache.shader_map.emplace(uid, module); + return module; +} + +VkShaderModule ShaderCache::GetPixelShaderForUid(const PixelShaderUid& uid) +{ + auto it = m_ps_cache.shader_map.find(uid); + if (it != m_ps_cache.shader_map.end()) + return it->second; + + // Not in the cache, so compile the shader. + ShaderCompiler::SPIRVCodeVector spv; + VkShaderModule module = VK_NULL_HANDLE; + ShaderCode source_code = + GeneratePixelShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData()); + if (ShaderCompiler::CompileFragmentShader(&spv, source_code.GetBuffer().c_str(), + source_code.GetBuffer().length())) + { + module = Util::CreateShaderModule(spv.data(), spv.size()); + + // Append to shader cache if it created successfully. + if (module != VK_NULL_HANDLE) + { + m_ps_cache.disk_cache.Append(uid, spv.data(), static_cast(spv.size())); + INCSTAT(stats.numPixelShadersCreated); + INCSTAT(stats.numPixelShadersAlive); + } + } + + // We still insert null entries to prevent further compilation attempts. + m_ps_cache.shader_map.emplace(uid, module); + return module; +} + +void ShaderCache::RecompileSharedShaders() +{ + DestroySharedShaders(); + if (!CompileSharedShaders()) + PanicAlert("Failed to recompile shared shaders."); +} + +void ShaderCache::ReloadShaderAndPipelineCaches() +{ + SavePipelineCache(); + DestroyShaderCaches(); + DestroyPipelineCache(); + + if (g_ActiveConfig.bShaderCache) + { + LoadShaderCaches(); + LoadPipelineCache(); + } + else + { + CreatePipelineCache(); + } +} + +std::string ShaderCache::GetUtilityShaderHeader() const +{ + std::stringstream ss; + if (g_ActiveConfig.iMultisamples > 1) + { + ss << "#define MSAA_ENABLED 1" << std::endl; + ss << "#define MSAA_SAMPLES " << g_ActiveConfig.iMultisamples << std::endl; + if (g_ActiveConfig.bSSAA) + ss << "#define SSAA_ENABLED 1" << std::endl; + } + + u32 efb_layers = (g_ActiveConfig.iStereoMode != STEREO_OFF) ? 2 : 1; + ss << "#define EFB_LAYERS " << efb_layers << std::endl; + + return ss.str(); +} + +// Comparison operators for PipelineInfos +// Since these all boil down to POD types, we can just memcmp the entire thing for speed +// The is_trivially_copyable check fails on MSVC due to BitField. +// TODO: Can we work around this any way? +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && !defined(_MSC_VER) +static_assert(std::has_trivial_copy_constructor::value, + "PipelineInfo is trivially copyable"); +#elif !defined(_MSC_VER) +static_assert(std::is_trivially_copyable::value, + "PipelineInfo is trivially copyable"); +#endif + +std::size_t PipelineInfoHash::operator()(const PipelineInfo& key) const +{ + return static_cast(XXH64(&key, sizeof(key), 0)); +} + +bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +} + +bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs) +{ + return !operator==(lhs, rhs); +} + +bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0; +} + +bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0; +} + +bool operator==(const SamplerState& lhs, const SamplerState& rhs) +{ + return lhs.bits == rhs.bits; +} + +bool operator!=(const SamplerState& lhs, const SamplerState& rhs) +{ + return !operator==(lhs, rhs); +} + +bool operator>(const SamplerState& lhs, const SamplerState& rhs) +{ + return lhs.bits > rhs.bits; +} + +bool operator<(const SamplerState& lhs, const SamplerState& rhs) +{ + return lhs.bits < rhs.bits; +} + +std::size_t ComputePipelineInfoHash::operator()(const ComputePipelineInfo& key) const +{ + return static_cast(XXH64(&key, sizeof(key), 0)); +} + +bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +} + +bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) +{ + return !operator==(lhs, rhs); +} + +bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0; +} + +bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs) +{ + return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0; +} + +bool ShaderCache::CompileSharedShaders() +{ + static const char PASSTHROUGH_VERTEX_SHADER_SOURCE[] = R"( + layout(location = 0) in vec4 ipos; + layout(location = 5) in vec4 icol0; + layout(location = 8) in vec3 itex0; + + layout(location = 0) out vec3 uv0; + layout(location = 1) out vec4 col0; + + void main() + { + gl_Position = ipos; + uv0 = itex0; + col0 = icol0; + } + )"; + + static const char PASSTHROUGH_GEOMETRY_SHADER_SOURCE[] = R"( + layout(triangles) in; + layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; + + layout(location = 0) in vec3 in_uv0[]; + layout(location = 1) in vec4 in_col0[]; + + layout(location = 0) out vec3 out_uv0; + layout(location = 1) out vec4 out_col0; + + void main() + { + for (int j = 0; j < EFB_LAYERS; j++) + { + for (int i = 0; i < 3; i++) + { + gl_Layer = j; + gl_Position = gl_in[i].gl_Position; + out_uv0 = vec3(in_uv0[i].xy, float(j)); + out_col0 = in_col0[i]; + EmitVertex(); + } + EndPrimitive(); + } + } + )"; + + static const char SCREEN_QUAD_VERTEX_SHADER_SOURCE[] = R"( + layout(location = 0) out vec3 uv0; + + void main() + { + /* + * id &1 &2 clamp(*2-1) + * 0 0,0 0,0 -1,-1 TL + * 1 1,0 1,0 1,-1 TR + * 2 0,2 0,1 -1,1 BL + * 3 1,2 1,1 1,1 BR + */ + vec2 rawpos = vec2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f)); + gl_Position = vec4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f); + uv0 = vec3(rawpos, 0.0f); + } + )"; + + static const char SCREEN_QUAD_GEOMETRY_SHADER_SOURCE[] = R"( + layout(triangles) in; + layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; + + layout(location = 0) in vec3 in_uv0[]; + + layout(location = 0) out vec3 out_uv0; + + void main() + { + for (int j = 0; j < EFB_LAYERS; j++) + { + for (int i = 0; i < 3; i++) + { + gl_Layer = j; + gl_Position = gl_in[i].gl_Position; + out_uv0 = vec3(in_uv0[i].xy, float(j)); + EmitVertex(); + } + EndPrimitive(); + } + } + )"; + + std::string header = GetUtilityShaderHeader(); + + m_screen_quad_vertex_shader = + Util::CompileAndCreateVertexShader(header + SCREEN_QUAD_VERTEX_SHADER_SOURCE); + m_passthrough_vertex_shader = + Util::CompileAndCreateVertexShader(header + PASSTHROUGH_VERTEX_SHADER_SOURCE); + if (m_screen_quad_vertex_shader == VK_NULL_HANDLE || + m_passthrough_vertex_shader == VK_NULL_HANDLE) + { + return false; + } + + if (g_ActiveConfig.iStereoMode != STEREO_OFF && g_vulkan_context->SupportsGeometryShaders()) + { + m_screen_quad_geometry_shader = + Util::CompileAndCreateGeometryShader(header + SCREEN_QUAD_GEOMETRY_SHADER_SOURCE); + m_passthrough_geometry_shader = + Util::CompileAndCreateGeometryShader(header + PASSTHROUGH_GEOMETRY_SHADER_SOURCE); + if (m_screen_quad_geometry_shader == VK_NULL_HANDLE || + m_passthrough_geometry_shader == VK_NULL_HANDLE) + { + return false; + } + } + + return true; +} + +void ShaderCache::DestroySharedShaders() +{ + auto DestroyShader = [this](VkShaderModule& shader) { + if (shader != VK_NULL_HANDLE) + { + vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr); + shader = VK_NULL_HANDLE; + } + }; + + DestroyShader(m_screen_quad_vertex_shader); + DestroyShader(m_passthrough_vertex_shader); + DestroyShader(m_screen_quad_geometry_shader); + DestroyShader(m_passthrough_geometry_shader); +} +} diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCache.h b/Source/Core/VideoBackends/Vulkan/ShaderCache.h new file mode 100644 index 0000000000..7a83472e38 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/ShaderCache.h @@ -0,0 +1,172 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/LinearDiskCache.h" + +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/ObjectCache.h" + +#include "VideoCommon/GeometryShaderGen.h" +#include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/VertexShaderGen.h" + +namespace Vulkan +{ +class CommandBufferManager; +class VertexFormat; +class StreamBuffer; + +class CommandBufferManager; +class VertexFormat; +class StreamBuffer; + +struct PipelineInfo +{ + // These are packed in descending order of size, to avoid any padding so that the structure + // can be copied/compared as a single block of memory. 64-bit pointer size is assumed. + const VertexFormat* vertex_format; + VkPipelineLayout pipeline_layout; + VkShaderModule vs; + VkShaderModule gs; + VkShaderModule ps; + VkRenderPass render_pass; + BlendingState blend_state; + RasterizationState rasterization_state; + DepthStencilState depth_stencil_state; + VkPrimitiveTopology primitive_topology; +}; + +struct PipelineInfoHash +{ + std::size_t operator()(const PipelineInfo& key) const; +}; + +bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs); +bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs); +bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs); +bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs); +bool operator==(const SamplerState& lhs, const SamplerState& rhs); +bool operator!=(const SamplerState& lhs, const SamplerState& rhs); +bool operator>(const SamplerState& lhs, const SamplerState& rhs); +bool operator<(const SamplerState& lhs, const SamplerState& rhs); + +struct ComputePipelineInfo +{ + VkPipelineLayout pipeline_layout; + VkShaderModule cs; +}; + +struct ComputePipelineInfoHash +{ + std::size_t operator()(const ComputePipelineInfo& key) const; +}; + +bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); +bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); +bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); +bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs); + +class ShaderCache +{ +public: + ShaderCache(); + ~ShaderCache(); + + // Get utility shader header based on current config. + std::string GetUtilityShaderHeader() const; + + // Accesses ShaderGen shader caches + VkShaderModule GetVertexShaderForUid(const VertexShaderUid& uid); + VkShaderModule GetGeometryShaderForUid(const GeometryShaderUid& uid); + VkShaderModule GetPixelShaderForUid(const PixelShaderUid& uid); + + // Perform at startup, create descriptor layouts, compiles all static shaders. + bool Initialize(); + + // 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); + + // 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 GetPipelineWithCacheResult(const PipelineInfo& info); + + // Creates a compute pipeline, and does not track the handle. + VkPipeline CreateComputePipeline(const ComputePipelineInfo& info); + + // Find a pipeline by the specified description, if not found, attempts to create it + VkPipeline GetComputePipeline(const ComputePipelineInfo& info); + + // Clears our pipeline cache of all objects. This is necessary when recompiling shaders, + // as drivers are free to return the same pointer again, which means that we may end up using + // and old pipeline object if they are not cleared first. Some stutter may be experienced + // while our cache is rebuilt on use, but the pipeline cache object should mitigate this. + // NOTE: Ensure that none of these objects are in use before calling. + void ClearPipelineCache(); + + // Saves the pipeline cache to disk. Call when shutting down. + void SavePipelineCache(); + + // Recompile shared shaders, call when stereo mode changes. + void RecompileSharedShaders(); + + // Reload pipeline cache. This will destroy all pipelines. + void ReloadShaderAndPipelineCaches(); + + // Shared shader accessors + VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; } + 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; } +private: + bool CreatePipelineCache(); + bool LoadPipelineCache(); + bool ValidatePipelineCache(const u8* data, size_t data_length); + void DestroyPipelineCache(); + void LoadShaderCaches(); + void DestroyShaderCaches(); + bool CompileSharedShaders(); + void DestroySharedShaders(); + + template + struct ShaderModuleCache + { + std::map shader_map; + LinearDiskCache disk_cache; + }; + ShaderModuleCache m_vs_cache; + ShaderModuleCache m_gs_cache; + ShaderModuleCache m_ps_cache; + + std::unordered_map m_pipeline_objects; + std::unordered_map + m_compute_pipeline_objects; + VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE; + std::string m_pipeline_cache_filename; + + // Utility/shared shaders + VkShaderModule m_screen_quad_vertex_shader = VK_NULL_HANDLE; + VkShaderModule m_passthrough_vertex_shader = VK_NULL_HANDLE; + VkShaderModule m_screen_quad_geometry_shader = VK_NULL_HANDLE; + VkShaderModule m_passthrough_geometry_shader = VK_NULL_HANDLE; +}; + +extern std::unique_ptr g_shader_cache; + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 0b7833be5c..a03437a697 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -13,6 +13,7 @@ #include "VideoBackends/Vulkan/Constants.h" #include "VideoBackends/Vulkan/FramebufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/ShaderCache.h" #include "VideoBackends/Vulkan/StreamBuffer.h" #include "VideoBackends/Vulkan/Util.h" #include "VideoBackends/Vulkan/VertexFormat.h" @@ -181,7 +182,7 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid) pinfo.pipeline_layout = uid.ps_uid.GetUidData()->bounding_box ? g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_BBOX) : g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD); - pinfo.vs = g_object_cache->GetVertexShaderForUid(uid.vs_uid); + pinfo.vs = g_shader_cache->GetVertexShaderForUid(uid.vs_uid); if (pinfo.vs == VK_NULL_HANDLE) { WARN_LOG(VIDEO, "Failed to get vertex shader from cached UID."); @@ -189,14 +190,14 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid) } if (g_vulkan_context->SupportsGeometryShaders() && !uid.gs_uid.GetUidData()->IsPassthrough()) { - pinfo.gs = g_object_cache->GetGeometryShaderForUid(uid.gs_uid); + pinfo.gs = g_shader_cache->GetGeometryShaderForUid(uid.gs_uid); if (pinfo.gs == VK_NULL_HANDLE) { WARN_LOG(VIDEO, "Failed to get geometry shader from cached UID."); return false; } } - pinfo.ps = g_object_cache->GetPixelShaderForUid(uid.ps_uid); + pinfo.ps = g_shader_cache->GetPixelShaderForUid(uid.ps_uid); if (pinfo.ps == VK_NULL_HANDLE) { WARN_LOG(VIDEO, "Failed to get pixel shader from cached UID."); @@ -208,7 +209,7 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid) pinfo.blend_state.hex = uid.blend_state_bits; pinfo.primitive_topology = uid.primitive_topology; - VkPipeline pipeline = g_object_cache->GetPipeline(pinfo); + VkPipeline pipeline = g_shader_cache->GetPipeline(pinfo); if (pipeline == VK_NULL_HANDLE) { WARN_LOG(VIDEO, "Failed to get pipeline from cached UID."); @@ -327,7 +328,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type) if (vs_uid != m_vs_uid) { - m_pipeline_state.vs = g_object_cache->GetVertexShaderForUid(vs_uid); + m_pipeline_state.vs = g_shader_cache->GetVertexShaderForUid(vs_uid); m_vs_uid = vs_uid; changed = true; } @@ -340,7 +341,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type) if (gs_uid.GetUidData()->IsPassthrough()) m_pipeline_state.gs = VK_NULL_HANDLE; else - m_pipeline_state.gs = g_object_cache->GetGeometryShaderForUid(gs_uid); + m_pipeline_state.gs = g_shader_cache->GetGeometryShaderForUid(gs_uid); m_gs_uid = gs_uid; changed = true; @@ -349,7 +350,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type) if (ps_uid != m_ps_uid) { - m_pipeline_state.ps = g_object_cache->GetPixelShaderForUid(ps_uid); + m_pipeline_state.ps = g_shader_cache->GetPixelShaderForUid(ps_uid); m_ps_uid = ps_uid; changed = true; } @@ -887,7 +888,7 @@ void StateTracker::EndClearRenderPass() VkPipeline StateTracker::GetPipelineAndCacheUID(const PipelineInfo& info) { - auto result = g_object_cache->GetPipelineWithCacheResult(info); + auto result = g_shader_cache->GetPipelineWithCacheResult(info); // Add to the UID cache if it is a new pipeline. if (!result.second && g_ActiveConfig.bShaderCache) diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h index ef09608fe8..03d7464cee 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.h +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h @@ -11,7 +11,7 @@ #include "Common/CommonTypes.h" #include "Common/LinearDiskCache.h" #include "VideoBackends/Vulkan/Constants.h" -#include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/ShaderCache.h" #include "VideoCommon/GeometryShaderGen.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/PixelShaderGen.h" diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp index 30d251d343..30817706b7 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp @@ -299,7 +299,7 @@ bool TextureCache::CompileShaders() } )"; - std::string header = g_object_cache->GetUtilityShaderHeader(); + std::string header = g_shader_cache->GetUtilityShaderHeader(); std::string source; source = header + COPY_SHADER_SOURCE; @@ -385,8 +385,8 @@ void TextureCache::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, UtilityShaderDraw draw(command_buffer, g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_PUSH_CONSTANT), - m_render_pass, g_object_cache->GetPassthroughVertexShader(), - g_object_cache->GetPassthroughGeometryShader(), + m_render_pass, g_shader_cache->GetPassthroughVertexShader(), + g_shader_cache->GetPassthroughGeometryShader(), is_depth_copy ? m_efb_depth_to_tex_shader : m_efb_color_to_tex_shader); draw.SetPushConstants(colmat, (is_depth_copy ? sizeof(float) * 20 : sizeof(float) * 28)); diff --git a/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp b/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp index 95f5c64f62..bef93e3d8e 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp @@ -200,7 +200,7 @@ void TextureConverter::ConvertTexture(TextureCacheBase::TCacheEntry* dst_entry, // Bind and draw to the destination. UtilityShaderDraw draw(command_buffer, g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_TEXTURE_CONVERSION), - render_pass, g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, + render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_palette_conversion_shaders[palette_format]); VkRect2D region = {{0, 0}, {dst_entry->GetWidth(), dst_entry->GetHeight()}}; @@ -240,7 +240,7 @@ void TextureConverter::EncodeTextureToMemory(VkImageView src_texture, u8* dest_p UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_PUSH_CONSTANT), - m_encoding_render_pass, g_object_cache->GetScreenQuadVertexShader(), + m_encoding_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, shader); // Uniform - int4 of left,top,native_width,scale @@ -299,7 +299,7 @@ void TextureConverter::EncodeTextureToMemoryYUYV(void* dst_ptr, u32 dst_width, u u32 output_width = dst_width / 2; UtilityShaderDraw draw(command_buffer, g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), - m_encoding_render_pass, g_object_cache->GetPassthroughVertexShader(), + m_encoding_render_pass, g_shader_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, m_rgb_to_yuyv_shader); VkRect2D region = {{0, 0}, {output_width, dst_height}}; draw.BeginRenderPass(m_encoding_render_framebuffer, region); @@ -376,7 +376,7 @@ void TextureConverter::DecodeYUYVTextureFromMemory(VKTexture* dst_texture, const // Convert from the YUYV data now in the intermediate texture to RGBA in the destination. UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_TEXTURE_CONVERSION), - m_encoding_render_pass, g_object_cache->GetScreenQuadVertexShader(), + m_encoding_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_yuyv_to_rgb_shader); VkRect2D region = {{0, 0}, {src_width, src_height}}; draw.BeginRenderPass(dst_texture->GetFramebuffer(), region); @@ -838,7 +838,7 @@ bool TextureConverter::CompileYUYVConversionShaders() } )"; - std::string header = g_object_cache->GetUtilityShaderHeader(); + std::string header = g_shader_cache->GetUtilityShaderHeader(); std::string source = header + RGB_TO_YUYV_SHADER_SOURCE; m_rgb_to_yuyv_shader = Util::CompileAndCreateFragmentShader(source); source = header + YUYV_TO_RGB_SHADER_SOURCE; diff --git a/Source/Core/VideoBackends/Vulkan/Util.cpp b/Source/Core/VideoBackends/Vulkan/Util.cpp index 5ab32b5a25..f1f4f42b1b 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.cpp +++ b/Source/Core/VideoBackends/Vulkan/Util.cpp @@ -12,6 +12,7 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/ShaderCache.h" #include "VideoBackends/Vulkan/ShaderCompiler.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/StreamBuffer.h" @@ -720,7 +721,7 @@ void UtilityShaderDraw::BindDescriptors() bool UtilityShaderDraw::BindPipeline() { - VkPipeline pipeline = g_object_cache->GetPipeline(m_pipeline_info); + VkPipeline pipeline = g_shader_cache->GetPipeline(m_pipeline_info); if (pipeline == VK_NULL_HANDLE) { PanicAlert("Failed to get pipeline for backend shader draw"); @@ -873,7 +874,7 @@ void ComputeShaderDispatcher::BindDescriptors() bool ComputeShaderDispatcher::BindPipeline() { - VkPipeline pipeline = g_object_cache->GetComputePipeline(m_pipeline_info); + VkPipeline pipeline = g_shader_cache->GetComputePipeline(m_pipeline_info); if (pipeline == VK_NULL_HANDLE) { PanicAlert("Failed to get pipeline for backend compute dispatch"); diff --git a/Source/Core/VideoBackends/Vulkan/Util.h b/Source/Core/VideoBackends/Vulkan/Util.h index 035aecba81..6847409e9b 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.h +++ b/Source/Core/VideoBackends/Vulkan/Util.h @@ -10,13 +10,13 @@ #include "Common/CommonTypes.h" #include "VideoBackends/Vulkan/Constants.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/ShaderCache.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/TextureConfig.h" namespace Vulkan { class CommandBufferManager; -class ObjectCache; class StateTracker; namespace Util diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index e937f06f33..36292751b0 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -225,8 +225,8 @@ void VKTexture::ScaleTextureRectangle(const MathUtil::Rectangle& dst_rect, UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), TextureCache::GetInstance()->GetTextureCopyRenderPass(), - g_object_cache->GetPassthroughVertexShader(), - g_object_cache->GetPassthroughGeometryShader(), + g_shader_cache->GetPassthroughVertexShader(), + g_shader_cache->GetPassthroughGeometryShader(), TextureCache::GetInstance()->GetCopyShader()); VkRect2D region = { diff --git a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj index f6ec82bb66..3492f7b0f2 100644 --- a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj +++ b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj @@ -41,6 +41,7 @@ + @@ -67,6 +68,7 @@ + diff --git a/Source/Core/VideoBackends/Vulkan/main.cpp b/Source/Core/VideoBackends/Vulkan/main.cpp index 2fdcae7e98..4354a7a58b 100644 --- a/Source/Core/VideoBackends/Vulkan/main.cpp +++ b/Source/Core/VideoBackends/Vulkan/main.cpp @@ -215,19 +215,22 @@ bool VideoBackend::Initialize(void* window_handle) // Create main wrapper instances. g_object_cache = std::make_unique(); + g_shader_cache = std::make_unique(); g_framebuffer_manager = std::make_unique(); g_renderer = std::make_unique(std::move(swap_chain)); // Invoke init methods on main wrapper classes. // These have to be done before the others because the destructors // for the remaining classes may call methods on these. - if (!g_object_cache->Initialize() || !FramebufferManager::GetInstance()->Initialize() || - !StateTracker::CreateInstance() || !Renderer::GetInstance()->Initialize()) + if (!g_object_cache->Initialize() || !g_shader_cache->Initialize() || + !FramebufferManager::GetInstance()->Initialize() || !StateTracker::CreateInstance() || + !Renderer::GetInstance()->Initialize()) { PanicAlert("Failed to initialize Vulkan classes."); g_renderer.reset(); StateTracker::DestroyInstance(); g_framebuffer_manager.reset(); + g_shader_cache.reset(); g_object_cache.reset(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); @@ -250,6 +253,7 @@ bool VideoBackend::Initialize(void* window_handle) g_renderer.reset(); StateTracker::DestroyInstance(); g_framebuffer_manager.reset(); + g_shader_cache.reset(); g_object_cache.reset(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); @@ -276,6 +280,7 @@ void VideoBackend::Shutdown() { g_command_buffer_mgr->WaitForGPUIdle(); + g_shader_cache.reset(); g_object_cache.reset(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); @@ -291,7 +296,7 @@ void VideoBackend::Video_Cleanup() // Save all cached pipelines out to disk for next time. if (g_ActiveConfig.bShaderCache) - g_object_cache->SavePipelineCache(); + g_shader_cache->SavePipelineCache(); g_perf_query.reset(); g_texture_cache.reset();