From 153b8bfc7c8c8711f8c2ce40f88085355d870b6a Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Fri, 18 Aug 2023 02:25:54 -0300
Subject: [PATCH] Implement support for masked stencil clears on Vulkan (#5589)

* Implement support for masked stencil clears on Vulkan

* PR feedback
---
 .../FramebufferParams.cs                      |  10 ++
 src/Ryujinx.Graphics.Vulkan/HelperShader.cs   | 108 ++++++++++++------
 src/Ryujinx.Graphics.Vulkan/PipelineBase.cs   |   6 +-
 src/Ryujinx.Graphics.Vulkan/PipelineFull.cs   |  36 ++++++
 .../Ryujinx.Graphics.Vulkan.csproj            |   1 +
 ...DepthStencilClearFragmentShaderSource.frag |   8 ++
 .../DepthStencilClearFragment.spv             | Bin 0 -> 468 bytes
 7 files changed, 131 insertions(+), 38 deletions(-)
 create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag
 create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv

diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index 749d5929..7600c2d5 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -148,6 +148,16 @@ namespace Ryujinx.Graphics.Vulkan
             return _attachments[index];
         }
 
+        public Auto<DisposableImageView> GetDepthStencilAttachment()
+        {
+            if (!HasDepthStencil)
+            {
+                return null;
+            }
+
+            return _attachments[AttachmentsCount - 1];
+        }
+
         public ComponentType GetAttachmentComponentType(int index)
         {
             if (_colors != null && (uint)index < _colors.Length)
diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
index 684ed068..a6d23764 100644
--- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
         private readonly IProgram _programColorClearF;
         private readonly IProgram _programColorClearSI;
         private readonly IProgram _programColorClearUI;
+        private readonly IProgram _programDepthStencilClear;
         private readonly IProgram _programStrideChange;
         private readonly IProgram _programConvertD32S8ToD24S8;
         private readonly IProgram _programConvertIndexBuffer;
@@ -105,6 +106,12 @@ namespace Ryujinx.Graphics.Vulkan
                 new ShaderSource(ReadSpirv("ColorClearUIFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv),
             }, colorClearResourceLayout);
 
+            _programDepthStencilClear = gd.CreateProgramWithMinimalLayout(new[]
+            {
+                new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ReadSpirv("DepthStencilClearFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorClearResourceLayout);
+
             var strideChangeResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
@@ -446,10 +453,6 @@ namespace Ryujinx.Graphics.Vulkan
                 0f,
                 1f);
 
-            Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
-            scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
-
             if (dstIsDepthOrStencil)
             {
                 _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
@@ -470,7 +473,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat);
             _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
-            _pipeline.SetScissors(scissors);
+            _pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
 
             if (clearAlpha)
             {
@@ -547,12 +550,8 @@ namespace Ryujinx.Graphics.Vulkan
                 0f,
                 1f);
 
-            Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
-            scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
-
             _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat);
-            _pipeline.SetScissors(scissors);
+            _pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
             _pipeline.SetViewports(viewports);
             _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
 
@@ -639,7 +638,11 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
-        private static StencilTestDescriptor CreateStencilTestDescriptor(bool enabled)
+        private static StencilTestDescriptor CreateStencilTestDescriptor(
+            bool enabled,
+            int refValue = 0,
+            int compareMask = 0xff,
+            int writeMask = 0xff)
         {
             return new StencilTestDescriptor(
                 enabled,
@@ -647,16 +650,16 @@ namespace Ryujinx.Graphics.Vulkan
                 StencilOp.Replace,
                 StencilOp.Replace,
                 StencilOp.Replace,
-                0,
-                0xff,
-                0xff,
+                refValue,
+                compareMask,
+                writeMask,
                 CompareOp.Always,
                 StencilOp.Replace,
                 StencilOp.Replace,
                 StencilOp.Replace,
-                0,
-                0xff,
-                0xff);
+                refValue,
+                compareMask,
+                writeMask);
         }
 
         public void Clear(
@@ -695,10 +698,6 @@ namespace Ryujinx.Graphics.Vulkan
                 0f,
                 1f);
 
-            Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
-            scissors[0] = scissor;
-
             IProgram program;
 
             if (type == ComponentType.SignedInteger)
@@ -718,7 +717,7 @@ namespace Ryujinx.Graphics.Vulkan
             _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
             _pipeline.SetRenderTargetColorMasks(new[] { componentMask });
             _pipeline.SetViewports(viewports);
-            _pipeline.SetScissors(scissors);
+            _pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
             _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
             _pipeline.Draw(4, 1, 0, 0);
             _pipeline.Finish();
@@ -726,6 +725,56 @@ namespace Ryujinx.Graphics.Vulkan
             gd.BufferManager.Delete(bufferHandle);
         }
 
