Metal: Compute Shaders (#19)

* check for too bix texture bindings

* implement lod query

* print shader stage name

* always have fragment input

* resolve merge conflicts

* fix: lod query

* fix: casting texture coords

* support non-array memories

* use structure types for buffers

* implement compute pipeline cache

* compute dispatch

* improve error message

* rebind compute state

* bind compute textures

* pass local size as an argument to dispatch

* implement texture buffers

* hack: change vertex index to vertex id

* pass support buffer as an argument to every function

* return at the end of function

* fix: certain missing compute bindings

* implement texture base

* improve texture binding system

* remove useless exception

* move texture handle to texture base

* fix: segfault when using disposed textures

---------

Co-authored-by: Samuliak <samuliak77@gmail.com>
Co-authored-by: SamoZ256 <96914946+SamoZ256@users.noreply.github.com>
This commit is contained in:
Isaac Marovitz 2024-05-29 16:21:59 +01:00
parent 131ab75d55
commit b064d76a4f
26 changed files with 718 additions and 224 deletions

View file

@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Metal
private readonly Pipeline _pipeline;
private readonly RenderPipelineCache _renderPipelineCache;
private readonly ComputePipelineCache _computePipelineCache;
private readonly DepthStencilCache _depthStencilCache;
private EncoderState _currentState = new();
@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Metal
{
_pipeline = pipeline;
_renderPipelineCache = new(device);
_computePipelineCache = new(device);
_depthStencilCache = new(device);
// Zero buffer
@ -50,6 +52,7 @@ namespace Ryujinx.Graphics.Metal
_currentState.BackFaceStencil.Dispose();
_renderPipelineCache.Dispose();
_computePipelineCache.Dispose();
_depthStencilCache.Dispose();
}
@ -77,8 +80,8 @@ namespace Ryujinx.Graphics.Metal
SetScissors(renderCommandEncoder);
SetViewports(renderCommandEncoder);
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
SetBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
SetRenderBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
SetRenderBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
SetCullMode(renderCommandEncoder);
SetFrontFace(renderCommandEncoder);
SetStencilRefValue(renderCommandEncoder);
@ -107,7 +110,7 @@ namespace Ryujinx.Graphics.Metal
if (_currentState.RenderTargets[i] != null)
{
var passAttachment = renderPassDescriptor.ColorAttachments.Object((ulong)i);
passAttachment.Texture = _currentState.RenderTargets[i].MTLTexture;
passAttachment.Texture = _currentState.RenderTargets[i].GetHandle();
passAttachment.LoadAction = _currentState.ClearLoadAction ? MTLLoadAction.Clear : MTLLoadAction.Load;
passAttachment.StoreAction = MTLStoreAction.Store;
}
@ -118,19 +121,19 @@ namespace Ryujinx.Graphics.Metal
if (_currentState.DepthStencil != null)
{
switch (_currentState.DepthStencil.MTLTexture.PixelFormat)
switch (_currentState.DepthStencil.GetHandle().PixelFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
depthAttachment.Texture = _currentState.DepthStencil.MTLTexture;
depthAttachment.Texture = _currentState.DepthStencil.GetHandle();
depthAttachment.LoadAction = MTLLoadAction.Load;
depthAttachment.StoreAction = MTLStoreAction.Store;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
stencilAttachment.Texture = _currentState.DepthStencil.MTLTexture;
stencilAttachment.Texture = _currentState.DepthStencil.GetHandle();
stencilAttachment.LoadAction = MTLLoadAction.Load;
stencilAttachment.StoreAction = MTLStoreAction.Store;
break;
@ -138,16 +141,16 @@ namespace Ryujinx.Graphics.Metal
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
depthAttachment.Texture = _currentState.DepthStencil.MTLTexture;
depthAttachment.Texture = _currentState.DepthStencil.GetHandle();
depthAttachment.LoadAction = MTLLoadAction.Load;
depthAttachment.StoreAction = MTLStoreAction.Store;
stencilAttachment.Texture = _currentState.DepthStencil.MTLTexture;
stencilAttachment.Texture = _currentState.DepthStencil.GetHandle();
stencilAttachment.LoadAction = MTLLoadAction.Load;
stencilAttachment.StoreAction = MTLStoreAction.Store;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.MTLTexture.PixelFormat}!");
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.GetHandle().PixelFormat}!");
break;
}
}
@ -166,10 +169,18 @@ namespace Ryujinx.Graphics.Metal
SetViewports(renderCommandEncoder);
SetScissors(renderCommandEncoder);
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
SetBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Vertex, _currentState.VertexTextures, _currentState.VertexSamplers);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Fragment, _currentState.FragmentTextures, _currentState.FragmentSamplers);
SetRenderBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
SetRenderBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
for (ulong i = 0; i < Constants.MaxTextures; i++)
{
SetRenderTexture(renderCommandEncoder, ShaderStage.Vertex, i, _currentState.VertexTextures[i]);
SetRenderTexture(renderCommandEncoder, ShaderStage.Fragment, i, _currentState.FragmentTextures[i]);
}
for (ulong i = 0; i < Constants.MaxSamplers; i++)
{
SetRenderSampler(renderCommandEncoder, ShaderStage.Vertex, i, _currentState.VertexSamplers[i]);
SetRenderSampler(renderCommandEncoder, ShaderStage.Fragment, i, _currentState.FragmentSamplers[i]);
}
// Cleanup
renderPassDescriptor.Dispose();
@ -177,11 +188,34 @@ namespace Ryujinx.Graphics.Metal
return renderCommandEncoder;
}
public void RebindState(MTLRenderCommandEncoder renderCommandEncoder)
public MTLComputeCommandEncoder CreateComputeCommandEncoder()
{
if (_currentState.Dirty.Pipeline)
var descriptor = new MTLComputePassDescriptor();
var computeCommandEncoder = _pipeline.CommandBuffer.ComputeCommandEncoder(descriptor);
// Rebind all the state
SetComputeBuffers(computeCommandEncoder, _currentState.UniformBuffers);
SetComputeBuffers(computeCommandEncoder, _currentState.StorageBuffers);
for (ulong i = 0; i < Constants.MaxTextures; i++)
{
SetPipelineState(renderCommandEncoder);
SetComputeTexture(computeCommandEncoder, i, _currentState.ComputeTextures[i]);
}
for (ulong i = 0; i < Constants.MaxSamplers; i++)
{
SetComputeSampler(computeCommandEncoder, i, _currentState.ComputeSamplers[i]);
}
// Cleanup
descriptor.Dispose();
return computeCommandEncoder;
}
public void RebindRenderState(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.Dirty.RenderPipeline)
{
SetRenderPipelineState(renderCommandEncoder);
}
if (_currentState.Dirty.DepthStencil)
@ -190,10 +224,22 @@ namespace Ryujinx.Graphics.Metal
}
// Clear the dirty flags
_currentState.Dirty.Clear();
_currentState.Dirty.RenderPipeline = false;
_currentState.Dirty.DepthStencil = false;
}
private readonly void SetPipelineState(MTLRenderCommandEncoder renderCommandEncoder)
public void RebindComputeState(MTLComputeCommandEncoder computeCommandEncoder)
{
if (_currentState.Dirty.ComputePipeline)
{
SetComputePipelineState(computeCommandEncoder);
}
// Clear the dirty flags
_currentState.Dirty.ComputePipeline = false;
}
private readonly void SetRenderPipelineState(MTLRenderCommandEncoder renderCommandEncoder)
{
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
@ -202,7 +248,7 @@ namespace Ryujinx.Graphics.Metal
if (_currentState.RenderTargets[i] != null)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
pipelineAttachment.PixelFormat = _currentState.RenderTargets[i].MTLTexture.PixelFormat;
pipelineAttachment.PixelFormat = _currentState.RenderTargets[i].GetHandle().PixelFormat;
pipelineAttachment.SourceAlphaBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationAlphaBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha;
@ -225,27 +271,27 @@ namespace Ryujinx.Graphics.Metal
if (_currentState.DepthStencil != null)
{
switch (_currentState.DepthStencil.MTLTexture.PixelFormat)
switch (_currentState.DepthStencil.GetHandle().PixelFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.MTLTexture.PixelFormat;
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.MTLTexture.PixelFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.MTLTexture.PixelFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.MTLTexture.PixelFormat;
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.MTLTexture.PixelFormat}!");
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.GetHandle().PixelFormat}!");
break;
}
}
@ -287,6 +333,18 @@ namespace Ryujinx.Graphics.Metal
}
}
private readonly void SetComputePipelineState(MTLComputeCommandEncoder computeCommandEncoder)
{
if (_currentState.ComputeFunction == null)
{
return;
}
var pipelineState = _computePipelineCache.GetOrCreate(_currentState.ComputeFunction.Value);
computeCommandEncoder.SetComputePipelineState(pipelineState);
}
public void UpdateIndexBuffer(BufferRange buffer, IndexType type)
{
if (buffer.Handle != BufferHandle.Null)
@ -307,17 +365,34 @@ namespace Ryujinx.Graphics.Metal
{
Program prg = (Program)program;
if (prg.VertexFunction == IntPtr.Zero)
if (prg.VertexFunction == IntPtr.Zero && prg.ComputeFunction == IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, "Invalid Vertex Function!");
if (prg.FragmentFunction == IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, "No compute function");
}
else
{
Logger.Error?.PrintMsg(LogClass.Gpu, "No vertex function");
}
return;
}
_currentState.VertexFunction = prg.VertexFunction;
_currentState.FragmentFunction = prg.FragmentFunction;
if (prg.VertexFunction != IntPtr.Zero)
{
_currentState.VertexFunction = prg.VertexFunction;
_currentState.FragmentFunction = prg.FragmentFunction;
// Mark dirty
_currentState.Dirty.Pipeline = true;
// Mark dirty
_currentState.Dirty.RenderPipeline = true;
}
if (prg.ComputeFunction != IntPtr.Zero)
{
_currentState.ComputeFunction = prg.ComputeFunction;
// Mark dirty
_currentState.Dirty.ComputePipeline = true;
}
}
public void UpdateRenderTargets(ITexture[] colors, ITexture depthStencil)
@ -383,7 +458,7 @@ namespace Ryujinx.Graphics.Metal
_currentState.VertexAttribs = vertexAttribs.ToArray();
// Mark dirty
_currentState.Dirty.Pipeline = true;
_currentState.Dirty.RenderPipeline = true;
}
public void UpdateBlendDescriptors(int index, BlendDescriptor blend)
@ -557,7 +632,7 @@ namespace Ryujinx.Graphics.Metal
}
// Mark dirty
_currentState.Dirty.Pipeline = true;
_currentState.Dirty.RenderPipeline = true;
}
// Inlineable
@ -579,10 +654,18 @@ namespace Ryujinx.Graphics.Metal
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
if (_pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetRenderBuffers(renderCommandEncoder, _currentState.UniformBuffers, true);
}
else if (_pipeline.CurrentEncoderType == EncoderType.Compute)
{
var computeCommandEncoder = new MTLComputeCommandEncoder(_pipeline.CurrentEncoder.Value);
SetComputeBuffers(computeCommandEncoder, _currentState.UniformBuffers);
}
}
}
@ -606,10 +689,18 @@ namespace Ryujinx.Graphics.Metal
}
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
if (_pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetRenderBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
}
else if (_pipeline.CurrentEncoderType == EncoderType.Compute)
{
var computeCommandEncoder = new MTLComputeCommandEncoder(_pipeline.CurrentEncoder.Value);
SetComputeBuffers(computeCommandEncoder, _currentState.StorageBuffers);
}
}
}
@ -653,29 +744,86 @@ namespace Ryujinx.Graphics.Metal
}
// Inlineable
public readonly void UpdateTextureAndSampler(ShaderStage stage, ulong binding, MTLTexture texture, MTLSamplerState sampler)
public readonly void UpdateTexture(ShaderStage stage, ulong binding, TextureBase texture)
{
if (binding > 30)
{
Logger.Warning?.Print(LogClass.Gpu, $"Texture binding ({binding}) must be <= 30");
return;
}
switch (stage)
{
case ShaderStage.Fragment:
_currentState.FragmentTextures[binding] = texture;
_currentState.FragmentSamplers[binding] = sampler;
break;
case ShaderStage.Vertex:
_currentState.VertexTextures[binding] = texture;
_currentState.VertexSamplers[binding] = sampler;
break;
case ShaderStage.Compute:
_currentState.ComputeTextures[binding] = texture;
break;
}
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
if (_pipeline.CurrentEncoder != null)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
// TODO: Only update the new ones
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Vertex, _currentState.VertexTextures, _currentState.VertexSamplers);
SetTextureAndSampler(renderCommandEncoder, ShaderStage.Fragment, _currentState.FragmentTextures, _currentState.FragmentSamplers);
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetRenderTexture(renderCommandEncoder, ShaderStage.Vertex, binding, texture);
SetRenderTexture(renderCommandEncoder, ShaderStage.Fragment, binding, texture);
}
else if (_pipeline.CurrentEncoderType == EncoderType.Compute)
{
var computeCommandEncoder = new MTLComputeCommandEncoder(_pipeline.CurrentEncoder.Value);
SetComputeTexture(computeCommandEncoder, binding, texture);
}
}
}
// Inlineable
public readonly void UpdateSampler(ShaderStage stage, ulong binding, MTLSamplerState sampler)
{
if (binding > 15)
{
Logger.Warning?.Print(LogClass.Gpu, $"Sampler binding ({binding}) must be <= 15");
return;
}
switch (stage)
{
case ShaderStage.Fragment:
_currentState.FragmentSamplers[binding] = sampler;
break;
case ShaderStage.Vertex:
_currentState.VertexSamplers[binding] = sampler;
break;
case ShaderStage.Compute:
_currentState.ComputeSamplers[binding] = sampler;
break;
}
if (_pipeline.CurrentEncoder != null)
{
if (_pipeline.CurrentEncoderType == EncoderType.Render)
{
var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value);
SetRenderSampler(renderCommandEncoder, ShaderStage.Vertex, binding, sampler);
SetRenderSampler(renderCommandEncoder, ShaderStage.Fragment, binding, sampler);
}
else if (_pipeline.CurrentEncoderType == EncoderType.Compute)
{
var computeCommandEncoder = new MTLComputeCommandEncoder(_pipeline.CurrentEncoder.Value);
SetComputeSampler(computeCommandEncoder, binding, sampler);
}
}
}
// Inlineable
public readonly void UpdateTextureAndSampler(ShaderStage stage, ulong binding, TextureBase texture, MTLSamplerState sampler)
{
UpdateTexture(stage, binding, texture);
UpdateSampler(stage, binding, sampler);
}
private readonly void SetDepthStencilState(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.DepthStencilState != null)
@ -807,10 +955,10 @@ namespace Ryujinx.Graphics.Metal
Index = bufferDescriptors.Length
});
SetBuffers(renderCommandEncoder, buffers);
SetRenderBuffers(renderCommandEncoder, buffers);
}
private readonly void SetBuffers(MTLRenderCommandEncoder renderCommandEncoder, List<BufferInfo> buffers, bool fragment = false)
private readonly void SetRenderBuffers(MTLRenderCommandEncoder renderCommandEncoder, List<BufferInfo> buffers, bool fragment = false)
{
foreach (var buffer in buffers)
{
@ -823,6 +971,14 @@ namespace Ryujinx.Graphics.Metal
}
}
private readonly void SetComputeBuffers(MTLComputeCommandEncoder computeCommandEncoder, List<BufferInfo> buffers)
{
foreach (var buffer in buffers)
{
computeCommandEncoder.SetBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
}
}
private readonly void SetCullMode(MTLRenderCommandEncoder renderCommandEncoder)
{
renderCommandEncoder.SetCullMode(_currentState.CullMode);
@ -838,41 +994,64 @@ namespace Ryujinx.Graphics.Metal
renderCommandEncoder.SetStencilReferenceValues((uint)_currentState.FrontRefValue, (uint)_currentState.BackRefValue);
}
private static void SetTextureAndSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, MTLTexture[] textures, MTLSamplerState[] samplers)
private static void SetRenderTexture(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, ulong binding, TextureBase texture)
{
for (int i = 0; i < textures.Length; i++)
if (texture == null)
{
var texture = textures[i];
if (texture != IntPtr.Zero)
{
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexTexture(texture, (ulong)i);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentTexture(texture, (ulong)i);
break;
}
}
return;
}
for (int i = 0; i < samplers.Length; i++)
var textureHandle = texture.GetHandle();
if (textureHandle != IntPtr.Zero)
{
var sampler = samplers[i];
if (sampler != IntPtr.Zero)
switch (stage)
{
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexSamplerState(sampler, (ulong)i);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentSamplerState(sampler, (ulong)i);
break;
}
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexTexture(textureHandle, binding);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentTexture(textureHandle, binding);
break;
}
}
}
private static void SetRenderSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, ulong binding, MTLSamplerState sampler)
{
if (sampler != IntPtr.Zero)
{
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexSamplerState(sampler, binding);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentSamplerState(sampler, binding);
break;
}
}
}
private static void SetComputeTexture(MTLComputeCommandEncoder computeCommandEncoder, ulong binding, TextureBase texture)
{
if (texture == null)
{
return;
}
var textureHandle = texture.GetHandle();
if (textureHandle != IntPtr.Zero)
{
computeCommandEncoder.SetTexture(textureHandle, binding);
}
}
private static void SetComputeSampler(MTLComputeCommandEncoder computeCommandEncoder, ulong binding, MTLSamplerState sampler)
{
if (sampler != IntPtr.Zero)
{
computeCommandEncoder.SetSamplerState(sampler, binding);
}
}
}
}