Просмотр исходного кода

Implement safe depth-stencil blit using stencil export extension (#4356)

* Implement safe depth-stencil blit using stencil export extension

* Delete depth-stencil blit with buffer path
gdkchan 3 лет назад
Родитель
Сommit
7528f94536

+ 2 - 2
Ryujinx.Graphics.Vulkan/FramebufferParams.cs

@@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
         {
             _device = device;
             _attachments = new[] { view };
-            _validColorAttachments = 1u;
+            _validColorAttachments = isDepthStencil ? 0u : 1u;
 
             Width = width;
             Height = height;
@@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             AttachmentSamples = new[] { samples };
             AttachmentFormats = new[] { format };
-            AttachmentIndices = new[] { 0 };
+            AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
 
             AttachmentsCount = 1;
 

+ 3 - 0
Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs

@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan
         public readonly bool SupportsGeometryShaderPassthrough;
         public readonly bool SupportsSubgroupSizeControl;
         public readonly bool SupportsShaderInt8;
+        public readonly bool SupportsShaderStencilExport;
         public readonly bool SupportsConditionalRendering;
         public readonly bool SupportsExtendedDynamicState;
         public readonly bool SupportsMultiView;
@@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
             bool supportsGeometryShaderPassthrough,
             bool supportsSubgroupSizeControl,
             bool supportsShaderInt8,
+            bool supportsShaderStencilExport,
             bool supportsConditionalRendering,
             bool supportsExtendedDynamicState,
             bool supportsMultiView,
@@ -71,6 +73,7 @@ namespace Ryujinx.Graphics.Vulkan
             SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
             SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
             SupportsShaderInt8 = supportsShaderInt8;
+            SupportsShaderStencilExport = supportsShaderStencilExport;
             SupportsConditionalRendering = supportsConditionalRendering;
             SupportsExtendedDynamicState = supportsExtendedDynamicState;
             SupportsMultiView = supportsMultiView;

+ 205 - 11
Ryujinx.Graphics.Vulkan/HelperShader.cs

@@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Vulkan
         private readonly IProgram _programConvertIndirectData;
         private readonly IProgram _programColorCopyToNonMs;
         private readonly IProgram _programColorDrawToMs;
+        private readonly IProgram _programDepthBlit;
+        private readonly IProgram _programStencilBlit;
 
         public HelperShader(VulkanRenderer gd, Device device)
         {
@@ -42,13 +44,13 @@ namespace Ryujinx.Graphics.Vulkan
             _samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
 
-            var colorBlitVertexBindings = new ShaderBindings(
+            var blitVertexBindings = new ShaderBindings(
                 new[] { 1 },
                 Array.Empty<int>(),
                 Array.Empty<int>(),
                 Array.Empty<int>());
 
-            var colorBlitFragmentBindings = new ShaderBindings(
+            var blitFragmentBindings = new ShaderBindings(
                 Array.Empty<int>(),
                 Array.Empty<int>(),
                 new[] { 0 },
@@ -56,14 +58,14 @@ namespace Ryujinx.Graphics.Vulkan
 
             _programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
 
             _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
 
             var colorClearFragmentBindings = new ShaderBindings(
@@ -74,19 +76,19 @@ namespace Ryujinx.Graphics.Vulkan
 
             _programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
                 new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
 
             _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
                 new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
 
             _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
                 new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
 
@@ -151,6 +153,21 @@ namespace Ryujinx.Graphics.Vulkan
             {
                 new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, convertIndirectDataBindings, ShaderStage.Compute, TargetLanguage.Spirv),
             });
+
+            _programDepthBlit = gd.CreateProgramWithMinimalLayout(new[]
+            {
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.DepthBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+            });
+
+            if (gd.Capabilities.SupportsShaderStencilExport)
+            {
+                _programStencilBlit = gd.CreateProgramWithMinimalLayout(new[]
+                {
+                    new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                    new ShaderSource(ShaderBinaries.StencilBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+                });
+            }
         }
 
         public void Blit(
@@ -162,6 +179,7 @@ namespace Ryujinx.Graphics.Vulkan
             VkFormat dstFormat,
             Extents2D srcRegion,
             Extents2D dstRegion,
+            bool isDepthOrStencil,
             bool linearFilter,
             bool clearAlpha = false)
         {
@@ -169,10 +187,17 @@ namespace Ryujinx.Graphics.Vulkan
 
             using var cbs = gd.CommandBufferPool.Rent();
 
-            Blit(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion, linearFilter, clearAlpha);
+            if (isDepthOrStencil)
+            {
+                BlitDepthStencil(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion);
+            }
+            else
+            {
+                BlitColor(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion, linearFilter, clearAlpha);
+            }
         }
 
-        public void Blit(
+        public void BlitColor(
             VulkanRenderer gd,
             CommandBufferScoped cbs,
             TextureView src,
@@ -255,6 +280,173 @@ namespace Ryujinx.Graphics.Vulkan
             gd.BufferManager.Delete(bufferHandle);
         }
 
+        private void BlitDepthStencil(
+            VulkanRenderer gd,
+            CommandBufferScoped cbs,
+            TextureView src,
+            Auto<DisposableImageView> dst,
+            int dstWidth,
+            int dstHeight,
+            VkFormat dstFormat,
+            Extents2D srcRegion,
+            Extents2D dstRegion)
+        {
+            _pipeline.SetCommandBuffer(cbs);
+
+            const int RegionBufferSize = 16;
+
+            Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
+
+            region[0] = (float)srcRegion.X1 / src.Width;
+            region[1] = (float)srcRegion.X2 / src.Width;
+            region[2] = (float)srcRegion.Y1 / src.Height;
+            region[3] = (float)srcRegion.Y2 / src.Height;
+
+            if (dstRegion.X1 > dstRegion.X2)
+            {
+                (region[0], region[1]) = (region[1], region[0]);
+            }
+
+            if (dstRegion.Y1 > dstRegion.Y2)
+            {
+                (region[2], region[3]) = (region[3], region[2]);
+            }
+
+            var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
+
+            gd.BufferManager.SetData<float>(bufferHandle, 0, region);
+
+            _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
+
+            Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
+
+            var rect = new Rectangle<float>(
+                MathF.Min(dstRegion.X1, dstRegion.X2),
+                MathF.Min(dstRegion.Y1, dstRegion.Y2),
+                MathF.Abs(dstRegion.X2 - dstRegion.X1),
+                MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
+
+            viewports[0] = new GAL.Viewport(
+                rect,
+                ViewportSwizzle.PositiveX,
+                ViewportSwizzle.PositiveY,
+                ViewportSwizzle.PositiveZ,
+                ViewportSwizzle.PositiveW,
+                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, true, dstFormat);
+            _pipeline.SetScissors(scissors);
+            _pipeline.SetViewports(viewports, false);
+            _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
+
+            var aspectFlags = src.Info.Format.ConvertAspectFlags();
+
+            if (aspectFlags.HasFlag(ImageAspectFlags.DepthBit))
+            {
+                var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth);
+
+                BlitDepthStencilDraw(depthTexture, isDepth: true);
+
+                if (depthTexture != src)
+                {
+                    depthTexture.Release();
+                }
+            }
+
+            if (aspectFlags.HasFlag(ImageAspectFlags.StencilBit) && _programStencilBlit != null)
+            {
+                var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil);
+
+                BlitDepthStencilDraw(stencilTexture, isDepth: false);
+
+                if (stencilTexture != src)
+                {
+                    stencilTexture.Release();
+                }
+            }
+
+            _pipeline.Finish(gd, cbs);
+
+            gd.BufferManager.Delete(bufferHandle);
+        }
+
+        private static TextureView CreateDepthOrStencilView(TextureView depthStencilTexture, DepthStencilMode depthStencilMode)
+        {
+            if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode)
+            {
+                return depthStencilTexture;
+            }
+
+            return (TextureView)depthStencilTexture.CreateView(new TextureCreateInfo(
+                depthStencilTexture.Info.Width,
+                depthStencilTexture.Info.Height,
+                depthStencilTexture.Info.Depth,
+                depthStencilTexture.Info.Levels,
+                depthStencilTexture.Info.Samples,
+                depthStencilTexture.Info.BlockWidth,
+                depthStencilTexture.Info.BlockHeight,
+                depthStencilTexture.Info.BytesPerPixel,
+                depthStencilTexture.Info.Format,
+                depthStencilMode,
+                depthStencilTexture.Info.Target,
+                SwizzleComponent.Red,
+                SwizzleComponent.Green,
+                SwizzleComponent.Blue,
+                SwizzleComponent.Alpha), 0, 0);
+        }
+
+        private void BlitDepthStencilDraw(TextureView src, bool isDepth)
+        {
+            _pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest);
+
+            if (isDepth)
+            {
+                _pipeline.SetProgram(_programDepthBlit);
+                _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, GAL.CompareOp.Always));
+            }
+            else
+            {
+                _pipeline.SetProgram(_programStencilBlit);
+                _pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
+            }
+
+            _pipeline.Draw(4, 1, 0, 0);
+
+            if (isDepth)
+            {
+                _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, GAL.CompareOp.Always));
+            }
+            else
+            {
+                _pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
+            }
+        }
+
+        private static StencilTestDescriptor CreateStencilTestDescriptor(bool enabled)
+        {
+            return new StencilTestDescriptor(
+                enabled,
+                GAL.CompareOp.Always,
+                GAL.StencilOp.Replace,
+                GAL.StencilOp.Replace,
+                GAL.StencilOp.Replace,
+                0,
+                0xff,
+                0xff,
+                GAL.CompareOp.Always,
+                GAL.StencilOp.Replace,
+                GAL.StencilOp.Replace,
+                GAL.StencilOp.Replace,
+                0,
+                0xff,
+                0xff);
+        }
+
         public void Clear(
             VulkanRenderer gd,
             Auto<DisposableImageView> dst,
@@ -993,6 +1185,8 @@ namespace Ryujinx.Graphics.Vulkan
                 _programConvertIndirectData.Dispose();
                 _programColorCopyToNonMs.Dispose();
                 _programColorDrawToMs.Dispose();
+                _programDepthBlit.Dispose();
+                _programStencilBlit?.Dispose();
                 _samplerNearest.Dispose();
                 _samplerLinear.Dispose();
                 _pipeline.Dispose();

+ 10 - 0
Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag

@@ -0,0 +1,10 @@
+#version 450 core
+
+layout (binding = 0, set = 2) uniform sampler2D texDepth;
+
+layout (location = 0) in vec2 tex_coord;
+
+void main()
+{
+    gl_FragDepth = texture(texDepth, tex_coord).r;
+}

+ 90 - 0
Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs

@@ -1459,5 +1459,95 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
             0x3B, 0x01, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0xE3, 0x00, 0x00, 0x00,
             0xF8, 0x00, 0x02, 0x00, 0xE5, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
         };
