This commit is contained in:
Isaac Marovitz 2024-05-19 16:53:51 -03:00 committed by GitHub
commit d87d33c68d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 5031 additions and 17 deletions

View File

@ -38,6 +38,7 @@
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview12" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
@ -49,4 +50,4 @@
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@ -87,6 +87,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
ProjectSection(ProjectDependencies) = postProject
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -249,6 +254,10 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -8,5 +8,6 @@ namespace Ryujinx.Common.Configuration
{
Vulkan,
OpenGl,
Metal
}
}

View File

@ -822,16 +822,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Creates shader translation options with the requested graphics API and flags.
/// The shader language is choosen based on the current configuration and graphics API.
/// The shader language is chosen based on the current configuration and graphics API.
/// </summary>
/// <param name="api">Target graphics API</param>
/// <param name="flags">Translation flags</param>
/// <returns>Translation options</returns>
private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
{
TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
? TargetLanguage.Spirv
: TargetLanguage.Glsl;
TargetLanguage lang = api switch
{
TargetApi.OpenGL => TargetLanguage.Glsl,
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
TargetApi.Metal => TargetLanguage.Msl,
};
return new TranslationOptions(lang, api, flags);
}

View File

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.Graphics.Metal
{
public struct BufferInfo
{
public IntPtr Handle;
public int Offset;
public int Index;
}
}

View File

@ -0,0 +1,13 @@
namespace Ryujinx.Graphics.Metal
{
static class Constants
{
// TODO: Check these values, these were largely copied from Vulkan
public const int MaxShaderStages = 5;
public const int MaxUniformBuffersPerStage = 18;
public const int MaxStorageBuffersPerStage = 16;
public const int MaxTexturesPerStage = 64;
public const int MaxCommandBuffersPerQueue = 16;
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
}
}

View File

@ -0,0 +1,22 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Metal
{
class CounterEvent : ICounterEvent
{
public CounterEvent()
{
Invalid = false;
}
public bool Invalid { get; set; }
public bool ReserveForHostAccess()
{
return true;
}
public void Flush() { }
public void Dispose() { }
}
}

View File

@ -0,0 +1,79 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
public struct DirtyFlags
{
public bool Pipeline = false;
public bool DepthStencil = false;
public DirtyFlags() { }
public void MarkAll() {
Pipeline = true;
DepthStencil = true;
}
public void Clear()
{
Pipeline = false;
DepthStencil = false;
}
}
[SupportedOSPlatform("macos")]
public struct EncoderState
{
public const int MaxColorAttachments = 8;
public MTLFunction? VertexFunction = null;
public MTLFunction? FragmentFunction = null;
public Dictionary<ulong, MTLTexture> FragmentTextures = new();
public Dictionary<ulong, MTLSamplerState> FragmentSamplers = new();
public Dictionary<ulong, MTLTexture> VertexTextures = new();
public Dictionary<ulong, MTLSamplerState> VertexSamplers = new();
public List<BufferInfo> UniformBuffers = [];
public List<BufferInfo> StorageBuffers = [];
public MTLBuffer IndexBuffer = default;
public MTLIndexType IndexType = MTLIndexType.UInt16;
public ulong IndexBufferOffset = 0;
public MTLDepthStencilState? DepthStencilState = null;
public MTLDepthClipMode DepthClipMode = MTLDepthClipMode.Clip;
public MTLCompareFunction DepthCompareFunction = MTLCompareFunction.Always;
public bool DepthWriteEnabled = false;
public MTLStencilDescriptor BackFaceStencil = new();
public MTLStencilDescriptor FrontFaceStencil = new();
public bool StencilTestEnabled = false;
public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
public MTLCullMode CullMode = MTLCullMode.None;
public MTLWinding Winding = MTLWinding.Clockwise;
public MTLViewport[] Viewports = [];
public MTLScissorRect[] Scissors = [];
// Changes to attachments take recreation!
public MTLTexture DepthStencil = default;
public MTLTexture[] RenderTargets = new MTLTexture[MaxColorAttachments];
public Dictionary<int, BlendDescriptor> BlendDescriptors = new();
public ColorF BlendColor = new();
public VertexBufferDescriptor[] VertexBuffers = [];
public VertexAttribDescriptor[] VertexAttribs = [];
// Dirty flags
public DirtyFlags Dirty = new();
public EncoderState() { }
}
}

View File

@ -0,0 +1,684 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct EncoderStateManager
{
private readonly MTLDevice _device;
private readonly Pipeline _pipeline;
private EncoderState _currentState = new();
private EncoderState _backState = new();
public readonly MTLBuffer IndexBuffer => _currentState.IndexBuffer;
public readonly MTLIndexType IndexType => _currentState.IndexType;
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
public readonly PrimitiveTopology Topology => _currentState.Topology;
public EncoderStateManager(MTLDevice device, Pipeline pipeline)
{
_device = device;
_pipeline = pipeline;
}
public void SwapStates()
{
(_currentState, _backState) = (_backState, _currentState);
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
_pipeline.EndCurrentPass();
}
}
public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{
// Initialise Pass & State
var renderPassDescriptor = new MTLRenderPassDescriptor();
for (int i = 0; i < EncoderState.MaxColorAttachments; i++)
{
if (_currentState.RenderTargets[i] != IntPtr.Zero)
{
var passAttachment = renderPassDescriptor.ColorAttachments.Object((ulong)i);
passAttachment.Texture = _currentState.RenderTargets[i];
passAttachment.LoadAction = MTLLoadAction.Load;
}
}
var depthAttachment = renderPassDescriptor.DepthAttachment;
var stencilAttachment = renderPassDescriptor.StencilAttachment;
if (_currentState.DepthStencil != IntPtr.Zero)
{
switch (_currentState.DepthStencil.PixelFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
depthAttachment.Texture = _currentState.DepthStencil;
depthAttachment.LoadAction = MTLLoadAction.Load;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
stencilAttachment.Texture = _currentState.DepthStencil;
stencilAttachment.LoadAction = MTLLoadAction.Load;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
depthAttachment.Texture = _currentState.DepthStencil;
depthAttachment.LoadAction = MTLLoadAction.Load;
var unpackedFormat = FormatTable.PackedStencilToXFormat(_currentState.DepthStencil.PixelFormat);
var stencilView = _currentState.DepthStencil.NewTextureView(unpackedFormat);
stencilAttachment.Texture = stencilView;
stencilAttachment.LoadAction = MTLLoadAction.Load;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.PixelFormat}!");
break;
}
}
// Initialise Encoder
var renderCommandEncoder = _pipeline.CommandBuffer.RenderCommandEncoder(renderPassDescriptor);
// Mark all state as dirty to ensure it is set on the encoder
_currentState.Dirty.MarkAll();
// Rebind all the state
SetDepthClamp(renderCommandEncoder);
SetCullMode(renderCommandEncoder);
SetFrontFace(renderCommandEncoder);
SetViewports(renderCommandEncoder);
SetScissors(renderCommandEncoder);
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
SetBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Vertex, _currentState.VertexTextures, _currentState.VertexSamplers);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Fragment, _currentState.FragmentTextures, _currentState.FragmentSamplers);
return renderCommandEncoder;
}
public void RebindState(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.Dirty.Pipeline)
{
SetPipelineState(renderCommandEncoder);
}
if (_currentState.Dirty.DepthStencil)
{
SetDepthStencilState(renderCommandEncoder);
}
_currentState.Dirty.Clear();
}
private void SetPipelineState(MTLRenderCommandEncoder renderCommandEncoder) {
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
for (int i = 0; i < EncoderState.MaxColorAttachments; i++)
{
if (_currentState.RenderTargets[i] != IntPtr.Zero)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
pipelineAttachment.PixelFormat = _currentState.RenderTargets[i].PixelFormat;
pipelineAttachment.SourceAlphaBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationAlphaBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationRGBBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
if (_currentState.BlendDescriptors.TryGetValue(i, out BlendDescriptor blendDescriptor))
{
pipelineAttachment.SetBlendingEnabled(blendDescriptor.Enable);
pipelineAttachment.AlphaBlendOperation = blendDescriptor.AlphaOp.Convert();
pipelineAttachment.RgbBlendOperation = blendDescriptor.ColorOp.Convert();
pipelineAttachment.SourceAlphaBlendFactor = blendDescriptor.AlphaSrcFactor.Convert();
pipelineAttachment.DestinationAlphaBlendFactor = blendDescriptor.AlphaDstFactor.Convert();
pipelineAttachment.SourceRGBBlendFactor = blendDescriptor.ColorSrcFactor.Convert();
pipelineAttachment.DestinationRGBBlendFactor = blendDescriptor.ColorDstFactor.Convert();
}
}
}
if (_currentState.DepthStencil != IntPtr.Zero)
{
switch (_currentState.DepthStencil.PixelFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.PixelFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.PixelFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.PixelFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.PixelFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.PixelFormat}!");
break;
}
}
renderPipelineDescriptor.VertexDescriptor = BuildVertexDescriptor(_currentState.VertexBuffers, _currentState.VertexAttribs);
if (_currentState.VertexFunction != null)
{
renderPipelineDescriptor.VertexFunction = _currentState.VertexFunction.Value;
}
else
{
return;
}
if (_currentState.FragmentFunction != null)
{
renderPipelineDescriptor.FragmentFunction = _currentState.FragmentFunction.Value;
}
var error = new NSError(IntPtr.Zero);
var pipelineState = _device.NewRenderPipelineState(renderPipelineDescriptor, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
renderCommandEncoder.SetRenderPipelineState(pipelineState);
renderCommandEncoder.SetBlendColor(
_currentState.BlendColor.Red,
_currentState.BlendColor.Green,
_currentState.BlendColor.Blue,
_currentState.BlendColor.Alpha);
}
public void UpdateIndexBuffer(BufferRange buffer, IndexType type)
{
if (buffer.Handle != BufferHandle.Null)
{
_currentState.IndexType = type.Convert();
_currentState.IndexBufferOffset = (ulong)buffer.Offset;
var handle = buffer.Handle;
_currentState.IndexBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
}
}
public void UpdatePrimitiveTopology(PrimitiveTopology topology)
{
_currentState.Topology = topology;
}
public void UpdateProgram(IProgram program)
{
Program prg = (Program)program;
if (prg.VertexFunction == IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, "Invalid Vertex Function!");
return;
}
_currentState.VertexFunction = prg.VertexFunction;
_currentState.FragmentFunction = prg.FragmentFunction;
// Mark dirty
_currentState.Dirty.Pipeline = true;
}
public void UpdateRenderTargets(ITexture[] colors, ITexture depthStencil)
{
_currentState.RenderTargets = new MTLTexture[EncoderState.MaxColorAttachments];
for (int i = 0; i < colors.Length; i++)
{
if (colors[i] is not Texture tex)
{
continue;
}
_currentState.RenderTargets[i] = tex.MTLTexture;
}
if (depthStencil is Texture depthTexture)
{
_currentState.DepthStencil = depthTexture.MTLTexture;
}
// Requires recreating pipeline
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
_pipeline.EndCurrentPass();
}
}
public void UpdateVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{
_currentState.VertexAttribs = vertexAttribs.ToArray();
// Mark dirty
_currentState.Dirty.Pipeline = true;
}
public void UpdateBlendDescriptors(int index, BlendDescriptor blend)
{
_currentState.BlendDescriptors[index] = blend;
_currentState.BlendColor = blend.BlendConstant;
}
// Inlineable
public void UpdateStencilState(StencilTestDescriptor stencilTest)
{
_currentState.BackFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = stencilTest.BackSFail.Convert(),
DepthFailureOperation = stencilTest.BackDpFail.Convert(),
DepthStencilPassOperation = stencilTest.BackDpPass.Convert(),
StencilCompareFunction = stencilTest.BackFunc.Convert(),
ReadMask = (uint)stencilTest.BackFuncMask,
WriteMask = (uint)stencilTest.BackMask
};
_currentState.FrontFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = stencilTest.FrontSFail.Convert(),
DepthFailureOperation = stencilTest.FrontDpFail.Convert(),
DepthStencilPassOperation = stencilTest.FrontDpPass.Convert(),
StencilCompareFunction = stencilTest.FrontFunc.Convert(),
ReadMask = (uint)stencilTest.FrontFuncMask,
WriteMask = (uint)stencilTest.FrontMask
};
_currentState.StencilTestEnabled = stencilTest.TestEnable;
var descriptor = new MTLDepthStencilDescriptor
{
DepthCompareFunction = _currentState.DepthCompareFunction,
DepthWriteEnabled = _currentState.DepthWriteEnabled
};
if (_currentState.StencilTestEnabled)
{
descriptor.BackFaceStencil = _currentState.BackFaceStencil;
descriptor.FrontFaceStencil = _currentState.FrontFaceStencil;
}
_currentState.DepthStencilState = _device.NewDepthStencilState(descriptor);
// Mark dirty
_currentState.Dirty.DepthStencil = true;
}
// Inlineable
public void UpdateDepthState(DepthTestDescriptor depthTest)
{
_currentState.DepthCompareFunction = depthTest.TestEnable ? depthTest.Func.Convert() : MTLCompareFunction.Always;
_currentState.DepthWriteEnabled = depthTest.WriteEnable;
var descriptor = new MTLDepthStencilDescriptor
{
DepthCompareFunction = _currentState.DepthCompareFunction,
DepthWriteEnabled = _currentState.DepthWriteEnabled
};
if (_currentState.StencilTestEnabled)
{
descriptor.BackFaceStencil = _currentState.BackFaceStencil;
descriptor.FrontFaceStencil = _currentState.FrontFaceStencil;
}
_currentState.DepthStencilState = _device.NewDepthStencilState(descriptor);
// Mark dirty
_currentState.Dirty.DepthStencil = true;
}
// Inlineable
public void UpdateDepthClamp(bool clamp)
{
_currentState.DepthClipMode = clamp ? MTLDepthClipMode.Clamp : MTLDepthClipMode.Clip;
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetDepthClamp(renderCommandEncoder);
}
}
// Inlineable
public void UpdateScissors(ReadOnlySpan<Rectangle<int>> regions)
{
int maxScissors = Math.Min(regions.Length, _currentState.Viewports.Length);
if (maxScissors == 0)
{
return;
}
_currentState.Scissors = new MTLScissorRect[maxScissors];
for (int i = 0; i < maxScissors; i++)
{
var region = regions[i];
_currentState.Scissors[i] = new MTLScissorRect
{
height = Math.Clamp((ulong)region.Height, 0, (ulong)_currentState.Viewports[i].height),
width = Math.Clamp((ulong)region.Width, 0, (ulong)_currentState.Viewports[i].width),
x = (ulong)region.X,
y = (ulong)region.Y
};
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetScissors(renderCommandEncoder);
}
}
// Inlineable
public void UpdateViewports(ReadOnlySpan<Viewport> viewports)
{
static float Clamp(float value)
{
return Math.Clamp(value, 0f, 1f);
}
_currentState.Viewports = new MTLViewport[viewports.Length];
for (int i = 0; i < viewports.Length; i++)
{
var viewport = viewports[i];
_currentState.Viewports[i] = new MTLViewport
{
originX = viewport.Region.X,
originY = viewport.Region.Y,
width = viewport.Region.Width,
height = viewport.Region.Height,
znear = Clamp(viewport.DepthNear),
zfar = Clamp(viewport.DepthFar)
};
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetViewports(renderCommandEncoder);
}
}
public void UpdateVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
{
_currentState.VertexBuffers = vertexBuffers.ToArray();
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
}
// Mark dirty
_currentState.Dirty.Pipeline = true;
}
// Inlineable
public void UpdateUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_currentState.UniformBuffers = [];
foreach (BufferAssignment buffer in buffers)
{
if (buffer.Range.Size != 0)
{
_currentState.UniformBuffers.Add(new BufferInfo
{
Handle = buffer.Range.Handle.ToIntPtr(),
Offset = buffer.Range.Offset,
Index = buffer.Binding
});
}
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
}
}
// Inlineable
public void UpdateStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_currentState.StorageBuffers = [];
foreach (BufferAssignment buffer in buffers)
{
if (buffer.Range.Size != 0)
{
// TODO: DONT offset the binding by 15
_currentState.StorageBuffers.Add(new BufferInfo
{
Handle = buffer.Range.Handle.ToIntPtr(),
Offset = buffer.Range.Offset,
Index = buffer.Binding + 15
});
}
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
}
}
// Inlineable
public void UpdateCullMode(bool enable, Face face)
{
_currentState.CullMode = enable ? face.Convert() : MTLCullMode.None;
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetCullMode(renderCommandEncoder);
}
}
// Inlineable
public void UpdateFrontFace(FrontFace frontFace)
{
_currentState.Winding = frontFace.Convert();
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetFrontFace(renderCommandEncoder);
}
}
// Inlineable
public readonly void UpdateTextureAndSampler(ShaderStage stage, ulong binding, MTLTexture texture, MTLSamplerState sampler)
{
switch (stage)
{
case ShaderStage.Fragment:
_currentState.FragmentTextures[binding] = texture;
_currentState.FragmentSamplers[binding] = sampler;
break;
case ShaderStage.Vertex:
_currentState.VertexTextures[binding] = texture;
_currentState.VertexSamplers[binding] = sampler;
break;
}
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
// TODO: Only update the new ones
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Vertex, _currentState.VertexTextures, _currentState.VertexSamplers);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Fragment, _currentState.FragmentTextures, _currentState.FragmentSamplers);
}
}
private void SetDepthStencilState(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.DepthStencilState != null)
{
renderCommandEncoder.SetDepthStencilState(_currentState.DepthStencilState.Value);
}
}
private void SetDepthClamp(MTLRenderCommandEncoder renderCommandEncoder)
{
renderCommandEncoder.SetDepthClipMode(_currentState.DepthClipMode);
}
private unsafe void SetScissors(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.Scissors.Length > 0)
{
fixed (MTLScissorRect* pMtlScissors = _currentState.Scissors)
{
renderCommandEncoder.SetScissorRects((IntPtr)pMtlScissors, (ulong)_currentState.Scissors.Length);
}
}
}
private unsafe void SetViewports(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.Viewports.Length > 0)
{
fixed (MTLViewport* pMtlViewports = _currentState.Viewports)
{
renderCommandEncoder.SetViewports((IntPtr)pMtlViewports, (ulong)_currentState.Viewports.Length);
}
}
}
private MTLVertexDescriptor BuildVertexDescriptor(VertexBufferDescriptor[] bufferDescriptors, VertexAttribDescriptor[] attribDescriptors)
{
var vertexDescriptor = new MTLVertexDescriptor();
uint indexMask = 0;
// TODO: Handle 'zero' buffers
for (int i = 0; i < attribDescriptors.Length; i++)
{
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = attribDescriptors[i].Format.Convert();
indexMask |= 1u << attribDescriptors[i].BufferIndex;
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
attrib.Offset = (ulong)attribDescriptors[i].Offset;
}
for (int i = 0; i < bufferDescriptors.Length; i++)
{
var layout = vertexDescriptor.Layouts.Object((ulong)i);
layout.Stride = (indexMask & (1u << i)) != 0 ? (ulong)bufferDescriptors[i].Stride : 0;
}
return vertexDescriptor;
}
private void SetVertexBuffers(MTLRenderCommandEncoder renderCommandEncoder, VertexBufferDescriptor[] bufferDescriptors)
{
var buffers = new List<BufferInfo>();
for (int i = 0; i < bufferDescriptors.Length; i++)
{
if (bufferDescriptors[i].Buffer.Handle.ToIntPtr() != IntPtr.Zero)
{
buffers.Add(new BufferInfo
{
Handle = bufferDescriptors[i].Buffer.Handle.ToIntPtr(),
Offset = bufferDescriptors[i].Buffer.Offset,
Index = i
});
}
}
SetBuffers(renderCommandEncoder, buffers);
}
private void SetBuffers(MTLRenderCommandEncoder renderCommandEncoder, List<BufferInfo> buffers, bool fragment = false)
{
foreach (var buffer in buffers)
{
renderCommandEncoder.SetVertexBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
if (fragment)
{
renderCommandEncoder.SetFragmentBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
}
}
}
private void SetCullMode(MTLRenderCommandEncoder renderCommandEncoder)
{
renderCommandEncoder.SetCullMode(_currentState.CullMode);
}
private void SetFrontFace(MTLRenderCommandEncoder renderCommandEncoder)
{
renderCommandEncoder.SetFrontFacingWinding(_currentState.Winding);
}
private void SetTextureAndSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, Dictionary<ulong, MTLTexture> textures, Dictionary<ulong, MTLSamplerState> samplers)
{
foreach (var texture in textures)
{
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexTexture(texture.Value, texture.Key);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentTexture(texture.Value, texture.Key);
break;
}
}
foreach (var sampler in samplers)
{
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexSamplerState(sampler.Value, sampler.Key);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentSamplerState(sampler.Value, sampler.Key);
break;
}
}
}
}
}

