summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp
diff options
context:
space:
mode:
authorAnthony <Helios747@users.noreply.github.com>2017-07-30 00:42:36 -0700
committerGitHub <noreply@github.com>2017-07-30 00:42:36 -0700
commit5bad2ee4a4129e734ff4f4d861cedc4cd573df55 (patch)
treecd3c3ccc6e35417858499449d774670c3983cb13 /Source/Core/VideoBackends/Vulkan/ShaderCache.cpp
parent5e6d5493165d1214f513b91eaeb4e162d21fdd3e (diff)
parent357c55442f3dbdf6f23d16af5362d1a615cb84c1 (diff)
Merge pull request #5832 from stenzek/ubershader-prereq
Ubershader prerequisites
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/ShaderCache.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCache.cpp1029
1 files changed, 1029 insertions, 0 deletions
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 <algorithm>
+#include <sstream>
+#include <type_traits>
+#include <xxhash.h>
+
+#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<ShaderCache> 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<VkBool32>(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<VkBlendFactor, 8> 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<VkBlendFactor, 8> 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<VkBlendFactor, 8> 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<VkBlendFactor, 8> 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<VkLogicOp, 16> 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<VkBool32>(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<u32>(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<VkPipeline, bool> 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<u32, u8>
+{
+public:
+ PipelineCacheReadCallback(std::vector<u8>* data) : m_data(data) {}
+ void Read(const u32& key, const u8* value, u32 value_size) override
+ {
+ m_data->resize(value_size);
+ if (value_size > 0)
+ memcpy(m_data->data(), value, value_size);
+ }
+
+private:
+ std::vector<u8>* m_data;
+};
+
+class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader<u32, u8>
+{
+public:
+ void Read(const u32& key, const u8* value, u32 value_size) override {}
+};
+
+bool 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<u8> disk_data;
+ LinearDiskCache<u32, u8> disk_cache;
+ PipelineCacheReadCallback read_callback(&disk_data);
+ if (disk_cache.OpenAndRead(m_pipeline_cache_filename, read_callback) != 1)
+ disk_data.clear();
+
+ if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
+ {
+ // Don't use this data. In fact, we should delete it to prevent it from being used next time.
+ File::Delete(m_pipeline_cache_filename);
+ return CreatePipelineCache();
+ }
+
+ VkPipelineCacheCreateInfo info = {
+ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineCacheCreateFlags flags
+ disk_data.size(), // size_t initialDataSize
+ disk_data.data() // const void* pInitialData
+ };
+
+ VkResult res =
+ vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
+ if (res == VK_SUCCESS)
+ return true;
+
+ // Failed to create pipeline cache, try with it empty.
+ LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed, trying empty cache: ");
+ return CreatePipelineCache();
+}
+
+// Based on Vulkan 1.0 specification,
+// Table 9.1. Layout for pipeline cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE
+// NOTE: This data is assumed to be in little-endian format.
+#pragma pack(push, 4)
+struct VK_PIPELINE_CACHE_HEADER
+{
+ u32 header_length;
+ u32 header_version;
+ u32 vendor_id;
+ u32 device_id;
+ u8 uuid[VK_UUID_SIZE];
+};
+#pragma pack(pop)
+// TODO: Remove the #if here when GCC 5 is a minimum build requirement.
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
+static_assert(std::has_trivial_copy_constructor<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#else
+static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#endif
+
+bool 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<u8> data(data_size);
+ res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size,
+ data.data());
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
+ return;
+ }
+
+ // Delete the old cache and re-create.
+ File::Delete(m_pipeline_cache_filename);
+
+ // We write a single key of 1, with the entire pipeline cache data.
+ // Not ideal, but our disk cache class does not support just writing a single blob
+ // of data without specifying a key.
+ LinearDiskCache<u32, u8> disk_cache;
+ PipelineCacheReadIgnoreCallback callback;
+ disk_cache.OpenAndRead(m_pipeline_cache_filename, callback);
+ disk_cache.Append(1, data.data(), static_cast<u32>(data.size()));
+ disk_cache.Close();
+}
+
+// Cache inserter that is called back when reading from the file
+template <typename Uid>
+struct ShaderCacheReader : public LinearDiskCacheReader<Uid, u32>
+{
+ ShaderCacheReader(std::map<Uid, VkShaderModule>& 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<Uid, VkShaderModule>& m_shader_map;
+};
+
+void ShaderCache::LoadShaderCaches()
+{
+ ShaderCacheReader<VertexShaderUid> vs_reader(m_vs_cache.shader_map);
+ m_vs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "VS", true, true),
+ vs_reader);
+
+ ShaderCacheReader<PixelShaderUid> 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<GeometryShaderUid> 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<int>(m_ps_cache.shader_map.size()));
+ SETSTAT(stats.numPixelShadersAlive, static_cast<int>(m_ps_cache.shader_map.size()));
+ SETSTAT(stats.numVertexShadersCreated, static_cast<int>(m_vs_cache.shader_map.size()));
+ SETSTAT(stats.numVertexShadersAlive, static_cast<int>(m_vs_cache.shader_map.size()));
+}
+
+template <typename T>
+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<u32>(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<u32>(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<u32>(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<PipelineInfo>::value,
+ "PipelineInfo is trivially copyable");
+#elif !defined(_MSC_VER)
+static_assert(std::is_trivially_copyable<PipelineInfo>::value,
+ "PipelineInfo is trivially copyable");
+#endif
+
+std::size_t PipelineInfoHash::operator()(const PipelineInfo& key) const
+{
+ return static_cast<std::size_t>(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<std::size_t>(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);
+}
+}