mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-28 01:27:11 +02:00
EXPERIMENTAL: Metal backend (#441)
This is not a continuation of the Metal backend; this is simply bringing the branch up to date and merging it as-is behind an experiment. --------- Co-authored-by: Isaac Marovitz <isaacryu@icloud.com> Co-authored-by: Samuliak <samuliak77@gmail.com> Co-authored-by: SamoZ256 <96914946+SamoZ256@users.noreply.github.com> Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Co-authored-by: riperiperi <rhy3756547@hotmail.com> Co-authored-by: Gabriel A <gab.dark.100@gmail.com>
This commit is contained in:
parent
3094df54dd
commit
852823104f
131 changed files with 14992 additions and 140 deletions
286
src/Ryujinx.Graphics.Metal/Program.cs
Normal file
286
src/Ryujinx.Graphics.Metal/Program.cs
Normal file
|
@ -0,0 +1,286 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using SharpMetal.Foundation;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class Program : IProgram
|
||||
{
|
||||
private ProgramLinkStatus _status;
|
||||
private readonly ShaderSource[] _shaders;
|
||||
private readonly GCHandle[] _handles;
|
||||
private int _successCount;
|
||||
|
||||
private readonly MetalRenderer _renderer;
|
||||
|
||||
public MTLFunction VertexFunction;
|
||||
public MTLFunction FragmentFunction;
|
||||
public MTLFunction ComputeFunction;
|
||||
public ComputeSize ComputeLocalSize { get; }
|
||||
|
||||
private HashTableSlim<PipelineUid, MTLRenderPipelineState> _graphicsPipelineCache;
|
||||
private MTLComputePipelineState? _computePipelineCache;
|
||||
private bool _firstBackgroundUse;
|
||||
|
||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||
// Argument buffer sizes for Vertex or Compute stages
|
||||
public int[] ArgumentBufferSizes { get; }
|
||||
// Argument buffer sizes for Fragment stage
|
||||
public int[] FragArgumentBufferSizes { get; }
|
||||
|
||||
public Program(
|
||||
MetalRenderer renderer,
|
||||
MTLDevice device,
|
||||
ShaderSource[] shaders,
|
||||
ResourceLayout resourceLayout,
|
||||
ComputeSize computeLocalSize = default)
|
||||
{
|
||||
_renderer = renderer;
|
||||
renderer.Programs.Add(this);
|
||||
|
||||
ComputeLocalSize = computeLocalSize;
|
||||
_shaders = shaders;
|
||||
_handles = new GCHandle[_shaders.Length];
|
||||
|
||||
_status = ProgramLinkStatus.Incomplete;
|
||||
|
||||
for (int i = 0; i < _shaders.Length; i++)
|
||||
{
|
||||
ShaderSource shader = _shaders[i];
|
||||
|
||||
using var compileOptions = new MTLCompileOptions
|
||||
{
|
||||
PreserveInvariance = true,
|
||||
LanguageVersion = MTLLanguageVersion.Version31,
|
||||
};
|
||||
var index = i;
|
||||
|
||||
_handles[i] = device.NewLibrary(StringHelper.NSString(shader.Code), compileOptions, (library, error) => CompilationResultHandler(library, error, index));
|
||||
}
|
||||
|
||||
(BindingSegments, ArgumentBufferSizes, FragArgumentBufferSizes) = BuildBindingSegments(resourceLayout.SetUsages);
|
||||
}
|
||||
|
||||
public void CompilationResultHandler(MTLLibrary library, NSError error, int index)
|
||||
{
|
||||
var shader = _shaders[index];
|
||||
|
||||
if (_handles[index].IsAllocated)
|
||||
{
|
||||
_handles[index].Free();
|
||||
}
|
||||
|
||||
if (error != IntPtr.Zero)
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.Gpu, shader.Code);
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"{shader.Stage} shader linking failed: \n{StringHelper.String(error.LocalizedDescription)}");
|
||||
_status = ProgramLinkStatus.Failure;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (shader.Stage)
|
||||
{
|
||||
case ShaderStage.Compute:
|
||||
ComputeFunction = library.NewFunction(StringHelper.NSString("kernelMain"));
|
||||
break;
|
||||
case ShaderStage.Vertex:
|
||||
VertexFunction = library.NewFunction(StringHelper.NSString("vertexMain"));
|
||||
break;
|
||||
case ShaderStage.Fragment:
|
||||
FragmentFunction = library.NewFunction(StringHelper.NSString("fragmentMain"));
|
||||
break;
|
||||
default:
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Cannot handle stage {shader.Stage}!");
|
||||
break;
|
||||
}
|
||||
|
||||
_successCount++;
|
||||
|
||||
if (_successCount >= _shaders.Length && _status != ProgramLinkStatus.Failure)
|
||||
{
|
||||
_status = ProgramLinkStatus.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private static (ResourceBindingSegment[][], int[], int[]) BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||
{
|
||||
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
|
||||
int[] argBufferSizes = new int[setUsages.Count];
|
||||
int[] fragArgBufferSizes = new int[setUsages.Count];
|
||||
|
||||
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
|
||||
{
|
||||
List<ResourceBindingSegment> currentSegments = new();
|
||||
|
||||
ResourceUsage currentUsage = default;
|
||||
int currentCount = 0;
|
||||
|
||||
for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
|
||||
{
|
||||
ResourceUsage usage = setUsages[setIndex].Usages[index];
|
||||
|
||||
if (currentUsage.Binding + currentCount != usage.Binding ||
|
||||
currentUsage.Type != usage.Type ||
|
||||
currentUsage.Stages != usage.Stages ||
|
||||
currentUsage.ArrayLength > 1 ||
|
||||
usage.ArrayLength > 1)
|
||||
{
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentUsage.Binding,
|
||||
currentCount,
|
||||
currentUsage.Type,
|
||||
currentUsage.Stages,
|
||||
currentUsage.ArrayLength > 1));
|
||||
|
||||
var size = currentCount * ResourcePointerSize(currentUsage.Type);
|
||||
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
|
||||
{
|
||||
fragArgBufferSizes[setIndex] += size;
|
||||
}
|
||||
|
||||
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
|
||||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
|
||||
{
|
||||
argBufferSizes[setIndex] += size;
|
||||
}
|
||||
}
|
||||
|
||||
currentUsage = usage;
|
||||
currentCount = usage.ArrayLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentUsage.Binding,
|
||||
currentCount,
|
||||
currentUsage.Type,
|
||||
currentUsage.Stages,
|
||||
currentUsage.ArrayLength > 1));
|
||||
|
||||
var size = currentCount * ResourcePointerSize(currentUsage.Type);
|
||||
if (currentUsage.Stages.HasFlag(ResourceStages.Fragment))
|
||||
{
|
||||
fragArgBufferSizes[setIndex] += size;
|
||||
}
|
||||
|
||||
if (currentUsage.Stages.HasFlag(ResourceStages.Vertex) ||
|
||||
currentUsage.Stages.HasFlag(ResourceStages.Compute))
|
||||
{
|
||||
argBufferSizes[setIndex] += size;
|
||||
}
|
||||
}
|
||||
|
||||
segments[setIndex] = currentSegments.ToArray();
|
||||
}
|
||||
|
||||
return (segments, argBufferSizes, fragArgBufferSizes);
|
||||
}
|
||||
|
||||
private static int ResourcePointerSize(ResourceType type)
|
||||
{
|
||||
return (type == ResourceType.TextureAndSampler ? 2 : 1);
|
||||
}
|
||||
|
||||
public ProgramLinkStatus CheckProgramLink(bool blocking)
|
||||
{
|
||||
if (blocking)
|
||||
{
|
||||
while (_status == ProgramLinkStatus.Incomplete)
|
||||
{ }
|
||||
|
||||
return _status;
|
||||
}
|
||||
|
||||
return _status;
|
||||
}
|
||||
|
||||
public byte[] GetBinary()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public void AddGraphicsPipeline(ref PipelineUid key, MTLRenderPipelineState pipeline)
|
||||
{
|
||||
(_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
|
||||
}
|
||||
|
||||
public void AddComputePipeline(MTLComputePipelineState pipeline)
|
||||
{
|
||||
_computePipelineCache = pipeline;
|
||||
}
|
||||
|
||||
public bool TryGetGraphicsPipeline(ref PipelineUid key, out MTLRenderPipelineState pipeline)
|
||||
{
|
||||
if (_graphicsPipelineCache == null)
|
||||
{
|
||||
pipeline = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
|
||||
{
|
||||
if (_firstBackgroundUse)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
|
||||
_firstBackgroundUse = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_firstBackgroundUse = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetComputePipeline(out MTLComputePipelineState pipeline)
|
||||
{
|
||||
if (_computePipelineCache.HasValue)
|
||||
{
|
||||
pipeline = _computePipelineCache.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
pipeline = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_renderer.Programs.Remove(this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_graphicsPipelineCache != null)
|
||||
{
|
||||
foreach (MTLRenderPipelineState pipeline in _graphicsPipelineCache.Values)
|
||||
{
|
||||
pipeline.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_computePipelineCache?.Dispose();
|
||||
|
||||
VertexFunction.Dispose();
|
||||
FragmentFunction.Dispose();
|
||||
ComputeFunction.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue