Compare commits
139 Commits
4ee5f90d1c
...
287594959d
Author | SHA1 | Date |
---|---|---|
Isaac Marovitz | 287594959d | |
Marco Carvalho | a23d8cb92f | |
Isaac Marovitz | 42a9a116f4 | |
Isaac Marovitz | 4dbdd71273 | |
Isaac Marovitz | 49dc3ae6e6 | |
Isaac Marovitz | f0bec8115d | |
Isaac Marovitz | 5c3e33c220 | |
Isaac Marovitz | 230c6dafac | |
Isaac Marovitz | 0133642f93 | |
Isaac Marovitz | 5e65197a39 | |
Isaac Marovitz | 22d1fa20b2 | |
Isaac Marovitz | 9c4e202c14 | |
Isaac Marovitz | a393db6193 | |
Isaac Marovitz | df6c673def | |
Isaac Marovitz | 98824f8136 | |
Isaac Marovitz | 2103ddb700 | |
Isaac Marovitz | af9b29af35 | |
Isaac Marovitz | f9b77c8479 | |
Isaac Marovitz | ede82d2fff | |
Isaac Marovitz | ea41e60f69 | |
Isaac Marovitz | 0a7c834a15 | |
Isaac Marovitz | eea18a0c91 | |
Isaac Marovitz | a4dfdd6dc3 | |
Isaac Marovitz | f6983f57a1 | |
Isaac Marovitz | 6e21694bc0 | |
Isaac Marovitz | d5c0d8ad33 | |
Isaac Marovitz | 9c6b4fbcd1 | |
Isaac Marovitz | 3d3ea131a0 | |
Isaac Marovitz | 83d833471c | |
Isaac Marovitz | 5f188b489a | |
Isaac Marovitz | e73828d0f8 | |
Isaac Marovitz | 2000ccdb16 | |
Isaac Marovitz | 4d07034afb | |
Isaac Marovitz | 07cf6d3eb2 | |
Isaac Marovitz | 8dfa80ffb1 | |
Isaac Marovitz | b30b89a7b6 | |
Isaac Marovitz | 5feeb19ba8 | |
Isaac Marovitz | bf180d0ce7 | |
Isaac Marovitz | 0e29c3aff1 | |
Isaac Marovitz | 7cd8741200 | |
Isaac Marovitz | e2c8a83018 | |
Isaac Marovitz | 7f5cf1e639 | |
Isaac Marovitz | b3589be5e7 | |
Isaac Marovitz | d1294ed0e0 | |
Isaac Marovitz | 48b87e3085 | |
Isaac Marovitz | e2e452f157 | |
Isaac Marovitz | 37319cdea9 | |
Isaac Marovitz | 02c4970319 | |
Isaac Marovitz | 94c484e9cd | |
Isaac Marovitz | 1eb40d6e84 | |
Isaac Marovitz | da08d13171 | |
Isaac Marovitz | 8265b10e55 | |
Isaac Marovitz | 7cf118728d | |
Isaac Marovitz | 90089a64d6 | |
Isaac Marovitz | ec915819a5 | |
Isaac Marovitz | 7823588ef7 | |
Isaac Marovitz | efafe37930 | |
Isaac Marovitz | b768241519 | |
Isaac Marovitz | 3440593bfd | |
Isaac Marovitz | 8a6e3f7ece | |
Isaac Marovitz | 6989ee99ce | |
Isaac Marovitz | 86e9b7d794 | |
Isaac Marovitz | 8ca4205c53 | |
Isaac Marovitz | dfa2280307 | |
Isaac Marovitz | c09a69128a | |
Isaac Marovitz | a3955fce1d | |
Isaac Marovitz | 4ad11808c2 | |
Isaac Marovitz | 198c5c5080 | |
Isaac Marovitz | 257f2d0333 | |
Isaac Marovitz | 3fb32224f6 | |
Isaac Marovitz | e5288a6cce | |
Isaac Marovitz | ea9f099f0e | |
Isaac Marovitz | f7d921028b | |
Isaac Marovitz | 1facd1088f | |
Isaac Marovitz | a6963aed7b | |
Isaac Marovitz | 518bfa14b5 | |
Isaac Marovitz | 6e347ace42 | |
Isaac Marovitz | 589aaa8143 | |
Isaac Marovitz | de8f163f97 | |
Isaac Marovitz | 618bb12ad7 | |
Isaac Marovitz | 568e11af3f | |
Isaac Marovitz | a6ff3b79d5 | |
Isaac Marovitz | 38274e3fa6 | |
Isaac Marovitz | 1026f187da | |
Isaac Marovitz | 5adfeabeca | |
Isaac Marovitz | 653176bf7e | |
Isaac Marovitz | 96843890ad | |
Isaac Marovitz | e238bdbfde | |
Isaac Marovitz | 96753e756f | |
Isaac Marovitz | 4edbfdf223 | |
Isaac Marovitz | 0a88a46363 | |
Isaac Marovitz | ed99da9060 | |
Isaac Marovitz | a6e6216f62 | |
Isaac Marovitz | e65ba2d6f4 | |
Isaac Marovitz | cd0432b6ab | |
Isaac Marovitz | c29fc3a46c | |
Isaac Marovitz | da7472c5f5 | |
Isaac Marovitz | 48ccd7d5c1 | |
Isaac Marovitz | d32fa2f104 | |
Isaac Marovitz | 2273033072 | |
Isaac Marovitz | 2981c1155b | |
Isaac Marovitz | f11f221b25 | |
Isaac Marovitz | d52ab2a2f5 | |
Isaac Marovitz | 420bcd281c | |
Isaac Marovitz | 121273183e | |
Isaac Marovitz | 520ec41973 | |
Isaac Marovitz | 0bea809b4f | |
Isaac Marovitz | db4bce9495 | |
Isaac Marovitz | 4618042000 | |
Isaac Marovitz | 66a44ddc69 | |
Isaac Marovitz | e56e868cc6 | |
Isaac Marovitz | 77dab3e8f0 | |
Isaac Marovitz | b2aa7e7a64 | |
Isaac Marovitz | 3467a6b441 | |
Isaac Marovitz | 1b4dc8e2b3 | |
Isaac Marovitz | f5a59dea92 | |
Isaac Marovitz | 4389957bc7 | |
Isaac Marovitz | fa25700d2a | |
Isaac Marovitz | 9a7ff484d9 | |
Isaac Marovitz | 77818354c2 | |
Isaac Marovitz | 8d523f09b0 | |
Isaac Marovitz | 48a858a12c | |
Isaac Marovitz | 7b6bc00203 | |
Isaac Marovitz | cd8d64c38a | |
Isaac Marovitz | 41e9dd42a8 | |
Isaac Marovitz | ebccb81f6a | |
Isaac Marovitz | 91a4a1dd69 | |
Isaac Marovitz | 941d6b7c34 | |
Isaac Marovitz | 7bbfa33938 | |
Isaac Marovitz | 1e1570c2b3 | |
Isaac Marovitz | 8e6ce5e71e | |
Isaac Marovitz | 1f8135bb03 | |
Isaac Marovitz | c47e5ba2b4 | |
Isaac Marovitz | 1f5372c7a2 | |
Isaac Marovitz | 37e40daf95 | |
Isaac Marovitz | 9c8728781e | |
Isaac Marovitz | d5796eba22 | |
Isaac Marovitz | 7489a3d174 | |
Isaac Marovitz | afbffd8b5f |
|
@ -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>
|
|
@ -0,0 +1,61 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.16" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.16" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<<<<<<< HEAD
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
|
||||
=======
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.4.0" />
|
||||
>>>>>>> 546c1ffc0 (Fix some rebase errors)
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<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-preview11" />
|
||||
<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" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<<<<<<< HEAD
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
|
||||
=======
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
>>>>>>> 546c1ffc0 (Fix some rebase errors)
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -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
|
||||
|
|
|
@ -857,8 +857,14 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
threads.ForEach((thread) => thread.Start());
|
||||
threads.ForEach((thread) => thread.Join());
|
||||
foreach (var thread in threads)
|
||||
{
|
||||
thread.Start();
|
||||
}
|
||||
foreach (var thread in threads)
|
||||
{
|
||||
thread.Join();
|
||||
}
|
||||
|
||||
threads.Clear();
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@ namespace Ryujinx.Common.Configuration
|
|||
{
|
||||
Vulkan,
|
||||
OpenGl,
|
||||
Metal
|
||||
}
|
||||
}
|
||||
|
|
|
@ -395,8 +395,14 @@ namespace Ryujinx.Graphics.Gpu
|
|||
{
|
||||
Renderer.CreateSync(SyncNumber, strict);
|
||||
|
||||
SyncActions.ForEach(action => action.SyncPreAction(syncpoint));
|
||||
SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint));
|
||||
foreach (var action in SyncActions)
|
||||
{
|
||||
action.SyncPreAction(syncpoint);
|
||||
}
|
||||
foreach (var action in SyncpointActions)
|
||||
{
|
||||
action.SyncPreAction(syncpoint);
|
||||
}
|
||||
|
||||
SyncNumber++;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public struct BufferInfo
|
||||
{
|
||||
public IntPtr Handle;
|
||||
public int Offset;
|
||||
public int Index;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
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);
|
||||
// Approximate
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using SharpMetal.Foundation;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class HelperShaders
|
||||
{
|
||||
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/HelperShadersSource.metal";
|
||||
|
||||
public HelperShader BlitShader;
|
||||
|
||||
public HelperShaders(MTLDevice device)
|
||||
{
|
||||
var error = new NSError(IntPtr.Zero);
|
||||
|
||||
var shaderSource = EmbeddedResources.ReadAllText(ShadersSourcePath);
|
||||
var library = device.NewLibrary(StringHelper.NSString(shaderSource), new(IntPtr.Zero), ref error);
|
||||
if (error != IntPtr.Zero)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Library: {StringHelper.String(error.LocalizedDescription)}");
|
||||
}
|
||||
|
||||
BlitShader = new HelperShader(library, "vertexBlit", "fragmentBlit");
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
public readonly struct HelperShader
|
||||
{
|
||||
public readonly MTLFunction VertexFunction;
|
||||
public readonly MTLFunction FragmentFunction;
|
||||
|
||||
public HelperShader(MTLLibrary library, string vertex, string fragment)
|
||||
{
|
||||
VertexFunction = library.NewFunction(StringHelper.NSString(vertex));
|
||||
FragmentFunction = library.NewFunction(StringHelper.NSString(fragment));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 vertexBlit(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 fragmentBlit(CopyVertexOut in [[stage_in]],
|
||||
texture2d<float, access::sample> texture [[texture(0)]],
|
||||
sampler sampler [[sampler(0)]]) {
|
||||
return texture.sample(sampler, in.uv);
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
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;
|
||||
|
||||
_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,
|
||||
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,
|
||||
supportsScaledVertexFormats: true,
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,820 @@
|
|||
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.Collections.Generic;
|
||||
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 HelperShaders _helperShaders;
|
||||
|
||||
private MTLCommandBuffer _commandBuffer;
|
||||
private MTLCommandEncoder? _currentEncoder;
|
||||
private EncoderType _currentEncoderType = EncoderType.None;
|
||||
private MTLTexture[] _renderTargets = [];
|
||||
|
||||
private RenderEncoderState _renderEncoderState;
|
||||
private readonly MTLVertexDescriptor _vertexDescriptor = new();
|
||||
private List<BufferInfo> _vertexBuffers = [];
|
||||
private List<BufferInfo> _uniformBuffers = [];
|
||||
private List<BufferInfo> _storageBuffers = [];
|
||||
|
||||
private MTLBuffer _indexBuffer;
|
||||
private MTLIndexType _indexType;
|
||||
private ulong _indexBufferOffset;
|
||||
private MTLClearColor _clearColor;
|
||||
|
||||
public Pipeline(MTLDevice device, MTLCommandQueue commandQueue)
|
||||
{
|
||||
_device = device;
|
||||
_commandQueue = commandQueue;
|
||||
_helperShaders = new HelperShaders(_device);
|
||||
|
||||
_renderEncoderState = new RenderEncoderState(
|
||||
_helperShaders.BlitShader.VertexFunction,
|
||||
_helperShaders.BlitShader.FragmentFunction,
|
||||
_device);
|
||||
|
||||
_commandBuffer = _commandQueue.CommandBuffer();
|
||||
}
|
||||
|
||||
public MTLRenderCommandEncoder GetOrCreateRenderEncoder()
|
||||
{
|
||||
if (_currentEncoder != null)
|
||||
{
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
return new MTLRenderCommandEncoder(_currentEncoder.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return BeginRenderPass();
|
||||
}
|
||||
|
||||
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 descriptor = new MTLRenderPassDescriptor();
|
||||
for (int i = 0; i < _renderTargets.Length; i++)
|
||||
{
|
||||
if (_renderTargets[i] != null)
|
||||
{
|
||||
var attachment = descriptor.ColorAttachments.Object((ulong)i);
|
||||
attachment.Texture = _renderTargets[i];
|
||||
attachment.LoadAction = MTLLoadAction.Load;
|
||||
}
|
||||
}
|
||||
|
||||
var renderCommandEncoder = _commandBuffer.RenderCommandEncoder(descriptor);
|
||||
_renderEncoderState.SetEncoderState(renderCommandEncoder, _vertexDescriptor);
|
||||
|
||||
RebindBuffers(renderCommandEncoder);
|
||||
|
||||
_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();
|
||||
|
||||
var descriptor = new MTLRenderPassDescriptor();
|
||||
var colorAttachment = descriptor.ColorAttachments.Object(0);
|
||||
|
||||
colorAttachment.Texture = drawable.Texture;
|
||||
colorAttachment.LoadAction = MTLLoadAction.Clear;
|
||||
colorAttachment.ClearColor = _clearColor;
|
||||
|
||||
descriptor.ColorAttachments.SetObject(colorAttachment, 0);
|
||||
|
||||
var renderCommandEncoder = _commandBuffer.RenderCommandEncoder(descriptor);
|
||||
_renderEncoderState = new RenderEncoderState(
|
||||
_helperShaders.BlitShader.VertexFunction,
|
||||
_helperShaders.BlitShader.FragmentFunction,
|
||||
_device);
|
||||
_renderEncoderState.SetEncoderState(renderCommandEncoder, _vertexDescriptor);
|
||||
|
||||
var sampler = _device.NewSamplerState(new MTLSamplerDescriptor
|
||||
{
|
||||
MinFilter = MTLSamplerMinMagFilter.Nearest,
|
||||
MagFilter = MTLSamplerMinMagFilter.Nearest,
|
||||
MipFilter = MTLSamplerMipFilter.NotMipmapped
|
||||
});
|
||||
|
||||
renderCommandEncoder.SetFragmentTexture(tex.MTLTexture, 0);
|
||||
renderCommandEncoder.SetFragmentSamplerState(sampler, 0);
|
||||
|
||||
renderCommandEncoder.DrawPrimitives(MTLPrimitiveType.Triangle, 0, 6);
|
||||
renderCommandEncoder.EndEncoding();
|
||||
|
||||
_commandBuffer.PresentDrawable(drawable);
|
||||
_commandBuffer.Commit();
|
||||
|
||||
_commandBuffer = _commandQueue.CommandBuffer();
|
||||
}
|
||||
|
||||
public void Barrier()
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void RebindBuffers(MTLRenderCommandEncoder renderCommandEncoder)
|
||||
{
|
||||
foreach (var vertexBuffer in _vertexBuffers)
|
||||
{
|
||||
renderCommandEncoder.SetVertexBuffer(new MTLBuffer(vertexBuffer.Handle), (ulong)vertexBuffer.Offset, (ulong)vertexBuffer.Index);
|
||||
}
|
||||
|
||||
foreach (var uniformBuffer in _uniformBuffers)
|
||||
{
|
||||
renderCommandEncoder.SetVertexBuffer(new MTLBuffer(uniformBuffer.Handle), (ulong)uniformBuffer.Offset, (ulong)uniformBuffer.Index);
|
||||
renderCommandEncoder.SetFragmentBuffer(new MTLBuffer(uniformBuffer.Handle), (ulong)uniformBuffer.Offset, (ulong)uniformBuffer.Index);
|
||||
}
|
||||
|
||||
foreach (var storageBuffer in _storageBuffers)
|
||||
{
|
||||
renderCommandEncoder.SetVertexBuffer(new MTLBuffer(storageBuffer.Handle), (ulong)storageBuffer.Offset, (ulong)storageBuffer.Index);
|
||||
renderCommandEncoder.SetFragmentBuffer(new MTLBuffer(storageBuffer.Handle), (ulong)storageBuffer.Offset, (ulong)storageBuffer.Index);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_clearColor = new MTLClearColor { red = color.Red, green = color.Green, blue = color.Blue, alpha = color.Alpha };
|
||||
}
|
||||
|
||||
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 = _renderEncoderState.Topology.Convert();
|
||||
|
||||
renderCommandEncoder.DrawPrimitives(primitiveType, (ulong)firstVertex, (ulong)vertexCount, (ulong)instanceCount, (ulong)firstInstance);
|
||||
}
|
||||
|
||||
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
|
||||
// TODO: Support topology re-indexing to provide support for TriangleFans
|
||||
var primitiveType = _renderEncoderState.Topology.Convert();
|
||||
|
||||
renderCommandEncoder.DrawIndexedPrimitives(primitiveType, (ulong)indexCount, _indexType, _indexBuffer, _indexBufferOffset, (ulong)instanceCount, firstVertex, (ulong)firstInstance);
|
||||
}
|
||||
|
||||
public void DrawIndexedIndirect(BufferRange indirectBuffer)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Advanced blend is not supported in Metal!");
|
||||
}
|
||||
|
||||
public void SetBlendState(int index, BlendDescriptor blend)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetDepthClamp(bool clamp)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetDepthMode(DepthMode mode)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetDepthTest(DepthTestDescriptor depthTest)
|
||||
{
|
||||
var depthStencilState = _renderEncoderState.UpdateDepthState(
|
||||
depthTest.TestEnable ? depthTest.Func.Convert() : MTLCompareFunction.Always,
|
||||
depthTest.WriteEnable);
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
new MTLRenderCommandEncoder(_currentEncoder.Value).SetDepthStencilState(depthStencilState);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFaceCulling(bool enable, Face face)
|
||||
{
|
||||
var cullMode = enable ? face.Convert() : MTLCullMode.None;
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
new MTLRenderCommandEncoder(_currentEncoder.Value).SetCullMode(cullMode);
|
||||
}
|
||||
|
||||
_renderEncoderState.CullMode = cullMode;
|
||||
}
|
||||
|
||||
public void SetFrontFace(FrontFace frontFace)
|
||||
{
|
||||
var winding = frontFace.Convert();
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
new MTLRenderCommandEncoder(_currentEncoder.Value).SetFrontFacingWinding(winding);
|
||||
}
|
||||
|
||||
_renderEncoderState.Winding = winding;
|
||||
}
|
||||
|
||||
public void SetIndexBuffer(BufferRange buffer, IndexType type)
|
||||
{
|
||||
if (buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
_indexType = type.Convert();
|
||||
_indexBufferOffset = (ulong)buffer.Offset;
|
||||
var handle = buffer.Handle;
|
||||
_indexBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Not supported in Metal
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Wide-line is not supported without private Metal API");
|
||||
}
|
||||
|
||||
public void SetLogicOpState(bool enable, LogicalOp op)
|
||||
{
|
||||
// Not supported in Metal
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Not supported in Metal
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_renderEncoderState.Topology = topology;
|
||||
}
|
||||
|
||||
public void SetProgram(IProgram program)
|
||||
{
|
||||
Program prg = (Program)program;
|
||||
|
||||
if (prg.VertexFunction == IntPtr.Zero)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, "Invalid Vertex Function!");
|
||||
return;
|
||||
}
|
||||
|
||||
_renderEncoderState = new RenderEncoderState(
|
||||
prg.VertexFunction,
|
||||
prg.FragmentFunction,
|
||||
_device);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_renderTargets = new MTLTexture[colors.Length];
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
if (colors[i] is not Texture tex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tex.MTLTexture != null)
|
||||
{
|
||||
_renderTargets[i] = tex.MTLTexture;
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate Render Command Encoder
|
||||
BeginRenderPass();
|
||||
}
|
||||
|
||||
public unsafe void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
|
||||
{
|
||||
int maxScissors = Math.Min(regions.Length, _renderEncoderState.ViewportCount);
|
||||
|
||||
if (maxScissors == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mtlScissorRects = new MTLScissorRect[maxScissors];
|
||||
|
||||
for (int i = 0; i < maxScissors; i++)
|
||||
{
|
||||
var region = regions[i];
|
||||
|
||||
mtlScissorRects[i] = new MTLScissorRect
|
||||
{
|
||||
height = (ulong)region.Height,
|
||||
width = (ulong)region.Width,
|
||||
x = (ulong)region.X,
|
||||
y = (ulong)region.Y
|
||||
};
|
||||
}
|
||||
|
||||
_renderEncoderState.UpdateScissors(mtlScissorRects);
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
fixed (MTLScissorRect* pMtlScissorRects = mtlScissorRects)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
renderCommandEncoder.SetScissorRects((IntPtr)pMtlScissorRects, (ulong)regions.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
||||
{
|
||||
var backFace = 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
|
||||
};
|
||||
|
||||
var frontFace = 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
|
||||
};
|
||||
|
||||
var depthStencilState = _renderEncoderState.UpdateStencilState(backFace, frontFace);
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
new MTLRenderCommandEncoder(_currentEncoder.Value).SetDepthStencilState(depthStencilState);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
_storageBuffers = [];
|
||||
|
||||
foreach (BufferAssignment buffer in buffers)
|
||||
{
|
||||
if (buffer.Range.Size != 0)
|
||||
{
|
||||
_storageBuffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = buffer.Range.Handle.ToIntPtr(),
|
||||
Offset = buffer.Range.Offset,
|
||||
Index = buffer.Binding
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
RebindBuffers(renderCommandEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
|
||||
{
|
||||
if (texture is Texture tex)
|
||||
{
|
||||
if (sampler is Sampler samp)
|
||||
{
|
||||
MTLRenderCommandEncoder renderCommandEncoder;
|
||||
MTLComputeCommandEncoder computeCommandEncoder;
|
||||
|
||||
var mtlTexture = tex.MTLTexture;
|
||||
var mtlSampler = samp.GetSampler();
|
||||
var index = (ulong)binding;
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
renderCommandEncoder.SetFragmentTexture(mtlTexture, index);
|
||||
renderCommandEncoder.SetFragmentSamplerState(mtlSampler, index);
|
||||
break;
|
||||
case ShaderStage.Vertex:
|
||||
renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
renderCommandEncoder.SetVertexTexture(mtlTexture, index);
|
||||
renderCommandEncoder.SetVertexSamplerState(mtlSampler, index);
|
||||
break;
|
||||
case ShaderStage.Compute:
|
||||
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 SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
_uniformBuffers = [];
|
||||
|
||||
foreach (BufferAssignment buffer in buffers)
|
||||
{
|
||||
if (buffer.Range.Size != 0)
|
||||
{
|
||||
_uniformBuffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = buffer.Range.Handle.ToIntPtr(),
|
||||
Offset = buffer.Range.Offset,
|
||||
Index = buffer.Binding
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
RebindBuffers(renderCommandEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUserClipDistance(int index, bool enableClip)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
||||
{
|
||||
for (int i = 0; i < vertexAttribs.Length; i++)
|
||||
{
|
||||
if (!vertexAttribs[i].IsZero)
|
||||
{
|
||||
// TODO: Format should not be hardcoded
|
||||
var attrib = _vertexDescriptor.Attributes.Object((ulong)i);
|
||||
attrib.Format = MTLVertexFormat.Float4;
|
||||
attrib.BufferIndex = (ulong)vertexAttribs[i].BufferIndex;
|
||||
attrib.Offset = (ulong)vertexAttribs[i].Offset;
|
||||
|
||||
var layout = _vertexDescriptor.Layouts.Object((ulong)vertexAttribs[i].BufferIndex);
|
||||
layout.Stride = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
|
||||
{
|
||||
_vertexBuffers = [];
|
||||
|
||||
for (int i = 0; i < vertexBuffers.Length; i++)
|
||||
{
|
||||
if (vertexBuffers[i].Stride != 0)
|
||||
{
|
||||
var layout = _vertexDescriptor.Layouts.Object((ulong)i);
|
||||
layout.Stride = (ulong)vertexBuffers[i].Stride;
|
||||
|
||||
_vertexBuffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = vertexBuffers[i].Buffer.Handle.ToIntPtr(),
|
||||
Offset = vertexBuffers[i].Buffer.Offset,
|
||||
Index = i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
RebindBuffers(renderCommandEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetViewports(ReadOnlySpan<Viewport> viewports)
|
||||
{
|
||||
var mtlViewports = new MTLViewport[viewports.Length];
|
||||
|
||||
for (int i = 0; i < viewports.Length; i++)
|
||||
{
|
||||
var viewport = viewports[i];
|
||||
mtlViewports[i] = new MTLViewport
|
||||
{
|
||||
originX = viewport.Region.X,
|
||||
originY = viewport.Region.Y,
|
||||
width = viewport.Region.Width,
|
||||
height = viewport.Region.Height,
|
||||
znear = viewport.DepthNear,
|
||||
zfar = viewport.DepthFar
|
||||
};
|
||||
}
|
||||
|
||||
_renderEncoderState.UpdateViewport(mtlViewports);
|
||||
if (_currentEncoderType == EncoderType.Render)
|
||||
{
|
||||
fixed (MTLViewport* pMtlViewports = mtlViewports)
|
||||
{
|
||||
var renderCommandEncoder = GetOrCreateRenderEncoder();
|
||||
renderCommandEncoder.SetViewports((IntPtr)pMtlViewports, (ulong)viewports.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void EndTransformFeedback()
|
||||
{
|
||||
// Metal does not support Transform Feedback
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||
{
|
||||
// Metal does not support Transform Feedback
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EndCurrentPass();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Foundation;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
struct RenderEncoderState
|
||||
{
|
||||
private readonly MTLDevice _device;
|
||||
private readonly MTLFunction? _vertexFunction = null;
|
||||
private readonly MTLFunction? _fragmentFunction = null;
|
||||
private MTLDepthStencilState? _depthStencilState = null;
|
||||
|
||||
private MTLCompareFunction _depthCompareFunction = MTLCompareFunction.Always;
|
||||
private bool _depthWriteEnabled = false;
|
||||
|
||||
private MTLStencilDescriptor _backFaceStencil = new MTLStencilDescriptor();
|
||||
private MTLStencilDescriptor _frontFaceStencil = new MTLStencilDescriptor();
|
||||
|
||||
public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
|
||||
public MTLCullMode CullMode = MTLCullMode.None;
|
||||
public MTLWinding Winding = MTLWinding.Clockwise;
|
||||
|
||||
private MTLViewport[] _viewports = [];
|
||||
private MTLScissorRect[] _scissors = [];
|
||||
public int ViewportCount => _viewports.Length;
|
||||
|
||||
public RenderEncoderState(MTLFunction vertexFunction, MTLFunction fragmentFunction, MTLDevice device)
|
||||
{
|
||||
_vertexFunction = vertexFunction;
|
||||
_fragmentFunction = fragmentFunction;
|
||||
_device = device;
|
||||
}
|
||||
|
||||
public unsafe readonly void SetEncoderState(MTLRenderCommandEncoder renderCommandEncoder, MTLVertexDescriptor vertexDescriptor)
|
||||
{
|
||||
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor
|
||||
{
|
||||
VertexDescriptor = vertexDescriptor
|
||||
};
|
||||
|
||||
if (_vertexFunction != null)
|
||||
{
|
||||
renderPipelineDescriptor.VertexFunction = _vertexFunction.Value;
|
||||
}
|
||||
|
||||
if (_fragmentFunction != null)
|
||||
{
|
||||
renderPipelineDescriptor.FragmentFunction = _fragmentFunction.Value;
|
||||
}
|
||||
|
||||
var attachment = renderPipelineDescriptor.ColorAttachments.Object(0);
|
||||
attachment.SetBlendingEnabled(true);
|
||||
attachment.PixelFormat = MTLPixelFormat.BGRA8Unorm;
|
||||
attachment.SourceAlphaBlendFactor = MTLBlendFactor.SourceAlpha;
|
||||
attachment.DestinationAlphaBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
|
||||
attachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha;
|
||||
attachment.DestinationRGBBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
|
||||
|
||||
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.SetCullMode(CullMode);
|
||||
renderCommandEncoder.SetFrontFacingWinding(Winding);
|
||||
|
||||
if (_depthStencilState != null)
|
||||
{
|
||||
renderCommandEncoder.SetDepthStencilState(_depthStencilState.Value);
|
||||
}
|
||||
|
||||
if (_viewports.Length > 0)
|
||||
{
|
||||
fixed (MTLViewport* pMtlViewports = _viewports)
|
||||
{
|
||||
renderCommandEncoder.SetViewports((IntPtr)pMtlViewports, (ulong)_viewports.Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (_scissors.Length > 0)
|
||||
{
|
||||
fixed (MTLScissorRect* pMtlScissors = _scissors)
|
||||
{
|
||||
renderCommandEncoder.SetScissorRects((IntPtr)pMtlScissors, (ulong)_scissors.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MTLDepthStencilState UpdateStencilState(MTLStencilDescriptor backFace, MTLStencilDescriptor frontFace)
|
||||
{
|
||||
_backFaceStencil = backFace;
|
||||
_frontFaceStencil = frontFace;
|
||||
|
||||
_depthStencilState = _device.NewDepthStencilState(new MTLDepthStencilDescriptor
|
||||
{
|
||||
DepthCompareFunction = _depthCompareFunction,
|
||||
DepthWriteEnabled = _depthWriteEnabled,
|
||||
BackFaceStencil = _backFaceStencil,
|
||||
FrontFaceStencil = _frontFaceStencil
|
||||
});
|
||||
|
||||
return _depthStencilState.Value;
|
||||
}
|
||||
|
||||
public MTLDepthStencilState UpdateDepthState(MTLCompareFunction depthCompareFunction, bool depthWriteEnabled)
|
||||
{
|
||||
_depthCompareFunction = depthCompareFunction;
|
||||
_depthWriteEnabled = depthWriteEnabled;
|
||||
|
||||
var state = _device.NewDepthStencilState(new MTLDepthStencilDescriptor
|
||||
{
|
||||
DepthCompareFunction = _depthCompareFunction,
|
||||
DepthWriteEnabled = _depthWriteEnabled,
|
||||
BackFaceStencil = _backFaceStencil,
|
||||
FrontFaceStencil = _frontFaceStencil
|
||||
});
|
||||
|
||||
_depthStencilState = state;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void UpdateScissors(MTLScissorRect[] scissors)
|
||||
{
|
||||
_scissors = scissors;
|
||||
}
|
||||
|
||||
public void UpdateViewport(MTLViewport[] viewports)
|
||||
{
|
||||
_viewports = viewports;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<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="HelperShadersSource.metal" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,44 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
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 = (uint)info.MaxAnisotropy,
|
||||
SAddressMode = info.AddressU.Convert(),
|
||||
TAddressMode = info.AddressV.Convert(),
|
||||
RAddressMode = info.AddressP.Convert()
|
||||
});
|
||||
|
||||
_mtlSamplerState = samplerState;
|
||||
}
|
||||
|
||||
public MTLSamplerState GetSampler()
|
||||
{
|
||||
return _mtlSamplerState;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
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.ShaderRead,
|
||||
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;
|
||||
}
|
||||
|
||||
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 (descriptor.PixelFormat == MTLPixelFormat.ABGR4Unorm || info.Format == Format.A1B5G5R5Unorm)
|
||||
{
|
||||
var tempB = swizzleB;
|
||||
var tempA = swizzleA;
|
||||
|
||||
swizzleB = swizzleG;
|
||||
swizzleA = swizzleR;
|
||||
swizzleR = tempA;
|
||||
swizzleG = tempB;
|
||||
}
|
||||
|
||||
descriptor.Swizzle = new MTLTextureSwizzleChannels
|
||||
{
|
||||
red = swizzleR,
|
||||
green = swizzleG,
|
||||
blue = swizzleB,
|
||||
alpha = swizzleA
|
||||
};
|
||||
|
||||
MTLTexture = _device.NewTexture(descriptor);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
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();
|
||||
|
||||
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(";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "undef";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.Graphics.Shader.CodeGen.Msl
|
||||
{
|
||||
static class HelperFunctionNames
|
||||
{
|
||||
public static string SwizzleAdd = "helperSwizzleAdd";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
inline bool voteAllEqual(bool value)
|
||||
{
|
||||
return simd_all(value) || !simd_any(value);
|
||||
}
|
|
@ -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}\".";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)})";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.AtomicBinary, "atomic_abs_explicit");
|
||||
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.Cast, "int");
|
||||
Add(Instruction.ConvertFP32ToU32, InstType.Cast, "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.Cast, "float");
|
||||
Add(Instruction.ConvertS32ToFP64, 0); // MSL does not have a 64-bit FP
|
||||
Add(Instruction.ConvertU32ToFP32, InstType.Cast, "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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
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 texCall = "texture.";
|
||||
|
||||
int srcIndex = 0;
|
||||
|
||||
string Src(AggregateType type)
|
||||
{
|
||||
return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
|
||||
}
|
||||
|
||||
if (intCoords)
|
||||
{
|
||||
texCall += "read(";
|
||||
}
|
||||
else
|
||||
{
|
||||
texCall += "sample(";
|
||||
|
||||
string samplerName = GetSamplerName(context.Properties, texOp);
|
||||
|
||||
texCall += samplerName;
|
||||
}
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
int pCount = coordsCount;
|
||||
|
||||
int arrayIndexElem = -1;
|
||||
|
||||
if (isArray)
|
||||
{
|
||||
arrayIndexElem = pCount++;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
if (arrayIndexElem == index)
|
||||
{
|
||||
elems[index] = Src(AggregateType.S32);
|
||||
|
||||
if (!intCoords)
|
||||
{
|
||||
elems[index] = "float(" + elems[index] + ")";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
elems[index] = Src(coordType);
|
||||
}
|
||||
}
|
||||
|
||||
string prefix = intCoords ? "int" : "float";
|
||||
|
||||
return prefix + count + "(" + string.Join(", ", elems) + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return Src(coordType);
|
||||
}
|
||||
}
|
||||
|
||||
Append(AssemblePVector(pCount));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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,
|
||||
Cast = 1 << 13,
|
||||
|
||||
ArityMask = 0xff,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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),
|
||||
_ => (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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var constantBuffer in context.Properties.ConstantBuffers.Values)
|
||||
{
|
||||
args = args.Append($"constant float4 *{constantBuffer.Name} [[buffer({constantBuffer.Binding})]]").ToArray();
|
||||
}
|
||||
|
||||
foreach (var storageBuffers in context.Properties.StorageBuffers.Values)
|
||||
{
|
||||
args = args.Append($"device float4 *{storageBuffers.Name} [[buffer({storageBuffers.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -4,5 +4,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
OpenGL,
|
||||
Vulkan,
|
||||
Metal
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
Glsl,
|
||||
Spirv,
|
||||
Arb,
|
||||
Msl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}"));
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -179,6 +179,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.
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}">
|
||||
|
|
Loading…
Reference in New Issue