+        public void Clear(
+            VulkanRenderer gd,
+            Auto<DisposableImageView> dst,
+            float depthValue,
+            bool depthMask,
+            int stencilValue,
+            int stencilMask,
+            int dstWidth,
+            int dstHeight,
+            VkFormat dstFormat,
+            Rectangle<int> scissor)
+        {
+            const int ClearColorBufferSize = 16;
+
+            gd.FlushAllCommands();
+
+            using var cbs = gd.CommandBufferPool.Rent();
+
+            _pipeline.SetCommandBuffer(cbs);
+
+            var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize);
+
+            gd.BufferManager.SetData<float>(bufferHandle, 0, stackalloc float[] { depthValue });
+
+            _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
+
+            Span<Viewport> viewports = stackalloc Viewport[1];
+
+            viewports[0] = new Viewport(
+                new Rectangle<float>(0, 0, dstWidth, dstHeight),
+                ViewportSwizzle.PositiveX,
+                ViewportSwizzle.PositiveY,
+                ViewportSwizzle.PositiveZ,
+                ViewportSwizzle.PositiveW,
+                0f,
+                1f);
+
+            _pipeline.SetProgram(_programDepthStencilClear);
+            _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat);
+            _pipeline.SetViewports(viewports);
+            _pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
+            _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
+            _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always));
+            _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask));
+            _pipeline.Draw(4, 1, 0, 0);
+            _pipeline.Finish();
+
+            gd.BufferManager.Delete(bufferHandle);
+        }
+
         public void DrawTexture(
             VulkanRenderer gd,
             PipelineBase pipeline,
@@ -778,8 +827,6 @@ namespace Ryujinx.Graphics.Vulkan
                 0f,
                 1f);
 
-            Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
             pipeline.SetProgram(_programColorBlit);
             pipeline.SetViewports(viewports);
             pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
@@ -1119,11 +1166,7 @@ namespace Ryujinx.Graphics.Vulkan
                     0f,
                     1f);
 
-                Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
-                scissors[0] = new Rectangle<int>(0, 0, dst.Width, dst.Height);
-
-                _pipeline.SetScissors(scissors);
+                _pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) });
                 _pipeline.SetViewports(viewports);
                 _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
 
@@ -1251,12 +1294,8 @@ namespace Ryujinx.Graphics.Vulkan
                 0f,
                 1f);
 
-            Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
-
-            scissors[0] = new Rectangle<int>(0, 0, dst.Width, dst.Height);
-
             _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
-            _pipeline.SetScissors(scissors);
+            _pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) });
             _pipeline.SetViewports(viewports);
             _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
 
@@ -1731,6 +1770,7 @@ namespace Ryujinx.Graphics.Vulkan
                 _programColorClearF.Dispose();
                 _programColorClearSI.Dispose();
                 _programColorClearUI.Dispose();
+                _programDepthStencilClear.Dispose();
                 _programStrideChange.Dispose();
                 _programConvertIndexBuffer.Dispose();
                 _programConvertIndirectData.Dispose();
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 67b16ec9..54b67f35 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -243,10 +243,8 @@ namespace Ryujinx.Graphics.Vulkan
             Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
         }
 
