diff --git a/src/Ryujinx.Graphics.Metal/HelperShader.cs b/src/Ryujinx.Graphics.Metal/HelperShader.cs index 270a628dc..ed9a7f656 100644 --- a/src/Ryujinx.Graphics.Metal/HelperShader.cs +++ b/src/Ryujinx.Graphics.Metal/HelperShader.cs @@ -26,6 +26,10 @@ namespace Ryujinx.Graphics.Metal private readonly List _programsColorClear = new(); private readonly IProgram _programDepthStencilClear; private readonly IProgram _programStrideChange; + private readonly IProgram _programDepthBlit; + private readonly IProgram _programDepthBlitMs; + private readonly IProgram _programStencilBlit; + private readonly IProgram _programStencilBlitMs; private readonly EncoderState _helperShaderState = new(); @@ -53,7 +57,7 @@ namespace Ryujinx.Graphics.Metal _programColorBlitMs = new Program( [ new ShaderSource(blitMsSource, ShaderStage.Fragment, TargetLanguage.Msl), - new ShaderSource(blitMsSource, ShaderStage.Vertex, TargetLanguage.Msl) + new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl) ], blitResourceLayout, device); var colorClearResourceLayout = new ResourceLayoutBuilder() @@ -87,6 +91,34 @@ namespace Ryujinx.Graphics.Metal [ new ShaderSource(strideChangeSource, ShaderStage.Compute, TargetLanguage.Msl) ], strideChangeResourceLayout, device, new ComputeSize(64, 1, 1)); + + var depthBlitSource = ReadMsl("DepthBlit.metal"); + _programDepthBlit = new Program( + [ + new ShaderSource(depthBlitSource, ShaderStage.Fragment, TargetLanguage.Msl), + new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl) + ], blitResourceLayout, device); + + var depthBlitMsSource = ReadMsl("DepthBlitMs.metal"); + _programDepthBlitMs = new Program( + [ + new ShaderSource(depthBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl), + new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl) + ], blitResourceLayout, device); + + var stencilBlitSource = ReadMsl("StencilBlit.metal"); + _programStencilBlit = new Program( + [ + new ShaderSource(stencilBlitSource, ShaderStage.Fragment, TargetLanguage.Msl), + new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl) + ], blitResourceLayout, device); + + var stencilBlitMsSource = ReadMsl("StencilBlitMs.metal"); + _programStencilBlitMs = new Program( + [ + new ShaderSource(stencilBlitMsSource, ShaderStage.Fragment, TargetLanguage.Msl), + new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl) + ], blitResourceLayout, device); } private static string ReadMsl(string fileName) @@ -193,6 +225,155 @@ namespace Ryujinx.Graphics.Metal _pipeline.SwapState(null); } + public unsafe void BlitDepthStencil( + CommandBufferScoped cbs, + Texture src, + Texture dst, + Extents2D srcRegion, + Extents2D dstRegion) + { + _pipeline.SwapState(_helperShaderState); + + const int RegionBufferSize = 16; + + Span region = stackalloc float[RegionBufferSize / sizeof(float)]; + + region[0] = srcRegion.X1 / (float)src.Width; + region[1] = srcRegion.X2 / (float)src.Width; + region[2] = srcRegion.Y1 / (float)src.Height; + region[3] = srcRegion.Y2 / (float)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]); + } + + using var buffer = _renderer.BufferManager.ReserveOrCreate(cbs, RegionBufferSize); + buffer.Holder.SetDataUnchecked(buffer.Offset, region); + _pipeline.SetUniformBuffers([new BufferAssignment(0, buffer.Range)]); + + Span viewports = stackalloc Viewport[16]; + + var rect = new Rectangle( + 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 Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + Span> scissors = stackalloc Rectangle[16]; + + scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); + + _pipeline.SetRenderTargets([], dst); + _pipeline.SetScissors(scissors); + _pipeline.SetViewports(viewports); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + + if (src.Info.Format is + Format.D16Unorm or + Format.D32Float or + Format.X8UintD24Unorm or + Format.D24UnormS8Uint or + Format.D32FloatS8Uint or + Format.S8UintD24Unorm) + { + var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth); + + BlitDepthStencilDraw(depthTexture, isDepth: true); + + if (depthTexture != src) + { + depthTexture.Release(); + } + } + + if (src.Info.Format is + Format.S8Uint or + Format.D24UnormS8Uint or + Format.D32FloatS8Uint or + Format.S8UintD24Unorm) + { + var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil); + + BlitDepthStencilDraw(stencilTexture, isDepth: false); + + if (stencilTexture != src) + { + stencilTexture.Release(); + } + } + + // Restore previous state + _pipeline.SwapState(null); + } + + private static Texture CreateDepthOrStencilView(Texture depthStencilTexture, DepthStencilMode depthStencilMode) + { + if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode) + { + return depthStencilTexture; + } + + return (Texture)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(Texture src, bool isDepth) + { + if (isDepth) + { + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); + } + else + { + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit); + _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); + } + + _pipeline.Draw(4, 1, 0, 0); + + if (isDepth) + { + _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); + } + else + { + _pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); + } + } + public unsafe void DrawTexture( ITexture src, ISampler srcSampler, diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index 00020267b..a20d33e00 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -197,9 +197,17 @@ namespace Ryujinx.Graphics.Metal Texture dst, Extents2D srcRegion, Extents2D dstRegion, + bool isDepthOrStencil, bool linearFilter) { - _renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, linearFilter); + if (isDepthOrStencil) + { + _renderer.HelperShader.BlitDepthStencil(Cbs, src, dst, srcRegion, dstRegion); + } + else + { + _renderer.HelperShader.BlitColor(Cbs, src, dst, srcRegion, dstRegion, linearFilter); + } } public void Barrier() @@ -258,10 +266,8 @@ namespace Ryujinx.Graphics.Metal { var depthStencil = _encoderStateManager.DepthStencil; - // TODO: Remove workaround for Wonder which has an invalid texture due to unsupported format if (depthStencil == null) { - Logger.Warning?.PrintMsg(LogClass.Gpu, "Attempted to clear invalid depth stencil!"); return; } diff --git a/src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj b/src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj index 8b6de05f3..8cbdc8bcb 100644 --- a/src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj +++ b/src/Ryujinx.Graphics.Metal/Ryujinx.Graphics.Metal.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal b/src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal index b8e01ffe8..09c5d76ca 100644 --- a/src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal +++ b/src/Ryujinx.Graphics.Metal/Shaders/BlitMs.metal @@ -7,35 +7,11 @@ struct CopyVertexOut { float2 uv; }; -struct TexCoords { - float data[4]; -}; - -struct ConstantBuffers { - constant TexCoords* tex_coord; -}; - struct Textures { texture2d_ms texture; }; -vertex CopyVertexOut vertexMain(uint vid [[vertex_id]], - constant ConstantBuffers &constant_buffers [[buffer(20)]]) { - CopyVertexOut out; - - int low = vid & 1; - int high = vid >> 1; - out.uv.x = constant_buffers.tex_coord->data[low]; - out.uv.y = constant_buffers.tex_coord->data[2 + high]; - out.position.x = (float(low) - 0.5f) * 2.0f; - out.position.y = (float(high) - 0.5f) * 2.0f; - out.position.z = 0.0f; - out.position.w = 1.0f; - - return out; -} - fragment float4 fragmentMain(CopyVertexOut in [[stage_in]], constant Textures &textures [[buffer(22)]], uint sample_id [[sample_id]]) { diff --git a/src/Ryujinx.Graphics.Metal/Shaders/DepthBlit.metal b/src/Ryujinx.Graphics.Metal/Shaders/DepthBlit.metal new file mode 100644 index 000000000..c6b547be8 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/Shaders/DepthBlit.metal @@ -0,0 +1,27 @@ +#include + +using namespace metal; + +struct CopyVertexOut { + float4 position [[position]]; + float2 uv; +}; + +struct Textures +{ + texture2d texture; + sampler sampler; +}; + +struct FragmentOut { + float depth [[depth(any)]]; +}; + +fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]], + constant Textures &textures [[buffer(22)]]) { + FragmentOut out; + + out.depth = textures.texture.sample(textures.sampler, in.uv).r; + + return out; +} diff --git a/src/Ryujinx.Graphics.Metal/Shaders/DepthBlitMs.metal b/src/Ryujinx.Graphics.Metal/Shaders/DepthBlitMs.metal new file mode 100644 index 000000000..9fb5e6e50 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/Shaders/DepthBlitMs.metal @@ -0,0 +1,29 @@ +#include + +using namespace metal; + +struct CopyVertexOut { + float4 position [[position]]; + float2 uv; +}; + +struct Textures +{ + texture2d_ms texture; +}; + +struct FragmentOut { + float depth [[depth(any)]]; +}; + +fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]], + constant Textures &textures [[buffer(22)]], + uint sample_id [[sample_id]]) { + FragmentOut out; + + uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height()); + uint2 tex_coord = uint2(in.uv * float2(tex_size)); + out.depth = textures.texture.read(tex_coord, sample_id).r; + + return out; +} diff --git a/src/Ryujinx.Graphics.Metal/Shaders/StencilBlit.metal b/src/Ryujinx.Graphics.Metal/Shaders/StencilBlit.metal new file mode 100644 index 000000000..da7c6e90a --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/Shaders/StencilBlit.metal @@ -0,0 +1,27 @@ +#include + +using namespace metal; + +struct CopyVertexOut { + float4 position [[position]]; + float2 uv; +}; + +struct Textures +{ + texture2d texture; + sampler sampler; +}; + +struct FragmentOut { + uint stencil [[stencil]]; +}; + +fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]], + constant Textures &textures [[buffer(22)]]) { + FragmentOut out; + + out.stencil = textures.texture.sample(textures.sampler, in.uv).r; + + return out; +} diff --git a/src/Ryujinx.Graphics.Metal/Shaders/StencilBlitMs.metal b/src/Ryujinx.Graphics.Metal/Shaders/StencilBlitMs.metal new file mode 100644 index 000000000..3948eacc7 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/Shaders/StencilBlitMs.metal @@ -0,0 +1,29 @@ +#include + +using namespace metal; + +struct CopyVertexOut { + float4 position [[position]]; + float2 uv; +}; + +struct Textures +{ + texture2d_ms texture; +}; + +struct FragmentOut { + uint stencil [[stencil]]; +}; + +fragment FragmentOut fragmentMain(CopyVertexOut in [[stage_in]], + constant Textures &textures [[buffer(22)]], + uint sample_id [[sample_id]]) { + FragmentOut out; + + uint2 tex_size = uint2(textures.texture.get_width(), textures.texture.get_height()); + uint2 tex_coord = uint2(in.uv * float2(tex_size)); + out.stencil = textures.texture.read(tex_coord, sample_id).r; + + return out; +} diff --git a/src/Ryujinx.Graphics.Metal/Texture.cs b/src/Ryujinx.Graphics.Metal/Texture.cs index 9392b5e6d..5ca92eb1e 100644 --- a/src/Ryujinx.Graphics.Metal/Texture.cs +++ b/src/Ryujinx.Graphics.Metal/Texture.cs @@ -250,7 +250,9 @@ namespace Ryujinx.Graphics.Metal var dst = (Texture)destination; - Pipeline.Blit(this, dst, srcRegion, dstRegion, linearFilter); + bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil(); + + Pipeline.Blit(this, dst, srcRegion, dstRegion, isDepthOrStencil, linearFilter); } public void CopyTo(BufferRange range, int layer, int level, int stride)