View File

@ -0,0 +1,268 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class EnumConversion
{
public static MTLSamplerAddressMode Convert(this AddressMode mode)
{
return mode switch
{
AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, // TODO: Should be clamp.
AddressMode.Repeat => MTLSamplerAddressMode.Repeat,
AddressMode.MirrorClamp => MTLSamplerAddressMode.MirrorClampToEdge, // TODO: Should be mirror clamp.
AddressMode.MirroredRepeat => MTLSamplerAddressMode.MirrorRepeat,
AddressMode.ClampToBorder => MTLSamplerAddressMode.ClampToBorderColor,
AddressMode.ClampToEdge => MTLSamplerAddressMode.ClampToEdge,
AddressMode.MirrorClampToEdge => MTLSamplerAddressMode.MirrorClampToEdge,
AddressMode.MirrorClampToBorder => MTLSamplerAddressMode.ClampToBorderColor, // TODO: Should be mirror clamp to border.
_ => LogInvalidAndReturn(mode, nameof(AddressMode), MTLSamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
};
}
public static MTLBlendFactor Convert(this BlendFactor factor)
{
return factor switch
{
BlendFactor.Zero or BlendFactor.ZeroGl => MTLBlendFactor.Zero,
BlendFactor.One or BlendFactor.OneGl => MTLBlendFactor.One,
BlendFactor.SrcColor or BlendFactor.SrcColorGl => MTLBlendFactor.SourceColor,
BlendFactor.OneMinusSrcColor or BlendFactor.OneMinusSrcColorGl => MTLBlendFactor.OneMinusSourceColor,
BlendFactor.SrcAlpha or BlendFactor.SrcAlphaGl => MTLBlendFactor.SourceAlpha,
BlendFactor.OneMinusSrcAlpha or BlendFactor.OneMinusSrcAlphaGl => MTLBlendFactor.OneMinusSourceAlpha,
BlendFactor.DstAlpha or BlendFactor.DstAlphaGl => MTLBlendFactor.DestinationAlpha,
BlendFactor.OneMinusDstAlpha or BlendFactor.OneMinusDstAlphaGl => MTLBlendFactor.OneMinusDestinationAlpha,
BlendFactor.DstColor or BlendFactor.DstColorGl => MTLBlendFactor.DestinationColor,
BlendFactor.OneMinusDstColor or BlendFactor.OneMinusDstColorGl => MTLBlendFactor.OneMinusDestinationColor,
BlendFactor.SrcAlphaSaturate or BlendFactor.SrcAlphaSaturateGl => MTLBlendFactor.SourceAlphaSaturated,
BlendFactor.Src1Color or BlendFactor.Src1ColorGl => MTLBlendFactor.Source1Color,
BlendFactor.OneMinusSrc1Color or BlendFactor.OneMinusSrc1ColorGl => MTLBlendFactor.OneMinusSource1Color,
BlendFactor.Src1Alpha or BlendFactor.Src1AlphaGl => MTLBlendFactor.Source1Alpha,
BlendFactor.OneMinusSrc1Alpha or BlendFactor.OneMinusSrc1AlphaGl => MTLBlendFactor.OneMinusSource1Alpha,
BlendFactor.ConstantColor => MTLBlendFactor.BlendColor,
BlendFactor.OneMinusConstantColor => MTLBlendFactor.OneMinusBlendColor,
BlendFactor.ConstantAlpha => MTLBlendFactor.BlendAlpha,
BlendFactor.OneMinusConstantAlpha => MTLBlendFactor.OneMinusBlendAlpha,
_ => LogInvalidAndReturn(factor, nameof(BlendFactor), MTLBlendFactor.Zero)
};
}
public static MTLBlendOperation Convert(this BlendOp op)
{
return op switch
{
BlendOp.Add or BlendOp.AddGl => MTLBlendOperation.Add,
BlendOp.Subtract or BlendOp.SubtractGl => MTLBlendOperation.Subtract,
BlendOp.ReverseSubtract or BlendOp.ReverseSubtractGl => MTLBlendOperation.ReverseSubtract,
BlendOp.Minimum => MTLBlendOperation.Min,
BlendOp.Maximum => MTLBlendOperation.Max,
_ => LogInvalidAndReturn(op, nameof(BlendOp), MTLBlendOperation.Add)
};
}
public static MTLCompareFunction Convert(this CompareOp op)
{
return op switch
{
CompareOp.Never or CompareOp.NeverGl => MTLCompareFunction.Never,
CompareOp.Less or CompareOp.LessGl => MTLCompareFunction.Less,
CompareOp.Equal or CompareOp.EqualGl => MTLCompareFunction.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => MTLCompareFunction.LessEqual,
CompareOp.Greater or CompareOp.GreaterGl => MTLCompareFunction.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => MTLCompareFunction.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => MTLCompareFunction.GreaterEqual,
CompareOp.Always or CompareOp.AlwaysGl => MTLCompareFunction.Always,
_ => LogInvalidAndReturn(op, nameof(CompareOp), MTLCompareFunction.Never)
};
}
public static MTLCullMode Convert(this Face face)
{
return face switch
{
Face.Back => MTLCullMode.Back,
Face.Front => MTLCullMode.Front,
Face.FrontAndBack => MTLCullMode.None,
_ => LogInvalidAndReturn(face, nameof(Face), MTLCullMode.Back)
};
}
public static MTLWinding Convert(this FrontFace frontFace)
{
return frontFace switch
{
FrontFace.Clockwise => MTLWinding.Clockwise,
FrontFace.CounterClockwise => MTLWinding.CounterClockwise,
_ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise)
};
}
public static MTLIndexType Convert(this IndexType type)
{
return type switch
{
IndexType.UShort => MTLIndexType.UInt16,
IndexType.UInt => MTLIndexType.UInt32,
_ => LogInvalidAndReturn(type, nameof(IndexType), MTLIndexType.UInt16)
};
}
public static MTLSamplerMinMagFilter Convert(this MagFilter filter)
{
return filter switch
{
MagFilter.Nearest => MTLSamplerMinMagFilter.Nearest,
MagFilter.Linear => MTLSamplerMinMagFilter.Linear,
_ => LogInvalidAndReturn(filter, nameof(MagFilter), MTLSamplerMinMagFilter.Nearest)
};
}
public static (MTLSamplerMinMagFilter, MTLSamplerMipFilter) Convert(this MinFilter filter)
{
return filter switch
{
MinFilter.Nearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
MinFilter.NearestMipmapNearest => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest),
MinFilter.LinearMipmapNearest => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Nearest),
MinFilter.NearestMipmapLinear => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Linear),
MinFilter.LinearMipmapLinear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear),
_ => LogInvalidAndReturn(filter, nameof(MinFilter), (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest))
};
}
// TODO: Metal does not have native support for Triangle Fans but it is possible to emulate with TriangleStrip and moving around the indices
public static MTLPrimitiveType Convert(this PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Points => MTLPrimitiveType.Point,
PrimitiveTopology.Lines => MTLPrimitiveType.Line,
PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip,
PrimitiveTopology.Triangles => MTLPrimitiveType.Triangle,
PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip,
_ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), MTLPrimitiveType.Triangle)
};
}
public static MTLStencilOperation Convert(this StencilOp op)
{
return op switch
{
StencilOp.Keep or StencilOp.KeepGl => MTLStencilOperation.Keep,
StencilOp.Zero or StencilOp.ZeroGl => MTLStencilOperation.Zero,
StencilOp.Replace or StencilOp.ReplaceGl => MTLStencilOperation.Replace,
StencilOp.IncrementAndClamp or StencilOp.IncrementAndClampGl => MTLStencilOperation.IncrementClamp,
StencilOp.DecrementAndClamp or StencilOp.DecrementAndClampGl => MTLStencilOperation.DecrementClamp,
StencilOp.Invert or StencilOp.InvertGl => MTLStencilOperation.Invert,
StencilOp.IncrementAndWrap or StencilOp.IncrementAndWrapGl => MTLStencilOperation.IncrementWrap,
StencilOp.DecrementAndWrap or StencilOp.DecrementAndWrapGl => MTLStencilOperation.DecrementWrap,
_ => LogInvalidAndReturn(op, nameof(StencilOp), MTLStencilOperation.Keep)
};
}
public static MTLTextureType Convert(this Target target)
{
return target switch
{
Target.TextureBuffer => MTLTextureType.TextureBuffer,
Target.Texture1D => MTLTextureType.Type1D,
Target.Texture1DArray => MTLTextureType.Type1DArray,
Target.Texture2D => MTLTextureType.Type2D,
Target.Texture2DArray => MTLTextureType.Type2DArray,
Target.Texture2DMultisample => MTLTextureType.Type2DMultisample,
Target.Texture2DMultisampleArray => MTLTextureType.Type2DMultisampleArray,
Target.Texture3D => MTLTextureType.Type3D,
Target.Cubemap => MTLTextureType.Cube,
Target.CubemapArray => MTLTextureType.CubeArray,
_ => LogInvalidAndReturn(target, nameof(Target), MTLTextureType.Type2D)
};
}
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)
};
}
public static MTLVertexFormat Convert(this Format format)
{
return format switch
{
Format.R16Float => MTLVertexFormat.Half,
Format.R16G16Float => MTLVertexFormat.Half2,
Format.R16G16B16Float => MTLVertexFormat.Half3,
Format.R16G16B16A16Float => MTLVertexFormat.Half4,
Format.R32Float => MTLVertexFormat.Float,
Format.R32G32Float => MTLVertexFormat.Float2,
Format.R32G32B32Float=> MTLVertexFormat.Float3,
Format.R11G11B10Float => MTLVertexFormat.FloatRG11B10,
Format.R32G32B32A32Float => MTLVertexFormat.Float4,
Format.R8Uint => MTLVertexFormat.UChar,
Format.R8G8Uint => MTLVertexFormat.UChar2,
Format.R8G8B8Uint => MTLVertexFormat.UChar3,
Format.R8G8B8A8Uint => MTLVertexFormat.UChar4,
Format.R16Uint => MTLVertexFormat.UShort,
Format.R16G16Uint => MTLVertexFormat.UShort2,
Format.R16G16B16Uint => MTLVertexFormat.UShort3,
Format.R16G16B16A16Uint => MTLVertexFormat.UShort4,
Format.R32Uint => MTLVertexFormat.UInt,
Format.R32G32Uint => MTLVertexFormat.UInt2,
Format.R32G32B32Uint => MTLVertexFormat.UInt3,
Format.R32G32B32A32Uint => MTLVertexFormat.UInt4,
Format.R8Sint => MTLVertexFormat.Char,
Format.R8G8Sint => MTLVertexFormat.Char2,
Format.R8G8B8Sint => MTLVertexFormat.Char3,
Format.R8G8B8A8Sint => MTLVertexFormat.Char4,
Format.R16Sint => MTLVertexFormat.Short,
Format.R16G16Sint => MTLVertexFormat.Short2,
Format.R16G16B16Sint => MTLVertexFormat.Short3,
Format.R16G16B16A16Sint => MTLVertexFormat.Short4,
Format.R32Sint => MTLVertexFormat.Int,
Format.R32G32Sint => MTLVertexFormat.Int2,
Format.R32G32B32Sint => MTLVertexFormat.Int3,
Format.R32G32B32A32Sint => MTLVertexFormat.Int4,
Format.R8Unorm => MTLVertexFormat.UCharNormalized,
Format.R8G8Unorm => MTLVertexFormat.UChar2Normalized,
Format.R8G8B8Unorm => MTLVertexFormat.UChar3Normalized,
Format.R8G8B8A8Unorm => MTLVertexFormat.UChar4Normalized,
Format.R16Unorm => MTLVertexFormat.UShortNormalized,
Format.R16G16Unorm => MTLVertexFormat.UShort2Normalized,
Format.R16G16B16Unorm => MTLVertexFormat.UShort3Normalized,
Format.R16G16B16A16Unorm => MTLVertexFormat.UShort4Normalized,
Format.R10G10B10A2Unorm => MTLVertexFormat.UInt1010102Normalized,
Format.R8Snorm => MTLVertexFormat.CharNormalized,
Format.R8G8Snorm => MTLVertexFormat.Char2Normalized,
Format.R8G8B8Snorm => MTLVertexFormat.Char3Normalized,
Format.R8G8B8A8Snorm => MTLVertexFormat.Char4Normalized,
Format.R16Snorm => MTLVertexFormat.ShortNormalized,
Format.R16G16Snorm => MTLVertexFormat.Short2Normalized,
Format.R16G16B16Snorm => MTLVertexFormat.Short3Normalized,
Format.R16G16B16A16Snorm => MTLVertexFormat.Short4Normalized,
Format.R10G10B10A2Snorm => MTLVertexFormat.Int1010102Normalized,
_ => LogInvalidAndReturn(format, nameof(Format), MTLVertexFormat.Float4)
};
}
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
{
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
return defaultValue;
}
}
}

