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.Versioning; namespace Ryujinx.Graphics.Metal { [SupportedOSPlatform("macos")] class Program : IProgram { private readonly ProgramLinkStatus _status; public MTLFunction VertexFunction; public MTLFunction FragmentFunction; public MTLFunction ComputeFunction; public ComputeSize ComputeLocalSize { get; } private HashTableSlim _graphicsPipelineCache; private MTLComputePipelineState? _computePipelineCache; private bool _firstBackgroundUse; public ResourceBindingSegment[][] ClearSegments { get; } 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; 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?.PrintMsg(LogClass.Gpu, shader.Code); Logger.Warning?.Print(LogClass.Gpu, $"{shader.Stage} shader linking failed: \n{StringHelper.String(libraryError.LocalizedDescription)}"); _status = ProgramLinkStatus.Failure; return; } switch (shaders[index].Stage) { case ShaderStage.Compute: ComputeFunction = shaderLibrary.NewFunction(StringHelper.NSString("kernelMain")); 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; } } ClearSegments = BuildClearSegments(resourceLayout.Sets); (BindingSegments, ArgumentBufferSizes, FragArgumentBufferSizes) = BuildBindingSegments(resourceLayout.SetUsages); _status = ProgramLinkStatus.Success; } private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection sets) { ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][]; for (int setIndex = 0; setIndex < sets.Count; setIndex++) { List currentSegments = new(); ResourceDescriptor currentDescriptor = default; int currentCount = 0; for (int index = 0; index < sets[setIndex].Descriptors.Count; index++) { ResourceDescriptor descriptor = sets[setIndex].Descriptors[index]; if (currentDescriptor.Binding + currentCount != descriptor.Binding || currentDescriptor.Type != descriptor.Type || currentDescriptor.Stages != descriptor.Stages || currentDescriptor.Count > 1 || descriptor.Count > 1) { if (currentCount != 0) { currentSegments.Add(new ResourceBindingSegment( currentDescriptor.Binding, currentCount, currentDescriptor.Type, currentDescriptor.Stages, currentDescriptor.Count > 1)); } currentDescriptor = descriptor; currentCount = descriptor.Count; } else { currentCount += descriptor.Count; } } if (currentCount != 0) { currentSegments.Add(new ResourceBindingSegment( currentDescriptor.Binding, currentCount, currentDescriptor.Type, currentDescriptor.Stages, currentDescriptor.Count > 1)); } segments[setIndex] = currentSegments.ToArray(); } return segments; } 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) { return _status; } public byte[] GetBinary() { return ""u8.ToArray(); } 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(); } } }