From 950803c2b7cc3e5c5bf02465749a37d7c5f5fc82 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sun, 19 Apr 2026 09:40:23 -0700 Subject: [PATCH] renderer_vulkan: Implement multisample pipeline/renderpass support Allows multi-sample render passes and graphics pipelines to be created, using sample-rate shading rather than coverage-based MSAA. --- .../renderer_vulkan/vk_graphics_pipeline.cpp | 8 +- .../renderer_vulkan/vk_graphics_pipeline.h | 3 +- .../renderer_vulkan/vk_render_manager.cpp | 175 ++++++++++++++++-- .../renderer_vulkan/vk_render_manager.h | 21 ++- .../renderer_vulkan/vk_shader_disk_cache.h | 4 +- 5 files changed, 182 insertions(+), 29 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 7c078802d..586a7f500 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -163,8 +163,9 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) { }; const vk::PipelineMultisampleStateCreateInfo multisampling = { - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = false, + .rasterizationSamples = vk::SampleCountFlagBits(info.state.attachments.sample_count), + .sampleShadingEnable = true, + .minSampleShading = 1.0f, }; const vk::PipelineColorBlendAttachmentState colorblend_attachment = { @@ -275,7 +276,8 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) { .pDynamicState = &dynamic_info, .layout = pipeline_layout, .renderPass = renderpass_cache.GetRenderpass(info.state.attachments.color, - info.state.attachments.depth, false), + info.state.attachments.depth, false, + info.state.attachments.sample_count), }; if (fail_on_compile_required) { diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 5f817094e..8ab0933c2 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -214,6 +214,7 @@ static_assert(std::is_trivial_v); struct AttachmentInfo { VideoCore::PixelFormat color; VideoCore::PixelFormat depth; + u8 sample_count; static consteval u64 StructHash() { constexpr u64 STRUCT_VERSION = 0; @@ -225,7 +226,7 @@ struct AttachmentInfo { LAYOUT_HASH, // fields - FIELD_HASH(color), FIELD_HASH(depth)); + FIELD_HASH(color), FIELD_HASH(depth), FIELD_HASH(sample_count)); } }; static_assert(std::is_trivial_v); diff --git a/src/video_core/renderer_vulkan/vk_render_manager.cpp b/src/video_core/renderer_vulkan/vk_render_manager.cpp index b3615e1ae..73885f497 100644 --- a/src/video_core/renderer_vulkan/vk_render_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_render_manager.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include #include "common/assert.h" #include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -37,7 +39,7 @@ void RenderManager::BeginRendering(const Framebuffer* framebuffer, .framebuffer = framebuffer->Handle(), .render_pass = framebuffer->RenderPass(), .render_area = render_area, - .clear = {}, + .clears = {}, .do_clear = false, }; images = framebuffer->Images(); @@ -58,8 +60,8 @@ void RenderManager::BeginRendering(const RenderPass& new_pass) { .renderPass = info.render_pass, .framebuffer = info.framebuffer, .renderArea = info.render_area, - .clearValueCount = info.do_clear ? 1u : 0u, - .pClearValues = &info.clear, + .clearValueCount = info.do_clear ? 2u : 0u, + .pClearValues = info.clears.data(), }; cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline); }); @@ -77,7 +79,7 @@ void RenderManager::EndRendering() { u32 num_barriers = 0; vk::PipelineStageFlags pipeline_flags{}; vk::AccessFlags src_access_flags{}; - std::array barriers; + std::array barriers; for (u32 i = 0; i < images.size(); i++) { if (!images[i]) { continue; @@ -138,7 +140,8 @@ void RenderManager::EndRendering() { } vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color, - VideoCore::PixelFormat depth, bool is_clear) { + VideoCore::PixelFormat depth, bool is_clear, + u8 sample_count) { std::scoped_lock lock{cache_mutex}; const u32 color_index = @@ -151,13 +154,23 @@ vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color, ASSERT_MSG(color_index <= NumColorFormats && depth_index <= NumDepthFormats, "Invalid color index {} and/or depth_index {}", color_index, depth_index); - vk::UniqueRenderPass& renderpass = cached_renderpasses[color_index][depth_index][is_clear]; + ASSERT_MSG(sample_count && std::has_single_bit(sample_count) && sample_count <= MaxSamples, + "Invalid sample count {}", static_cast(sample_count)); + + const u32 samples_index = static_cast(std::bit_width(sample_count) - 1); + + vk::UniqueRenderPass& renderpass = + cached_renderpasses[color_index][depth_index][samples_index][is_clear]; if (!renderpass) { const vk::Format color_format = instance.GetTraits(color).native; const vk::Format depth_format = instance.GetTraits(depth).native; const vk::AttachmentLoadOp load_op = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; - renderpass = CreateRenderPass(color_format, depth_format, load_op); + + renderpass = (sample_count > 1) + ? CreateRenderPassMSAA(color_format, depth_format, load_op, + static_cast(sample_count)) + : CreateRenderPass(color_format, depth_format, load_op); } return *renderpass; @@ -165,27 +178,27 @@ vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color, vk::UniqueRenderPass RenderManager::CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op) const { - u32 attachment_count = 0; - std::array attachments; + boost::container::static_vector attachments{}; bool use_color = false; vk::AttachmentReference color_attachment_ref{}; bool use_depth = false; vk::AttachmentReference depth_attachment_ref{}; if (color != vk::Format::eUndefined) { - attachments[attachment_count] = vk::AttachmentDescription{ + attachments.emplace_back(vk::AttachmentDescription{ .format = color, + .samples = vk::SampleCountFlagBits::e1, .loadOp = load_op, .storeOp = vk::AttachmentStoreOp::eStore, .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, .initialLayout = vk::ImageLayout::eGeneral, .finalLayout = vk::ImageLayout::eGeneral, - }; + }); color_attachment_ref = vk::AttachmentReference{ - .attachment = attachment_count++, + .attachment = static_cast(attachments.size() - 1), .layout = vk::ImageLayout::eGeneral, }; @@ -193,18 +206,19 @@ vk::UniqueRenderPass RenderManager::CreateRenderPass(vk::Format color, vk::Forma } if (depth != vk::Format::eUndefined) { - attachments[attachment_count] = vk::AttachmentDescription{ + attachments.emplace_back(vk::AttachmentDescription{ .format = depth, + .samples = vk::SampleCountFlagBits::e1, .loadOp = load_op, .storeOp = vk::AttachmentStoreOp::eStore, .stencilLoadOp = load_op, .stencilStoreOp = vk::AttachmentStoreOp::eStore, .initialLayout = vk::ImageLayout::eGeneral, .finalLayout = vk::ImageLayout::eGeneral, - }; + }); depth_attachment_ref = vk::AttachmentReference{ - .attachment = attachment_count++, + .attachment = static_cast(attachments.size() - 1), .layout = vk::ImageLayout::eGeneral, }; @@ -217,12 +231,11 @@ vk::UniqueRenderPass RenderManager::CreateRenderPass(vk::Format color, vk::Forma .pInputAttachments = nullptr, .colorAttachmentCount = use_color ? 1u : 0u, .pColorAttachments = &color_attachment_ref, - .pResolveAttachments = 0, .pDepthStencilAttachment = use_depth ? &depth_attachment_ref : nullptr, }; const vk::RenderPassCreateInfo renderpass_info = { - .attachmentCount = attachment_count, + .attachmentCount = static_cast(attachments.size()), .pAttachments = attachments.data(), .subpassCount = 1, .pSubpasses = &subpass, @@ -233,4 +246,132 @@ vk::UniqueRenderPass RenderManager::CreateRenderPass(vk::Format color, vk::Forma return instance.GetDevice().createRenderPassUnique(renderpass_info); } +vk::UniqueRenderPass RenderManager::CreateRenderPassMSAA( + vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op, + vk::SampleCountFlagBits sample_count) const { + boost::container::static_vector attachments{}; + + vk::AttachmentReference2 color_resolve_attachment = {.attachment = VK_ATTACHMENT_UNUSED}; + vk::AttachmentReference2 depth_resolve_attachment = {.attachment = VK_ATTACHMENT_UNUSED}; + + bool use_color = false; + vk::AttachmentReference2 color_attachment_ref{}; + bool use_depth = false; + vk::AttachmentReference2 depth_attachment_ref{}; + + if (color != vk::Format::eUndefined) { + attachments.emplace_back(vk::AttachmentDescription2{ + .format = color, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eGeneral, + .finalLayout = vk::ImageLayout::eGeneral, + }); + + color_attachment_ref = vk::AttachmentReference2{ + .attachment = static_cast(attachments.size() - 1), + .layout = vk::ImageLayout::eGeneral, + .aspectMask = vk::ImageAspectFlagBits::eColor, + }; + + use_color = true; + } + + if (depth != vk::Format::eUndefined) { + attachments.emplace_back(vk::AttachmentDescription2{ + .format = depth, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = load_op, + .stencilStoreOp = vk::AttachmentStoreOp::eStore, + .initialLayout = vk::ImageLayout::eGeneral, + .finalLayout = vk::ImageLayout::eGeneral, + }); + + depth_attachment_ref = vk::AttachmentReference2{ + .attachment = static_cast(attachments.size() - 1), + .layout = vk::ImageLayout::eGeneral, + .aspectMask = vk::ImageAspectFlagBits::eDepth, + }; + + use_depth = true; + } + + // In the case of MSAA, each attachment gets an additional MSAA attachment that now becomes the + // main attachment and the original attachments now get resolved into + if (sample_count > vk::SampleCountFlagBits::e1) { + if (color != vk::Format::eUndefined) { + attachments.emplace_back(vk::AttachmentDescription2{ + .format = color, + .samples = sample_count, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eGeneral, + .finalLayout = vk::ImageLayout::eGeneral, + }); + + color_resolve_attachment = color_attachment_ref; + + color_attachment_ref = vk::AttachmentReference2{ + .attachment = static_cast(attachments.size() - 1), + .layout = vk::ImageLayout::eGeneral, + }; + } + + if (depth != vk::Format::eUndefined) { + attachments.emplace_back(vk::AttachmentDescription2{ + .format = depth, + .samples = sample_count, + .loadOp = load_op, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = load_op, + .stencilStoreOp = vk::AttachmentStoreOp::eStore, + .initialLayout = vk::ImageLayout::eGeneral, + .finalLayout = vk::ImageLayout::eGeneral, + }); + + depth_resolve_attachment = depth_attachment_ref; + + depth_attachment_ref = vk::AttachmentReference2{ + .attachment = static_cast(attachments.size() - 1), + .layout = vk::ImageLayout::eGeneral, + }; + } + } + + const vk::StructureChain + subpass = { + vk::SubpassDescription2{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = use_color ? 1u : 0u, + .pColorAttachments = &color_attachment_ref, + .pResolveAttachments = &color_resolve_attachment, + .pDepthStencilAttachment = use_depth ? &depth_attachment_ref : nullptr, + }, + vk::SubpassDescriptionDepthStencilResolve{ + .depthResolveMode = vk::ResolveModeFlagBits::eSampleZero, + .stencilResolveMode = vk::ResolveModeFlagBits::eSampleZero, + .pDepthStencilResolveAttachment = &depth_resolve_attachment}, + }; + + const vk::RenderPassCreateInfo2 renderpass_info = { + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass.get(), + .dependencyCount = 0, + .pDependencies = nullptr, + }; + + return instance.GetDevice().createRenderPass2Unique(renderpass_info); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_render_manager.h b/src/video_core/renderer_vulkan/vk_render_manager.h index 3ebbd817b..0b5fdf606 100644 --- a/src/video_core/renderer_vulkan/vk_render_manager.h +++ b/src/video_core/renderer_vulkan/vk_render_manager.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "common/math_util.h" @@ -23,20 +24,22 @@ struct RenderPass { vk::Framebuffer framebuffer; vk::RenderPass render_pass; vk::Rect2D render_area; - vk::ClearValue clear; + std::array clears; u32 do_clear; bool operator==(const RenderPass& other) const noexcept { return std::tie(framebuffer, render_pass, render_area, do_clear) == std::tie(other.framebuffer, other.render_pass, other.render_area, other.do_clear) && - std::memcmp(&clear, &other.clear, sizeof(vk::ClearValue)) == 0; + std::memcmp(&clears, &other.clears, sizeof(clears)) == 0; } }; class RenderManager { static constexpr u32 NumColorFormats = static_cast(VideoCore::PixelFormat::NumColorFormat); static constexpr u32 NumDepthFormats = static_cast(VideoCore::PixelFormat::NumDepthFormat); + static constexpr size_t MaxSamples = 8; + static_assert(std::has_single_bit(MaxSamples)); public: explicit RenderManager(const Instance& instance, Scheduler& scheduler); @@ -53,20 +56,26 @@ public: /// Returns the renderpass associated with the color-depth format pair vk::RenderPass GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, - bool is_clear); + bool is_clear, u8 sample_count = 1); private: /// Creates a renderpass configured appropriately and stores it in cached_renderpasses vk::UniqueRenderPass CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op) const; + /// Creates an MSAA renderpass configured appropriately and stores it in cached_renderpasses + vk::UniqueRenderPass CreateRenderPassMSAA(vk::Format color, vk::Format depth, + vk::AttachmentLoadOp load_op, + vk::SampleCountFlagBits sample_count) const; + private: const Instance& instance; Scheduler& scheduler; - vk::UniqueRenderPass cached_renderpasses[NumColorFormats + 1][NumDepthFormats + 1][2]; + vk::UniqueRenderPass cached_renderpasses[NumColorFormats + 1][NumDepthFormats + 1] + [std::bit_width(MaxSamples)][2]; std::mutex cache_mutex; - std::array images; - std::array aspects; + std::array images; + std::array aspects; bool shadow_rendering{}; RenderPass pass{}; u32 num_draws{}; diff --git a/src/video_core/renderer_vulkan/vk_shader_disk_cache.h b/src/video_core/renderer_vulkan/vk_shader_disk_cache.h index e05d43d09..51152cd59 100644 --- a/src/video_core/renderer_vulkan/vk_shader_disk_cache.h +++ b/src/video_core/renderer_vulkan/vk_shader_disk_cache.h @@ -142,13 +142,13 @@ private: static_assert(sizeof(GSConfigEntry) == 48); struct PLConfigEntry { - static constexpr u8 EXPECTED_VERSION = 0; + static constexpr u8 EXPECTED_VERSION = 1; u64 version; // Surprise tool that can help us later StaticPipelineInfo pl_info; }; - static_assert(sizeof(PLConfigEntry) == 152); + static_assert(sizeof(PLConfigEntry) == 160); class CacheFile; class CacheEntry {