View File

@ -0,0 +1,199 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
static class FormatTable
{
private static readonly MTLPixelFormat[] _table;
static FormatTable()
{
_table = new MTLPixelFormat[Enum.GetNames(typeof(Format)).Length];
Add(Format.R8Unorm, MTLPixelFormat.R8Unorm);
Add(Format.R8Snorm, MTLPixelFormat.R8Snorm);
Add(Format.R8Uint, MTLPixelFormat.R8Uint);
Add(Format.R8Sint, MTLPixelFormat.R8Sint);
Add(Format.R16Float, MTLPixelFormat.R16Float);
Add(Format.R16Unorm, MTLPixelFormat.R16Unorm);
Add(Format.R16Snorm, MTLPixelFormat.R16Snorm);
Add(Format.R16Uint, MTLPixelFormat.R16Uint);
Add(Format.R16Sint, MTLPixelFormat.R16Sint);
Add(Format.R32Float, MTLPixelFormat.R32Float);
Add(Format.R32Uint, MTLPixelFormat.R32Uint);
Add(Format.R32Sint, MTLPixelFormat.R32Sint);
Add(Format.R8G8Unorm, MTLPixelFormat.RG8Unorm);
Add(Format.R8G8Snorm, MTLPixelFormat.RG8Snorm);
Add(Format.R8G8Uint, MTLPixelFormat.RG8Uint);
Add(Format.R8G8Sint, MTLPixelFormat.RG8Sint);
Add(Format.R16G16Float, MTLPixelFormat.RG16Float);
Add(Format.R16G16Unorm, MTLPixelFormat.RG16Unorm);
Add(Format.R16G16Snorm, MTLPixelFormat.RG16Snorm);
Add(Format.R16G16Uint, MTLPixelFormat.RG16Uint);
Add(Format.R16G16Sint, MTLPixelFormat.RG16Sint);
Add(Format.R32G32Float, MTLPixelFormat.RG32Float);
Add(Format.R32G32Uint, MTLPixelFormat.RG32Uint);
Add(Format.R32G32Sint, MTLPixelFormat.RG32Sint);
// Add(Format.R8G8B8Unorm, MTLPixelFormat.R8G8B8Unorm);
// Add(Format.R8G8B8Snorm, MTLPixelFormat.R8G8B8Snorm);
// Add(Format.R8G8B8Uint, MTLPixelFormat.R8G8B8Uint);
// Add(Format.R8G8B8Sint, MTLPixelFormat.R8G8B8Sint);
// Add(Format.R16G16B16Float, MTLPixelFormat.R16G16B16Float);
// Add(Format.R16G16B16Unorm, MTLPixelFormat.R16G16B16Unorm);
// Add(Format.R16G16B16Snorm, MTLPixelFormat.R16G16B16SNorm);
// Add(Format.R16G16B16Uint, MTLPixelFormat.R16G16B16Uint);
// Add(Format.R16G16B16Sint, MTLPixelFormat.R16G16B16Sint);
// Add(Format.R32G32B32Float, MTLPixelFormat.R32G32B32Sfloat);
// Add(Format.R32G32B32Uint, MTLPixelFormat.R32G32B32Uint);
// Add(Format.R32G32B32Sint, MTLPixelFormat.R32G32B32Sint);
Add(Format.R8G8B8A8Unorm, MTLPixelFormat.RGBA8Unorm);
Add(Format.R8G8B8A8Snorm, MTLPixelFormat.RGBA8Snorm);
Add(Format.R8G8B8A8Uint, MTLPixelFormat.RGBA8Uint);
Add(Format.R8G8B8A8Sint, MTLPixelFormat.RGBA8Sint);
Add(Format.R16G16B16A16Float, MTLPixelFormat.RGBA16Float);
Add(Format.R16G16B16A16Unorm, MTLPixelFormat.RGBA16Unorm);
Add(Format.R16G16B16A16Snorm, MTLPixelFormat.RGBA16Snorm);
Add(Format.R16G16B16A16Uint, MTLPixelFormat.RGBA16Uint);
Add(Format.R16G16B16A16Sint, MTLPixelFormat.RGBA16Sint);
Add(Format.R32G32B32A32Float, MTLPixelFormat.RGBA32Float);
Add(Format.R32G32B32A32Uint, MTLPixelFormat.RGBA32Uint);
Add(Format.R32G32B32A32Sint, MTLPixelFormat.RGBA32Sint);
Add(Format.S8Uint, MTLPixelFormat.Stencil8);
Add(Format.D16Unorm, MTLPixelFormat.Depth16Unorm);
// Add(Format.S8UintD24Unorm, MTLPixelFormat.BGRA8Unorm);
Add(Format.D32Float, MTLPixelFormat.Depth32Float);
Add(Format.D24UnormS8Uint, MTLPixelFormat.Depth24UnormStencil8);
Add(Format.D32FloatS8Uint, MTLPixelFormat.Depth32FloatStencil8);
Add(Format.R8G8B8A8Srgb, MTLPixelFormat.RGBA8UnormsRGB);
// Add(Format.R4G4Unorm, MTLPixelFormat.R4G4Unorm);
// Add(Format.R4G4B4A4Unorm, MTLPixelFormat.R4G4B4A4Unorm);
// Add(Format.R5G5B5X1Unorm, MTLPixelFormat.R5G5B5X1Unorm);
// Add(Format.R5G5B5A1Unorm, MTLPixelFormat.R5G5B5A1Unorm);
Add(Format.R5G6B5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.R10G10B10A2Unorm, MTLPixelFormat.RGB10A2Unorm);
Add(Format.R10G10B10A2Uint, MTLPixelFormat.RGB10A2Uint);
Add(Format.R11G11B10Float, MTLPixelFormat.RG11B10Float);
Add(Format.R9G9B9E5Float, MTLPixelFormat.RGB9E5Float);
Add(Format.Bc1RgbaUnorm, MTLPixelFormat.BC1RGBA);
Add(Format.Bc2Unorm, MTLPixelFormat.BC2RGBA);
Add(Format.Bc3Unorm, MTLPixelFormat.BC3RGBA);
Add(Format.Bc1RgbaSrgb, MTLPixelFormat.BC1RGBAsRGB);
Add(Format.Bc2Srgb, MTLPixelFormat.BC2RGBAsRGB);
Add(Format.Bc3Srgb, MTLPixelFormat.BC3RGBAsRGB);
Add(Format.Bc4Unorm, MTLPixelFormat.BC4RUnorm);
Add(Format.Bc4Snorm, MTLPixelFormat.BC4RSnorm);
Add(Format.Bc5Unorm, MTLPixelFormat.BC5RGUnorm);
Add(Format.Bc5Snorm, MTLPixelFormat.BC5RGSnorm);
Add(Format.Bc7Unorm, MTLPixelFormat.BC7RGBAUnorm);
Add(Format.Bc7Srgb, MTLPixelFormat.BC7RGBAUnormsRGB);
Add(Format.Bc6HSfloat, MTLPixelFormat.BC6HRGBFloat);
Add(Format.Bc6HUfloat, MTLPixelFormat.BC6HRGBUfloat);
Add(Format.Etc2RgbUnorm, MTLPixelFormat.ETC2RGB8);
// Add(Format.Etc2RgbaUnorm, MTLPixelFormat.ETC2RGBA8);
Add(Format.Etc2RgbPtaUnorm, MTLPixelFormat.ETC2RGB8A1);
Add(Format.Etc2RgbSrgb, MTLPixelFormat.ETC2RGB8sRGB);
// Add(Format.Etc2RgbaSrgb, MTLPixelFormat.ETC2RGBA8sRGB);
Add(Format.Etc2RgbPtaSrgb, MTLPixelFormat.ETC2RGB8A1sRGB);
// Add(Format.R8Uscaled, MTLPixelFormat.R8Uscaled);
// Add(Format.R8Sscaled, MTLPixelFormat.R8Sscaled);
// Add(Format.R16Uscaled, MTLPixelFormat.R16Uscaled);
// Add(Format.R16Sscaled, MTLPixelFormat.R16Sscaled);
// Add(Format.R32Uscaled, MTLPixelFormat.R32Uscaled);
// Add(Format.R32Sscaled, MTLPixelFormat.R32Sscaled);
// Add(Format.R8G8Uscaled, MTLPixelFormat.R8G8Uscaled);
// Add(Format.R8G8Sscaled, MTLPixelFormat.R8G8Sscaled);
// Add(Format.R16G16Uscaled, MTLPixelFormat.R16G16Uscaled);
// Add(Format.R16G16Sscaled, MTLPixelFormat.R16G16Sscaled);
// Add(Format.R32G32Uscaled, MTLPixelFormat.R32G32Uscaled);
// Add(Format.R32G32Sscaled, MTLPixelFormat.R32G32Sscaled);
// Add(Format.R8G8B8Uscaled, MTLPixelFormat.R8G8B8Uscaled);
// Add(Format.R8G8B8Sscaled, MTLPixelFormat.R8G8B8Sscaled);
// Add(Format.R16G16B16Uscaled, MTLPixelFormat.R16G16B16Uscaled);
// Add(Format.R16G16B16Sscaled, MTLPixelFormat.R16G16B16Sscaled);
// Add(Format.R32G32B32Uscaled, MTLPixelFormat.R32G32B32Uscaled);
// Add(Format.R32G32B32Sscaled, MTLPixelFormat.R32G32B32Sscaled);
// Add(Format.R8G8B8A8Uscaled, MTLPixelFormat.R8G8B8A8Uscaled);
// Add(Format.R8G8B8A8Sscaled, MTLPixelFormat.R8G8B8A8Sscaled);
// Add(Format.R16G16B16A16Uscaled, MTLPixelFormat.R16G16B16A16Uscaled);
// Add(Format.R16G16B16A16Sscaled, MTLPixelFormat.R16G16B16A16Sscaled);
// Add(Format.R32G32B32A32Uscaled, MTLPixelFormat.R32G32B32A32Uscaled);
// Add(Format.R32G32B32A32Sscaled, MTLPixelFormat.R32G32B32A32Sscaled);
// Add(Format.R10G10B10A2Snorm, MTLPixelFormat.A2B10G10R10SNormPack32);
// Add(Format.R10G10B10A2Sint, MTLPixelFormat.A2B10G10R10SintPack32);
// Add(Format.R10G10B10A2Uscaled, MTLPixelFormat.A2B10G10R10UscaledPack32);
// Add(Format.R10G10B10A2Sscaled, MTLPixelFormat.A2B10G10R10SscaledPack32);
Add(Format.Astc4x4Unorm, MTLPixelFormat.ASTC4x4LDR);
Add(Format.Astc5x4Unorm, MTLPixelFormat.ASTC5x4LDR);
Add(Format.Astc5x5Unorm, MTLPixelFormat.ASTC5x5LDR);
Add(Format.Astc6x5Unorm, MTLPixelFormat.ASTC6x5LDR);
Add(Format.Astc6x6Unorm, MTLPixelFormat.ASTC6x6LDR);
Add(Format.Astc8x5Unorm, MTLPixelFormat.ASTC8x5LDR);
Add(Format.Astc8x6Unorm, MTLPixelFormat.ASTC8x6LDR);
Add(Format.Astc8x8Unorm, MTLPixelFormat.ASTC8x8LDR);
Add(Format.Astc10x5Unorm, MTLPixelFormat.ASTC10x5LDR);
Add(Format.Astc10x6Unorm, MTLPixelFormat.ASTC10x6LDR);
Add(Format.Astc10x8Unorm, MTLPixelFormat.ASTC10x8LDR);
Add(Format.Astc10x10Unorm, MTLPixelFormat.ASTC10x10LDR);
Add(Format.Astc12x10Unorm, MTLPixelFormat.ASTC12x10LDR);
Add(Format.Astc12x12Unorm, MTLPixelFormat.ASTC12x12LDR);
Add(Format.Astc4x4Srgb, MTLPixelFormat.ASTC4x4sRGB);
Add(Format.Astc5x4Srgb, MTLPixelFormat.ASTC5x4sRGB);
Add(Format.Astc5x5Srgb, MTLPixelFormat.ASTC5x5sRGB);
Add(Format.Astc6x5Srgb, MTLPixelFormat.ASTC6x5sRGB);
Add(Format.Astc6x6Srgb, MTLPixelFormat.ASTC6x6sRGB);
Add(Format.Astc8x5Srgb, MTLPixelFormat.ASTC8x5sRGB);
Add(Format.Astc8x6Srgb, MTLPixelFormat.ASTC8x6sRGB);
Add(Format.Astc8x8Srgb, MTLPixelFormat.ASTC8x8sRGB);
Add(Format.Astc10x5Srgb, MTLPixelFormat.ASTC10x5sRGB);
Add(Format.Astc10x6Srgb, MTLPixelFormat.ASTC10x6sRGB);
Add(Format.Astc10x8Srgb, MTLPixelFormat.ASTC10x8sRGB);
Add(Format.Astc10x10Srgb, MTLPixelFormat.ASTC10x10sRGB);
Add(Format.Astc12x10Srgb, MTLPixelFormat.ASTC12x10sRGB);
Add(Format.Astc12x12Srgb, MTLPixelFormat.ASTC12x12sRGB);
Add(Format.B5G6R5Unorm, MTLPixelFormat.B5G6R5Unorm);
Add(Format.B5G5R5A1Unorm, MTLPixelFormat.BGR5A1Unorm);
Add(Format.A1B5G5R5Unorm, MTLPixelFormat.A1BGR5Unorm);
Add(Format.B8G8R8A8Unorm, MTLPixelFormat.BGRA8Unorm);
Add(Format.B8G8R8A8Srgb, MTLPixelFormat.BGRA8UnormsRGB);
}
private static void Add(Format format, MTLPixelFormat mtlFormat)
{
_table[(int)format] = mtlFormat;
}
public static MTLPixelFormat GetFormat(Format format)
{
var mtlFormat = _table[(int)format];
if (mtlFormat == MTLPixelFormat.Depth24UnormStencil8 || mtlFormat == MTLPixelFormat.Depth32FloatStencil8)
{
if (!MTLDevice.CreateSystemDefaultDevice().Depth24Stencil8PixelFormatSupported)
{
mtlFormat = MTLPixelFormat.Depth32Float;
}
}
return mtlFormat;
}
public static MTLPixelFormat PackedStencilToXFormat(MTLPixelFormat format)
{
switch (format)
{
case MTLPixelFormat.Depth24UnormStencil8:
return MTLPixelFormat.X24Stencil8;
case MTLPixelFormat.Depth32FloatStencil8:
return MTLPixelFormat.X32Stencil8;
default:
Logger.Warning?.PrintMsg(LogClass.Gpu, $"Attempted to get stencil format for non packed format {format}!");
return MTLPixelFormat.Invalid;
}
}
}
}

