Ryujinx/src/Ryujinx.Graphics.Metal/State/PipelineState.cs
riperiperi e02df72323 State and cache optimization (#27)
* WIP pipeline/depth state cache rework

* Fix some issues

* Fix some more default values

* Reduce allocations for state changes

* fix helpershader stuff

* explanation comment

* fix depth bias
2024-09-28 19:03:01 -04:00

338 lines
13 KiB
C#

using Ryujinx.Common.Logging;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct PipelineState
{
public PipelineUid Internal;
public uint StagesCount
{
readonly get => (byte)((Internal.Id0 >> 0) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint VertexAttributeDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 8) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public uint VertexBindingDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 16) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFF00FFFF) | ((ulong)value << 16);
}
public uint ColorBlendAttachmentStateCount
{
readonly get => (byte)((Internal.Id0 >> 24) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00FFFFFF) | ((ulong)value << 24);
}
/*
* Can be an input to a pipeline, but not sure what the situation for that is.
public PrimitiveTopology Topology
{
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
*/
// Reserved for when API is available.
public int LogicOp
{
readonly get => (int)((Internal.Id0 >> 32) & 0xF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFF0FFFFFFFF) | ((ulong)value << 32);
}
//?
public bool PrimitiveRestartEnable
{
readonly get => ((Internal.Id0 >> 36) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFEFFFFFFFFF) | ((value ? 1UL : 0UL) << 36);
}
public bool RasterizerDiscardEnable
{
readonly get => ((Internal.Id0 >> 37) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFDFFFFFFFFF) | ((value ? 1UL : 0UL) << 37);
}
// Reserved for when API is available.
public bool LogicOpEnable
{
readonly get => ((Internal.Id0 >> 38) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFBFFFFFFFFF) | ((value ? 1UL : 0UL) << 38);
}
public bool AlphaToCoverageEnable
{
readonly get => ((Internal.Id0 >> 40) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFEFFFFFFFFFF) | ((value ? 1UL : 0UL) << 40);
}
public bool AlphaToOneEnable
{
readonly get => ((Internal.Id0 >> 41) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFDFFFFFFFFFF) | ((value ? 1UL : 0UL) << 41);
}
public MTLPixelFormat DepthStencilFormat
{
readonly get => (MTLPixelFormat)(Internal.Id0 >> 48);
set => Internal.Id0 = (Internal.Id0 & 0x0000FFFFFFFFFFFF) | ((ulong)value << 48);
}
// Not sure how to appropriately use this, but it does need to be passed for tess.
public uint PatchControlPoints
{
readonly get => (uint)((Internal.Id1 >> 0) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint SamplesCount
{
readonly get => (uint)((Internal.Id1 >> 32) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)value << 32);
}
// Advanced blend not supported
private struct RenderPipelineDescriptorResult : IDisposable
{
public MTLRenderPipelineDescriptor Pipeline;
private MTLVertexDescriptor _vertex;
public RenderPipelineDescriptorResult(MTLRenderPipelineDescriptor pipeline, MTLVertexDescriptor vertex)
{
Pipeline = pipeline;
_vertex = vertex;
}
public void Dispose()
{
Pipeline.Dispose();
_vertex.Dispose();
}
}
private readonly void BuildColorAttachment(MTLRenderPipelineColorAttachmentDescriptor descriptor, ColorBlendStateUid blendState)
{
descriptor.PixelFormat = blendState.PixelFormat;
descriptor.SetBlendingEnabled(blendState.Enable);
descriptor.AlphaBlendOperation = blendState.AlphaBlendOperation;
descriptor.RgbBlendOperation = blendState.RgbBlendOperation;
descriptor.SourceAlphaBlendFactor = blendState.SourceAlphaBlendFactor;
descriptor.DestinationAlphaBlendFactor = blendState.DestinationAlphaBlendFactor;
descriptor.SourceRGBBlendFactor = blendState.SourceRGBBlendFactor;
descriptor.DestinationRGBBlendFactor = blendState.DestinationRGBBlendFactor;
descriptor.WriteMask = blendState.WriteMask;
}
private readonly MTLVertexDescriptor BuildVertexDescriptor()
{
var vertexDescriptor = new MTLVertexDescriptor();
for (int i = 0; i < VertexAttributeDescriptionsCount; i++)
{
VertexInputAttributeUid uid = Internal.VertexAttributes[i];
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = uid.Format;
attrib.Offset = uid.Offset;
attrib.BufferIndex = uid.BufferIndex;
}
for (int i = 0; i < VertexBindingDescriptionsCount; i++)
{
VertexInputLayoutUid uid = Internal.VertexBindings[i];
var layout = vertexDescriptor.Layouts.Object((ulong)i);
layout.StepFunction = uid.StepFunction;
layout.StepRate = uid.StepRate;
layout.Stride = uid.Stride;
}
return vertexDescriptor;
}
private RenderPipelineDescriptorResult CreateRenderDescriptor(Program program)
{
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var blendState = Internal.ColorBlendState[i];
if (blendState.PixelFormat != MTLPixelFormat.Invalid)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
BuildColorAttachment(pipelineAttachment, blendState);
}
}
MTLPixelFormat dsFormat = DepthStencilFormat;
if (dsFormat != MTLPixelFormat.Invalid)
{
switch (dsFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {dsFormat}!");
break;
}
}
/* TODO: enable when sharpmetal fixes the bindings
renderPipelineDescriptor.AlphaToCoverageEnabled = AlphaToCoverageEnable;
renderPipelineDescriptor.AlphaToOneEnabled = AlphaToOneEnable;
renderPipelineDescriptor.RasterizationEnabled = !RasterizerDiscardEnable;
*/
renderPipelineDescriptor.SampleCount = Math.Max(1, SamplesCount);
var vertexDescriptor = BuildVertexDescriptor();
renderPipelineDescriptor.VertexDescriptor = vertexDescriptor;
renderPipelineDescriptor.VertexFunction = program.VertexFunction;
if (program.FragmentFunction.NativePtr != 0)
{
renderPipelineDescriptor.FragmentFunction = program.FragmentFunction;
}
return new RenderPipelineDescriptorResult(renderPipelineDescriptor, vertexDescriptor);
}
public MTLRenderPipelineState CreateRenderPipeline(MTLDevice device, Program program)
{
if (program.TryGetGraphicsPipeline(ref Internal, out var pipelineState))
{
return pipelineState;
}
using RenderPipelineDescriptorResult descriptors = CreateRenderDescriptor(program);
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewRenderPipelineState(descriptors.Pipeline, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddGraphicsPipeline(ref Internal, pipelineState);
return pipelineState;
}
public static MTLComputePipelineState CreateComputePipeline(MTLDevice device, Program program)
{
if (program.TryGetComputePipeline(out var pipelineState))
{
return pipelineState;
}
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewComputePipelineState(program.ComputeFunction, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Compute Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddComputePipeline(pipelineState);
return pipelineState;
}
public void Initialize()
{
SamplesCount = 1;
Internal.ResetColorState();
}
/*
* TODO, this is from vulkan.
private void UpdateVertexAttributeDescriptions(VulkanRenderer gd)
{
// Vertex attributes exceeding the stride are invalid.
// In metal, they cause glitches with the vertex shader fetching incorrect values.
// To work around this, we reduce the format to something that doesn't exceed the stride if possible.
// The assumption is that the exceeding components are not actually accessed on the shader.
for (int index = 0; index < VertexAttributeDescriptionsCount; index++)
{
var attribute = Internal.VertexAttributeDescriptions[index];
int vbIndex = GetVertexBufferIndex(attribute.Binding);
if (vbIndex >= 0)
{
ref var vb = ref Internal.VertexBindingDescriptions[vbIndex];
Format format = attribute.Format;
while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride)
{
Format newFormat = FormatTable.DropLastComponent(format);
if (newFormat == format)
{
// That case means we failed to find a format that fits within the stride,
// so just restore the original format and give up.
format = attribute.Format;
break;
}
format = newFormat;
}
if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format))
{
attribute.Format = format;
}
}
_vertexAttributeDescriptions2[index] = attribute;
}
}
private int GetVertexBufferIndex(uint binding)
{
for (int index = 0; index < VertexBindingDescriptionsCount; index++)
{
if (Internal.VertexBindingDescriptions[index].Binding == binding)
{
return index;
}
}
return -1;
}
*/
}
}