diff --git a/Common/GPU/Shader.h b/Common/GPU/Shader.h index ae06e9248e..018d2bf98e 100644 --- a/Common/GPU/Shader.h +++ b/Common/GPU/Shader.h @@ -97,6 +97,7 @@ struct UniformDef { enum class SamplerFlags { ARRAY_ON_VULKAN = 1, + MULTISAMPLE = 2, // Only supported on Vulkan, read with texelFetch. }; ENUM_CLASS_BITOPS(SamplerFlags); diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp index 4d6df0b476..224ee4ca0e 100644 --- a/Common/GPU/ShaderWriter.cpp +++ b/Common/GPU/ShaderWriter.cpp @@ -391,7 +391,9 @@ void ShaderWriter::DeclareTexture2D(const SamplerDef &def) { break; case GLSL_VULKAN: // texBindingBase_ is used for the thin3d descriptor set layout, where they start at 1. - if (def.flags & SamplerFlags::ARRAY_ON_VULKAN) { + if (def.flags & SamplerFlags::MULTISAMPLE) { + F("layout(set = 0, binding = %d) uniform sampler2DMS%s %s;\n", def.binding + texBindingBase_, (def.flags & SamplerFlags::ARRAY_ON_VULKAN) ? "Array" : "", def.name); + } else if (def.flags & SamplerFlags::ARRAY_ON_VULKAN) { F("layout(set = 0, binding = %d) uniform sampler2DArray %s;\n", def.binding + texBindingBase_, def.name); } else { F("layout(set = 0, binding = %d) uniform sampler2D %s;\n", def.binding + texBindingBase_, def.name); diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp index da0d06311c..9a95f9bcbf 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.cpp +++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp @@ -178,7 +178,8 @@ void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VulkanBarrierBatch *barr ici.tiling = VK_IMAGE_TILING_OPTIMAL; ici.format = format; ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - if (sampleCount == VK_SAMPLE_COUNT_1_BIT) { + // Multisampled color is sampled too, to read single samples where averaging them would be wrong. + if (sampleCount == VK_SAMPLE_COUNT_1_BIT || color) { ici.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; } if (color) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index d51997dbb6..f28069f19c 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -989,7 +989,11 @@ void VulkanQueueRunner::LogReadbackImage(const VKRStep &step) { void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer cmd, const int curFrame, QueueProfileContext &profile) { for (size_t i = 0; i < step.preTransitions.size(); i++) { const TransitionRequest &iter = step.preTransitions[i]; - if (iter.aspect == VK_IMAGE_ASPECT_COLOR_BIT && iter.fb->color.layout != iter.targetLayout) { + if (iter.samples) { + if (iter.fb->msaaColor.image != VK_NULL_HANDLE && iter.fb->msaaColor.layout != iter.targetLayout) { + recordBarrier_.TransitionColorImageAuto(&iter.fb->msaaColor, iter.targetLayout); + } + } else if (iter.aspect == VK_IMAGE_ASPECT_COLOR_BIT && iter.fb->color.layout != iter.targetLayout) { recordBarrier_.TransitionColorImageAuto( &iter.fb->color, iter.targetLayout @@ -1341,6 +1345,10 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR } recordBarrier_.TransitionColorImageAuto(&fb->color, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + if (fb->msaaColor.image != VK_NULL_HANDLE) { + // Texturing from the samples (BindFramebufferAsTexture) moves this one out of the attachment layout. + recordBarrier_.TransitionColorImageAuto(&fb->msaaColor, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } // If the render pass doesn't touch depth, we can avoid a layout transition of the depth buffer. if (fb->depth.image && RenderPassTypeHasDepth(step.render.renderPassType)) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index eea23961cf..3a83bf583e 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -129,9 +129,10 @@ struct TransitionRequest { VKRFramebuffer *fb; VkImageAspectFlags aspect; // COLOR or DEPTH VkImageLayout targetLayout; + bool samples = false; // The multisampled color image, instead of the resolved one. bool operator == (const TransitionRequest &other) const { - return fb == other.fb && aspect == other.aspect && targetLayout == other.targetLayout; + return fb == other.fb && aspect == other.aspect && targetLayout == other.targetLayout && samples == other.samples; } }; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 12aad52aa3..a1c2286b53 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1550,7 +1550,7 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, steps_.push_back(step); } -VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int layer) { +VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int layer, bool samples) { _dbg_assert_(curRenderStep_ != nullptr); _dbg_assert_(fb != nullptr); @@ -1558,6 +1558,13 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in _dbg_assert_(aspectBit == VK_IMAGE_ASPECT_COLOR_BIT || aspectBit == VK_IMAGE_ASPECT_DEPTH_BIT); // Mark the dependency, check for required transitions, and return the image. + if (samples && fb->msaaColor.image != VK_NULL_HANDLE) { + _dbg_assert_(aspectBit == VK_IMAGE_ASPECT_COLOR_BIT); + curRenderStep_->dependencies.insert(fb); + TransitionRequest rq{ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true }; + curRenderStep_->preTransitions.insert(rq); + return layer == -1 ? fb->msaaColor.texAllLayersView : fb->msaaColor.texLayerViews[layer]; + } // Optimization: If possible, use final*Layout to put the texture into the correct layout "early". for (int i = (int)steps_.size() - 1; i >= 0; i--) { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index d8df63f634..e990c157e8 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -270,7 +270,8 @@ public: // as the other backends, even though there's no actual binding happening here. // For layer, we use the same convention as thin3d, where layer = -1 means all layers together. For texturing, that means that you // get an array texture view. - VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer); + // With samples, binds the multisampled color image (for texelFetch of single samples) instead of the resolved one. + VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer, bool samples = false); bool CopyFramebufferToMemory(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag); void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 9e28c5a92a..9cb178a646 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -460,6 +460,7 @@ public: // These functions should be self explanatory. void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override; void BindFramebufferAsTexture(Framebuffer *fbo, int binding, Aspect channelBit, int layer) override; + void BindFramebufferSamplesAsTexture(Framebuffer *fbo, int binding, int layer) override; void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; @@ -973,7 +974,8 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) caps_.framebufferDepthBlitSupported = vulkan->GetDeviceInfo().canBlitToPreferredDepthStencilFormat; caps_.framebufferStencilBlitSupported = caps_.framebufferDepthBlitSupported; caps_.framebufferDepthCopySupported = true; // Will pretty much always be the case. - caps_.framebufferSeparateDepthCopySupported = true; // Will pretty much always be the case. + caps_.framebufferSeparateDepthCopySupported = true; + caps_.framebufferSampleReadSupported = true; // Will pretty much always be the case. // This doesn't affect what depth/stencil format is actually used, see VulkanQueueRunner. caps_.preferredDepthBufferFormat = DataFormatFromVulkanDepth(vulkan->GetDeviceInfo().preferredDepthStencilFormat); caps_.texture3DSupported = true; @@ -1952,6 +1954,14 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, Aspect c boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, layer); } +void VKContext::BindFramebufferSamplesAsTexture(Framebuffer *fbo, int binding, int layer) { + VKFramebuffer *fb = (VKFramebuffer *)fbo; + _assert_(binding >= 0 && binding < MAX_BOUND_TEXTURES); + _assert_(fb != curFramebuffer_); + boundTextures_[binding].reset(nullptr); + boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, VK_IMAGE_ASPECT_COLOR_BIT, layer, true); +} + void VKContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { VKFramebuffer *fb = (VKFramebuffer *)fbo; if (fb) { diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 75edf35f92..dd248f2970 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -608,6 +608,7 @@ struct DeviceCaps { bool framebufferBlitSupported; bool framebufferDepthCopySupported; bool framebufferSeparateDepthCopySupported; + bool framebufferSampleReadSupported = false; // BindFramebufferSamplesAsTexture works. bool framebufferDepthBlitSupported; bool framebufferStencilBlitSupported; bool framebufferFetchSupported; @@ -824,6 +825,9 @@ public: // binding must be < MAX_TEXTURE_SLOTS (0, 1 are okay if it's 2). virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, Aspect aspect, int layer) = 0; + // Binds a multisampled framebuffer's color samples, for texelFetch from a sampler with SamplerFlags::MULTISAMPLE. + // Check framebufferSampleReadSupported. + virtual void BindFramebufferSamplesAsTexture(Framebuffer *fbo, int binding, int layer) {} // Framebuffer fetch / input attachment support, needs to be explicit in Vulkan. virtual void BindCurrentFramebufferForColorInput() {} diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 4c8fca4178..41ca3ea086 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -233,6 +233,7 @@ static const CommandLineParam g_autoParams[] = { {POFF(odsLog), CmdParamType::Bool, "odslog", 'o', "Also log through OutputDebugString (Windows)", CmdLineMode::Headless}, {POFF(generateInterpreterDispatch), CmdParamType::Bool, "generate-interpreter-dispatch", '\0', "Generate C++ interpreter dispatch code (ExecInstruction) to stdout and exit", CmdLineMode::Headless}, {POFF(resolutionScale), CmdParamType::Int, "resolution-scale", '\0', "Set the resolution scale factor"}, + {POFF(msaa), CmdParamType::Int, "msaa", '\0', "Set the MSAA level (0 = off, 1 = 2x, 2 = 4x, 3 = 8x)"}, {POFF(debuggerPort), CmdParamType::Int, "debugger", '\0', "Enable the WebSocket debugger on this port (0 = pick automatically); see docs/WebSocketDebugger.md"}, {POFF(debuggerRunPort), CmdParamType::Int, "debugger-run", '\0', "Like --debugger, but starts running instead of waiting at the entry point", CmdLineMode::Headless}, {POFF(autoSaveLoadSymbols), CmdParamType::Bool, "auto-save-load-symbols", '\0', "Auto save/load per-module and per-game symbol files (see bAutoSaveLoadSymbols)", CmdLineMode::Both}, @@ -617,6 +618,11 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.DoNotSaveSetting(&g_Config.iInternalResolution); } + if (msaa.has_value()) { + g_Config.iMultiSampleLevel = msaa.value(); + g_Config.DoNotSaveSetting(&g_Config.iMultiSampleLevel); + } + if (memReadAction.has_value()) { g_Config.iExceptionActionMemRead = memReadAction.value(); g_Config.DoNotSaveSetting(&g_Config.iExceptionActionMemRead); diff --git a/Core/CmdLine.h b/Core/CmdLine.h index 4df2726356..2d7b160787 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -160,6 +160,7 @@ struct CommandLineOptions { // Headless options that may also be mildly useful in application mode std::optional resolutionScale; + std::optional msaa; // Headless options diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index 06b7fa4bce..80f3f2a68e 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -69,6 +69,27 @@ Draw2DPipelineInfo GenerateDraw2DCopyColorFs(ShaderWriter &writer) { }; } +static const SamplerDef samplersMS[1] = { + { 0, "tex", SamplerFlags::ARRAY_ON_VULKAN | SamplerFlags::MULTISAMPLE }, +}; + +// Source and destination are the same size, so the fragment coordinate is the texel. +Draw2DPipelineInfo GenerateDraw2DCopyColorSample0Fs(ShaderWriter &writer) { + writer.DeclareSamplers(samplersMS); + writer.BeginFSMain(Slice::empty(), varyings); + writer.C(" vec4 outColor = texelFetch(tex, ivec3(ivec2(gl_FragCoord.xy), 0), 0);\n"); + writer.EndFSMain("outColor"); + + Draw2DPipelineInfo info{ + "draw2d_copy_color_sample0", + RASTER_COLOR, + RASTER_COLOR, + samplersMS, + }; + info.readSamples = true; + return info; +} + Draw2DPipelineInfo GenerateDraw2DCopyColorRect2LinFs(ShaderWriter &writer) { writer.DeclareSamplers(samplers); writer.BeginFSMain(g_draw2Duniforms, varyings); @@ -374,6 +395,16 @@ Draw2DPipeline *FramebufferManagerCommon::Get2DPipeline(Draw2DShader shader) { } pipeline = draw2DPipelineColorRect2Lin_; break; + + case DRAW2D_COPY_COLOR_SAMPLE0: + if (!draw_->GetDeviceCaps().framebufferSampleReadSupported) { + return nullptr; + } + if (!draw2DPipelineColorSample0_) { + draw2DPipelineColorSample0_ = draw2D_.Create2DPipeline(&GenerateDraw2DCopyColorSample0Fs); + } + pipeline = draw2DPipelineColorSample0_; + break; case DRAW2D_COPY_DEPTH: if (!draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) { // Can't do it diff --git a/GPU/Common/Draw2D.h b/GPU/Common/Draw2D.h index 076d319957..a311cf06db 100644 --- a/GPU/Common/Draw2D.h +++ b/GPU/Common/Draw2D.h @@ -20,6 +20,7 @@ enum Draw2DShader { DRAW2D_565_TO_DEPTH, DRAW2D_565_TO_DEPTH_DESWIZZLE, DRAW2D_COPY_COLOR_RECT2LIN, + DRAW2D_COPY_COLOR_SAMPLE0, // From a multisampled framebuffer, its first sample instead of the average. Needs framebufferSampleReadSupported. }; inline RasterChannel Draw2DSourceChannel(Draw2DShader shader) { @@ -40,6 +41,7 @@ struct Draw2DPipelineInfo { RasterChannel readChannel; RasterChannel writeChannel; Slice samplers; + bool readSamples = false; // Binds the source with BindFramebufferSamplesAsTexture. }; extern const UniformDef g_draw2Duniforms[2]; diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 65954fc2ec..36305007c3 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -217,13 +217,15 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } // Note: the precision qualifiers must match the vertex shader! - WRITE(p, "layout (location = 1) %s in lowp vec4 v_color0;\n", shading); + // Centroid, matching the vertex shader. + const char *colorQualifier = *shading ? shading : "centroid"; + WRITE(p, "layout (location = 1) %s in lowp vec4 v_color0;\n", colorQualifier); if (lmode) { - WRITE(p, "layout (location = 2) %s in lowp vec3 v_color1;\n", shading); + WRITE(p, "layout (location = 2) %s in lowp vec3 v_color1;\n", colorQualifier); } WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n"); if (doTexture) { - WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n"); + WRITE(p, "layout (location = 0) centroid in highp vec3 v_texcoord;\n"); } if (fsMinmaxDiscard || fsDepthClamp) { WRITE(p, "layout (location = 4) in highp vec2 v_zw;\n"); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index d24dc00018..269f8cffe4 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1053,8 +1053,12 @@ void FramebufferManagerCommon::BlitFramebufferDepth(VirtualFramebuffer *src, Vir void FramebufferManagerCommon::CopyDepthRect(Draw::Framebuffer *src, int srcX, int srcY, Draw::Framebuffer *dst, int dstX, int dstY, int w, int h, int scaleFactor, const char *tag) { const Draw::DeviceCaps &caps = draw_->GetDeviceCaps(); const bool sameSamples = src->MultiSampleLevel() == dst->MultiSampleLevel(); - if (sameSamples && (caps.framebufferSeparateDepthCopySupported || (!caps.framebufferDepthBlitSupported && caps.framebufferCopySupported))) { + // Only a copy takes all the samples, and blits of multisampled buffers aren't supported at all. + const bool multisampled = src->MultiSampleLevel() != 0 || dst->MultiSampleLevel() != 0; + if (sameSamples && (caps.framebufferSeparateDepthCopySupported || (!caps.framebufferDepthBlitSupported && caps.framebufferCopySupported) || (multisampled && caps.framebufferCopySupported))) { draw_->CopyFramebufferImage(src, 0, srcX, srcY, 0, dst, 0, dstX, dstY, 0, w, h, 1, Draw::Aspect::DEPTH_BIT, tag); + } else if (multisampled) { + WARN_LOG_ONCE(depthRectMSAA, Log::FrameBuf, "Can't copy multisampled depth here (%s)", tag); } else if (caps.framebufferDepthBlitSupported) { draw_->BlitFramebuffer(src, srcX, srcY, srcX + w, srcY + h, dst, dstX, dstY, dstX + w, dstY + h, Draw::Aspect::DEPTH_BIT, Draw::FB_BLIT_NEAREST, tag); } else if (caps.fragmentShaderDepthWriteSupported && caps.textureDepthSupported) { @@ -3076,11 +3080,33 @@ static const char *TempFBOReasonToString(TempFBO reason) { case TempFBO::COPY: return "copy"; case TempFBO::STENCIL: return "stencil"; case TempFBO::DEPTH_SHIFT: return "depth_shift"; + case TempFBO::SAMPLE0: return "sample0"; default: break; } return ""; } +Draw::Framebuffer *FramebufferManagerCommon::CopyColorSample0(VirtualFramebuffer *vfb) { + if (!vfb->fbo || vfb->fbo->MultiSampleLevel() == 0) { + return nullptr; + } + Draw2DPipeline *pipeline = Get2DPipeline(DRAW2D_COPY_COLOR_SAMPLE0); + if (!pipeline) { + return nullptr; + } + const int w = vfb->fbo->Width(); + const int h = vfb->fbo->Height(); + Draw::Framebuffer *copy = GetTempFBO(TempFBO::SAMPLE0, w, h); + if (!copy) { + return nullptr; + } + BlitUsingRaster(vfb->fbo, 0.0f, 0.0f, w, h, copy, 0.0f, 0.0f, w, h, false, vfb->renderScaleFactor, pipeline, "CopyColorSample0"); + draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); + RebindFramebuffer("After CopyColorSample0"); + return copy; +} + Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h) { u64 key = ((u64)reason << 48) | ((u32)w << 16) | h; auto it = tempFBOs_.find(key); @@ -3090,10 +3116,12 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u } bool z_stencil = reason == TempFBO::STENCIL || reason == TempFBO::DEPTH_SHIFT; + // The depth shift copies to and from a framebuffer, which needs the same samples. + const int msaaLevel = reason == TempFBO::DEPTH_SHIFT ? msaaLevel_ : 0; 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(), 0, z_stencil, name }); + Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, GetFramebufferLayers(), msaaLevel, z_stencil, name }); if (!fbo) { return nullptr; } @@ -3538,6 +3566,7 @@ void FramebufferManagerCommon::ReleasePipelines() { DoRelease(depthReadbackPipeline_); DoRelease(draw2DPipelineCopyColor_); DoRelease(draw2DPipelineColorRect2Lin_); + DoRelease(draw2DPipelineColorSample0_); DoRelease(draw2DPipelineCopyDepth_); DoRelease(draw2DPipelineEncodeDepth_); DoRelease(draw2DPipeline565ToDepth_); @@ -3654,6 +3683,11 @@ void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX useBlit = false; useCopy = false; } + // Copy depth where we can: a raster copy isn't exact (Burnout Dominator's recursive rendering, #11100), and with + // multisampling it reads the single-sampled depth, which is never resolved, while a copy takes all the samples. + if (channel == RASTER_DEPTH && src != dst && src->fbo->MultiSampleLevel() == dst->fbo->MultiSampleLevel() && draw_->GetDeviceCaps().framebufferSeparateDepthCopySupported) { + useCopy = true; + } float srcXFactor = src->renderScaleFactor; float srcYFactor = src->renderScaleFactor; @@ -3749,7 +3783,11 @@ void FramebufferManagerCommon::BlitUsingRaster( draw_->BindTexture(0, nullptr); // This will get optimized away in case it's already bound (in VK and GL at least..) draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag ? tag : "BlitUsingRaster"); - draw_->BindFramebufferAsTexture(src, 0, pipeline->info.readChannel == RASTER_COLOR ? Draw::Aspect::COLOR_BIT : Draw::Aspect::DEPTH_BIT, Draw::ALL_LAYERS); + if (pipeline->info.readSamples) { + draw_->BindFramebufferSamplesAsTexture(src, 0, Draw::ALL_LAYERS); + } else { + draw_->BindFramebufferAsTexture(src, 0, pipeline->info.readChannel == RASTER_COLOR ? Draw::Aspect::COLOR_BIT : Draw::Aspect::DEPTH_BIT, Draw::ALL_LAYERS); + } if (destX1 == 0.0f && destY1 == 0.0f && destX2 >= destW && destY2 >= destH) { // We overwrite the whole channel of the framebuffer, so we can invalidate the current contents. diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 596b04f26e..3f09413277 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -233,6 +233,8 @@ enum class TempFBO { STENCIL, // Depth moved within one framebuffer, for rendering at an X offset. DEPTH_SHIFT, + // The first samples of a multisampled framebuffer. + SAMPLE0, }; inline Draw::DataFormat GEFormatToThin3D(GEBufferFormat geFormat) { @@ -460,6 +462,9 @@ public: virtual void DeviceRestore(Draw::DrawContext *draw); Draw::Framebuffer *GetTempFBO(TempFBO reason, u16 w, u16 h); + // A single-sampled copy of the first sample of each pixel of a multisampled framebuffer, or null if it isn't + // multisampled or the backend can't. For reading colors as values, which averaging the samples would corrupt. + Draw::Framebuffer *CopyColorSample0(VirtualFramebuffer *vfb); // Debug features virtual bool GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxRes); @@ -675,6 +680,7 @@ protected: // Draw2D pipelines Draw2DPipeline *draw2DPipelineCopyColor_ = nullptr; Draw2DPipeline *draw2DPipelineColorRect2Lin_ = nullptr; + Draw2DPipeline *draw2DPipelineColorSample0_ = nullptr; Draw2DPipeline *draw2DPipelineCopyDepth_ = nullptr; Draw2DPipeline *draw2DPipelineEncodeDepth_ = nullptr; Draw2DPipeline *draw2DPipeline565ToDepth_ = nullptr; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 0ffb3a2b16..2841d326b0 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2479,6 +2479,8 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer bool smoothedDepal = false; u32 depthUpperBits = 0; + VirtualFramebuffer sample0Vfb; + if (need_depalettize) { if (clutRenderAddress_ == 0xFFFFFFFF) { clutTexture = clutTextureCache_.GetClutTexture(clutFormat, clutHash_, clutBufRaw_); @@ -2495,6 +2497,17 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer framebufferManager_->BlitUsingRaster(dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); } + // With multisampling, take one sample instead of the average, since the colors are looked up as indices. + // Dragon Ball Z: Tenkaichi Tag Team's outlines look up its framebuffer as CLUT32. (A smooth color grade + // loses its antialiasing this way, but that's the smaller loss.) + if (!depth) { + if (Draw::Framebuffer *sample0 = framebufferManager_->CopyColorSample0(framebuffer)) { + sample0Vfb = *framebuffer; + sample0Vfb.fbo = sample0; + framebuffer = &sample0Vfb; + } + } + if (useShaderDepal) { _dbg_assert_(!depth); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 8d27eaa676..ed5025a6d7 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -203,12 +203,15 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "layout (location = %d) in vec3 color1;\n", (int)PspAttributeLocation::COLOR1); } - WRITE(p, "layout (location = 1) %sout lowp vec4 v_color0;\n", shading); + // Centroid keeps them inside the primitive on pixels it only partly covers with MSAA, so that + // textures aren't sampled from outside the UV range. Without MSAA, it's the same as center. + const char *colorQualifier = *shading ? shading : "centroid "; + WRITE(p, "layout (location = 1) %sout lowp vec4 v_color0;\n", colorQualifier); if (lmode) { - WRITE(p, "layout (location = 2) %sout lowp vec3 v_color1;\n", shading); + WRITE(p, "layout (location = 2) %sout lowp vec3 v_color1;\n", colorQualifier); } - WRITE(p, "layout (location = 0) out highp vec3 v_texcoord;\n"); + WRITE(p, "layout (location = 0) centroid out highp vec3 v_texcoord;\n"); WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n");