View File

@ -0,0 +1,14 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Metal
{
static class Handle
{
public static IntPtr ToIntPtr(this BufferHandle handle)
{
return Unsafe.As<BufferHandle, IntPtr>(ref handle);
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Metal
{
static partial class HardwareInfoTools
{
private readonly static IntPtr _kCFAllocatorDefault = IntPtr.Zero;
private readonly static UInt32 _kCFStringEncodingASCII = 0x0600;
private const string IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit";
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
[LibraryImport(IOKit, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr IOServiceMatching(string name);
[LibraryImport(IOKit)]
private static partial IntPtr IOServiceGetMatchingService(IntPtr mainPort, IntPtr matching);
[LibraryImport(IOKit)]
private static partial IntPtr IORegistryEntryCreateCFProperty(IntPtr entry, IntPtr key, IntPtr allocator, UInt32 options);
[LibraryImport(CoreFoundation, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr CFStringCreateWithCString(IntPtr allocator, string cString, UInt32 encoding);
[LibraryImport(CoreFoundation)]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool CFStringGetCString(IntPtr theString, IntPtr buffer, long bufferSizes, UInt32 encoding);
[LibraryImport(CoreFoundation)]
public static partial IntPtr CFDataGetBytePtr(IntPtr theData);
static string GetNameFromId(uint id)
{
return id switch
{
0x1002 => "AMD",
0x106B => "Apple",
0x10DE => "NVIDIA",
0x13B5 => "ARM",
0x8086 => "Intel",
_ => $"0x{id:X}"
};
}
public static string GetVendor()
{
var serviceDict = IOServiceMatching("IOGPU");
var service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
var cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "vendor-id", _kCFStringEncodingASCII);
var cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
byte[] buffer = new byte[4];
var bufferPtr = CFDataGetBytePtr(cfProperty);
Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
var vendorId = BitConverter.ToUInt32(buffer);
return GetNameFromId(vendorId);
}
public static string GetModel()
{
var serviceDict = IOServiceMatching("IOGPU");
var service = IOServiceGetMatchingService(IntPtr.Zero, serviceDict);
var cfString = CFStringCreateWithCString(_kCFAllocatorDefault, "model", _kCFStringEncodingASCII);
var cfProperty = IORegistryEntryCreateCFProperty(service, cfString, _kCFAllocatorDefault, 0);
char[] buffer = new char[64];
IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
if (CFStringGetCString(cfProperty, bufferPtr, buffer.Length, _kCFStringEncodingASCII))
{
var model = Marshal.PtrToStringUTF8(bufferPtr);
Marshal.FreeHGlobal(bufferPtr);
return model;
}
return "";
}
}
}

View File

@ -0,0 +1,206 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
enum ComponentType
{
Float,
SignedInteger,
UnsignedInteger,
}
[SupportedOSPlatform("macos")]
class HelperShader : IDisposable
{
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
private readonly Pipeline _pipeline;
private MTLDevice _device;
private readonly IProgram _programColorBlit;
private readonly IProgram _programColorClearF;
private readonly IProgram _programColorClearSI;
private readonly IProgram _programColorClearUI;
private readonly IProgram _programDepthStencilClear;
public HelperShader(MTLDevice device, Pipeline pipeline)
{
_device = device;
_pipeline = pipeline;
var blitSource = ReadMsl("Blit.metal");
_programColorBlit = new Program(
[
new ShaderSource(blitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl)
], device);
// var colorClearFSource = ReadMsl("ColorClearF.metal");
// _programColorClearF = new Program(
// [
// new ShaderSource(colorClearFSource, ShaderStage.Fragment, TargetLanguage.Msl),
// new ShaderSource(colorClearFSource, ShaderStage.Vertex, TargetLanguage.Msl)
// ], device);
//
// var colorClearSiSource = ReadMsl("ColorClearSI.metal");
// _programColorClearSI = new Program(
// [
// new ShaderSource(colorClearSiSource, ShaderStage.Fragment, TargetLanguage.Msl),
// new ShaderSource(colorClearSiSource, ShaderStage.Vertex, TargetLanguage.Msl)
// ], device);
//
// var colorClearUiSource = ReadMsl("ColorClearUI.metal");
// _programColorClearUI = new Program(
// [
// new ShaderSource(colorClearUiSource, ShaderStage.Fragment, TargetLanguage.Msl),
// new ShaderSource(colorClearUiSource, ShaderStage.Vertex, TargetLanguage.Msl)
// ], device);
//
// var depthStencilClearSource = ReadMsl("DepthStencilClear.metal");
// _programDepthStencilClear = new Program(
// [
// new ShaderSource(depthStencilClearSource, ShaderStage.Fragment, TargetLanguage.Msl),
// new ShaderSource(depthStencilClearSource, ShaderStage.Vertex, TargetLanguage.Msl)
// ], device);
}
private static string ReadMsl(string fileName)
{
return EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
}
public void BlitColor(
ITexture source,
ITexture destination)
{
var sampler = _device.NewSamplerState(new MTLSamplerDescriptor
{
MinFilter = MTLSamplerMinMagFilter.Nearest,
MagFilter = MTLSamplerMinMagFilter.Nearest,
MipFilter = MTLSamplerMipFilter.NotMipmapped
});
_pipeline.SetProgram(_programColorBlit);
_pipeline.SetRenderTargets([destination], null);
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, source, new Sampler(sampler));
_pipeline.SetPrimitiveTopology(PrimitiveTopology.Triangles);
_pipeline.Draw(6, 1, 0, 0);
_pipeline.Finish();
}
public void ClearColor(
Texture dst,
uint componentMask,
int dstWidth,
int dstHeight,
ComponentType type,
Rectangle<int> scissor)
{
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);
IProgram program;
if (type == ComponentType.SignedInteger)
{
program = _programColorClearSI;
}
else if (type == ComponentType.UnsignedInteger)
{
program = _programColorClearUI;
}
else
{
program = _programColorClearF;
}
_pipeline.SetProgram(program);
// _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetRenderTargetColorMasks([componentMask]);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors([scissor]);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0);
_pipeline.Finish();
}
public void ClearDepthStencil(
Texture dst,
float depthValue,
bool depthMask,
int stencilValue,
int stencilMask,
int dstWidth,
int dstHeight,
Rectangle<int> scissor)
{
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);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors([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();
}
private static StencilTestDescriptor CreateStencilTestDescriptor(
bool enabled,
int refValue = 0,
int compareMask = 0xff,
int writeMask = 0xff)
{
return new StencilTestDescriptor(
enabled,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask,
CompareOp.Always,
StencilOp.Replace,
StencilOp.Replace,
StencilOp.Replace,
refValue,
compareMask,
writeMask);
}
public void Dispose()
{
_programColorBlit.Dispose();
_programColorClearF.Dispose();
_programColorClearSI.Dispose();
_programColorClearUI.Dispose();
_programDepthStencilClear.Dispose();
_pipeline.Dispose();
}
}
}

View File

@ -0,0 +1,273 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
public sealed class MetalRenderer : IRenderer
{
private readonly MTLDevice _device;
private readonly MTLCommandQueue _queue;
private readonly Func<CAMetalLayer> _getMetalLayer;
private Pipeline _pipeline;
private Window _window;
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
public bool PreferThreading => true;
public IPipeline Pipeline => _pipeline;
public IWindow Window => _window;
public MetalRenderer(Func<CAMetalLayer> metalLayer)
{
_device = MTLDevice.CreateSystemDefaultDevice();
if (_device.ArgumentBuffersSupport != MTLArgumentBuffersTier.Tier2)
{
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
}
_queue = _device.NewCommandQueue();
_getMetalLayer = metalLayer;
}
public void Initialize(GraphicsDebugLevel logLevel)
{
var layer = _getMetalLayer();
layer.Device = _device;
layer.FramebufferOnly = false;
_window = new Window(this, layer);
_pipeline = new Pipeline(_device, _queue);
}
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
{
throw new NotImplementedException();
}
public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
{
return CreateBuffer(size, access);
}
public BufferHandle CreateBuffer(IntPtr pointer, int size)
{
var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
var bufferPtr = buffer.NativePtr;
return Unsafe.As<IntPtr, BufferHandle>(ref bufferPtr);
}
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
{
throw new NotImplementedException();
}
public IImageArray CreateImageArray(int size, bool isBuffer)
{
throw new NotImplementedException();
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
var buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared);
if (access == BufferAccess.FlushPersistent)
{
buffer.SetPurgeableState(MTLPurgeableState.NonVolatile);
}
var bufferPtr = buffer.NativePtr;
return Unsafe.As<IntPtr, BufferHandle>(ref bufferPtr);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
return new Program(shaders, _device);
}
public ISampler CreateSampler(SamplerCreateInfo info)
{
return new Sampler(_device, info);
}
public ITexture CreateTexture(TextureCreateInfo info)
{
var texture = new Texture(_device, _pipeline, info);
return texture;
}
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
throw new NotImplementedException();
}
public bool PrepareHostMapping(IntPtr address, ulong size)
{
// TODO: Metal Host Mapping
return false;
}
public void CreateSync(ulong id, bool strict)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void DeleteBuffer(BufferHandle buffer)
{
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
mtlBuffer.SetPurgeableState(MTLPurgeableState.Empty);
}
public unsafe PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
return new PinnedSpan<byte>(IntPtr.Add(mtlBuffer.Contents, offset).ToPointer(), size);
}
public Capabilities GetCapabilities()
{
// TODO: Finalize these values
return new Capabilities(
api: TargetApi.Metal,
vendorName: HardwareInfoTools.GetVendor(),
hasFrontFacingBug: false,
hasVectorIndexingBug: true,
needsFragmentOutputSpecialization: true,
reduceShaderPrecision: true,
supportsAstcCompression: true,
supportsBc123Compression: true,
supportsBc45Compression: true,
supportsBc67Compression: true,
supportsEtc2Compression: true,
supports3DTextureCompression: true,
supportsBgraFormat: true,
supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
supportsScaledVertexFormats: true,
supportsSnormBufferTextureFormat: true,
supportsSparseBuffer: false,
supports5BitComponentFormat: true,
supportsBlendEquationAdvanced: false,
supportsFragmentShaderInterlock: true,
supportsFragmentShaderOrderingIntel: false,
supportsGeometryShader: false,
supportsGeometryShaderPassthrough: false,
supportsTransformFeedback: false,
supportsImageLoadFormatted: false,
supportsLayerVertexTessellation: false,
supportsMismatchingViewFormat: true,
supportsCubemapView: true,
supportsNonConstantTextureOffset: false,
supportsQuads: false,
// TODO: Metal Bindless Support
supportsSeparateSampler: false,
supportsShaderBallot: false,
supportsShaderBarrierDivergence: false,
supportsShaderFloat64: false,
supportsTextureGatherOffsets: false,
supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: false,
supportsViewportIndexVertexTessellation: false,
supportsViewportMask: false,
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: false,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
maximumImagesPerStage: Constants.MaxTextureBindings,
maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength,
maximumSupportedAnisotropy: 0,
shaderSubgroupSize: 256,
storageBufferOffsetAlignment: 0,
textureBufferOffsetAlignment: 0,
gatherBiasPrecision: 0
);
}
public ulong GetCurrentSync()
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
return 0;
}
public HardwareInfo GetHardwareInfo()
{
return new HardwareInfo(HardwareInfoTools.GetVendor(), HardwareInfoTools.GetModel(), "Apple");
}
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
throw new NotImplementedException();
}
public unsafe void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
var span = new Span<byte>(mtlBuffer.Contents.ToPointer(), (int)mtlBuffer.Length);
data.CopyTo(span[offset..]);
if (mtlBuffer.StorageMode == MTLStorageMode.Managed)
{
mtlBuffer.DidModifyRange(new NSRange
{
location = (ulong)offset,
length = (ulong)data.Length
});
}
}
public void UpdateCounters()
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void PreFrame()
{
}
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
var counterEvent = new CounterEvent();
resultHandler?.Invoke(counterEvent, type == CounterType.SamplesPassed ? (ulong)1 : 0);
return counterEvent;
}
public void ResetCounter(CounterType type)
{
// https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/creating_a_counter_sample_buffer_to_store_a_gpu_s_counter_data_during_a_pass?language=objc
}
public void WaitSync(ulong id)
{
throw new NotImplementedException();
}
public void SetInterruptAction(Action<Action> interruptAction)
{
// Not needed for now
}
public void Screenshot()
{
// TODO: Screenshots
}
public void Dispose()
{
_pipeline.Dispose();
_window.Dispose();
}
}
}

View File