+
+        public static readonly byte[] DepthBlitFragmentShaderSource = new byte[]
+        {
+            0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x17, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+            0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+            0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00,
+            0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x46, 0x72, 0x61, 0x67, 0x44,
+            0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x74, 0x65, 0x78, 0x44, 0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
+            0x10, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F, 0x63, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00,
+            0x47, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+            0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0x19, 0x00, 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x17, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x17, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x0A, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00,
+            0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+            0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+            0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+        };
+
+        public static readonly byte[] StencilBlitFragmentShaderSource = new byte[]
+        {
+            0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x18, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+            0x95, 0x13, 0x00, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x53, 0x50, 0x56, 0x5F, 0x45, 0x58, 0x54, 0x5F,
+            0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x65, 0x6E, 0x63, 0x69, 0x6C, 0x5F, 0x65,
+            0x78, 0x70, 0x6F, 0x72, 0x74, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00,
+            0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x07, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
+            0x08, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0xA3, 0x13, 0x00, 0x00,
+            0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x09, 0x00,
+            0x47, 0x4C, 0x5F, 0x41, 0x52, 0x42, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74,
+            0x65, 0x6E, 0x63, 0x69, 0x6C, 0x5F, 0x65, 0x78, 0x70, 0x6F, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x46, 0x72, 0x61, 0x67, 0x53,
+            0x74, 0x65, 0x6E, 0x63, 0x69, 0x6C, 0x52, 0x65, 0x66, 0x41, 0x52, 0x42, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x53, 0x74, 0x65, 0x6E, 0x63,
+            0x69, 0x6C, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F,
+            0x63, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x96, 0x13, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
+            0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00,
+            0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x1B, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
+            0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+            0x11, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+            0x0D, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x17, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+            0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+        };
     }
 }

