diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index dd1a5e071..a0af3c33f 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using SharpMetal.Foundation; @@ -15,14 +16,11 @@ namespace Ryujinx.Graphics.Metal private readonly MTLCommandQueue _mtlCommandQueue; private MTLCommandBuffer _commandBuffer; - private MTLRenderCommandEncoder _renderCommandEncoder; - private MTLRenderPipelineState _renderPipelineState; - private MTLBlitCommandEncoder _blitCommandEncoder; + private MTLCommandEncoder _currentEncoder; - public MTLRenderCommandEncoder RenderCommandEncoder => _renderCommandEncoder; - public MTLBlitCommandEncoder BlitCommandEncoder => _blitCommandEncoder; + public MTLCommandEncoder CurrentEncoder; - private PrimitiveTopology _topology; + private RenderEncoderState _renderEncoderState; private MTLBuffer _indexBuffer; private MTLIndexType _indexType; @@ -35,35 +33,56 @@ namespace Ryujinx.Graphics.Metal var renderPipelineDescriptor = new MTLRenderPipelineDescriptor(); var error = new NSError(IntPtr.Zero); - _renderPipelineState = _device.NewRenderPipelineState(renderPipelineDescriptor, ref error); + _renderEncoderState = new(_device.NewRenderPipelineState(renderPipelineDescriptor, ref error)); if (error != IntPtr.Zero) { - // throw new Exception($"Failed to create render pipeline state! {StringHelp}"); - throw new Exception($"Failed to create render pipeline state!"); + Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}"); } - CreateCommandBuffer(); + _commandBuffer = _mtlCommandQueue.CommandBuffer(); + } + + public void EndCurrentPass() + { + if (_currentEncoder != null) + { + _currentEncoder.EndEncoding(); + _currentEncoder = null; + } + } + + public MTLRenderCommandEncoder BeginRenderPass() + { + EndCurrentPass(); + + var descriptor = new MTLRenderPassDescriptor { }; + var renderCommandEncoder = _commandBuffer.RenderCommandEncoder(descriptor); + _renderEncoderState.SetEncoderState(renderCommandEncoder); + + _currentEncoder = renderCommandEncoder; + return renderCommandEncoder; + } + + public MTLBlitCommandEncoder BeginBlitPass() + { + EndCurrentPass(); + + var descriptor = new MTLBlitPassDescriptor { }; + var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor); + + _currentEncoder = blitCommandEncoder; + return blitCommandEncoder; } public void Present() { - _renderCommandEncoder.EndEncoding(); - _blitCommandEncoder.EndEncoding(); + EndCurrentPass(); + // TODO: Give command buffer a valid MTLDrawable // _commandBuffer.PresentDrawable(); _commandBuffer.Commit(); - CreateCommandBuffer(); - } - - public void CreateCommandBuffer() - { _commandBuffer = _mtlCommandQueue.CommandBuffer(); - - _renderCommandEncoder = _commandBuffer.RenderCommandEncoder(new MTLRenderPassDescriptor()); - _renderCommandEncoder.SetRenderPipelineState(_renderPipelineState); - - _blitCommandEncoder = _commandBuffer.BlitCommandEncoder(new MTLBlitPassDescriptor()); } public void Barrier() @@ -73,7 +92,27 @@ namespace Ryujinx.Graphics.Metal public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) { - throw new NotImplementedException(); + MTLBlitCommandEncoder blitCommandEncoder; + + if (_currentEncoder is MTLBlitCommandEncoder encoder) + { + blitCommandEncoder = encoder; + } + else + { + blitCommandEncoder = BeginBlitPass(); + } + + // Might need a closer look, range's count, lower, and upper bound + // must be a multiple of 4 + MTLBuffer mtlBuffer = new(Unsafe.As(ref destination)); + blitCommandEncoder.FillBuffer(mtlBuffer, + new NSRange + { + location = (ulong)offset, + length = (ulong)size + }, + (byte)value); } public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) @@ -104,18 +143,40 @@ namespace Ryujinx.Graphics.Metal public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - // TODO: Support topology re-indexing to provide support for TriangleFans - var _primitiveType = _topology.Convert(); + MTLRenderCommandEncoder renderCommandEncoder; - _renderCommandEncoder.DrawPrimitives(_primitiveType, (ulong)firstVertex, (ulong)vertexCount, (ulong)instanceCount, (ulong)firstInstance); + if (_currentEncoder is MTLRenderCommandEncoder encoder) + { + renderCommandEncoder = encoder; + } + else + { + renderCommandEncoder = BeginRenderPass(); + } + + // TODO: Support topology re-indexing to provide support for TriangleFans + var primitiveType = _renderEncoderState.Topology.Convert(); + + renderCommandEncoder.DrawPrimitives(primitiveType, (ulong)firstVertex, (ulong)vertexCount, (ulong)instanceCount, (ulong)firstInstance); } public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) { - // TODO: Support topology re-indexing to provide support for TriangleFans - var _primitiveType = _topology.Convert(); + MTLRenderCommandEncoder renderCommandEncoder; - _renderCommandEncoder.DrawIndexedPrimitives(_primitiveType, (ulong)indexCount, _indexType, _indexBuffer, _indexBufferOffset, (ulong)instanceCount, firstVertex, (ulong)firstInstance); + if (_currentEncoder is MTLRenderCommandEncoder encoder) + { + renderCommandEncoder = encoder; + } + else + { + renderCommandEncoder = BeginRenderPass(); + } + + // TODO: Support topology re-indexing to provide support for TriangleFans + var primitiveType = _renderEncoderState.Topology.Convert(); + + renderCommandEncoder.DrawIndexedPrimitives(primitiveType, (ulong)indexCount, _indexType, _indexBuffer, _indexBufferOffset, (ulong)instanceCount, firstVertex, (ulong)firstInstance); } public void DrawIndexedIndirect(BufferRange indirectBuffer) @@ -180,12 +241,26 @@ namespace Ryujinx.Graphics.Metal public void SetFaceCulling(bool enable, Face face) { - _renderCommandEncoder.SetCullMode(enable ? face.Convert() : MTLCullMode.None); + var cullMode = enable ? face.Convert() : MTLCullMode.None; + + if (_currentEncoder is MTLRenderCommandEncoder renderCommandEncoder) + { + renderCommandEncoder.SetCullMode(cullMode); + } + + _renderEncoderState.CullMode = cullMode; } public void SetFrontFace(FrontFace frontFace) { - _renderCommandEncoder.SetFrontFacingWinding(frontFace.Convert()); + var winding = frontFace.Convert(); + + if (_currentEncoder is MTLRenderCommandEncoder renderCommandEncoder) + { + renderCommandEncoder.SetFrontFacingWinding(winding); + } + + _renderEncoderState.Winding = winding; } public void SetIndexBuffer(BufferRange buffer, IndexType type) @@ -244,7 +319,7 @@ namespace Ryujinx.Graphics.Metal public void SetPrimitiveTopology(PrimitiveTopology topology) { - _topology = topology; + _renderEncoderState.Topology = topology; } public void SetProgram(IProgram program) diff --git a/src/Ryujinx.Graphics.Metal/RenderEncoderState.cs b/src/Ryujinx.Graphics.Metal/RenderEncoderState.cs new file mode 100644 index 000000000..5f078ded6 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/RenderEncoderState.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL; +using SharpMetal.Metal; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.Metal +{ + [SupportedOSPlatform("macos")] + struct RenderEncoderState + { + public MTLRenderPipelineState RenderPipelineState; + public PrimitiveTopology Topology = PrimitiveTopology.Triangles; + public MTLCullMode CullMode = MTLCullMode.None; + public MTLWinding Winding = MTLWinding.Clockwise; + + public RenderEncoderState(MTLRenderPipelineState renderPipelineState) + { + RenderPipelineState = renderPipelineState; + } + + public void SetEncoderState(MTLRenderCommandEncoder renderCommandEncoder) + { + renderCommandEncoder.SetRenderPipelineState(RenderPipelineState); + renderCommandEncoder.SetCullMode(CullMode); + renderCommandEncoder.SetFrontFacingWinding(Winding); + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/StringHelper.cs b/src/Ryujinx.Graphics.Metal/StringHelper.cs new file mode 100644 index 000000000..21cd474dc --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/StringHelper.cs @@ -0,0 +1,30 @@ +using SharpMetal.Foundation; +using SharpMetal.ObjectiveCCore; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.Metal +{ + [SupportedOSPlatform("macos")] + public class StringHelper + { + public static NSString NSString(string source) + { + return new(ObjectiveC.IntPtr_objc_msgSend(new ObjectiveCClass("NSString"), "stringWithUTF8String:", source)); + } + + public static unsafe string String(NSString source) + { + char[] sourceBuffer = new char[source.Length]; + fixed (char* pSourceBuffer = sourceBuffer) + { + ObjectiveC.bool_objc_msgSend(source, + "getCString:maxLength:encoding:", + pSourceBuffer, + source.MaximumLengthOfBytes(NSStringEncoding.UTF16) + 1, + (ulong)NSStringEncoding.UTF16); + } + + return new string(sourceBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/Texture.cs b/src/Ryujinx.Graphics.Metal/Texture.cs index b03fb26c8..c976dba6d 100644 --- a/src/Ryujinx.Graphics.Metal/Texture.cs +++ b/src/Ryujinx.Graphics.Metal/Texture.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Metal private readonly MTLDevice _device; public MTLTexture MTLTexture; - public TextureCreateInfo Info => Info; + public TextureCreateInfo Info => _info; public int Width => Info.Width; public int Height => Info.Height; @@ -46,9 +46,20 @@ namespace Ryujinx.Graphics.Metal public void CopyTo(ITexture destination, int firstLayer, int firstLevel) { + MTLBlitCommandEncoder blitCommandEncoder; + + if (_pipeline.CurrentEncoder is MTLBlitCommandEncoder encoder) + { + blitCommandEncoder = encoder; + } + else + { + blitCommandEncoder = _pipeline.BeginBlitPass(); + } + if (destination is Texture destinationTexture) { - _pipeline.BlitCommandEncoder.CopyFromTexture( + blitCommandEncoder.CopyFromTexture( MTLTexture, (ulong)firstLayer, (ulong)firstLevel, @@ -62,9 +73,20 @@ namespace Ryujinx.Graphics.Metal public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) { + MTLBlitCommandEncoder blitCommandEncoder; + + if (_pipeline.CurrentEncoder is MTLBlitCommandEncoder encoder) + { + blitCommandEncoder = encoder; + } + else + { + blitCommandEncoder = _pipeline.BeginBlitPass(); + } + if (destination is Texture destinationTexture) { - _pipeline.BlitCommandEncoder.CopyFromTexture( + blitCommandEncoder.CopyFromTexture( MTLTexture, (ulong)srcLayer, (ulong)srcLevel, @@ -111,25 +133,83 @@ namespace Ryujinx.Graphics.Metal public void SetData(SpanOrArray data, int layer, int level) { - throw new NotImplementedException(); + MTLBlitCommandEncoder blitCommandEncoder; + + if (_pipeline.CurrentEncoder is MTLBlitCommandEncoder encoder) + { + blitCommandEncoder = encoder; + } + else + { + blitCommandEncoder = _pipeline.BeginBlitPass(); + } + + ulong bytesPerRow = (ulong)Info.GetMipStride(level); + ulong bytesPerImage = 0; + if (MTLTexture.TextureType == MTLTextureType.Type3D) + { + bytesPerImage = bytesPerRow * (ulong)Info.Height; + } + + unsafe + { + var dataSpan = data.Span; + var mtlBuffer = _device.NewBuffer((ulong)dataSpan.Length, MTLResourceOptions.ResourceStorageModeShared); + var bufferSpan = new Span(mtlBuffer.Contents.ToPointer(), dataSpan.Length); + dataSpan.CopyTo(bufferSpan); + + blitCommandEncoder.CopyFromBuffer( + mtlBuffer, + 0, + bytesPerRow, + bytesPerImage, + new MTLSize { width = MTLTexture.Width, height = MTLTexture.Height }, + MTLTexture, + (ulong)layer, + (ulong)level, + new MTLOrigin() + ); + } } - public unsafe void SetData(SpanOrArray data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { - // TODO: Figure out bytesPerRow - // For an ordinary or packed pixel format, the stride, in bytes, between rows of source data. - // For a compressed pixel format, the stride is the number of bytes from the beginning of one row of blocks to the beginning of the next. - if (MTLTexture.IsSparse) - ulong bytesPerRow = 0; - var mtlRegion = new MTLRegion - { - origin = new MTLOrigin { x = (ulong)region.X, y = (ulong)region.Y }, - size = new MTLSize { width = (ulong)region.Width, height = (ulong)region.Height }, - }; + MTLBlitCommandEncoder blitCommandEncoder; - fixed (byte* pData = data.Span) + if (_pipeline.CurrentEncoder is MTLBlitCommandEncoder encoder) { - MTLTexture.ReplaceRegion(mtlRegion, (ulong)level, (ulong)layer, new IntPtr(pData), bytesPerRow, 0); + blitCommandEncoder = encoder; + } + else + { + blitCommandEncoder = _pipeline.BeginBlitPass(); + } + + ulong bytesPerRow = (ulong)Info.GetMipStride(level); + ulong bytesPerImage = 0; + if (MTLTexture.TextureType == MTLTextureType.Type3D) + { + bytesPerImage = bytesPerRow * (ulong)Info.Height; + } + + unsafe + { + var dataSpan = data.Span; + var mtlBuffer = _device.NewBuffer((ulong)dataSpan.Length, MTLResourceOptions.ResourceStorageModeShared); + var bufferSpan = new Span(mtlBuffer.Contents.ToPointer(), dataSpan.Length); + dataSpan.CopyTo(bufferSpan); + + blitCommandEncoder.CopyFromBuffer( + mtlBuffer, + 0, + bytesPerRow, + bytesPerImage, + new MTLSize { width = (ulong)region.Width, height = (ulong)region.Height }, + MTLTexture, + (ulong)layer, + (ulong)level, + new MTLOrigin { x = (ulong)region.X, y = (ulong)region.Y } + ); } }