@ -0,0 +1,557 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
enum EncoderType
{
Blit,
Compute,
Render,
None
}
[SupportedOSPlatform("macos")]
class Pipeline : IPipeline, IDisposable
{
private readonly MTLDevice _device;
private readonly MTLCommandQueue _commandQueue;
private readonly HelperShader _helperShader;
private MTLCommandBuffer _commandBuffer;
public MTLCommandBuffer CommandBuffer => _commandBuffer;
private MTLCommandEncoder? _currentEncoder;
public MTLCommandEncoder? CurrentEncoder => _currentEncoder;
private EncoderType _currentEncoderType = EncoderType.None;
public EncoderType CurrentEncoderType => _currentEncoderType;
private EncoderStateManager _encoderStateManager;
public Pipeline(MTLDevice device, MTLCommandQueue commandQueue)
{
_device = device;
_commandQueue = commandQueue;
_helperShader = new HelperShader(_device, this);
_commandBuffer = _commandQueue.CommandBuffer();
_encoderStateManager = new EncoderStateManager(_device, this);
}
public MTLRenderCommandEncoder GetOrCreateRenderEncoder()
{
MTLRenderCommandEncoder renderCommandEncoder;
if (_currentEncoder == null || _currentEncoderType != EncoderType.Render)
{
renderCommandEncoder = BeginRenderPass();
}
else
{
renderCommandEncoder = new MTLRenderCommandEncoder(_currentEncoder.Value);
}
_encoderStateManager.RebindState(renderCommandEncoder);
return renderCommandEncoder;
}
public MTLBlitCommandEncoder GetOrCreateBlitEncoder()
{
if (_currentEncoder != null)
{
if (_currentEncoderType == EncoderType.Blit)
{
return new MTLBlitCommandEncoder(_currentEncoder.Value);
}
}
return BeginBlitPass();
}
public MTLComputeCommandEncoder GetOrCreateComputeEncoder()
{
if (_currentEncoder != null)
{
if (_currentEncoderType == EncoderType.Compute)
{
return new MTLComputeCommandEncoder(_currentEncoder.Value);
}
}
return BeginComputePass();
}
public void EndCurrentPass()
{
if (_currentEncoder != null)
{
switch (_currentEncoderType)
{
case EncoderType.Blit:
new MTLBlitCommandEncoder(_currentEncoder.Value).EndEncoding();
_currentEncoder = null;
break;
case EncoderType.Compute:
new MTLComputeCommandEncoder(_currentEncoder.Value).EndEncoding();
_currentEncoder = null;
break;
case EncoderType.Render:
new MTLRenderCommandEncoder(_currentEncoder.Value).EndEncoding();
_currentEncoder = null;
break;
default:
throw new ArgumentOutOfRangeException();
}
_currentEncoderType = EncoderType.None;
}
}
public MTLRenderCommandEncoder BeginRenderPass()
{
EndCurrentPass();
var renderCommandEncoder = _encoderStateManager.CreateRenderCommandEncoder();
_currentEncoder = renderCommandEncoder;
_currentEncoderType = EncoderType.Render;
return renderCommandEncoder;
}
public MTLBlitCommandEncoder BeginBlitPass()
{
EndCurrentPass();
var descriptor = new MTLBlitPassDescriptor();
var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor);
_currentEncoder = blitCommandEncoder;
_currentEncoderType = EncoderType.Blit;
return blitCommandEncoder;
}
public MTLComputeCommandEncoder BeginComputePass()
{
EndCurrentPass();
var descriptor = new MTLComputePassDescriptor();
var computeCommandEncoder = _commandBuffer.ComputeCommandEncoder(descriptor);
_currentEncoder = computeCommandEncoder;
_currentEncoderType = EncoderType.Compute;
return computeCommandEncoder;
}
public void Present(CAMetalDrawable drawable, ITexture texture)
{
if (texture is not Texture tex)
{
return;
}
EndCurrentPass();
_encoderStateManager.SwapStates();
// TODO: Clean this up
var textureInfo = new TextureCreateInfo((int)drawable.Texture.Width, (int)drawable.Texture.Height, (int)drawable.Texture.Depth, (int)drawable.Texture.MipmapLevelCount, (int)drawable.Texture.SampleCount, 0, 0, 0, Format.B8G8R8A8Unorm, 0, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha);
var dest = new Texture(_device, this, textureInfo, drawable.Texture, 0, 0);
_helperShader.BlitColor(tex, dest);
_commandBuffer.PresentDrawable(drawable);
_commandBuffer.Commit();
_commandBuffer = _commandQueue.CommandBuffer();
}
public void Finish()
{
_encoderStateManager.SwapStates();
}
public void Barrier()
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
{
var blitCommandEncoder = GetOrCreateBlitEncoder();
// Might need a closer look, range's count, lower, and upper bound
// must be a multiple of 4
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(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)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void CommandBufferBarrier()
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
{
var blitCommandEncoder = GetOrCreateBlitEncoder();
MTLBuffer sourceBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref source));
MTLBuffer destinationBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref destination));
blitCommandEncoder.CopyFromBuffer(
sourceBuffer,
(ulong)srcOffset,
destinationBuffer,
(ulong)dstOffset,
(ulong)size);
}
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
{
var renderCommandEncoder = GetOrCreateRenderEncoder();
// TODO: Support topology re-indexing to provide support for TriangleFans
var primitiveType = _encoderStateManager.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)
{
var renderCommandEncoder = GetOrCreateRenderEncoder();
// TODO: Support topology re-indexing to provide support for TriangleFans
var primitiveType = _encoderStateManager.Topology.Convert();
renderCommandEncoder.DrawIndexedPrimitives(
primitiveType,
(ulong)indexCount,
_encoderStateManager.IndexType,
_encoderStateManager.IndexBuffer,
_encoderStateManager.IndexBufferOffset,
(ulong)instanceCount,
firstVertex,
(ulong)firstInstance);
}
public void DrawIndexedIndirect(BufferRange indirectBuffer)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void DrawIndirect(BufferRange indirectBuffer)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetBlendState(AdvancedBlendDescriptor blend)
{
// Metal does not support advanced blend.
}
public void SetBlendState(int index, BlendDescriptor blend)
{
_encoderStateManager.UpdateBlendDescriptors(index, blend);
}
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetDepthClamp(bool clamp)
{
_encoderStateManager.UpdateDepthClamp(clamp);
}
public void SetDepthMode(DepthMode mode)
{
// Metal does not support depth clip control.
}
public void SetDepthTest(DepthTestDescriptor depthTest)
{
_encoderStateManager.UpdateDepthState(depthTest);
}
public void SetFaceCulling(bool enable, Face face)
{
_encoderStateManager.UpdateCullMode(enable, face);
}
public void SetFrontFace(FrontFace frontFace)
{
_encoderStateManager.UpdateFrontFace(frontFace);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_encoderStateManager.UpdateIndexBuffer(buffer, type);
}
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetLineParameters(float width, bool smooth)
{
// Metal does not support wide-lines.
}
public void SetLogicOpState(bool enable, LogicalOp op)
{
// Metal does not support logic operations.
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
{
// Metal does not support polygon mode.
}
public void SetPrimitiveRestart(bool enable, int index)
{
// 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
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
{
_encoderStateManager.UpdatePrimitiveTopology(topology);
}
public void SetProgram(IProgram program)
{
_encoderStateManager.UpdateProgram(program);
}
public void SetRasterizerDiscard(bool discard)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
{
_encoderStateManager.UpdateRenderTargets(colors, depthStencil);
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
{
_encoderStateManager.UpdateScissors(regions);
}
public void SetStencilTest(StencilTestDescriptor stencilTest)
{
_encoderStateManager.UpdateStencilState(stencilTest);
}
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateUniformBuffers(buffers);
}
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_encoderStateManager.UpdateStorageBuffers(buffers);
}
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
{
if (texture is Texture tex)
{
if (sampler is Sampler samp)
{
var mtlTexture = tex.MTLTexture;
var mtlSampler = samp.GetSampler();
var index = (ulong)binding;
switch (stage)
{
case ShaderStage.Vertex:
case ShaderStage.Fragment:
_encoderStateManager.UpdateTextureAndSampler(stage, index, mtlTexture, mtlSampler);
break;
case ShaderStage.Compute:
var computeCommandEncoder = GetOrCreateComputeEncoder();
computeCommandEncoder.SetTexture(mtlTexture, index);
computeCommandEncoder.SetSamplerState(mtlSampler, index);
break;
default:
throw new ArgumentOutOfRangeException(nameof(stage), stage, "Unsupported shader stage!");
}
}
}
}
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetUserClipDistance(int index, bool enableClip)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{
_encoderStateManager.UpdateVertexAttribs(vertexAttribs);
}
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
{
_encoderStateManager.UpdateVertexBuffers(vertexBuffers);
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
{
_encoderStateManager.UpdateViewports(viewports);
}
public void TextureBarrier()
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
// renderCommandEncoder.MemoryBarrier(MTLBarrierScope.Textures, );
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void TextureBarrierTiled()
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();
// renderCommandEncoder.MemoryBarrier(MTLBarrierScope.Textures, );
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
{
// TODO: Implementable via indirect draw commands
return false;
}
public void EndHostConditionalRendering()
{
// TODO: Implementable via indirect draw commands
}
public void BeginTransformFeedback(PrimitiveTopology topology)
{
// Metal does not support transform feedback.
}
public void EndTransformFeedback()
{
// Metal does not support transform feedback.
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{
// Metal does not support transform feedback.
}
public void Dispose()
{
EndCurrentPass();
}
}
}

View File

@ -0,0 +1,69 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Program : IProgram
{
private readonly ProgramLinkStatus _status;
public MTLFunction VertexFunction;
public MTLFunction FragmentFunction;
public MTLFunction ComputeFunction;
public Program(ShaderSource[] shaders, MTLDevice device)
{
for (int index = 0; index < shaders.Length; index++)
{
ShaderSource shader = shaders[index];
var libraryError = new NSError(IntPtr.Zero);
var shaderLibrary = device.NewLibrary(StringHelper.NSString(shader.Code), new MTLCompileOptions(IntPtr.Zero), ref libraryError);
if (libraryError != IntPtr.Zero)
{
Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{StringHelper.String(libraryError.LocalizedDescription)}");
_status = ProgramLinkStatus.Failure;
return;
}
switch (shaders[index].Stage)
{
case ShaderStage.Compute:
ComputeFunction = shaderLibrary.NewFunction(StringHelper.NSString("computeMain"));
break;
case ShaderStage.Vertex:
VertexFunction = shaderLibrary.NewFunction(StringHelper.NSString("vertexMain"));
break;
case ShaderStage.Fragment:
FragmentFunction = shaderLibrary.NewFunction(StringHelper.NSString("fragmentMain"));
break;
default:
Logger.Warning?.Print(LogClass.Gpu, $"Cannot handle stage {shaders[index].Stage}!");
break;
}
}
_status = ProgramLinkStatus.Success;
}
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
return _status;
}
public byte[] GetBinary()
{
return ""u8.ToArray();
}
public void Dispose()
{
return;
}
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Shaders\Blit.metal" />
<EmbeddedResource Include="Shaders\ColorClearF.metal" />
<EmbeddedResource Include="Shaders\ColorClearSI.metal" />
<EmbeddedResource Include="Shaders\ColorClearUI.metal" />
<EmbeddedResource Include="Shaders\DepthStencilClear.metal" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,50 @@
using Ryujinx.Graphics.GAL;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Sampler : ISampler
{
private readonly MTLSamplerState _mtlSamplerState;
public Sampler(MTLDevice device, SamplerCreateInfo info)
{
(MTLSamplerMinMagFilter minFilter, MTLSamplerMipFilter mipFilter) = info.MinFilter.Convert();
var samplerState = device.NewSamplerState(new MTLSamplerDescriptor
{
BorderColor = MTLSamplerBorderColor.TransparentBlack,
MinFilter = minFilter,
MagFilter = info.MagFilter.Convert(),
MipFilter = mipFilter,
CompareFunction = info.CompareOp.Convert(),
LodMinClamp = info.MinLod,
LodMaxClamp = info.MaxLod,
LodAverage = false,
MaxAnisotropy = Math.Max((uint)info.MaxAnisotropy, 1),
SAddressMode = info.AddressU.Convert(),
TAddressMode = info.AddressV.Convert(),
RAddressMode = info.AddressP.Convert()
});
_mtlSamplerState = samplerState;
}
public Sampler(MTLSamplerState samplerState)
{
_mtlSamplerState = samplerState;
}
public MTLSamplerState GetSampler()
{
return _mtlSamplerState;
}
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,39 @@
#include <metal_stdlib>
using namespace metal;
// ------------------
// Simple Blit Shader
// ------------------
constant float2 quadVertices[] = {
float2(-1, -1),
float2(-1, 1),
float2( 1, 1),
float2(-1, -1),
float2( 1, 1),
float2( 1, -1)
};
struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};
vertex CopyVertexOut vertexMain(unsigned short vid [[vertex_id]]) {
float2 position = quadVertices[vid];
CopyVertexOut out;
out.position = float4(position, 0, 1);
out.position.y = -out.position.y;
out.uv = position * 0.5f + 0.5f;
return out;
}
fragment float4 fragmentMain(CopyVertexOut in [[stage_in]],
texture2d<float, access::sample> texture [[texture(0)]],
sampler sampler [[sampler(0)]]) {
return texture.sample(sampler, in.uv);
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,338 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Texture : ITexture, IDisposable
{
private readonly TextureCreateInfo _info;
private readonly Pipeline _pipeline;
private readonly MTLDevice _device;
public MTLTexture MTLTexture;
public TextureCreateInfo Info => _info;
public int Width => Info.Width;
public int Height => Info.Height;
public int Depth => Info.Depth;
public Texture(MTLDevice device, Pipeline pipeline, TextureCreateInfo info)
{
_device = device;
_pipeline = pipeline;
_info = info;
var descriptor = new MTLTextureDescriptor
{
PixelFormat = FormatTable.GetFormat(Info.Format),
Usage = MTLTextureUsage.Unknown,
SampleCount = (ulong)Info.Samples,
TextureType = Info.Target.Convert(),
Width = (ulong)Info.Width,
Height = (ulong)Info.Height,
MipmapLevelCount = (ulong)Info.Levels
};
if (info.Target == Target.Texture3D)
{
descriptor.Depth = (ulong)Info.Depth;
}
else if (info.Target != Target.Cubemap)
{
descriptor.ArrayLength = (ulong)Info.Depth;
}
descriptor.Swizzle = GetSwizzle(info, descriptor.PixelFormat);
MTLTexture = _device.NewTexture(descriptor);
}
public Texture(MTLDevice device, Pipeline pipeline, TextureCreateInfo info, MTLTexture sourceTexture, int firstLayer, int firstLevel)
{
_device = device;
_pipeline = pipeline;
_info = info;
var pixelFormat = FormatTable.GetFormat(Info.Format);
var textureType = Info.Target.Convert();
NSRange levels;
levels.location = (ulong)firstLevel;
levels.length = (ulong)Info.Levels;
NSRange slices;
slices.location = (ulong)firstLayer;
slices.length = 1;
if (info.Target == Target.Texture3D)
{
slices.length = (ulong)Info.Depth;
}
else if (info.Target != Target.Cubemap)
{
slices.length = (ulong)Info.Depth;
}
var swizzle = GetSwizzle(info, pixelFormat);
MTLTexture = sourceTexture.NewTextureView(pixelFormat, textureType, levels, slices, swizzle);
}
private MTLTextureSwizzleChannels GetSwizzle(TextureCreateInfo info, MTLPixelFormat pixelFormat)
{
var swizzleR = Info.SwizzleR.Convert();
var swizzleG = Info.SwizzleG.Convert();
var swizzleB = Info.SwizzleB.Convert();
var swizzleA = Info.SwizzleA.Convert();
if (info.Format == Format.R5G5B5A1Unorm ||
info.Format == Format.R5G5B5X1Unorm ||
info.Format == Format.R5G6B5Unorm)
{
(swizzleB, swizzleR) = (swizzleR, swizzleB);
}
else if (pixelFormat == MTLPixelFormat.ABGR4Unorm || info.Format == Format.A1B5G5R5Unorm)
{
var tempB = swizzleB;
var tempA = swizzleA;
swizzleB = swizzleG;
swizzleA = swizzleR;
swizzleR = tempA;
swizzleG = tempB;
}
return new MTLTextureSwizzleChannels
{
red = swizzleR,
green = swizzleG,
blue = swizzleB,
alpha = swizzleA
};
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
if (destination is Texture destinationTexture)
{
blitCommandEncoder.CopyFromTexture(
MTLTexture,
(ulong)firstLayer,
(ulong)firstLevel,
destinationTexture.MTLTexture,
(ulong)firstLayer,
(ulong)firstLevel,
MTLTexture.ArrayLength,
MTLTexture.MipmapLevelCount);
}
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
if (destination is Texture destinationTexture)
{
blitCommandEncoder.CopyFromTexture(
MTLTexture,
(ulong)srcLayer,
(ulong)srcLevel,
destinationTexture.MTLTexture,
(ulong)dstLayer,
(ulong)dstLevel,
MTLTexture.ArrayLength,
MTLTexture.MipmapLevelCount);
}
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
// var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
//
// if (destination is Texture destinationTexture)
// {
//
// }
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
ulong bytesPerImage = 0;
if (MTLTexture.TextureType == MTLTextureType.Type3D)
{
bytesPerImage = bytesPerRow * (ulong)Info.Height;
}
var handle = range.Handle;
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
blitCommandEncoder.CopyFromTexture(
MTLTexture,
(ulong)layer,
(ulong)level,
new MTLOrigin(),
new MTLSize { width = MTLTexture.Width, height = MTLTexture.Height, depth = MTLTexture.Depth },
mtlBuffer,
(ulong)range.Offset,
bytesPerRow,
bytesPerImage);
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
return new Texture(_device, _pipeline, info, MTLTexture, firstLayer, firstLevel);
}
public PinnedSpan<byte> GetData()
{
throw new NotImplementedException();
}
public PinnedSpan<byte> GetData(int layer, int level)
{
throw new NotImplementedException();
}
// TODO: Handle array formats
public unsafe void SetData(IMemoryOwner<byte> data)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
var dataSpan = data.Memory.Span;
var mtlBuffer = _device.NewBuffer((ulong)dataSpan.Length, MTLResourceOptions.ResourceStorageModeShared);
var bufferSpan = new Span<byte>(mtlBuffer.Contents.ToPointer(), dataSpan.Length);
dataSpan.CopyTo(bufferSpan);
int width = Info.Width;
int height = Info.Height;
int depth = Info.Depth;
int levels = Info.GetLevelsClamped();
bool is3D = Info.Target == Target.Texture3D;
int offset = 0;
for (int level = 0; level < levels; level++)
{
int mipSize = Info.GetMipSize(level);
int endOffset = offset + mipSize;
if ((uint)endOffset > (uint)dataSpan.Length)
{
return;
}
blitCommandEncoder.CopyFromBuffer(
mtlBuffer,
(ulong)offset,
(ulong)Info.GetMipStride(level),
(ulong)mipSize,
new MTLSize { width = (ulong)width, height = (ulong)height, depth = is3D ? (ulong)depth : 1 },
MTLTexture,
0,
(ulong)level,
new MTLOrigin()
);
offset += mipSize;
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (is3D)
{
depth = Math.Max(1, depth >> 1);
}
}
}
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
ulong bytesPerImage = 0;
if (MTLTexture.TextureType == MTLTextureType.Type3D)
{
bytesPerImage = bytesPerRow * (ulong)Info.Height;
}
unsafe
{
var dataSpan = data.Memory.Span;
var mtlBuffer = _device.NewBuffer((ulong)dataSpan.Length, MTLResourceOptions.ResourceStorageModeShared);
var bufferSpan = new Span<byte>(mtlBuffer.Contents.ToPointer(), dataSpan.Length);
dataSpan.CopyTo(bufferSpan);
blitCommandEncoder.CopyFromBuffer(
mtlBuffer,
0,
bytesPerRow,
bytesPerImage,
new MTLSize { width = MTLTexture.Width, height = MTLTexture.Height, depth = MTLTexture.Depth },
MTLTexture,
(ulong)layer,
(ulong)level,
new MTLOrigin()
);
}
}
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
ulong bytesPerImage = 0;
if (MTLTexture.TextureType == MTLTextureType.Type3D)
{
bytesPerImage = bytesPerRow * (ulong)Info.Height;
}
unsafe
{
var dataSpan = data.Memory.Span;
var mtlBuffer = _device.NewBuffer((ulong)dataSpan.Length, MTLResourceOptions.ResourceStorageModeShared);
var bufferSpan = new Span<byte>(mtlBuffer.Contents.ToPointer(), dataSpan.Length);
dataSpan.CopyTo(bufferSpan);
blitCommandEncoder.CopyFromBuffer(
mtlBuffer,
0,
bytesPerRow,
bytesPerImage,
new MTLSize { width = (ulong)region.Width, height = (ulong)region.Height, depth = 1 },
MTLTexture,
(ulong)layer,
(ulong)level,
new MTLOrigin { x = (ulong)region.X, y = (ulong)region.Y }
);
}
}
public void SetStorage(BufferRange buffer)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void Release()
{
Dispose();
}
public void Dispose()
{
MTLTexture.SetPurgeableState(MTLPurgeableState.Volatile);
}
}
}