+ 12 - 0
Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag

@@ -0,0 +1,12 @@
+#version 450 core
+
+#extension GL_ARB_shader_stencil_export : require
+
+layout (binding = 0, set = 2) uniform isampler2D texStencil;
+
+layout (location = 0) in vec2 tex_coord;
+
+void main()
+{
+    gl_FragStencilRefARB = texture(texStencil, tex_coord).r;
+}

+ 5 - 199
Ryujinx.Graphics.Vulkan/TextureView.cs

@@ -362,21 +362,16 @@ namespace Ryujinx.Graphics.Vulkan
                         levels,
                         linearFilter);
 
-                    return;
-                }
-                else if (srcFormat == GAL.Format.D32FloatS8Uint && srcFormat == dstFormat && SupportsBlitFromD32FS8ToD32FAndS8())
-                {
-                    BlitDepthStencilWithBuffer(_gd, cbs, src, dst, srcRegion, dstRegion);
-
                     return;
                 }
             }
 
+            bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
+
             if (VulkanConfiguration.UseSlowSafeBlitOnAmd &&
                 (_gd.Vendor == Vendor.Amd || _gd.IsMoltenVk) &&
                 src.Info.Target == Target.Texture2D &&
-                dst.Info.Target == Target.Texture2D &&
-                !dst.Info.Format.IsDepthOrStencil())
+                dst.Info.Target == Target.Texture2D)
             {
                 _gd.HelperShader.Blit(
                     _gd,
@@ -387,6 +382,7 @@ namespace Ryujinx.Graphics.Vulkan
                     dst.VkFormat,
                     srcRegion,
                     dstRegion,
+                    isDepthOrStencil,
                     linearFilter);
 
                 return;
@@ -395,7 +391,7 @@ namespace Ryujinx.Graphics.Vulkan
             Auto<DisposableImage> srcImage;
             Auto<DisposableImage> dstImage;
 
-            if (dst.Info.Format.IsDepthOrStencil())
+            if (isDepthOrStencil)
             {
                 srcImage = src.Storage.CreateAliasedColorForDepthStorageUnsafe(srcFormat).GetImage();
                 dstImage = dst.Storage.CreateAliasedColorForDepthStorageUnsafe(dstFormat).GetImage();
@@ -426,189 +422,6 @@ namespace Ryujinx.Graphics.Vulkan
                 ImageAspectFlags.ColorBit);
         }
 
