// Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKPipeline.h" #include "VideoBackends/Vulkan/VKShader.h" #include #include #include #include #include "Common/FileUtil.h" #include "Common/LinearDiskCache.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/Constants.h" #include "VideoCommon/VideoCommon.h" namespace Vulkan { ObjectCache* g_object_cache = nullptr; ObjectCache::ObjectCache() = default; ObjectCache::~ObjectCache() { VKPipeline::DestroySharedLibraries(); DestroyPipelineCache(); DestroySamplers(); DestroyPipelineLayouts(); DestroyDescriptorSetLayouts(); DestroyRenderPassCache(); } bool ObjectCache::Initialize() { VKPipeline::ResetSharedPipelines(); VKPipeline::CreatePool(); VKShader::CreatePool(); VKTexture::CreatePool(); VKFramebuffer::CreatePool(); if (!CreateDescriptorSetLayouts()) return false; if (!CreatePipelineLayouts()) return false; if (!CreateStaticSamplers()) return false; if (!m_texture_upload_buffer.Init(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, TEXTURE_UPLOAD_BUFFER_SIZE, VK_HEAP_UPLOAD_CACHED)) { PanicAlert("Failed to create texture upload buffer"); return false; } if (g_ActiveConfig.bShaderCache) { if (!LoadPipelineCache()) return false; } else { if (!CreatePipelineCache()) return false; } return true; } void ObjectCache::Shutdown() { if (g_ActiveConfig.bShaderCache && m_pipeline_cache != VK_NULL_HANDLE) SavePipelineCache(); } void ObjectCache::ClearSamplerCache() { u32 i; for (i = 0; i < m_sampler_capacity; i++) { if (m_samplers[i].used && m_samplers[i].sampler != VK_NULL_HANDLE) vkDestroySampler(g_vulkan_context->GetDevice(), m_samplers[i].sampler, nullptr); m_samplers[i].used = 0; } m_num_samplers = 0; } static u32 SamplerHash(u64 key) { key ^= key >> 33; key *= 0xff51afd7ed558ccdULL; key ^= key >> 33; return static_cast(key); } /* Doubles the table (or makes the first), placing what it held again. */ bool ObjectCache::GrowSamplers() { const u32 capacity = m_sampler_capacity ? m_sampler_capacity * 2 : 256; SamplerSlot* const samplers = static_cast(calloc(capacity, sizeof(SamplerSlot))); u32 i; if (!samplers) return false; for (i = 0; i < m_sampler_capacity; i++) { u32 j; if (!m_samplers[i].used) continue; j = SamplerHash(m_samplers[i].key) & (capacity - 1); while (samplers[j].used) j = (j + 1) & (capacity - 1); samplers[j] = m_samplers[i]; } free(m_samplers); m_samplers = samplers; m_sampler_capacity = capacity; return true; } void ObjectCache::DestroySamplers() { ClearSamplerCache(); free(m_samplers); m_samplers = nullptr; m_sampler_capacity = 0; if (m_point_sampler != VK_NULL_HANDLE) { vkDestroySampler(g_vulkan_context->GetDevice(), m_point_sampler, nullptr); m_point_sampler = VK_NULL_HANDLE; } if (m_linear_sampler != VK_NULL_HANDLE) { vkDestroySampler(g_vulkan_context->GetDevice(), m_linear_sampler, nullptr); m_linear_sampler = VK_NULL_HANDLE; } } bool ObjectCache::CreateDescriptorSetLayouts() { // The geometry shader buffer must be last in this binding set, as we don't include it // if geometry shaders are not supported by the device. See the decrement below. static const std::array standard_ubo_bindings{{ {UBO_DESCRIPTOR_SET_BINDING_PS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {UBO_DESCRIPTOR_SET_BINDING_VS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}, {UBO_DESCRIPTOR_SET_BINDING_CUST, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}, {UBO_DESCRIPTOR_SET_BINDING_GS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_GEOMETRY_BIT}, }}; constexpr u32 MAX_PIXEL_SAMPLER_ARRAY_SIZE = 8; constexpr u32 TOTAL_PIXEL_SAMPLER_BINDINGS = 1 + (VideoCommon::MAX_PIXEL_SHADER_SAMPLERS - MAX_PIXEL_SAMPLER_ARRAY_SIZE); static_assert(VideoCommon::MAX_PIXEL_SHADER_SAMPLERS == 16, "Update descriptor sampler bindings"); static const std::array standard_sampler_bindings{{ {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_PIXEL_SAMPLER_ARRAY_SIZE, VK_SHADER_STAGE_FRAGMENT_BIT}, {8, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {9, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {10, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {11, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {12, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {13, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {14, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {15, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, }}; // The dynamic veretex loader's vertex buffer must be last here, for similar reasons static const std::array standard_ssbo_bindings{{ {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT}, }}; static const std::array utility_ubo_bindings{{ {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}, }}; // Utility samplers aren't dynamically indexed. static const std::array utility_sampler_bindings{{ {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {6, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {7, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {8, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, }}; static const std::array compute_set_bindings{{ {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {6, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {7, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {8, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {9, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {10, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {11, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {12, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {13, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {14, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {15, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {16, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {17, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {18, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, }}; std::array ubo_bindings = standard_ubo_bindings; std::array create_infos{{ {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(ubo_bindings.size()), ubo_bindings.data()}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(standard_sampler_bindings.size()), standard_sampler_bindings.data()}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(standard_ssbo_bindings.size()), standard_ssbo_bindings.data()}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(utility_ubo_bindings.size()), utility_ubo_bindings.data()}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(utility_sampler_bindings.size()), utility_sampler_bindings.data()}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(compute_set_bindings.size()), compute_set_bindings.data()}, }}; // Don't set the GS bit if geometry shaders aren't available. if (g_ActiveConfig.UseVSForLinePointExpand()) { if (g_backend_info.bSupportsGeometryShaders) ubo_bindings[UBO_DESCRIPTOR_SET_BINDING_GS].stageFlags |= VK_SHADER_STAGE_VERTEX_BIT; else ubo_bindings[UBO_DESCRIPTOR_SET_BINDING_GS].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; } else if (!g_backend_info.bSupportsGeometryShaders) { create_infos[DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS].bindingCount--; } // Remove the dynamic vertex loader's buffer if it'll never be needed if (!g_backend_info.bSupportsDynamicVertexLoader) create_infos[DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS].bindingCount--; for (size_t i = 0; i < create_infos.size(); i++) { VkResult res = vkCreateDescriptorSetLayout(g_vulkan_context->GetDevice(), &create_infos[i], nullptr, &m_descriptor_set_layouts[i]); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout failed: "); return false; } } return true; } void ObjectCache::DestroyDescriptorSetLayouts() { for (VkDescriptorSetLayout layout : m_descriptor_set_layouts) { if (layout != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(g_vulkan_context->GetDevice(), layout, nullptr); } } bool ObjectCache::CreatePipelineLayouts() { // Descriptor sets for each pipeline layout. // In the standard set, the SSBO must be the last descriptor, as we do not include it // when fragment stores and atomics are not supported by the device. const std::array standard_sets{ m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SAMPLERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS], }; const std::array uber_sets{ m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SAMPLERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS], }; const std::array utility_sets{ m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UTILITY_UNIFORM_BUFFER], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UTILITY_SAMPLERS], }; const std::array compute_sets{ m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_COMPUTE], }; // Info for each pipeline layout std::array pipeline_layout_info{{ // Standard {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(standard_sets.size()), standard_sets.data(), 0, nullptr}, // Uber {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(uber_sets.size()), uber_sets.data(), 0, nullptr}, // Utility {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(utility_sets.size()), utility_sets.data(), 0, nullptr}, // Compute {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast(compute_sets.size()), compute_sets.data(), 0, nullptr}, }}; const bool ssbos_in_standard = g_backend_info.bSupportsBBox || g_ActiveConfig.UseVSForLinePointExpand(); // If bounding box is unsupported, don't bother with the SSBO descriptor set. if (!ssbos_in_standard) pipeline_layout_info[PIPELINE_LAYOUT_STANDARD].setLayoutCount--; // If neither SSBO-using feature is supported, skip in ubershaders too if (!ssbos_in_standard && !g_backend_info.bSupportsDynamicVertexLoader) pipeline_layout_info[PIPELINE_LAYOUT_UBER].setLayoutCount--; for (size_t i = 0; i < pipeline_layout_info.size(); i++) { VkResult res; if ((res = vkCreatePipelineLayout(g_vulkan_context->GetDevice(), &pipeline_layout_info[i], nullptr, &m_pipeline_layouts[i])) != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout failed: "); return false; } } return true; } void ObjectCache::DestroyPipelineLayouts() { for (VkPipelineLayout layout : m_pipeline_layouts) { if (layout != VK_NULL_HANDLE) vkDestroyPipelineLayout(g_vulkan_context->GetDevice(), layout, nullptr); } } bool ObjectCache::CreateStaticSamplers() { VkSamplerCreateInfo create_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkSamplerCreateFlags flags VK_FILTER_NEAREST, // VkFilter magFilter VK_FILTER_NEAREST, // VkFilter minFilter VK_SAMPLER_MIPMAP_MODE_NEAREST, // VkSamplerMipmapMode mipmapMode VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // VkSamplerAddressMode addressModeU VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // VkSamplerAddressMode addressModeV VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeW 0.0f, // float mipLodBias VK_FALSE, // VkBool32 anisotropyEnable 1.0f, // float maxAnisotropy VK_FALSE, // VkBool32 compareEnable VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp std::numeric_limits::min(), // float minLod std::numeric_limits::max(), // float maxLod VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, // VkBorderColor borderColor VK_FALSE // VkBool32 unnormalizedCoordinates }; VkResult res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &m_point_sampler); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); return false; } // Most fields are shared across point<->linear samplers, so only change those necessary. create_info.minFilter = VK_FILTER_LINEAR; create_info.magFilter = VK_FILTER_LINEAR; create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &m_linear_sampler); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); return false; } return true; } VkSampler ObjectCache::GetSampler(const SamplerState& info) { const u64 key = info.Hex(); u32 slot = 0; if (m_sampler_capacity) { slot = SamplerHash(key) & (m_sampler_capacity - 1); while (m_samplers[slot].used) { if (m_samplers[slot].key == key) return m_samplers[slot].sampler; slot = (slot + 1) & (m_sampler_capacity - 1); } } static constexpr std::array filters = {{VK_FILTER_NEAREST, VK_FILTER_LINEAR}}; static constexpr std::array mipmap_modes = { {VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_SAMPLER_MIPMAP_MODE_LINEAR}}; static constexpr std::array address_modes = { {VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT}}; VkSamplerCreateInfo create_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkSamplerCreateFlags flags filters[u32(info.tm0.mag_filter.Value())], // VkFilter magFilter filters[u32(info.tm0.min_filter.Value())], // VkFilter minFilter mipmap_modes[u32(info.tm0.mipmap_filter.Value())], // VkSamplerMipmapMode mipmapMode address_modes[u32(info.tm0.wrap_u.Value())], // VkSamplerAddressMode addressModeU address_modes[u32(info.tm0.wrap_v.Value())], // VkSamplerAddressMode addressModeV VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeW info.tm0.lod_bias / 256.0f, // float mipLodBias VK_FALSE, // VkBool32 anisotropyEnable 0.0f, // float maxAnisotropy VK_FALSE, // VkBool32 compareEnable VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp info.tm1.min_lod / 16.0f, // float minLod info.tm1.max_lod / 16.0f, // float maxLod VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, // VkBorderColor borderColor VK_FALSE // VkBool32 unnormalizedCoordinates }; // Can we use anisotropic filtering with this sampler? if (info.tm0.anisotropic_filtering != 0 && g_vulkan_context->SupportsAnisotropicFiltering()) { // Cap anisotropy to device limits. create_info.anisotropyEnable = VK_TRUE; create_info.maxAnisotropy = std::min(static_cast(1 << info.tm0.anisotropic_filtering), g_vulkan_context->GetMaxSamplerAnisotropy()); } VkSampler sampler = VK_NULL_HANDLE; VkResult res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &sampler); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); // Store it even if it failed if ((m_num_samplers + 1) * 2 > m_sampler_capacity) { if (!GrowSamplers()) return sampler; /* not kept: made again the next time */ slot = SamplerHash(key) & (m_sampler_capacity - 1); while (m_samplers[slot].used) slot = (slot + 1) & (m_sampler_capacity - 1); } m_samplers[slot].key = key; m_samplers[slot].sampler = sampler; m_samplers[slot].used = 1; m_num_samplers++; return sampler; } VkRenderPass ObjectCache::GetRenderPass(VkFormat color_format, VkFormat depth_format, u32 multisamples, VkAttachmentLoadOp load_op) { u32 i; for (i = 0; i < m_num_render_passes; i++) { const RenderPassSlot* const slot = &m_render_passes[i]; if (slot->color_format == color_format && slot->depth_format == depth_format && slot->multisamples == multisamples && slot->load_op == load_op) { return slot->pass; } } if (m_num_render_passes == MAX_RENDER_PASSES) { ERROR_LOG(VIDEO, "Too many Vulkan render passes"); return VK_NULL_HANDLE; } /* the colour attachment, then depth: as the framebuffer lists their views */ VkAttachmentDescription attachments[2]; VkAttachmentReference color_reference; VkAttachmentReference depth_reference; u32 num_attachments = 0; const bool has_color = color_format != VK_FORMAT_UNDEFINED; const bool has_depth = depth_format != VK_FORMAT_UNDEFINED; if (has_color) { color_reference.attachment = num_attachments; color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments[num_attachments++] = { 0, color_format, static_cast(multisamples), load_op, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; } if (has_depth) { depth_reference.attachment = num_attachments; depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[num_attachments++] = { 0, depth_format, static_cast(multisamples), load_op, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; } VkSubpassDescription subpass = {0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, has_color ? 1u : 0u, has_color ? &color_reference : nullptr, nullptr, has_depth ? &depth_reference : nullptr, 0, nullptr}; VkRenderPassCreateInfo pass_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, 0, num_attachments, attachments, 1, &subpass, 0, nullptr}; VkRenderPass pass; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: "); return VK_NULL_HANDLE; } RenderPassSlot* const slot = &m_render_passes[m_num_render_passes++]; slot->color_format = color_format; slot->depth_format = depth_format; slot->multisamples = multisamples; slot->load_op = load_op; slot->pass = pass; return pass; } void ObjectCache::DestroyRenderPassCache() { u32 i; for (i = 0; i < m_num_render_passes; i++) vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_render_passes[i].pass, nullptr); m_num_render_passes = 0; } 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() { // The cache is made from the stored blob where it lies in the mapped file. struct Reader final : Common::LinearDiskCacheReader { explicit Reader(ObjectCache* cache_) : cache(cache_) {} void Read(const u32& key, const u8* value, u32 value_size) override { if (++count != 1 || !value_size) return; if (!cache->ValidatePipelineCache(value, value_size)) { invalid = true; return; } VkPipelineCacheCreateInfo info = { VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineCacheCreateFlags flags value_size, // size_t initialDataSize value // const void* pInitialData }; result = vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &cache->m_pipeline_cache); } ObjectCache* cache; VkResult result = VK_INCOMPLETE; u32 count = 0; bool invalid = false; }; // 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); Reader reader(this); { Common::LinearDiskCache disk_cache; disk_cache.OpenAndRead(m_pipeline_cache_filename, reader); } if (reader.invalid) { // 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(); } if (reader.count == 1 && reader.result == VK_SUCCESS) return true; if (reader.count > 1 && m_pipeline_cache != VK_NULL_HANDLE) { vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr); m_pipeline_cache = VK_NULL_HANDLE; } if (reader.result != VK_INCOMPLETE && reader.result != VK_SUCCESS) LOG_VULKAN_ERROR(reader.result, "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) static_assert(std::is_trivially_copyable::value, "VK_PIPELINE_CACHE_HEADER must be trivially copyable"); 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->GetDeviceInfo().vendorID) { ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect vendor ID (file: %#X, device: %#X)", header.vendor_id, g_vulkan_context->GetDeviceInfo().vendorID); return false; } if (header.device_id != g_vulkan_context->GetDeviceInfo().deviceID) { ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect device ID (file: %#X, device: %#X)", header.device_id, g_vulkan_context->GetDeviceInfo().deviceID); return false; } if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceInfo().pipelineCacheUUID, VK_UUID_SIZE) != 0) { ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect UUID"); return false; } return true; } void ObjectCache::DestroyPipelineCache() { 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; } // A single key of 1 with the whole pipeline cache, which the driver writes where the store // keeps it: written only when it changed. using Store = Common::LinearDiskCache; u8* const store = static_cast(std::malloc(Store::SingleSize(static_cast(data_size)))); if (!store) return; res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, Store::SingleValue(store)); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: "); std::free(store); return; } Store::WriteSingle(m_pipeline_cache_filename, 1, store, static_cast(data_size)); std::free(store); } void ObjectCache::ReloadPipelineCache() { SavePipelineCache(); if (g_ActiveConfig.bShaderCache) LoadPipelineCache(); else CreatePipelineCache(); } } // namespace Vulkan