View File

@ -0,0 +1,64 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class Window : IWindow, IDisposable
{
private readonly MetalRenderer _renderer;
private readonly CAMetalLayer _metalLayer;
public Window(MetalRenderer renderer, CAMetalLayer metalLayer)
{
_renderer = renderer;
_metalLayer = metalLayer;
}
// TODO: Handle ImageCrop
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
{
if (_renderer.Pipeline is Pipeline pipeline && texture is Texture tex)
{
var drawable = new CAMetalDrawable(ObjectiveC.IntPtr_objc_msgSend(_metalLayer, "nextDrawable"));
pipeline.Present(drawable, tex);
}
}
public void SetSize(int width, int height)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void ChangeVSyncMode(bool vsyncEnabled)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetAntiAliasing(AntiAliasing antialiasing)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetScalingFilter(ScalingFilter type)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetScalingFilterLevel(float level)
{
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
}
public void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled) { }
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,105 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System.Text;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
class CodeGenContext
{
public const string Tab = " ";
public StructuredFunction CurrentFunction { get; set; }
public StructuredProgramInfo Info { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public OperandManager OperandManager { get; }
private readonly StringBuilder _sb;
private int _level;
private string _indentation;
public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters)
{
Info = info;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
OperandManager = new OperandManager();
_sb = new StringBuilder();
}
public void AppendLine()
{
_sb.AppendLine();
}
public void AppendLine(string str)
{
_sb.AppendLine(_indentation + str);
}
public string GetCode()
{
return _sb.ToString();
}
public void EnterScope()
{
AppendLine("{");
_level++;
UpdateIndentation();
}
public void LeaveScope(string suffix = "")
{
if (_level == 0)
{
return;
}
_level--;
UpdateIndentation();
AppendLine("}" + suffix);
}
public StructuredFunction GetFunction(int id)
{
return Info.Functions[id];
}
private void UpdateIndentation()
{
_indentation = GetIndentation(_level);
}
private static string GetIndentation(int level)
{
string indentation = string.Empty;
for (int index = 0; index < level; index++)
{
indentation += Tab;
}
return indentation;
}
}
}

View File

@ -0,0 +1,220 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class Declarations
{
/*
* Description of MSL Binding Strategy
*
* There are a few fundamental differences between how GLSL and MSL handle I/O.
* This comment will set out to describe the reasons why things are done certain ways
* and to describe the overall binding model that we're striving for here.
*
* Main I/O Structs
*
* Each stage will have a main input and output struct labeled as [Stage][In/Out], i.e VertexIn.
* Every attribute within these structs will be labeled with an [[attribute(n)]] property,
* and the overall struct will be labeled with [[stage_in]] for input structs, and defined as the
* output type of the main shader function for the output struct. This struct also contains special
* attribute-based properties like [[position]], therefore these are not confined to 'user-defined' variables.
*
* Samplers & Textures
*
* Metal does not have a combined image sampler like sampler2D in GLSL, as a result we need to bind
* an individual texture and a sampler object for each instance of a combined image sampler.
* Therefore, the binding indices of straight up textures (i.e. without a sampler) must start
* after the last sampler/texture pair (n + Number of Pairs).
*
* Uniforms
*
* MSL does not have a concept of uniforms comparable to that of GLSL. As a result, instead of
* being declared outside of any function body, uniforms are part of the function signature in MSL.
* This applies to anything bound to the shader not included in the main I/O structs.
*/
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{
context.AppendLine("#include <metal_stdlib>");
context.AppendLine("#include <simd/simd.h>");
context.AppendLine();
context.AppendLine("using namespace metal;");
context.AppendLine();
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
{
}
DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
context.AppendLine();
DeclareOutputAttributes(context, info.IoDefinitions.Where(x => x.StorageKind == StorageKind.Output));
}
static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
{
return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
}
public static void DeclareLocals(CodeGenContext context, StructuredFunction function, ShaderStage stage)
{
switch (stage)
{
case ShaderStage.Vertex:
context.AppendLine("VertexOut out;");
break;
case ShaderStage.Fragment:
context.AppendLine("FragmentOut out;");
break;
}
foreach (AstOperand decl in function.Locals)
{
string name = context.OperandManager.DeclareLocal(decl);
context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
}
}
public static string GetVarTypeName(CodeGenContext context, AggregateType type)
{
return type switch
{
AggregateType.Void => "void",
AggregateType.Bool => "bool",
AggregateType.FP32 => "float",
AggregateType.S32 => "int",
AggregateType.U32 => "uint",
AggregateType.Vector2 | AggregateType.Bool => "bool2",
AggregateType.Vector2 | AggregateType.FP32 => "float2",
AggregateType.Vector2 | AggregateType.S32 => "int2",
AggregateType.Vector2 | AggregateType.U32 => "uint2",
AggregateType.Vector3 | AggregateType.Bool => "bool3",
AggregateType.Vector3 | AggregateType.FP32 => "float3",
AggregateType.Vector3 | AggregateType.S32 => "int3",
AggregateType.Vector3 | AggregateType.U32 => "uint3",
AggregateType.Vector4 | AggregateType.Bool => "bool4",
AggregateType.Vector4 | AggregateType.FP32 => "float4",
AggregateType.Vector4 | AggregateType.S32 => "int4",
AggregateType.Vector4 | AggregateType.U32 => "uint4",
_ => throw new ArgumentException($"Invalid variable type \"{type}\"."),
};
}
private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Definitions.IaIndexing)
{
// Not handled
}
else
{
if (inputs.Any())
{
string prefix = "";
switch (context.Definitions.Stage)
{
case ShaderStage.Vertex:
context.AppendLine($"struct VertexIn");
break;
case ShaderStage.Fragment:
context.AppendLine($"struct FragmentIn");
break;
case ShaderStage.Compute:
context.AppendLine($"struct KernelIn");
break;
}
context.EnterScope();
if (context.Definitions.Stage == ShaderStage.Fragment)
{
// TODO: check if it's needed
context.AppendLine("float4 position [[position]];");
}
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput: false));
string name = $"{DefaultNames.IAttributePrefix}{ioDefinition.Location}";
string suffix = context.Definitions.Stage switch
{
ShaderStage.Vertex => $" [[attribute({ioDefinition.Location})]]",
ShaderStage.Fragment => $" [[user(loc{ioDefinition.Location})]]",
_ => ""
};
context.AppendLine($"{type} {name}{suffix};");
}
context.LeaveScope(";");
}
}
}
private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Definitions.IaIndexing)
{
// Not handled
}
else
{
if (inputs.Any())
{
string prefix = "";
switch (context.Definitions.Stage)
{
case ShaderStage.Vertex:
context.AppendLine($"struct VertexOut");
break;
case ShaderStage.Fragment:
context.AppendLine($"struct FragmentOut");
break;
case ShaderStage.Compute:
context.AppendLine($"struct KernelOut");
break;
}
context.EnterScope();
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
string type = ioDefinition.IoVariable switch
{
IoVariable.Position => "float4",
IoVariable.PointSize => "float",
_ => GetVarTypeName(context, context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput: true))
};
string name = ioDefinition.IoVariable switch
{
IoVariable.Position => "position",
IoVariable.PointSize => "point_size",
IoVariable.FragmentOutputColor => "color",
_ => $"{DefaultNames.OAttributePrefix}{ioDefinition.Location}"
};
string suffix = ioDefinition.IoVariable switch
{
IoVariable.Position => " [[position]]",
IoVariable.PointSize => " [[point_size]]",
IoVariable.UserDefined => $" [[user(loc{ioDefinition.Location})]]",
IoVariable.FragmentOutputColor => $" [[color({ioDefinition.Location})]]",
_ => ""
};
context.AppendLine($"{type} {name}{suffix};");
}
context.LeaveScope(";");
}
}
}
}
}

View File

@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class DefaultNames
{
public const string LocalNamePrefix = "temp";
public const string PerPatchAttributePrefix = "patchAttr";
public const string IAttributePrefix = "inAttr";
public const string OAttributePrefix = "outAttr";
public const string ArgumentNamePrefix = "a";
public const string UndefinedName = "0";
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class HelperFunctionNames
{
public static string SwizzleAdd = "helperSwizzleAdd";
}
}

View File

@ -0,0 +1,4 @@
inline bool voteAllEqual(bool value)
{
return simd_all(value) || !simd_any(value);
}

View File

@ -0,0 +1,159 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenCall;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenMemory;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenVector;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGen
{
public static string GetExpression(CodeGenContext context, IAstNode node)
{
if (node is AstOperation operation)
{
return GetExpression(context, operation);
}
else if (node is AstOperand operand)
{
return context.OperandManager.GetExpression(context, operand);
}
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
}
private static string GetExpression(CodeGenContext context, AstOperation operation)
{
Instruction inst = operation.Inst;
InstInfo info = GetInstructionInfo(inst);
if ((info.Type & InstType.Call) != 0)
{
bool atomic = (info.Type & InstType.Atomic) != 0;
int arity = (int)(info.Type & InstType.ArityMask);
string args = string.Empty;
if (atomic)
{
// Hell
}
else
{
for (int argIndex = 0; argIndex < arity; argIndex++)
{
if (argIndex != 0)
{
args += ", ";
}
AggregateType dstType = GetSrcVarType(inst, argIndex);
args += GetSourceExpr(context, operation.GetSource(argIndex), dstType);
}
}
return info.OpName + '(' + args + ')';
}
else if ((info.Type & InstType.Op) != 0)
{
string op = info.OpName;
if (inst == Instruction.Return && operation.SourcesCount != 0)
{
return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}";
}
if (inst == Instruction.Return && context.Definitions.Stage is ShaderStage.Vertex or ShaderStage.Fragment)
{
return $"{op} out";
}
int arity = (int)(info.Type & InstType.ArityMask);
string[] expr = new string[arity];
for (int index = 0; index < arity; index++)
{
IAstNode src = operation.GetSource(index);
string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index));
bool isLhs = arity == 2 && index == 0;
expr[index] = Enclose(srcExpr, src, inst, info, isLhs);
}
switch (arity)
{
case 0:
return op;
case 1:
return op + expr[0];
case 2:
return $"{expr[0]} {op} {expr[1]}";
case 3:
return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}";
}
}
else if ((info.Type & InstType.Special) != 0)
{
switch (inst & Instruction.Mask)
{
case Instruction.Barrier:
return "|| BARRIER ||";
case Instruction.Call:
return Call(context, operation);
case Instruction.FSIBegin:
return "|| FSI BEGIN ||";
case Instruction.FSIEnd:
return "|| FSI END ||";
case Instruction.FindLSB:
return "|| FIND LSB ||";
case Instruction.FindMSBS32:
return "|| FIND MSB S32 ||";
case Instruction.FindMSBU32:
return "|| FIND MSB U32 ||";
case Instruction.GroupMemoryBarrier:
return "|| FIND GROUP MEMORY BARRIER ||";
case Instruction.ImageLoad:
return "|| IMAGE LOAD ||";
case Instruction.ImageStore:
return "|| IMAGE STORE ||";
case Instruction.ImageAtomic:
return "|| IMAGE ATOMIC ||";
case Instruction.Load:
return Load(context, operation);
case Instruction.Lod:
return "|| LOD ||";
case Instruction.MemoryBarrier:
return "|| MEMORY BARRIER ||";
case Instruction.Store:
return Store(context, operation);
case Instruction.TextureSample:
return TextureSample(context, operation);
case Instruction.TextureQuerySamples:
return TextureQuerySamples(context, operation);
case Instruction.TextureQuerySize:
return TextureQuerySize(context, operation);
case Instruction.VectorExtract:
return VectorExtract(context, operation);
case Instruction.VoteAllEqual:
return "|| VOTE ALL EQUAL ||";
}
}
// TODO: Return this to being an error
return $"Unexpected instruction type \"{info.Type}\".";
}
}
}

View File

@ -0,0 +1,25 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenCall
{
public static string Call(CodeGenContext context, AstOperation operation)
{
AstOperand funcId = (AstOperand)operation.GetSource(0);
var functon = context.GetFunction(funcId.Value);
string[] args = new string[operation.SourcesCount - 1];
for (int i = 0; i < args.Length; i++)
{
args[i] = GetSourceExpr(context, operation.GetSource(i + 1), functon.GetArgumentType(i));
}
return $"{functon.Name}({string.Join(", ", args)})";
}
}
}

View File

