// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/container/unordered_map.h" #include #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/surface.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { namespace { using VideoCore::Surface::PixelFormat; using VideoCore::Surface::SurfaceType; constexpr SurfaceType GetSurfaceType(PixelFormat format) { switch (format) { // Depth formats case PixelFormat::D16_UNORM: case PixelFormat::D32_FLOAT: case PixelFormat::X8_D24_UNORM: return SurfaceType::Depth; // Stencil formats case PixelFormat::S8_UINT: return SurfaceType::Stencil; // Depth+Stencil formats case PixelFormat::D24_UNORM_S8_UINT: case PixelFormat::S8_UINT_D24_UNORM: case PixelFormat::D32_FLOAT_S8_UINT: return SurfaceType::DepthStencil; // Everything else is a color texture default: return SurfaceType::ColorTexture; } } VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format, VkSampleCountFlagBits samples, VkAttachmentLoadOp load_op, VkAttachmentStoreOp store_op) { using MaxwellToVK::SurfaceFormat; const SurfaceType surface_type = GetSurfaceType(format); const bool has_stencil = surface_type == SurfaceType::DepthStencil || surface_type == SurfaceType::Stencil; return { .flags = {}, .format = SurfaceFormat(device, FormatType::Optimal, true, format).format, .samples = samples, .loadOp = load_op, .storeOp = store_op, .stencilLoadOp = has_stencil ? load_op : VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = has_stencil ? store_op : VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_GENERAL, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }; } struct ResolveAspects { bool depth; bool stencil; }; struct ResolveModes { VkResolveModeFlagBits depth; VkResolveModeFlagBits stencil; }; constexpr ResolveAspects GetResolveAspects(PixelFormat format) { const SurfaceType surface_type = GetSurfaceType(format); return ResolveAspects{ .depth = surface_type == SurfaceType::Depth || surface_type == SurfaceType::DepthStencil, .stencil = surface_type == SurfaceType::Stencil || surface_type == SurfaceType::DepthStencil, }; } ResolveModes PickResolveModes(const Device& device, PixelFormat format) { constexpr VkResolveModeFlagBits mode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; const ResolveAspects aspects = GetResolveAspects(format); const bool depth_mode_supported = (device.GetDepthResolveModes() & mode) != 0; const bool stencil_mode_supported = (device.GetStencilResolveModes() & mode) != 0; ResolveModes modes{ .depth = VK_RESOLVE_MODE_NONE, .stencil = VK_RESOLVE_MODE_NONE, }; if (aspects.depth && depth_mode_supported) { modes.depth = mode; } if (aspects.stencil && stencil_mode_supported) { modes.stencil = mode; } if (modes.depth == modes.stencil || device.SupportsIndependentResolveNone()) { return modes; } if (modes.depth != VK_RESOLVE_MODE_NONE && stencil_mode_supported) { modes.stencil = mode; } else if (modes.stencil != VK_RESOLVE_MODE_NONE && depth_mode_supported) { modes.depth = mode; } return modes; } } // Anonymous namespace bool SupportsDepthStencilResolve(const Device& device, PixelFormat depth_format) { if (depth_format == PixelFormat::Invalid || !device.IsKhrDepthStencilResolveSupported()) { return false; } const ResolveAspects aspects = GetResolveAspects(depth_format); if (!aspects.depth && !aspects.stencil) { return false; } const ResolveModes modes = PickResolveModes(device, depth_format); if ((aspects.depth && modes.depth == VK_RESOLVE_MODE_NONE) || (aspects.stencil && modes.stencil == VK_RESOLVE_MODE_NONE)) { return false; } return modes.depth == modes.stencil || device.SupportsIndependentResolveNone(); } RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {} VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { std::scoped_lock lock{mutex}; const auto [pair, is_new] = cache.try_emplace(key); if (!is_new) { return *pair->second; } static constexpr size_t MAX_ATTACHMENTS = 2 * std::tuple_size_v + 2; boost::container::static_vector descriptions; std::array references{}; u32 num_attachments{}; u32 num_colors{}; for (size_t index = 0; index < key.color_formats.size(); ++index) { const PixelFormat format{key.color_formats[index]}; const bool is_valid{format != PixelFormat::Invalid}; references[index] = VkAttachmentReference{ .attachment = is_valid ? num_colors : VK_ATTACHMENT_UNUSED, .layout = VK_IMAGE_LAYOUT_GENERAL, }; if (is_valid) { const VkAttachmentLoadOp load_op = (key.color_clear_mask & (1u << index)) != 0 ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; const VkAttachmentStoreOp store_op = (key.color_discard_mask & (1u << index)) != 0 ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; descriptions.push_back( AttachmentDescription(*device, format, key.samples, load_op, store_op)); num_attachments = static_cast(index + 1); ++num_colors; } } const bool has_depth{key.depth_format != PixelFormat::Invalid}; VkAttachmentReference depth_reference{}; if (key.depth_format != PixelFormat::Invalid) { depth_reference = VkAttachmentReference{ .attachment = num_colors, .layout = VK_IMAGE_LAYOUT_GENERAL, }; const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; const VkAttachmentStoreOp depth_store_op = key.depth_stencil_discard ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, depth_load_op, depth_store_op)); } std::array resolve_references{}; const bool do_resolve_color = key.resolve_color && key.samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0; if (do_resolve_color) { for (size_t index = 0; index < key.color_formats.size(); ++index) { const PixelFormat format{key.color_formats[index]}; const bool is_valid{format != PixelFormat::Invalid}; resolve_references[index] = VkAttachmentReference{ .attachment = is_valid ? static_cast(descriptions.size()) : VK_ATTACHMENT_UNUSED, .layout = VK_IMAGE_LAYOUT_GENERAL, }; if (is_valid) { VkAttachmentDescription resolve_desc = AttachmentDescription(*device, format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE); resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; descriptions.push_back(resolve_desc); } } } const bool do_resolve_depth_stencil = key.resolve_depth_stencil && has_depth && key.samples != VK_SAMPLE_COUNT_1_BIT && SupportsDepthStencilResolve(*device, key.depth_format); VkAttachmentReference depth_resolve_reference{}; if (do_resolve_depth_stencil) { depth_resolve_reference = VkAttachmentReference{ .attachment = static_cast(descriptions.size()), .layout = VK_IMAGE_LAYOUT_GENERAL, }; VkAttachmentDescription resolve_desc = AttachmentDescription(*device, key.depth_format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE); resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; descriptions.push_back(resolve_desc); } const VkSubpassDescription subpass{ .flags = 0, .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .inputAttachmentCount = 0, .pInputAttachments = nullptr, .colorAttachmentCount = num_attachments, .pColorAttachments = references.data(), .pResolveAttachments = do_resolve_color ? resolve_references.data() : nullptr, .pDepthStencilAttachment = has_depth ? &depth_reference : nullptr, .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, }; const VkSubpassDependency dependency{ .srcSubpass = 0, // Current subpass .dstSubpass = 0, // Same subpass (self-dependency) .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT }; if (device->IsKhrCreateRenderPass2Supported()) { boost::container::static_vector descriptions2; for (const VkAttachmentDescription& description : descriptions) { descriptions2.push_back(VkAttachmentDescription2{ .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2, .pNext = nullptr, .flags = description.flags, .format = description.format, .samples = description.samples, .loadOp = description.loadOp, .storeOp = description.storeOp, .stencilLoadOp = description.stencilLoadOp, .stencilStoreOp = description.stencilStoreOp, .initialLayout = description.initialLayout, .finalLayout = description.finalLayout, }); } const auto promote = [](const VkAttachmentReference& reference) { return VkAttachmentReference2{ .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2, .pNext = nullptr, .attachment = reference.attachment, .layout = reference.layout, .aspectMask = 0, }; }; std::array references2{}; std::array resolve_references2{}; for (size_t index = 0; index < references.size(); ++index) { references2[index] = promote(references[index]); resolve_references2[index] = promote(resolve_references[index]); } const VkAttachmentReference2 depth_reference2 = promote(depth_reference); const VkAttachmentReference2 depth_resolve_reference2 = promote(depth_resolve_reference); const ResolveModes resolve_modes = PickResolveModes(*device, key.depth_format); const VkSubpassDescriptionDepthStencilResolve depth_stencil_resolve{ .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE, .pNext = nullptr, .depthResolveMode = resolve_modes.depth, .stencilResolveMode = resolve_modes.stencil, .pDepthStencilResolveAttachment = &depth_resolve_reference2, }; const VkSubpassDescription2 subpass2{ .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2, .pNext = do_resolve_depth_stencil ? &depth_stencil_resolve : nullptr, .flags = 0, .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .viewMask = 0, .inputAttachmentCount = 0, .pInputAttachments = nullptr, .colorAttachmentCount = num_attachments, .pColorAttachments = references2.data(), .pResolveAttachments = do_resolve_color ? resolve_references2.data() : nullptr, .pDepthStencilAttachment = has_depth ? &depth_reference2 : nullptr, .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, }; const VkSubpassDependency2 dependency2{ .sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2, .pNext = nullptr, .srcSubpass = dependency.srcSubpass, .dstSubpass = dependency.dstSubpass, .srcStageMask = dependency.srcStageMask, .dstStageMask = dependency.dstStageMask, .srcAccessMask = dependency.srcAccessMask, .dstAccessMask = dependency.dstAccessMask, .dependencyFlags = dependency.dependencyFlags, .viewOffset = 0, }; pair->second = device->GetLogical().CreateRenderPass2({ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2, .pNext = nullptr, .flags = 0, .attachmentCount = static_cast(descriptions2.size()), .pAttachments = descriptions2.empty() ? nullptr : descriptions2.data(), .subpassCount = 1, .pSubpasses = &subpass2, .dependencyCount = 1, .pDependencies = &dependency2, .correlatedViewMaskCount = 0, .pCorrelatedViewMasks = nullptr, }); return *pair->second; } pair->second = device->GetLogical().CreateRenderPass({ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = nullptr, .flags = 0, .attachmentCount = static_cast(descriptions.size()), .pAttachments = descriptions.empty() ? nullptr : descriptions.data(), .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency, }); return *pair->second; } } // namespace Vulkan