-        public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
+        public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, bool stencilMask)
         {
-            // TODO: Use stencilMask (fully).
-
             if (FramebufferParams == null || !FramebufferParams.HasDepthStencil)
             {
                 return;
@@ -255,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan
             var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue));
             var flags = depthMask ? ImageAspectFlags.DepthBit : 0;
 
-            if (stencilMask != 0)
+            if (stencilMask)
             {
                 flags |= ImageAspectFlags.StencilBit;
             }
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
index dcc6c530..c3e6f37c 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -81,6 +81,42 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
+        public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
+        {
+            if (FramebufferParams == null)
+            {
+                return;
+            }
+
+            if (stencilMask != 0 && stencilMask != 0xff)
+            {
+                // We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
+                // because on Vulkan, the pipeline state does not affect clears.
+                var dstTexture = FramebufferParams.GetDepthStencilAttachment();
+                if (dstTexture == null)
+                {
+                    return;
+                }
+
+                // TODO: Clear only the specified layer.
+                Gd.HelperShader.Clear(
+                    Gd,
+                    dstTexture,
+                    depthValue,
+                    depthMask,
+                    stencilValue,
+                    stencilMask,
+                    (int)FramebufferParams.Width,
+                    (int)FramebufferParams.Height,
+                    FramebufferParams.AttachmentFormats[FramebufferParams.AttachmentsCount - 1],
+                    ClearScissor);
+            }
+            else
+            {
+                ClearRenderTargetDepthStencil(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask != 0);
+            }
+        }
+
         public void EndHostConditionalRendering()
         {
             if (Gd.Capabilities.SupportsConditionalRendering)
diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
index 34395118..8d30457e 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -42,6 +42,7 @@
     <EmbeddedResource Include="Shaders\SpirvBinaries\DepthBlitMsFragment.spv" />
     <EmbeddedResource Include="Shaders\SpirvBinaries\DepthDrawToMsFragment.spv" />
     <EmbeddedResource Include="Shaders\SpirvBinaries\DepthDrawToNonMsFragment.spv" />
+    <EmbeddedResource Include="Shaders\SpirvBinaries\DepthStencilClearFragment.spv" />
     <EmbeddedResource Include="Shaders\SpirvBinaries\StencilBlitFragment.spv" />
     <EmbeddedResource Include="Shaders\SpirvBinaries\StencilBlitMsFragment.spv" />
     <EmbeddedResource Include="Shaders\SpirvBinaries\StencilDrawToMsFragment.spv" />
diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag
new file mode 100644
index 00000000..689a0fff
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag
@@ -0,0 +1,8 @@
+#version 450 core
+
+layout (location = 0) in vec4 clear_colour;
+
+void main()
+{
+    gl_FragDepth = clear_colour.x;
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv
new file mode 100644
index 0000000000000000000000000000000000000000..dcd3235b6c9a8eda2441711583ea42ad04b924bf
GIT binary patch
literal 468
zcmYk1%}T>i5QVQvlUA$!L99E8xD<*DErKWrVmICS043B?g=i(#7xbyz2!7vf6>pf#
z%(-XInVTjj%@DRiJG4SStWQ5AAOS3oc|3WT4BuCe!}E(X8J&<uqMUAMh6dSqDW6}x
zXc29q2TvS>zj~KC<x9w)2xKt+&t`Z!zo@47b-B2kzpb8V;pI_tR?W+LI$KuDk6L`p
z^snDw)9ZcYC1)Qey^f=6i1mOv2P<MU;(oqJ><T^iRa5Mcew*x_dlvk6$i7`%zP+Ae
z2lT$NAUvm=esA5z`3|*QHAlS6{nYm8`2~`ZZ~4pZ66&jQ4b!<EA%4SzyV`%FdP)2M
DtJ4{N

literal 0
HcmV?d00001