@ -0,0 +1,223 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.TypeConversion;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenHelper
{
private static readonly InstInfo[] _infoTable;
static InstGenHelper()
{
_infoTable = new InstInfo[(int)Instruction.Count];
#pragma warning disable IDE0055 // Disable formatting
Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomic_add_explicit");
Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomic_and_explicit");
Add(Instruction.AtomicCompareAndSwap, InstType.AtomicBinary, "atomic_compare_exchange_weak_explicit");
Add(Instruction.AtomicMaxU32, InstType.AtomicBinary, "atomic_max_explicit");
Add(Instruction.AtomicMinU32, InstType.AtomicBinary, "atomic_min_explicit");
Add(Instruction.AtomicOr, InstType.AtomicBinary, "atomic_or_explicit");
Add(Instruction.AtomicSwap, InstType.AtomicBinary, "atomic_exchange_explicit");
Add(Instruction.AtomicXor, InstType.AtomicBinary, "atomic_xor_explicit");
Add(Instruction.Absolute, InstType.CallUnary, "abs");
Add(Instruction.Add, InstType.OpBinaryCom, "+", 2);
Add(Instruction.Ballot, InstType.CallUnary, "simd_ballot");
Add(Instruction.Barrier, InstType.Special);
Add(Instruction.BitCount, InstType.CallUnary, "popcount");
Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "extract_bits");
Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "extract_bits");
Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "insert_bits");
Add(Instruction.BitfieldReverse, InstType.CallUnary, "reverse_bits");
Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6);
Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7);
Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0);
Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8);
Add(Instruction.Call, InstType.Special);
Add(Instruction.Ceiling, InstType.CallUnary, "ceil");
Add(Instruction.Clamp, InstType.CallTernary, "clamp");
Add(Instruction.ClampU32, InstType.CallTernary, "clamp");
Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5);
Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4);
Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4);
Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4);
Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4);
Add(Instruction.CompareLess, InstType.OpBinary, "<", 4);
Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4);
Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4);
Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4);
Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5);
Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12);
Add(Instruction.ConvertFP32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP64ToFP32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP32ToS32, InstType.CallUnary, "int");
Add(Instruction.ConvertFP32ToU32, InstType.CallUnary, "uint");
Add(Instruction.ConvertFP64ToS32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertFP64ToU32, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertS32ToFP32, InstType.CallUnary, "float");
Add(Instruction.ConvertS32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.ConvertU32ToFP32, InstType.CallUnary, "float");
Add(Instruction.ConvertU32ToFP64, 0); // MSL does not have a 64-bit FP
Add(Instruction.Cosine, InstType.CallUnary, "cos");
Add(Instruction.Ddx, InstType.CallUnary, "dfdx");
Add(Instruction.Ddy, InstType.CallUnary, "dfdy");
Add(Instruction.Discard, InstType.CallNullary, "discard_fragment");
Add(Instruction.Divide, InstType.OpBinary, "/", 1);
Add(Instruction.EmitVertex, 0); // MSL does not have geometry shaders
Add(Instruction.EndPrimitive, 0); // MSL does not have geometry shaders
Add(Instruction.ExponentB2, InstType.CallUnary, "exp2");
Add(Instruction.FSIBegin, InstType.Special);
Add(Instruction.FSIEnd, InstType.Special);
// TODO: LSB and MSB Implementations https://github.com/KhronosGroup/SPIRV-Cross/blob/bccaa94db814af33d8ef05c153e7c34d8bd4d685/reference/shaders-msl-no-opt/asm/comp/bitscan.asm.comp#L8
Add(Instruction.FindLSB, InstType.Special);
Add(Instruction.FindMSBS32, InstType.Special);
Add(Instruction.FindMSBU32, InstType.Special);
Add(Instruction.Floor, InstType.CallUnary, "floor");
Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma");
Add(Instruction.GroupMemoryBarrier, InstType.Special);
Add(Instruction.ImageLoad, InstType.Special);
Add(Instruction.ImageStore, InstType.Special);
Add(Instruction.ImageAtomic, InstType.Special); // Metal 3.1+
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.Load, InstType.Special);
Add(Instruction.Lod, InstType.Special);
Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^", 10);
Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0);
Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11);
Add(Instruction.LoopBreak, InstType.OpNullary, "break");
Add(Instruction.LoopContinue, InstType.OpNullary, "continue");
Add(Instruction.PackDouble2x32, 0); // MSL does not have a 64-bit FP
Add(Instruction.PackHalf2x16, InstType.CallUnary, "pack_half_to_unorm2x16");
Add(Instruction.Maximum, InstType.CallBinary, "max");
Add(Instruction.MaximumU32, InstType.CallBinary, "max");
Add(Instruction.MemoryBarrier, InstType.Special);
Add(Instruction.Minimum, InstType.CallBinary, "min");
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
Add(Instruction.Modulo, InstType.CallBinary, "%");
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
Add(Instruction.MultiplyHighS32, InstType.CallBinary, "mulhi");
Add(Instruction.MultiplyHighU32, InstType.CallBinary, "mulhi");
Add(Instruction.Negate, InstType.OpUnary, "-");
Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "rsqrt");
Add(Instruction.Return, InstType.OpNullary, "return");
Add(Instruction.Round, InstType.CallUnary, "round");
Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3);
Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3);
Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3);
Add(Instruction.Shuffle, InstType.CallQuaternary, "simd_shuffle");
Add(Instruction.ShuffleDown, InstType.CallQuaternary, "simd_shuffle_down");
Add(Instruction.ShuffleUp, InstType.CallQuaternary, "simd_shuffle_up");
Add(Instruction.ShuffleXor, InstType.CallQuaternary, "simd_shuffle_xor");
Add(Instruction.Sine, InstType.CallUnary, "sin");
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
Add(Instruction.Store, InstType.Special);
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
Add(Instruction.TextureSample, InstType.Special);
Add(Instruction.TextureQuerySamples, InstType.Special);
Add(Instruction.TextureQuerySize, InstType.Special);
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
Add(Instruction.UnpackDouble2x32, 0); // MSL does not have a 64-bit FP
Add(Instruction.UnpackHalf2x16, InstType.CallUnary, "unpack_unorm2x16_to_half");
Add(Instruction.VectorExtract, InstType.Special);
Add(Instruction.VoteAll, InstType.CallUnary, "simd_all");
Add(Instruction.VoteAllEqual, InstType.Special);
Add(Instruction.VoteAny, InstType.CallUnary, "simd_any");
#pragma warning restore IDE0055
}
private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0)
{
_infoTable[(int)inst] = new InstInfo(flags, opName, precedence);
}
public static InstInfo GetInstructionInfo(Instruction inst)
{
return _infoTable[(int)(inst & Instruction.Mask)];
}
public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType)
{
return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType);
}
public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs)
{
InstInfo pInfo = GetInstructionInfo(pInst);
return Enclose(expr, node, pInst, pInfo, isLhs);
}
public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false)
{
if (NeedsParenthesis(node, pInst, pInfo, isLhs))
{
expr = "(" + expr + ")";
}
return expr;
}
public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs)
{
// If the node isn't a operation, then it can only be a operand,
// and those never needs to be surrounded in parenthesis.
if (node is not AstOperation operation)
{
// This is sort of a special case, if this is a negative constant,
// and it is consumed by a unary operation, we need to put on the parenthesis,
// as in MSL, while a sequence like ~-1 is valid, --2 is not.
if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary)
{
return true;
}
return false;
}
if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0)
{
return false;
}
InstInfo info = _infoTable[(int)(operation.Inst & Instruction.Mask)];
if ((info.Type & (InstType.Call | InstType.Special)) != 0)
{
return false;
}
if (info.Precedence < pInfo.Precedence)
{
return false;
}
if (info.Precedence == pInfo.Precedence && isLhs)
{
return false;
}
if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom)
{
return false;
}
return true;
}
private static bool IsNegativeConst(IAstNode node)
{
if (node is not AstOperand operand)
{
return false;
}
return operand.Type == OperandType.Constant && operand.Value < 0;
}
}
}

View File

@ -0,0 +1,323 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenMemory
{
public static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
string varName;
AggregateType varType;
int srcIndex = 0;
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
int inputsCount = isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount;
if (operation.Inst == Instruction.AtomicCompareAndSwap)
{
inputsCount--;
}
switch (storageKind)
{
case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (operation.GetSource(srcIndex++) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[binding]
: context.Properties.StorageBuffers[binding];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
StructureField field = buffer.Type.Fields[fieldIndex.Value];
varName = buffer.Name;
varType = field.Type;
break;
case StorageKind.LocalMemory:
case StorageKind.SharedMemory:
if (operation.GetSource(srcIndex++) is not AstOperand { Type: OperandType.Constant } bindingId)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
MemoryDefinition memory = storageKind == StorageKind.LocalMemory
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
varName = memory.Name;
varType = memory.Type;
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (operation.GetSource(srcIndex++) is not AstOperand varId || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = -1;
int component = 0;
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, vecIndex.Value, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetMslBuiltIn(
context.Definitions,
ioVariable,
location,
component,
isOutput,
isPerPatch);
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else
{
varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]";
}
}
if (isStore)
{
varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}";
}
return varName;
}
public static string Load(CodeGenContext context, AstOperation operation)
{
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string Store(CodeGenContext context, AstOperation operation)
{
return GenerateLoadOrStore(context, operation, isStore: true);
}
public static string TextureSample(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool colorIsVector = isGather || !isShadow;
string samplerName = GetSamplerName(context.Properties, texOp);
string texCall = $"tex_{samplerName}";
texCall += ".";
int srcIndex = 0;
string Src(AggregateType type)
{
return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
}
if (intCoords)
{
texCall += "read(";
}
else
{
texCall += "sample(";
texCall += $"samp_{samplerName}";
}
int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount;
if (isShadow && !isGather)
{
pCount++;
}
void Append(string str)
{
texCall += ", " + str;
}
AggregateType coordType = intCoords ? AggregateType.S32 : AggregateType.FP32;
string AssemblePVector(int count)
{
if (count > 1)
{
string[] elems = new string[count];
for (int index = 0; index < count; index++)
{
elems[index] = Src(coordType);
}
string prefix = intCoords ? "int" : "float";
return prefix + count + "(" + string.Join(", ", elems) + ")";
}
else
{
return Src(coordType);
}
}
Append(AssemblePVector(pCount));
if (isArray)
{
texCall += ", " + Src(AggregateType.S32);
}
texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : "");
return texCall;
}
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation textOp)
{
return resourceDefinitions.Textures[textOp.Binding].Name;
}
// TODO: Verify that this is valid in MSL
private static string GetMask(int index)
{
return $".{"rgba".AsSpan(index, 1)}";
}
private static string GetMaskMultiDest(int mask)
{
string swizzle = ".";
for (int i = 0; i < 4; i++)
{
if ((mask & (1 << i)) != 0)
{
swizzle += "xyzw"[i];
}
}
return swizzle;
}
public static string TextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
string textureName = "texture";
string texCall = textureName + ".";
texCall += $"get_num_samples()";
return texCall;
}
public static string TextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
string textureName = "texture";
string texCall = textureName + ".";
if (texOp.Index == 3)
{
texCall += $"get_num_mip_levels()";
}
else
{
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
texCall += "get_";
if (texOp.Index == 0)
{
texCall += "width";
}
else if (texOp.Index == 1)
{
texCall += "height";
}
else
{
texCall += "depth";
}
texCall += "(";
if (hasLod)
{
IAstNode lod = operation.GetSource(0);
string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, 0));
texCall += $"{lodExpr}";
}
texCall += $"){GetMask(texOp.Index)}";
}
return texCall;
}
}
}

View File

@ -0,0 +1,32 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions.InstGenHelper;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class InstGenVector
{
public static string VectorExtract(CodeGenContext context, AstOperation operation)
{
IAstNode vector = operation.GetSource(0);
IAstNode index = operation.GetSource(1);
string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector));
if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant)
{
char elem = "xyzw"[indexOperand.Value];
return $"{vectorExpr}.{elem}";
}
else
{
string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1));
return $"{vectorExpr}[{indexExpr}]";
}
}
}
}

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
readonly struct InstInfo
{
public InstType Type { get; }
public string OpName { get; }
public int Precedence { get; }
public InstInfo(InstType type, string opName, int precedence)
{
Type = type;
OpName = opName;
Precedence = precedence;
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
[Flags]
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
public enum InstType
{
OpNullary = Op | 0,
OpUnary = Op | 1,
OpBinary = Op | 2,
OpBinaryCom = Op | 2 | Commutative,
OpTernary = Op | 3,
CallNullary = Call | 0,
CallUnary = Call | 1,
CallBinary = Call | 2,
CallTernary = Call | 3,
CallQuaternary = Call | 4,
// The atomic instructions have one extra operand,
// for the storage slot and offset pair.
AtomicBinary = Call | Atomic | 3,
AtomicTernary = Call | Atomic | 4,
Commutative = 1 << 8,
Op = 1 << 9,
Call = 1 << 10,
Atomic = 1 << 11,
Special = 1 << 12,
ArityMask = 0xff,
}
}

View File

@ -0,0 +1,61 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{
static class IoMap
{
public static (string, AggregateType) GetMslBuiltIn(
ShaderDefinitions definitions,
IoVariable ioVariable,
int location,
int component,
bool isOutput,
bool isPerPatch)
{
return ioVariable switch
{
IoVariable.BaseInstance => ("base_instance", AggregateType.S32),
IoVariable.BaseVertex => ("base_vertex", AggregateType.S32),
IoVariable.ClipDistance => ("clip_distance", AggregateType.Array | AggregateType.FP32),
IoVariable.FragmentOutputColor => ("out.color", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputDepth => ("depth", AggregateType.FP32),
IoVariable.FrontFacing => ("front_facing", AggregateType.Bool),
IoVariable.InstanceId => ("instance_id", AggregateType.S32),
IoVariable.PointCoord => ("point_coord", AggregateType.Vector2),
IoVariable.PointSize => ("out.point_size", AggregateType.FP32),
IoVariable.Position => ("out.position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => ("primitive_id", AggregateType.S32),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch),
IoVariable.VertexId => ("vertex_id", AggregateType.S32),
IoVariable.ViewportIndex => ("viewport_array_index", AggregateType.S32),
IoVariable.FragmentCoord => ("in.position", AggregateType.Vector4 | AggregateType.FP32),
_ => (null, AggregateType.Invalid),
};
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
: (isOutput ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix);
if (location < 0)
{
return (name, definitions.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
string prefix = isOutput ? "out" : "in";
return (prefix + "." + name, definitions.GetUserDefinedType(location, isOutput));
}
}
}

View File

@ -0,0 +1,253 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Linq;
using static Ryujinx.Graphics.Shader.CodeGen.Msl.TypeConversion;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class MslGenerator
{
public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
if (parameters.Definitions.Stage is not (ShaderStage.Vertex or ShaderStage.Fragment or ShaderStage.Compute))
{
Logger.Warning?.Print(LogClass.Gpu, $"Attempted to generate unsupported shader type {parameters.Definitions.Stage}!");
return "";
}
CodeGenContext context = new(info, parameters);
Declarations.Declare(context, info);
if (info.Functions.Count != 0)
{
for (int i = 1; i < info.Functions.Count; i++)
{
PrintFunction(context, info.Functions[i], parameters.Definitions.Stage);
context.AppendLine();
}
}
PrintFunction(context, info.Functions[0], parameters.Definitions.Stage, true);
return context.GetCode();
}
private static void PrintFunction(CodeGenContext context, StructuredFunction function, ShaderStage stage, bool isMainFunc = false)
{
context.CurrentFunction = function;
context.AppendLine(GetFunctionSignature(context, function, stage, isMainFunc));
context.EnterScope();
Declarations.DeclareLocals(context, function, stage);
PrintBlock(context, function.MainBlock, isMainFunc);
context.LeaveScope();
}
private static string GetFunctionSignature(
CodeGenContext context,
StructuredFunction function,
ShaderStage stage,
bool isMainFunc = false)
{
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
for (int i = 0; i < function.InArguments.Length; i++)
{
args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
}
for (int i = 0; i < function.OutArguments.Length; i++)
{
int j = i + function.InArguments.Length;
// Likely need to be made into pointers
args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
}
string funcKeyword = "inline";
string funcName = null;
string returnType = Declarations.GetVarTypeName(context, function.ReturnType);
if (isMainFunc)
{
if (stage == ShaderStage.Vertex)
{
funcKeyword = "vertex";
funcName = "vertexMain";
returnType = "VertexOut";
}
else if (stage == ShaderStage.Fragment)
{
funcKeyword = "fragment";
funcName = "fragmentMain";
returnType = "FragmentOut";
}
else if (stage == ShaderStage.Compute)
{
funcKeyword = "kernel";
funcName = "kernelMain";
returnType = "void";
}
if (context.AttributeUsage.UsedInputAttributes != 0)
{
if (stage == ShaderStage.Vertex)
{
args = args.Prepend("VertexIn in [[stage_in]]").ToArray();
}
else if (stage == ShaderStage.Fragment)
{
args = args.Prepend("FragmentIn in [[stage_in]]").ToArray();
}
else if (stage == ShaderStage.Compute)
{
args = args.Prepend("KernelIn in [[stage_in]]").ToArray();
}
}
// TODO: add these only if they are used
if (stage == ShaderStage.Vertex)
{
args = args.Append("uint vertex_id [[vertex_id]]").ToArray();
args = args.Append("uint instance_id [[instance_id]]").ToArray();
}
foreach (var constantBuffer in context.Properties.ConstantBuffers.Values)
{
var varType = constantBuffer.Type.Fields[0].Type & ~AggregateType.Array;
args = args.Append($"constant {Declarations.GetVarTypeName(context, varType)} *{constantBuffer.Name} [[buffer({constantBuffer.Binding})]]").ToArray();
}
foreach (var storageBuffers in context.Properties.StorageBuffers.Values)
{
var varType = storageBuffers.Type.Fields[0].Type & ~AggregateType.Array;
// Offset the binding by 15 to avoid clashing with the constant buffers
args = args.Append($"device {Declarations.GetVarTypeName(context, varType)} *{storageBuffers.Name} [[buffer({storageBuffers.Binding + 15})]]").ToArray();
}
foreach (var texture in context.Properties.Textures.Values)
{
var textureTypeName = texture.Type.ToMslTextureType();
args = args.Append($"{textureTypeName} tex_{texture.Name} [[texture({texture.Binding})]]").ToArray();
// If the texture is not separate, we need to declare a sampler
if (!texture.Separate)
{
args = args.Append($"sampler samp_{texture.Name} [[sampler({texture.Binding})]]").ToArray();
}
}
}
return $"{funcKeyword} {returnType} {funcName ?? function.Name}({string.Join(", ", args)})";
}
private static void PrintBlock(CodeGenContext context, AstBlock block, bool isMainFunction)
{
AstBlockVisitor visitor = new(block);
visitor.BlockEntered += (sender, e) =>
{
switch (e.Block.Type)
{
case AstBlockType.DoWhile:
context.AppendLine("do");
break;
case AstBlockType.Else:
context.AppendLine("else");
break;
case AstBlockType.ElseIf:
context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})");
break;
case AstBlockType.If:
context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})");
break;
default:
throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\".");
}
context.EnterScope();
};
visitor.BlockLeft += (sender, e) =>
{
context.LeaveScope();
if (e.Block.Type == AstBlockType.DoWhile)
{
context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});");
}
};
bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence;
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
{
if (node is AstOperation operation)
{
if (!supportsBarrierDivergence)
{
if (operation.Inst == IntermediateRepresentation.Instruction.Barrier)
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Logger.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}
}
else if (operation.Inst == IntermediateRepresentation.Instruction.Return)
{
mayHaveReturned = true;
}
}
string expr = InstGen.GetExpression(context, operation);
if (expr != null)
{
context.AppendLine(expr + ";");
}
}
else if (node is AstAssignment assignment)
{
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination);
AggregateType srcType = OperandManager.GetNodeDestType(context, assignment.Source);
string dest = InstGen.GetExpression(context, assignment.Destination);
string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
context.AppendLine(dest + " = " + src + ";");
}
else if (node is AstComment comment)
{
context.AppendLine("// " + comment.Comment);
}
else
{
throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\".");
}
}
}
private static string GetCondExpr(CodeGenContext context, IAstNode cond)
{
AggregateType srcType = OperandManager.GetNodeDestType(context, cond);
return ReinterpretCast(context, cond, srcType, AggregateType.Bool);
}
}
}

