From 06af304c8dd670eafa0618de0b5683b9ad1745ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 27 Nov 2022 11:39:44 +0100 Subject: [PATCH] Multisampling groundwork --- Common/GPU/Vulkan/VulkanFramebuffer.cpp | 133 ++++++++++++++++------ Common/GPU/Vulkan/VulkanFramebuffer.h | 20 +++- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 10 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 12 +- Common/GPU/Vulkan/VulkanRenderManager.h | 2 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 11 +- Common/GPU/thin3d.h | 1 + Core/Config.cpp | 1 + Core/Config.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 10 +- GPU/Common/PresentationCommon.cpp | 4 +- GPU/Common/TextureCacheCommon.cpp | 1 + GPU/Vulkan/PipelineManagerVulkan.cpp | 9 +- 13 files changed, 157 insertions(+), 58 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp index 8f277cf676..0de8f76a3c 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.cpp +++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp @@ -2,14 +2,41 @@ #include "Common/GPU/Vulkan/VulkanFramebuffer.h" #include "Common/GPU/Vulkan/VulkanQueueRunner.h" -VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag) +VkSampleCountFlagBits SampleCountToFlagBits(int count) { + // TODO: Check hardware support here, or elsewhere? + // Some hardware only supports 4x. + switch (count) { + case 1: return VK_SAMPLE_COUNT_1_BIT; + case 2: return VK_SAMPLE_COUNT_2_BIT; + case 4: return VK_SAMPLE_COUNT_4_BIT; + case 8: return VK_SAMPLE_COUNT_8_BIT; + case 16: return VK_SAMPLE_COUNT_16_BIT; // rare + default: + _assert_(false); + return VK_SAMPLE_COUNT_1_BIT; + } +} + +VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, int _numSamples, bool createDepthStencilBuffer, const char *tag) : vulkan_(vk), tag_(tag), width(_width), height(_height), numLayers(_numLayers) { _dbg_assert_(tag); - CreateImage(vulkan_, initCmd, color, width, height, numLayers, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); + CreateImage(vulkan_, initCmd, color, width, height, numLayers, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); if (createDepthStencilBuffer) { - CreateImage(vulkan_, initCmd, depth, width, height, numLayers, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); + CreateImage(vulkan_, initCmd, depth, width, height, numLayers, VK_SAMPLE_COUNT_1_BIT, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); + } + + if (_numSamples > 1) { + sampleCount = SampleCountToFlagBits(_numSamples); + + // TODO: Create a different tag for these? + CreateImage(vulkan_, initCmd, msaaColor, width, height, numLayers, sampleCount, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); + if (createDepthStencilBuffer) { + CreateImage(vulkan_, initCmd, depth, width, height, numLayers, sampleCount, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); + } + } else { + sampleCount = VK_SAMPLE_COUNT_1_BIT; } UpdateTag(tag); @@ -55,7 +82,7 @@ VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPas } views[1] = depth.rtView; } - fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType); + fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType, sampleCount); fbci.attachmentCount = hasDepth ? 2 : 1; fbci.pAttachments = views; fbci.width = width; @@ -108,7 +135,7 @@ VKRFramebuffer::~VKRFramebuffer() { // NOTE: If numLayers > 1, it will create an array texture, rather than a normal 2D texture. // This requires a different sampling path! -void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) { +void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkSampleCountFlagBits sampleCount, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) { // We don't support more exotic layer setups for now. Mono or stereo. _dbg_assert_(numLayers == 1 || numLayers == 2); @@ -120,7 +147,7 @@ void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKR ici.extent.depth = 1; ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; ici.imageType = VK_IMAGE_TYPE_2D; - ici.samples = VK_SAMPLE_COUNT_1_BIT; + ici.samples = sampleCount; ici.tiling = VK_IMAGE_TILING_OPTIMAL; ici.format = format; // Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers. @@ -226,45 +253,76 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) { // Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827 // Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies -VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) { +VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) { bool selfDependency = RenderPassTypeHasInput(rpType); bool isBackbuffer = rpType == RenderPassType::BACKBUFFER; bool hasDepth = RenderPassTypeHasDepth(rpType); bool multiview = RenderPassTypeHasMultiView(rpType); + bool multisample = rpType & RenderPassType::MULTISAMPLE; if (multiview) { // TODO: Assert that the device has multiview support enabled. } - VkAttachmentDescription attachments[2] = {}; - attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; - attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction); - attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction); - attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[0].flags = 0; + int colorAttachmentIndex = 0; + int depthAttachmentIndex = 1; + + int attachmentCount = 0; + VkAttachmentDescription attachments[4]{}; + attachments[attachmentCount].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; + attachments[attachmentCount].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[attachmentCount].loadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.colorLoadAction); + attachments[attachmentCount].storeOp = ConvertStoreAction(key.colorStoreAction); + attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[attachmentCount].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachmentCount++; if (hasDepth) { - attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat; - attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction); - attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction); - attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction); - attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); - attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - attachments[1].flags = 0; + attachments[attachmentCount].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat; + attachments[attachmentCount].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[attachmentCount].loadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.depthLoadAction); + attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction); + attachments[attachmentCount].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction); + attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); + attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachmentCount++; + } + + if (multisample) { + colorAttachmentIndex = attachmentCount; + attachments[attachmentCount].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; + attachments[attachmentCount].samples = sampleCount; + attachments[attachmentCount].loadOp = ConvertLoadAction(key.colorLoadAction); + attachments[attachmentCount].storeOp = ConvertStoreAction(key.colorStoreAction); + attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachmentCount++; + + if (hasDepth) { + depthAttachmentIndex = attachmentCount; + attachments[attachmentCount].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat; + attachments[attachmentCount].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[attachmentCount].loadOp = ConvertLoadAction(key.depthLoadAction); + attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction); + attachments[attachmentCount].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction); + attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); + attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachmentCount++; + } } VkAttachmentReference color_reference{}; - color_reference.attachment = 0; + color_reference.attachment = colorAttachmentIndex; color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_reference{}; - depth_reference.attachment = 1; + depth_reference.attachment = depthAttachmentIndex; depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass{}; @@ -279,7 +337,14 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas } subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_reference; - subpass.pResolveAttachments = nullptr; + + VkAttachmentReference resolve_references[2]; + if (multisample) { + resolve_references[0].attachment = 0; // the non-msaa color buffer. + subpass.pResolveAttachments = resolve_references; + } else { + subpass.pResolveAttachments = nullptr; + } if (hasDepth) { subpass.pDepthStencilAttachment = &depth_reference; } @@ -291,7 +356,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas size_t numDeps = 0; VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; - rp.attachmentCount = hasDepth ? 2 : 1; + rp.attachmentCount = attachmentCount; rp.pAttachments = attachments; rp.subpassCount = 1; rp.pSubpasses = &subpass; @@ -342,12 +407,16 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas return pass; } -VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) { +VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType, VkSampleCountFlagBits sampleCount) { // When we create a render pass, we create all "types" of it immediately, // practical later when referring to it. Could change to on-demand if it feels motivated // but I think the render pass objects are cheap. + + // WARNING: We don't include sampleCount in the key, there's only the distinction multisampled or not + // which comes from the rpType. + // So you CAN NOT mix and match different non-one sample counts. if (!pass[(int)rpType]) { - pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType); + pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType, sampleCount); } return pass[(int)rpType]; } diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.h b/Common/GPU/Vulkan/VulkanFramebuffer.h index c52e933640..0247410df9 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.h +++ b/Common/GPU/Vulkan/VulkanFramebuffer.h @@ -15,12 +15,13 @@ enum class RenderPassType { HAS_DEPTH = 1, COLOR_INPUT = 2, // input attachment MULTIVIEW = 4, + MULTISAMPLE = 8, // This is the odd one out, and gets special handling in MergeRPTypes. // If this flag is set, none of the other flags can be set. // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible // so we don't bother with a non-depth version. - BACKBUFFER = 8, + BACKBUFFER = 16, TYPE_COUNT = BACKBUFFER + 1, }; @@ -54,7 +55,7 @@ struct VKRImage { class VKRFramebuffer { public: - VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag); + VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, int _numSamples, bool createDepthStencilBuffer, const char *tag); ~VKRFramebuffer(); VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType); @@ -62,10 +63,15 @@ public: int width = 0; int height = 0; int numLayers = 0; + VkSampleCountFlagBits sampleCount; VKRImage color{}; // color.image is always there. VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE. + // These are only initialized and used if numSamples > 1. + VKRImage msaaColor{}; + VKRImage msaaDepth{}; + const char *Tag() const { return tag_.c_str(); } @@ -76,13 +82,13 @@ public: return depth.image != VK_NULL_HANDLE; } - // TODO: Hide. - VulkanContext *vulkan_; + VulkanContext *Vulkan() const { return vulkan_; } private: - static void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag); + static void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkSampleCountFlagBits sampleCount, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag); VkFramebuffer framebuf[(size_t)RenderPassType::TYPE_COUNT]{}; + VulkanContext *vulkan_; std::string tag_; }; @@ -98,6 +104,8 @@ inline bool RenderPassTypeHasMultiView(RenderPassType type) { return (type & RenderPassType::MULTIVIEW) != 0; } +VkSampleCountFlagBits SampleCountToFlagBits(int count); + // Must be the same order as Draw::RPAction enum class VKRRenderPassLoadAction : uint8_t { KEEP, // default. avoid when possible. @@ -124,7 +132,7 @@ class VKRRenderPass { public: VKRRenderPass(const RPKey &key) : key_(key) {} - VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType); + VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType, VkSampleCountFlagBits sampleCount); void Destroy(VulkanContext *vulkan) { for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) { if (pass[i]) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 617c349b79..7f7d9e8717 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -198,7 +198,7 @@ bool VulkanQueueRunner::InitBackbufferFramebuffers(int width, int height) { VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view }; VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; - fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RenderPassType::BACKBUFFER); + fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RenderPassType::BACKBUFFER, VK_SAMPLE_COUNT_1_BIT); fb_info.attachmentCount = 2; fb_info.pAttachments = attachments; fb_info.width = width; @@ -1323,7 +1323,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c // Maybe a middle pass. But let's try to just block and compile here for now, this doesn't // happen all that much. graphicsPipeline->pipeline[(size_t)rpType] = Promise::CreateEmpty(); - graphicsPipeline->Create(vulkan_, renderPass->Get(vulkan_, rpType), rpType); + graphicsPipeline->Create(vulkan_, renderPass->Get(vulkan_, rpType, step.render.framebuffer ? step.render.framebuffer->sampleCount : VK_SAMPLE_COUNT_1_BIT), rpType); } VkPipeline pipeline = graphicsPipeline->pipeline[(size_t)rpType]->BlockUntilReady(); @@ -1523,6 +1523,8 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR bool hasDepth = RenderPassTypeHasDepth(step.render.renderPassType); + VkSampleCountFlagBits sampleCount; + if (step.render.framebuffer) { _dbg_assert_(step.render.finalColorLayout != VK_IMAGE_LAYOUT_UNDEFINED); _dbg_assert_(step.render.finalDepthStencilLayout != VK_IMAGE_LAYOUT_UNDEFINED); @@ -1535,6 +1537,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR VKRFramebuffer *fb = step.render.framebuffer; framebuf = fb->Get(renderPass, step.render.renderPassType); + sampleCount = fb->sampleCount; _dbg_assert_(framebuf != VK_NULL_HANDLE); w = fb->width; h = fb->height; @@ -1590,10 +1593,11 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR numClearVals = hasDepth ? 2 : 1; // We might do depth-less backbuffer in the future, though doubtful of the value. clearVal[1].depthStencil.depth = 0.0f; clearVal[1].depthStencil.stencil = 0; + sampleCount = VK_SAMPLE_COUNT_1_BIT; } VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; - rp_begin.renderPass = renderPass->Get(vulkan_, step.render.renderPassType); + rp_begin.renderPass = renderPass->Get(vulkan_, step.render.renderPassType, sampleCount); rp_begin.framebuffer = framebuf; VkRect2D rc = step.render.renderArea; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 98fb8a3edf..294f8d40d5 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -481,7 +481,7 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { return frameData_[curFrame].GetInitCmd(vulkan_); } -VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag) { +VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, const char *tag) { VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline(); _dbg_assert_(desc->vertexShader); _dbg_assert_(desc->fragmentShader); @@ -523,7 +523,7 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe } pipeline->pipeline[i] = Promise::CreateEmpty(); - compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType), rpType)); + compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType, sampleCount), rpType)); needsCompile = true; } if (needsCompile) @@ -575,17 +575,23 @@ void VulkanRenderManager::EndCurRenderStep() { if (curRenderStep_->render.framebuffer->numLayers > 1) { rpType = (RenderPassType)(rpType | RenderPassType::MULTIVIEW); } + + if (curRenderStep_->render.framebuffer->sampleCount != VK_SAMPLE_COUNT_1_BIT) { + rpType = (RenderPassType)(rpType | RenderPassType::MULTISAMPLE); + } } VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key); curRenderStep_->render.renderPassType = rpType; + VkSampleCountFlagBits sampleCount = curRenderStep_->render.framebuffer ? curRenderStep_->render.framebuffer->sampleCount : VK_SAMPLE_COUNT_1_BIT; + compileMutex_.lock(); bool needsCompile = false; for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_) { if (!pipeline->pipeline[(size_t)rpType]) { pipeline->pipeline[(size_t)rpType] = Promise::CreateEmpty(); - compileQueue_.push_back(CompileQueueEntry(pipeline, renderPass->Get(vulkan_, rpType), rpType)); + compileQueue_.push_back(CompileQueueEntry(pipeline, renderPass->Get(vulkan_, rpType, sampleCount), rpType)); needsCompile = true; } } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index fcc843535a..2e5cc1a1a5 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -217,7 +217,7 @@ public: // We delay creating pipelines until the end of the current render pass, so we can create the right type immediately. // Unless a variantBitmask is passed in, in which case we can just go ahead. // WARNING: desc must stick around during the lifetime of the pipeline! It's not enough to build it on the stack and drop it. - VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag); + VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, const char *tag); VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc); void NudgeCompilerThread() { diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 74ec191570..b944a1962c 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1210,7 +1210,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; raster->ToVulkan(&gDesc.rs); - pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << (size_t)RenderPassType::BACKBUFFER, tag ? tag : "thin3d"); + pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << (size_t)RenderPassType::BACKBUFFER, VK_SAMPLE_COUNT_1_BIT, tag ? tag : "thin3d"); if (desc.uniformDesc) { pipeline->dynamicUniformSize = (int)desc.uniformDesc->uniformBufferSize; @@ -1569,7 +1569,7 @@ public: } ~VKFramebuffer() { _assert_msg_(buf_, "Null buf_ in VKFramebuffer - double delete?"); - buf_->vulkan_->Delete().QueueCallback([](void *fb) { + buf_->Vulkan()->Delete().QueueCallback([](void *fb) { VKRFramebuffer *vfb = static_cast(fb); delete vfb; }, buf_); @@ -1584,8 +1584,13 @@ private: }; Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) { + _assert_(desc.numSamples > 0); + _assert_(desc.numLayers > 0); + _assert_(desc.width > 0); + _assert_(desc.height > 0); + VkCommandBuffer cmd = renderManager_.GetInitCmd(); - VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.numLayers, desc.z_stencil, desc.tag); + VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.numLayers, desc.numSamples, desc.z_stencil, desc.tag); return new VKFramebuffer(vkrfb); } diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index e49411bc6f..9cc732b86a 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -300,6 +300,7 @@ struct FramebufferDesc { int height; int depth; int numLayers; + int numSamples; bool z_stencil; const char *tag; // For graphics debuggers }; diff --git a/Core/Config.cpp b/Core/Config.cpp index 40e3478d3f..e7b10013af 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -901,6 +901,7 @@ static ConfigSetting graphicsSettings[] = { // Most low-performance (and many high performance) mobile GPUs do not support aniso anyway so defaulting to 4 is fine. ConfigSetting("AnisotropyLevel", &g_Config.iAnisotropyLevel, 4, true, true), + ConfigSetting("MultiSampleLevel", &g_Config.iMultiSampleLevel, 1, true, true), ReportedConfigSetting("VertexDecCache", &g_Config.bVertexCache, false, true, true), ReportedConfigSetting("TextureBackoffCache", &g_Config.bTextureBackoffCache, false, true, true), diff --git a/Core/Config.h b/Core/Config.h index 6e8a6e808d..342b5c3450 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -210,6 +210,7 @@ public: int iForceFullScreen = -1; // -1 = nope, 0 = force off, 1 = force on (not saved.) int iInternalResolution; // 0 = Auto (native), 1 = 1x (480x272), 2 = 2x, 3 = 3x, 4 = 4x and so on. int iAnisotropyLevel; // 0 - 5, powers of 2: 0 = 1x = no aniso + int iMultiSampleLevel; int bHighQualityDepth; bool bReplaceTextures; bool bSaveNewTextures; diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 05521c047e..e8f3139a10 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1652,7 +1652,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, shaderManager_->DirtyLastShader(); char tag[128]; size_t len = FormatFramebufferName(vfb, tag, sizeof(tag)); - vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), true, tag }); + vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), 1, true, tag }); if (Memory::IsVRAMAddress(vfb->fb_address) && vfb->fb_stride != 0) { NotifyMemInfo(MemBlockFlags::ALLOC, vfb->fb_address, ColorBufferByteSize(vfb), tag, len); } @@ -2019,7 +2019,7 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd char name[64]; snprintf(name, sizeof(name), "%08x_color_RAM", vfb->fb_address); textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_CREATED); - vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), true, name }); + vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), 1, true, name }); vfbs_.push_back(vfb); u32 byteSize = ColorBufferByteSize(vfb); @@ -2072,7 +2072,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram snprintf(name, sizeof(name), "download_temp"); // TODO: We don't have a way to create a depth-only framebuffer yet. // Also, at least on Vulkan we always create both depth and color, need to rework how we handle renderpasses. - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, channel == RASTER_DEPTH ? true : false, name }); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, 1, channel == RASTER_DEPTH ? true : false, name }); if (!nvfb->fbo) { ERROR_LOG(FRAMEBUF, "Error creating FBO! %d x %d", nvfb->renderWidth, nvfb->renderHeight); delete nvfb; @@ -2466,7 +2466,7 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u char name[128]; snprintf(name, sizeof(name), "tempfbo_%s_%dx%d", TempFBOReasonToString(reason), w / renderScaleFactor_, h / renderScaleFactor_); - Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, GetFramebufferLayers(), z_stencil, name }); + Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, GetFramebufferLayers(), 1, z_stencil, name }); if (!fbo) { return nullptr; } @@ -3156,7 +3156,7 @@ VirtualFramebuffer *FramebufferManagerCommon::ResolveFramebufferColorToFormat(Vi char tag[128]; FormatFramebufferName(vfb, tag, sizeof(tag)); - vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), true, tag }); + vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), 1, true, tag }); vfbs_.push_back(vfb); } diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 5980bbb95e..b94ba7d202 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -270,7 +270,7 @@ bool PresentationCommon::UpdatePostShader() { previousIndex_ = 0; for (int i = 0; i < FRAMES; ++i) { - previousFramebuffers_[i] = draw_->CreateFramebuffer({ w, h, 1, 1, false, "inter_presentation" }); + previousFramebuffers_[i] = draw_->CreateFramebuffer({ w, h, 1, 1, 1, false, "inter_presentation" }); if (!previousFramebuffers_[i]) { DestroyPostShader(); return false; @@ -386,7 +386,7 @@ bool PresentationCommon::AllocateFramebuffer(int w, int h) { } // No depth/stencil for post processing - Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, false, "presentation" }); + Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, 1, false, "presentation" }); if (!fbo) { return false; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index f5e7bc2889..308bcbd490 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1299,6 +1299,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { desc.depth = 1; desc.z_stencil = false; desc.numLayers = 1; + desc.numSamples = 1; desc.tag = "dynamic_clut"; dynamicClutFbo_ = draw_->CreateFramebuffer(desc); desc.tag = "dynamic_clut_temp"; diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index d1b2da92b6..61ff24fb2c 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -8,6 +8,7 @@ #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/GPU/Vulkan/VulkanContext.h" +#include "Core/Config.h" #include "GPU/Vulkan/VulkanUtil.h" #include "GPU/Vulkan/PipelineManagerVulkan.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" @@ -171,7 +172,7 @@ static std::string CutFromMain(std::string str) { } static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache, - VkPipelineLayout layout, PipelineFlags pipelineFlags, const VulkanPipelineRasterStateKey &key, + VkPipelineLayout layout, PipelineFlags pipelineFlags, VkSampleCountFlagBits sampleCount, const VulkanPipelineRasterStateKey &key, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantBitmask) { VulkanPipeline *vulkanPipeline = new VulkanPipeline(); VKRGraphicsPipelineDesc *desc = &vulkanPipeline->desc; @@ -307,7 +308,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, tag = FragmentShaderDesc(fs->GetID()) + " VS " + VertexShaderDesc(vs->GetID()); #endif - VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, pipelineFlags, variantBitmask, tag.c_str()); + VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, pipelineFlags, variantBitmask, sampleCount, tag.c_str()); vulkanPipeline->pipeline = pipeline; if (useBlendConstant) { @@ -351,8 +352,10 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager * pipelineFlags |= PipelineFlags::USES_MULTIVIEW; } + VkSampleCountFlagBits sampleCount = SampleCountToFlagBits(g_Config.iMultiSampleLevel); + VulkanPipeline *pipeline = CreateVulkanPipeline( - renderManager, pipelineCache_, layout, pipelineFlags, + renderManager, pipelineCache_, layout, pipelineFlags, sampleCount, rasterKey, decFmt, vs, fs, gs, useHwTransform, variantBitmask); pipelines_.Insert(key, pipeline);