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; public MTLFunction VertexFunction; public MTLFunction FragmentFunction; public MTLFunction ComputeFunction; public ComputeSize ComputeLocalSize { get; } private HashTableSlim _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(ShaderSource[] shaders, ResourceLayout resourceLayout, MTLDevice device, ComputeSize computeLocalSize = default) { 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 (_shaders[index].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 {_shaders[index].Stage}!"); break; } _successCount++; if (_successCount >= _shaders.Length && _status != ProgramLinkStatus.Failure) { _status = ProgramLinkStatus.Success; } } private static (ResourceBindingSegment[][], int[], int[]) BuildBindingSegments(ReadOnlyCollection 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 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 (_graphicsPipelineCache != null) { foreach (MTLRenderPipelineState pipeline in _graphicsPipelineCache.Values) { pipeline.Dispose(); } } _computePipelineCache?.Dispose(); VertexFunction.Dispose(); FragmentFunction.Dispose(); ComputeFunction.Dispose(); } } }