-        private static void BlitDepthStencilWithBuffer(
-            VulkanRenderer gd,
-            CommandBufferScoped cbs,
-            TextureView src,
-            TextureView dst,
-            Extents2D srcRegion,
-            Extents2D dstRegion)
-        {
-            int drBaseX = Math.Min(dstRegion.X1, dstRegion.X2);
-            int drBaseY = Math.Min(dstRegion.Y1, dstRegion.Y2);
-            int drWidth = Math.Abs(dstRegion.X2 - dstRegion.X1);
-            int drHeight = Math.Abs(dstRegion.Y2 - dstRegion.Y1);
-
-            var drOriginZero = new Extents2D(
-                dstRegion.X1 - drBaseX,
-                dstRegion.Y1 - drBaseY,
-                dstRegion.X2 - drBaseX,
-                dstRegion.Y2 - drBaseY);
-
-            var d32SrcStorageInfo = TextureStorage.NewCreateInfoWith(ref src._info, GAL.Format.D32Float, 4);
-            var d32DstStorageInfo = TextureStorage.NewCreateInfoWith(ref dst._info, GAL.Format.D32Float, 4, drWidth, drHeight);
-            var s8SrcStorageInfo = TextureStorage.NewCreateInfoWith(ref src._info, GAL.Format.S8Uint, 1);
-            var s8DstStorageInfo = TextureStorage.NewCreateInfoWith(ref dst._info, GAL.Format.S8Uint, 1, drWidth, drHeight);
-
-            using var d32SrcStorage = gd.CreateTextureStorage(d32SrcStorageInfo, src.Storage.ScaleFactor);
-            using var d32DstStorage = gd.CreateTextureStorage(d32DstStorageInfo, dst.Storage.ScaleFactor);
-            using var s8SrcStorage = gd.CreateTextureStorage(s8SrcStorageInfo, src.Storage.ScaleFactor);
-            using var s8DstStorage = gd.CreateTextureStorage(s8DstStorageInfo, dst.Storage.ScaleFactor);
-
-            void SlowBlit(TextureStorage srcTemp, TextureStorage dstTemp, ImageAspectFlags aspectFlags)
-            {
-                int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
-
-                int srcSize = 0;
-                int dstSize = 0;
-
-                for (int l = 0; l < levels; l++)
-                {
-                    srcSize += srcTemp.Info.GetMipSize2D(l);
-                    dstSize += dstTemp.Info.GetMipSize2D(l);
-                }
-
-                using var srcTempBuffer = gd.BufferManager.Create(gd, srcSize, deviceLocal: true);
-                using var dstTempBuffer = gd.BufferManager.Create(gd, dstSize, deviceLocal: true);
-
-                src.Storage.CopyFromOrToBuffer(
-                    cbs.CommandBuffer,
-                    srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
-                    src.GetImage().Get(cbs).Value,
-                    srcSize,
-                    to: true,
-                    0,
-                    0,
-                    src.FirstLayer,
-                    src.FirstLevel,
-                    1,
-                    levels,
-                    true,
-                    aspectFlags,
-                    false);
-
-                BufferHolder.InsertBufferBarrier(
-                    gd,
-                    cbs.CommandBuffer,
-                    srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
-                    AccessFlags.TransferWriteBit,
-                    AccessFlags.TransferReadBit,
-                    PipelineStageFlags.TransferBit,
-                    PipelineStageFlags.TransferBit,
-                    0,
-                    srcSize);
-
-                srcTemp.CopyFromOrToBuffer(
-                    cbs.CommandBuffer,
-                    srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
-                    srcTemp.GetImage().Get(cbs).Value,
-                    srcSize,
-                    to: false,
-                    0,
-                    0,
-                    0,
-                    0,
-                    1,
-                    levels,
-                    true,
-                    aspectFlags,
-                    false);
-
-                InsertImageBarrier(
-                    gd.Api,
-                    cbs.CommandBuffer,
-                    srcTemp.GetImage().Get(cbs).Value,
-                    AccessFlags.TransferWriteBit,
-                    AccessFlags.TransferReadBit,
-                    PipelineStageFlags.TransferBit,
-                    PipelineStageFlags.TransferBit,
-                    aspectFlags,
-                    0,
-                    0,
-                    1,
-                    levels);
-
-                TextureCopy.Blit(
-                    gd.Api,
-                    cbs.CommandBuffer,
-                    srcTemp.GetImage().Get(cbs).Value,
-                    dstTemp.GetImage().Get(cbs).Value,
-                    srcTemp.Info,
-                    dstTemp.Info,
-                    srcRegion,
-                    drOriginZero,
-                    0,
-                    0,
-                    0,
-                    0,
-                    1,
-                    levels,
-                    false,
-                    aspectFlags,
-                    aspectFlags);
-
-                InsertImageBarrier(
-                    gd.Api,
-                    cbs.CommandBuffer,
-                    dstTemp.GetImage().Get(cbs).Value,
-                    AccessFlags.TransferWriteBit,
-                    AccessFlags.TransferReadBit,
-                    PipelineStageFlags.TransferBit,
-                    PipelineStageFlags.TransferBit,
-                    aspectFlags,
-                    0,
-                    0,
-                    1,
-                    levels);
-
-                dstTemp.CopyFromOrToBuffer(
-                    cbs.CommandBuffer,
-                    dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
-                    dstTemp.GetImage().Get(cbs).Value,
-                    dstSize,
-                    to: true,
-                    0,
-                    0,
-                    0,
-                    0,
-                    1,
-                    levels,
-                    true,
-                    aspectFlags,
-                    false);
-
-                BufferHolder.InsertBufferBarrier(
-                    gd,
-                    cbs.CommandBuffer,
-                    dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
-                    AccessFlags.TransferWriteBit,
-                    AccessFlags.TransferReadBit,
-                    PipelineStageFlags.TransferBit,
-                    PipelineStageFlags.TransferBit,
-                    0,
-                    dstSize);
-
-                dst.Storage.CopyFromOrToBuffer(
-                    cbs.CommandBuffer,
-                    dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
-                    dst.GetImage().Get(cbs).Value,
-                    dstSize,
-                    to: false,
-                    drBaseX,
-                    drBaseY,
-                    dst.FirstLayer,
-                    dst.FirstLevel,
-                    1,
-                    levels,
-                    true,
-                    aspectFlags,
-                    false);
-            }
-
-            SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.DepthBit);
-            SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.StencilBit);
-        }
-
         public static unsafe void InsertImageBarrier(
             Vk api,
             CommandBuffer commandBuffer,
@@ -649,13 +462,6 @@ namespace Ryujinx.Graphics.Vulkan
                 memoryBarrier);
         }
 
