diff --git a/src/Ryujinx.Graphics.Metal/EnumConversion.cs b/src/Ryujinx.Graphics.Metal/EnumConversion.cs index 120c2d71e..0e23d8804 100644 --- a/src/Ryujinx.Graphics.Metal/EnumConversion.cs +++ b/src/Ryujinx.Graphics.Metal/EnumConversion.cs @@ -182,6 +182,20 @@ namespace Ryujinx.Graphics.Metal }; } + public static MTLTextureSwizzle Convert(this SwizzleComponent swizzleComponent) + { + return swizzleComponent switch + { + SwizzleComponent.Zero => MTLTextureSwizzle.Zero, + SwizzleComponent.One => MTLTextureSwizzle.One, + SwizzleComponent.Red => MTLTextureSwizzle.Red, + SwizzleComponent.Green => MTLTextureSwizzle.Green, + SwizzleComponent.Blue => MTLTextureSwizzle.Blue, + SwizzleComponent.Alpha => MTLTextureSwizzle.Alpha, + _ => LogInvalidAndReturn(swizzleComponent, nameof(SwizzleComponent), MTLTextureSwizzle.Zero), + }; + } + private static T2 LogInvalidAndReturn(T1 value, string name, T2 defaultValue = default) { Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}."); diff --git a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs index 0d8fd1f92..48ddfba7a 100644 --- a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs +++ b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs @@ -1,8 +1,8 @@ using Ryujinx.Common.Configuration; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader.Translation; +using SharpMetal.Metal; using System; -using SharpMetal; using System.Runtime.CompilerServices; using System.Runtime.Versioning; @@ -28,10 +28,10 @@ namespace Ryujinx.Graphics.Metal public void Initialize(GraphicsDebugLevel logLevel) { - _device = MTLDevice.MTLCreateSystemDefaultDevice(); - Queue = _device.NewCommandQueueWithMaxCommandBufferCount(Constants.MaxCommandBuffersPerQueue); + _device = MTLDevice.CreateSystemDefaultDevice(); + Queue = _device.NewCommandQueue(); - var commandBuffer = Queue.CommandBufferWithDescriptor(new MTLCommandBufferDescriptor { RetainedReferences = true }); + var commandBuffer = Queue.CommandBuffer(); _pipeline = new Pipeline(_device, commandBuffer); } @@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Metal { BufferCount++; - var buffer = _device.NewBufferWithBytesLengthOptions(pointer, (ulong)size, MTLResourceOptions.StorageModeShared); + var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared); var bufferPtr = buffer.NativePtr; return Unsafe.As(ref bufferPtr); } @@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.Metal { BufferCount++; - var buffer = _device.NewBufferWithLengthOptions((ulong)size, MTLResourceOptions.StorageModeShared); + var buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared); var bufferPtr = buffer.NativePtr; return Unsafe.As(ref bufferPtr); } @@ -74,7 +74,7 @@ namespace Ryujinx.Graphics.Metal { (MTLSamplerMinMagFilter minFilter, MTLSamplerMipFilter mipFilter) = info.MinFilter.Convert(); - var sampler = _device.CreateSamplerState(new MTLSamplerDescriptor + var sampler = _device.NewSamplerState(new MTLSamplerDescriptor { BorderColor = MTLSamplerBorderColor.TransparentBlack, MinFilter = minFilter, @@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Metal RAddressMode = info.AddressP.Convert() }); - throw new NotImplementedException(); + return new Sampler(sampler); } - public ITexture CreateTexture(TextureCreateInfo info, float scale) + public ITexture CreateTexture(TextureCreateInfo info) { MTLTextureDescriptor descriptor = new() { @@ -105,10 +105,10 @@ namespace Ryujinx.Graphics.Metal SampleCount = (ulong)info.Samples, }; - return CreateTextureView(info, scale); + return CreateTextureView(info); } - internal TextureView CreateTextureView(TextureCreateInfo info, float scale) + internal Texture CreateTextureView(TextureCreateInfo info) { throw new NotImplementedException(); } @@ -160,17 +160,21 @@ namespace Ryujinx.Graphics.Metal supportsFragmentShaderOrderingIntel: false, supportsGeometryShader: false, supportsGeometryShaderPassthrough: false, + supportsTransformFeedback: false, supportsImageLoadFormatted: false, supportsLayerVertexTessellation: false, supportsMismatchingViewFormat: true, supportsCubemapView: true, supportsNonConstantTextureOffset: false, supportsShaderBallot: false, + supportsShaderBarrierDivergence: false, + supportsShaderFloat64: false, supportsTextureShadowLod: false, supportsViewportIndexVertexTessellation: false, supportsViewportMask: false, supportsViewportSwizzle: false, supportsIndirectParameters: true, + supportsDepthClipControl: false, maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage, maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage, maximumTexturesPerStage: Constants.MaxTexturesPerStage, @@ -212,7 +216,7 @@ namespace Ryujinx.Graphics.Metal throw new NotImplementedException(); } - public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, bool hostReserved) + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved) { throw new NotImplementedException(); } @@ -243,4 +247,4 @@ namespace Ryujinx.Graphics.Metal _pipeline.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index c703a01a1..ff111a2ec 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -1,7 +1,9 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; +using SharpMetal.Foundation; +using SharpMetal.Metal; using System; -using SharpMetal; +using System.Runtime.CompilerServices; using System.Runtime.Versioning; namespace Ryujinx.Graphics.Metal @@ -9,16 +11,30 @@ namespace Ryujinx.Graphics.Metal [SupportedOSPlatform("macos")] public class Pipeline : IPipeline, IDisposable { + private MTLDevice _device; private MTLCommandBuffer _commandBuffer; private MTLRenderCommandEncoder _renderCommandEncoder; + private PrimitiveTopology _topology; + + private MTLBuffer _indexBuffer; + private MTLIndexType _indexType; + private ulong _indexBufferOffset; + public Pipeline(MTLDevice device, MTLCommandBuffer commandBuffer) { var renderPipelineDescriptor = new MTLRenderPipelineDescriptor(); - var renderPipelineState = device.CreateRenderPipelineState(renderPipelineDescriptor, out NSError _); + var error = new NSError(IntPtr.Zero); + _device = device; + var renderPipelineState = _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!"); + } _commandBuffer = commandBuffer; - _renderCommandEncoder = _commandBuffer.CreateRenderCommandEncoder(new MTLRenderPassDescriptor()); + _renderCommandEncoder = _commandBuffer.RenderCommandEncoder(new MTLRenderPassDescriptor()); _renderCommandEncoder.SetRenderPipelineState(renderPipelineState); } @@ -45,7 +61,7 @@ namespace Ryujinx.Graphics.Metal public void CommandBufferBarrier() { - throw new NotImplementedException(); + // TODO: Only required for MTLHeap or untracked resources } public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) @@ -60,12 +76,18 @@ namespace Ryujinx.Graphics.Metal public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - throw new NotImplementedException(); + // TODO: Support topology re-indexing to provide support for TriangleFans + var _primitiveType = _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) { - throw new NotImplementedException(); + // TODO: Support topology re-indexing to provide support for TriangleFans + var _primitiveType = _topology.Convert(); + + _renderCommandEncoder.DrawIndexedPrimitives(_primitiveType, (ulong)indexCount, _indexType, _indexBuffer, _indexBufferOffset, (ulong)instanceCount, firstVertex, (ulong)firstInstance); } public void DrawIndexedIndirect(BufferRange indirectBuffer) @@ -95,7 +117,7 @@ namespace Ryujinx.Graphics.Metal public void SetAlphaTest(bool enable, float reference, CompareOp op) { - throw new NotImplementedException(); + // Metal does not support alpha test. } public void SetBlendState(AdvancedBlendDescriptor blend) @@ -130,17 +152,23 @@ namespace Ryujinx.Graphics.Metal public void SetFaceCulling(bool enable, Face face) { - throw new NotImplementedException(); + _renderCommandEncoder.SetCullMode(enable ? face.Convert() : MTLCullMode.None); } public void SetFrontFace(FrontFace frontFace) { - throw new NotImplementedException(); + _renderCommandEncoder.SetFrontFacingWinding(frontFace.Convert()); } public void SetIndexBuffer(BufferRange buffer, IndexType type) { - throw new NotImplementedException(); + if (buffer.Handle != BufferHandle.Null) + { + _indexType = type.Convert(); + _indexBufferOffset = (ulong)buffer.Offset; + var handle = buffer.Handle; + _indexBuffer = new(Unsafe.As(ref handle)); + } } public void SetImage(int binding, ITexture texture, Format imageFormat) @@ -150,12 +178,12 @@ namespace Ryujinx.Graphics.Metal public void SetLineParameters(float width, bool smooth) { - throw new NotImplementedException(); + // Not supported in Metal } public void SetLogicOpState(bool enable, LogicalOp op) { - throw new NotImplementedException(); + // Not supported in Metal } public void SetMultisampleState(MultisampleDescriptor multisample) @@ -175,17 +203,20 @@ namespace Ryujinx.Graphics.Metal public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode) { - throw new NotImplementedException(); + // Not supported in Metal } public void SetPrimitiveRestart(bool enable, int index) { - throw new NotImplementedException(); + // TODO: Supported for LineStrip and TriangleStrip + // https://github.com/gpuweb/gpuweb/issues/1220#issuecomment-732483263 + // https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515520-drawindexedprimitives + // https://stackoverflow.com/questions/70813665/how-to-render-multiple-trianglestrips-using-metal } public void SetPrimitiveTopology(PrimitiveTopology topology) { - throw new NotImplementedException(); + _topology = topology; } public void SetProgram(IProgram program) @@ -198,11 +229,6 @@ namespace Ryujinx.Graphics.Metal throw new NotImplementedException(); } - public void SetRenderTargetScale(float scale) - { - throw new NotImplementedException(); - } - public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) { throw new NotImplementedException(); @@ -213,14 +239,33 @@ namespace Ryujinx.Graphics.Metal throw new NotImplementedException(); } - public void SetScissors(ReadOnlySpan> regions) + public unsafe void SetScissors(ReadOnlySpan> regions) { - throw new NotImplementedException(); + // TODO: Test max allowed scissor rects on device + var mtlScissorRects = new MTLScissorRect[regions.Length]; + + for (int i = 0; i < regions.Length; i++) + { + var region = regions[i]; + mtlScissorRects[i] = new MTLScissorRect + { + height = (ulong)region.Height, + width = (ulong)region.Width, + x = (ulong)region.X, + y = (ulong)region.Y + }; + } + + fixed (MTLScissorRect* pMtlScissorRects = mtlScissorRects) + { + // TODO: Fix this function which currently wont accept pointer as intended + // _renderCommandEncoder.SetScissorRects(pMtlScissorRects, regions.Length); + } } public void SetStencilTest(StencilTestDescriptor stencilTest) { - throw new NotImplementedException(); + // TODO } public void SetStorageBuffers(ReadOnlySpan buffers) @@ -253,9 +298,30 @@ namespace Ryujinx.Graphics.Metal throw new NotImplementedException(); } - public void SetViewports(ReadOnlySpan viewports, bool disableTransform) + public unsafe void SetViewports(ReadOnlySpan viewports) { - throw new NotImplementedException(); + // TODO: Test max allowed viewports on device + var mtlViewports = new MTLViewport[viewports.Length]; + + for (int i = 0; i < viewports.Length; i++) + { + var viewport = viewports[i]; + mtlViewports[i] = new MTLViewport + { + originX = viewport.Region.X, + originY = viewport.Region.Y, + width = viewport.Region.Width, + height = viewport.Region.Height, + znear = viewport.DepthNear, + zfar = viewport.DepthFar + }; + } + + fixed (MTLViewport* pMtlViewports = mtlViewports) + { + // TODO: Fix this function which currently wont accept pointer as intended + // _renderCommandEncoder.SetViewports(pMtlViewports, viewports.Length); + } } public void TextureBarrier() @@ -270,40 +336,34 @@ namespace Ryujinx.Graphics.Metal public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) { - throw new NotImplementedException(); + // TODO: Implementable via indirect draw commands + return false; } public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) { - throw new NotImplementedException(); + // TODO: Implementable via indirect draw commands + return false; } public void EndHostConditionalRendering() { - throw new NotImplementedException(); - } - - public void UpdateRenderScale(ReadOnlySpan scales, int totalCount, int fragmentCount) - { - throw new NotImplementedException(); + // TODO: Implementable via indirect draw commands } public void BeginTransformFeedback(PrimitiveTopology topology) { // Metal does not support Transform Feedback - throw new NotSupportedException(); } public void EndTransformFeedback() { // Metal does not support Transform Feedback - throw new NotSupportedException(); } public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { // Metal does not support Transform Feedback - throw new NotSupportedException(); } public void Dispose() @@ -311,4 +371,4 @@ namespace Ryujinx.Graphics.Metal } } -} \ No newline at end of file +} diff --git a/src/Ryujinx.Graphics.Metal/Sampler.cs b/src/Ryujinx.Graphics.Metal/Sampler.cs new file mode 100644 index 000000000..a40040c5f --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/Sampler.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; +using SharpMetal.Metal; + +namespace Ryujinx.Graphics.Metal +{ + public class Sampler : ISampler + { + private MTLSamplerState _mtlSamplerState; + + public Sampler(MTLSamplerState mtlSamplerState) + { + _mtlSamplerState = mtlSamplerState; + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/TextureView.cs b/src/Ryujinx.Graphics.Metal/Texture.cs similarity index 60% rename from src/Ryujinx.Graphics.Metal/TextureView.cs rename to src/Ryujinx.Graphics.Metal/Texture.cs index 0109a4f7e..064d6bd1a 100644 --- a/src/Ryujinx.Graphics.Metal/TextureView.cs +++ b/src/Ryujinx.Graphics.Metal/Texture.cs @@ -1,14 +1,44 @@ using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; +using SharpMetal.Metal; using System; +using System.Runtime.Versioning; namespace Ryujinx.Graphics.Metal { - class TextureView : ITexture, IDisposable + [SupportedOSPlatform("macos")] + class Texture : ITexture, IDisposable { - public int Width { get; } - public int Height { get; } - public float ScaleFactor { get; } + private readonly TextureCreateInfo _info; + + public MTLTexture MTLTexture; + public TextureCreateInfo Info => Info; + public int Width => Info.Width; + public int Height => Info.Height; + + public Texture(MTLDevice device, TextureCreateInfo info, int firstLayer, int firstLevel) + { + _info = info; + + var descriptor = new MTLTextureDescriptor(); + descriptor.PixelFormat = FormatTable.GetFormat(Info.Format); + // descriptor.Usage = + descriptor.Width = (ulong)Width; + descriptor.Height = (ulong)Height; + descriptor.Depth = (ulong)Info.Depth; + descriptor.SampleCount = (ulong)Info.Samples; + descriptor.TextureType = Info.Target.Convert(); + descriptor.Swizzle = new MTLTextureSwizzleChannels + { + red = Info.SwizzleR.Convert(), + green = Info.SwizzleG.Convert(), + blue = Info.SwizzleB.Convert(), + alpha = Info.SwizzleA.Convert() + }; + + MTLTexture = device.NewTexture(descriptor); + } + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) { throw new NotImplementedException();