View File

@ -0,0 +1,102 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class NumberFormatter
{
private const int MaxDecimal = 256;
public static bool TryFormat(int value, AggregateType dstType, out string formatted)
{
if (dstType == AggregateType.FP32)
{
return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted);
}
else if (dstType == AggregateType.S32)
{
formatted = FormatInt(value);
}
else if (dstType == AggregateType.U32)
{
formatted = FormatUint((uint)value);
}
else if (dstType == AggregateType.Bool)
{
formatted = value != 0 ? "true" : "false";
}
else
{
throw new ArgumentException($"Invalid variable type \"{dstType}\".");
}
return true;
}
public static string FormatFloat(float value)
{
if (!TryFormatFloat(value, out string formatted))
{
throw new ArgumentException("Failed to convert float value to string.");
}
return formatted;
}
public static bool TryFormatFloat(float value, out string formatted)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
formatted = null;
return false;
}
formatted = value.ToString("F9", CultureInfo.InvariantCulture);
if (!formatted.Contains('.'))
{
formatted += ".0f";
}
return true;
}
public static string FormatInt(int value, AggregateType dstType)
{
if (dstType == AggregateType.S32)
{
return FormatInt(value);
}
else if (dstType == AggregateType.U32)
{
return FormatUint((uint)value);
}
else
{
throw new ArgumentException($"Invalid variable type \"{dstType}\".");
}
}
public static string FormatInt(int value)
{
if (value <= MaxDecimal && value >= -MaxDecimal)
{
return value.ToString(CultureInfo.InvariantCulture);
}
return "0x" + value.ToString("X", CultureInfo.InvariantCulture);
}
public static string FormatUint(uint value)
{
if (value <= MaxDecimal && value >= 0)
{
return value.ToString(CultureInfo.InvariantCulture) + "u";
}
return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u";
}
}
}

View File

@ -0,0 +1,176 @@
using Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
class OperandManager
{
private readonly Dictionary<AstOperand, string> _locals;
public OperandManager()
{
_locals = new Dictionary<AstOperand, string>();
}
public string DeclareLocal(AstOperand operand)
{
string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}";
_locals.Add(operand, name);
return name;
}
public string GetExpression(CodeGenContext context, AstOperand operand)
{
return operand.Type switch
{
OperandType.Argument => GetArgumentName(operand.Value),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.LocalVariable => _locals[operand],
OperandType.Undefined => DefaultNames.UndefinedName,
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."),
};
}
public static string GetArgumentName(int argIndex)
{
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
}
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node)
{
if (node is AstOperation operation)
{
if (operation.Inst == Instruction.Load || operation.Inst.IsAtomic())
{
switch (operation.StorageKind)
{
case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (operation.GetSource(0) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
if (operation.GetSource(1) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
case StorageKind.LocalMemory:
case StorageKind.SharedMemory:
if (operation.GetSource(0) is not AstOperand { Type: OperandType.Constant } bindingId)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
return memory.Type & AggregateType.ElementTypeMask;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (operation.GetSource(0) is not AstOperand varId || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = operation.StorageKind == StorageKind.Output || operation.StorageKind == StorageKind.OutputPerPatch;
bool isPerPatch = operation.StorageKind == StorageKind.InputPerPatch || operation.StorageKind == StorageKind.OutputPerPatch;
int location = 0;
int component = 0;
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(1) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetMslBuiltIn(
context.Definitions,
ioVariable,
location,
component,
isOutput,
isPerPatch);
return varType & AggregateType.ElementTypeMask;
}
}
else if (operation.Inst == Instruction.Call)
{
AstOperand funcId = (AstOperand)operation.GetSource(0);
Debug.Assert(funcId.Type == OperandType.Constant);
return context.GetFunction(funcId.Value).ReturnType;
}
else if (operation.Inst == Instruction.VectorExtract)
{
return GetNodeDestType(context, operation.GetSource(0)) & ~AggregateType.ElementCountMask;
}
else if (operation is AstTextureOperation texOp)
{
if (texOp.Inst == Instruction.ImageLoad ||
texOp.Inst == Instruction.ImageStore ||
texOp.Inst == Instruction.ImageAtomic)
{
return texOp.GetVectorType(texOp.Format.GetComponentType());
}
else if (texOp.Inst == Instruction.TextureSample)
{
return texOp.GetVectorType(GetDestVarType(operation.Inst));
}
}
return GetDestVarType(operation.Inst);
}
else if (node is AstOperand operand)
{
if (operand.Type == OperandType.Argument)
{
int argIndex = operand.Value;
return context.CurrentFunction.GetArgumentType(argIndex);
}
return OperandInfo.GetVarType(operand);
}
else
{
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
}
}
}
}

View File

@ -0,0 +1,93 @@
using Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
{
static class TypeConversion
{
public static string ReinterpretCast(
CodeGenContext context,
IAstNode node,
AggregateType srcType,
AggregateType dstType)
{
if (node is AstOperand operand && operand.Type == OperandType.Constant)
{
if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted))
{
return formatted;
}
}
string expr = InstGen.GetExpression(context, node);
return ReinterpretCast(expr, node, srcType, dstType);
}
private static string ReinterpretCast(string expr, IAstNode node, AggregateType srcType, AggregateType dstType)
{
if (srcType == dstType)
{
return expr;
}
if (srcType == AggregateType.FP32)
{
switch (dstType)
{
case AggregateType.Bool:
return $"(as_type<int>({expr}) != 0)";
case AggregateType.S32:
return $"as_type<int>({expr})";
case AggregateType.U32:
return $"as_type<uint>({expr})";
}
}
else if (dstType == AggregateType.FP32)
{
switch (srcType)
{
case AggregateType.Bool:
return $"as_type<float>({ReinterpretBoolToInt(expr, node, AggregateType.S32)})";
case AggregateType.S32:
return $"as_type<float>({expr})";
case AggregateType.U32:
return $"as_type<float>({expr})";
}
}
else if (srcType == AggregateType.Bool)
{
return ReinterpretBoolToInt(expr, node, dstType);
}
else if (dstType == AggregateType.Bool)
{
expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true);
return $"({expr} != 0)";
}
else if (dstType == AggregateType.S32)
{
return $"int({expr})";
}
else if (dstType == AggregateType.U32)
{
return $"uint({expr})";
}
throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\".");
}
private static string ReinterpretBoolToInt(string expr, IAstNode node, AggregateType dstType)
{
string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType);
string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType);
expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false);
return $"({expr} ? {trueExpr} : {falseExpr})";
}
}
}

View File

@ -14,5 +14,8 @@
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="CodeGen\Msl\HelperFunctions\VoteAllEqual.metal" />
</ItemGroup>
</Project>

View File

@ -155,5 +155,31 @@ namespace Ryujinx.Graphics.Shader
return typeName;
}
public static string ToMslTextureType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.None => "texture",
SamplerType.Texture1D => "texture1d",
SamplerType.TextureBuffer => "texturebuffer",
SamplerType.Texture2D => "texture2d",
SamplerType.Texture3D => "texture3d",
SamplerType.TextureCube => "texturecube",
_ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "_ms";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "_array";
}
return typeName + "<float>";
}
}
}

View File

@ -4,5 +4,6 @@ namespace Ryujinx.Graphics.Shader.Translation
{
OpenGL,
Vulkan,
Metal
}
}

View File

@ -4,6 +4,6 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Glsl,
Spirv,
Arb,
Msl
}
}

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Shader.CodeGen;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Msl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
@ -372,6 +373,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, parameters)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, parameters)),
TargetLanguage.Msl => new ShaderProgram(info, TargetLanguage.Msl, MslGenerator.Generate(sInfo, parameters)),
_ => throw new NotImplementedException(Options.TargetLanguage.ToString()),
};
}

View File

@ -0,0 +1,46 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using SharpMetal.QuartzCore;
using System.Runtime.Versioning;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.Metal
{
[SupportedOSPlatform("macos")]
class MetalWindow : WindowBase
{
private CAMetalLayer _caMetalLayer;
public CAMetalLayer GetLayer()
{
return _caMetalLayer;
}
public MetalWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursorMode hideCursorMode)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode) { }
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;
protected override void InitializeWindowRenderer()
{
void CreateLayer()
{
_caMetalLayer = new CAMetalLayer(SDL_Metal_GetLayer(SDL_Metal_CreateView(WindowHandle)));
}
SDL2Driver.MainThreadDispatcher?.Invoke(CreateLayer);
}
protected override void InitializeRenderer() { }
protected override void FinalizeWindowRenderer() { }
protected override void SwapBuffers() { }
}
}

View File

@ -16,8 +16,10 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Metal;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Headless.SDL2.Metal;
using Ryujinx.Headless.SDL2.OpenGL;
using Ryujinx.Headless.SDL2.Vulkan;
using Ryujinx.HLE;
@ -498,9 +500,14 @@ namespace Ryujinx.Headless.SDL2
private static WindowBase CreateWindow(Options options)
{
return options.GraphicsBackend == GraphicsBackend.Vulkan
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
return options.GraphicsBackend switch
{
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode),
GraphicsBackend.Metal => OperatingSystem.IsMacOS() ?
new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode) :
throw new Exception("Attempted to use Metal renderer on non-macOS platform!"),
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
};
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
@ -532,6 +539,11 @@ namespace Ryujinx.Headless.SDL2
preferredGpuId);
}
if (options.GraphicsBackend == GraphicsBackend.Metal && window is MetalWindow metalWindow && OperatingSystem.IsMacOS())
{
return new MetalRenderer(metalWindow.GetLayer);
}
return new OpenGLRenderer();
}

View File

@ -29,6 +29,7 @@
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
</ItemGroup>

View File

@ -26,6 +26,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Metal;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
@ -821,6 +822,10 @@ namespace Ryujinx.Ava
VulkanHelper.GetRequiredInstanceExtensions,
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
}
else if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Metal && OperatingSystem.IsMacOS())
{
renderer = new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal).CreateSurface);
}
else
{
renderer = new OpenGLRenderer();
@ -1041,6 +1046,7 @@ namespace Ryujinx.Ava
{
GraphicsBackend.Vulkan => "Vulkan",
GraphicsBackend.OpenGl => "OpenGL",
GraphicsBackend.Metal => "Metal",
_ => throw new NotImplementedException()
},
$"GPU: {_renderer.GetHardwareInfo().GpuDriver}"));

View File

@ -182,6 +182,10 @@ namespace Ryujinx.Ava
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "metal")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Metal;
}
}
// Check if docked mode was overriden.

View File

@ -60,6 +60,7 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />

View File

@ -0,0 +1,20 @@
using SharpMetal.QuartzCore;
using System;
namespace Ryujinx.Ava.UI.Renderer
{
public class EmbeddedWindowMetal : EmbeddedWindow
{
public CAMetalLayer CreateSurface()
{
if (OperatingSystem.IsMacOS())
{
return new CAMetalLayer(MetalLayer);
}
else
{
throw new NotSupportedException();
}
}
}
}

View File

@ -17,14 +17,13 @@ namespace Ryujinx.Ava.UI.Renderer
{
InitializeComponent();
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
EmbeddedWindow = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
{
EmbeddedWindow = new EmbeddedWindowOpenGL();
}
else
{
EmbeddedWindow = new EmbeddedWindowVulkan();
}
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
GraphicsBackend.Metal => new EmbeddedWindowMetal(),
GraphicsBackend.Vulkan => new EmbeddedWindowVulkan(),
_ => throw new NotSupportedException()
};
Initialize();
}

View File

@ -111,6 +111,8 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool IsMetalAvailable => OperatingSystem.IsMacOS();
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;

View File

@ -43,6 +43,9 @@
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
<TextBlock Text="OpenGL" />
</ComboBoxItem>
<ComboBoxItem IsVisible="{Binding IsMetalAvailable}">
<TextBlock Text="Metal" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}">