-        private bool SupportsBlitFromD32FS8ToD32FAndS8()
-        {
-            var formatFeatureFlags = FormatFeatureFlags.BlitSrcBit | FormatFeatureFlags.BlitDstBit;
-            return _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.D32Float)  &&
-                   _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.S8Uint);
-        }
-
         public TextureView GetView(GAL.Format format)
         {
             if (format == Info.Format)

+ 1 - 0
Ryujinx.Graphics.Vulkan/VulkanInitialization.cs

@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
             "VK_EXT_fragment_shader_interlock",
             "VK_EXT_index_type_uint8",
             "VK_EXT_robustness2",
+            "VK_EXT_shader_stencil_export",
             "VK_KHR_shader_float16_int8",
             "VK_EXT_shader_subgroup_ballot",
             "VK_EXT_subgroup_size_control",

+ 1 - 0
Ryujinx.Graphics.Vulkan/VulkanRenderer.cs

@@ -263,6 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
                 supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"),
                 supportedExtensions.Contains("VK_EXT_subgroup_size_control"),
                 featuresShaderInt8.ShaderInt8,
+                supportedExtensions.Contains("VK_EXT_shader_stencil_export"),
                 supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
                 supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
                 features2.Features.MultiViewport,

+ 1 - 1
Ryujinx.Graphics.Vulkan/Window.cs

@@ -335,7 +335,7 @@ namespace Ryujinx.Graphics.Vulkan
             int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
             int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
 
-            _gd.HelperShader.Blit(
+            _gd.HelperShader.BlitColor(
                 _gd,
                 cbs,
                 view,