Metal: Better Bindings (#29)

* Tell GAL to use Vk model (and break everything)

* ResourceBindingSegments

* Set information on backend caps

* Get ready to break everything

* Refactor EncoderStateManager

* Remove padding from helper shaders

* Fix ref array sizes

* Seperate vert & frag buffers

* Shader-side changes

* Fixes

* Fix some helper shader resource layouts

* Sort by binding id

* Fix helper shader layouts

* Don’t do inline vertex buffer updates

* Check for null storage
This commit is contained in:
Isaac Marovitz 2024-07-01 18:24:10 +01:00
parent 971c270bcf
commit daee63c451
12 changed files with 709 additions and 453 deletions

View file

@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
}
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{
@ -103,7 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
}
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
if (_context.Capabilities.Api != TargetApi.OpenGL)
{
if (count == 1)
{

View file

@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Metal
public const int MaxUniformBuffersPerStage = 18;
public const int MaxStorageBuffersPerStage = 16;
public const int MaxTexturesPerStage = 64;
public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
public const int MaxColorAttachments = 8;
// TODO: Check this value
@ -18,9 +20,11 @@ namespace Ryujinx.Graphics.Metal
public const int MinResourceAlignment = 16;
// Must match constants set in shader generation
public const uint ZeroBufferIndex = 18;
public const uint ConstantBuffersIndex = 20;
public const uint StorageBuffersIndex = 21;
public const uint ZeroBufferIndex = 18;
public const uint TexturesIndex = 22;
public const uint ImagessIndex = 23;
}
}

View file

@ -1,6 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.State;
using Ryujinx.Graphics.Shader;
using SharpMetal.Metal;
using System;
using System.Linq;
@ -22,13 +23,13 @@ namespace Ryujinx.Graphics.Metal
StencilRef = 1 << 7,
Viewports = 1 << 8,
Scissors = 1 << 9,
Buffers = 1 << 10,
VertexTextures = 1 << 11,
FragmentTextures = 1 << 12,
ComputeTextures = 1 << 13,
Uniforms = 1 << 10,
Storages = 1 << 11,
Textures = 1 << 12,
Images = 1 << 13,
RenderAll = RenderPipeline | DepthStencil | DepthClamp | DepthBias | CullMode | FrontFace | StencilRef | Viewports | Scissors | Buffers | VertexTextures | FragmentTextures,
ComputeAll = ComputePipeline | Buffers | ComputeTextures,
RenderAll = RenderPipeline | DepthStencil | DepthClamp | DepthBias | CullMode | FrontFace | StencilRef | Viewports | Scissors | Uniforms | Storages | Textures | Images,
ComputeAll = ComputePipeline | Uniforms | Storages | Textures | Images,
All = RenderAll | ComputeAll,
}
@ -49,6 +50,20 @@ namespace Ryujinx.Graphics.Metal
}
}
record struct TextureRef
{
public ShaderStage Stage;
public Texture Storage;
public Sampler Sampler;
public TextureRef(ShaderStage stage, Texture storage, Sampler sampler)
{
Stage = stage;
Storage = storage;
Sampler = sampler;
}
}
struct PredrawState
{
public MTLCullMode CullMode;
@ -73,17 +88,9 @@ namespace Ryujinx.Graphics.Metal
public PipelineState Pipeline;
public DepthStencilUid DepthStencilUid;
public TextureBase[] FragmentTextures = new TextureBase[Constants.MaxTexturesPerStage];
public MTLSamplerState[] FragmentSamplers = new MTLSamplerState[Constants.MaxTexturesPerStage];
public TextureBase[] VertexTextures = new TextureBase[Constants.MaxTexturesPerStage];
public MTLSamplerState[] VertexSamplers = new MTLSamplerState[Constants.MaxTexturesPerStage];
public TextureBase[] ComputeTextures = new TextureBase[Constants.MaxTexturesPerStage];
public MTLSamplerState[] ComputeSamplers = new MTLSamplerState[Constants.MaxTexturesPerStage];
public BufferRef[] UniformBuffers = new BufferRef[Constants.MaxUniformBuffersPerStage];
public BufferRef[] StorageBuffers = new BufferRef[Constants.MaxStorageBuffersPerStage];
public readonly BufferRef[] UniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
public readonly BufferRef[] StorageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
public readonly TextureRef[] TextureRefs = new TextureRef[Constants.MaxTextureBindings];
public Auto<DisposableBuffer> IndexBuffer = default;
public MTLIndexType IndexType = MTLIndexType.UInt16;

View file

@ -179,8 +179,8 @@ namespace Ryujinx.Graphics.Metal
{
if (_currentState.Dirty.HasFlag(DirtyFlags.RenderPipeline))
{
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
SetRenderPipelineState(renderCommandEncoder);
SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.DepthStencil))
@ -223,21 +223,26 @@ namespace Ryujinx.Graphics.Metal
SetScissors(renderCommandEncoder);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.Buffers))
if (_currentState.Dirty.HasFlag(DirtyFlags.Uniforms))
{
SetRenderBuffers(renderCommandEncoder, _currentState.UniformBuffers, _currentState.StorageBuffers);
UpdateAndBind(renderCommandEncoder, _currentState.RenderProgram, MetalRenderer.UniformSetIndex);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.VertexTextures))
if (_currentState.Dirty.HasFlag(DirtyFlags.Storages))
{
SetRenderTextures(renderCommandEncoder, ShaderStage.Vertex, _currentState.VertexTextures, _currentState.VertexSamplers);
UpdateAndBind(renderCommandEncoder, _currentState.RenderProgram, MetalRenderer.StorageSetIndex);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.FragmentTextures))
if (_currentState.Dirty.HasFlag(DirtyFlags.Textures))
{
SetRenderTextures(renderCommandEncoder, ShaderStage.Fragment, _currentState.FragmentTextures, _currentState.FragmentSamplers);
UpdateAndBind(renderCommandEncoder, _currentState.RenderProgram, MetalRenderer.TextureSetIndex);
}
// if (_currentState.Dirty.HasFlag(DirtyFlags.Images))
// {
// UpdateAndBind(renderCommandEncoder, _currentState.RenderProgram, MetalRenderer.ImageSetIndex);
// }
_currentState.Dirty &= ~DirtyFlags.RenderAll;
}
@ -248,15 +253,27 @@ namespace Ryujinx.Graphics.Metal
SetComputePipelineState(computeCommandEncoder);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.Buffers))
if (_currentState.Dirty.HasFlag(DirtyFlags.Uniforms))
{
SetComputeBuffers(computeCommandEncoder, _currentState.UniformBuffers, _currentState.StorageBuffers);
UpdateAndBind(computeCommandEncoder, _currentState.ComputeProgram, MetalRenderer.UniformSetIndex);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.ComputeTextures))
if (_currentState.Dirty.HasFlag(DirtyFlags.Storages))
{
SetComputeTextures(computeCommandEncoder, _currentState.ComputeTextures, _currentState.ComputeSamplers);
UpdateAndBind(computeCommandEncoder, _currentState.ComputeProgram, MetalRenderer.StorageSetIndex);
}
if (_currentState.Dirty.HasFlag(DirtyFlags.Textures))
{
UpdateAndBind(computeCommandEncoder, _currentState.ComputeProgram, MetalRenderer.TextureSetIndex);
}
// if (_currentState.Dirty.HasFlag(DirtyFlags.Images))
// {
// UpdateAndBind(computeCommandEncoder, _currentState.ComputeProgram, MetalRenderer.ImageSetIndex);
// }
_currentState.Dirty &= ~DirtyFlags.ComputeAll;
}
private void SetRenderPipelineState(MTLRenderCommandEncoder renderCommandEncoder)
@ -694,10 +711,10 @@ namespace Ryujinx.Graphics.Metal
? null
: _bufferManager.GetBuffer(buffer.Handle, buffer.Write);
_currentState.UniformBuffers[index] = new BufferRef(mtlBuffer, ref buffer);
_currentState.UniformBufferRefs[index] = new BufferRef(mtlBuffer, ref buffer);
}
_currentState.Dirty |= DirtyFlags.Buffers;
_currentState.Dirty |= DirtyFlags.Uniforms;
}
public void UpdateStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
@ -711,10 +728,10 @@ namespace Ryujinx.Graphics.Metal
? null
: _bufferManager.GetBuffer(buffer.Handle, buffer.Write);
_currentState.StorageBuffers[index] = new BufferRef(mtlBuffer, ref buffer);
_currentState.StorageBufferRefs[index] = new BufferRef(mtlBuffer, ref buffer);
}
_currentState.Dirty |= DirtyFlags.Buffers;
_currentState.Dirty |= DirtyFlags.Storages;
}
public void UpdateStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
@ -724,10 +741,10 @@ namespace Ryujinx.Graphics.Metal
var mtlBuffer = buffers[i];
int index = first + i;
_currentState.StorageBuffers[index] = new BufferRef(mtlBuffer);
_currentState.StorageBufferRefs[index] = new BufferRef(mtlBuffer);
}
_currentState.Dirty |= DirtyFlags.Buffers;
_currentState.Dirty |= DirtyFlags.Storages;
}
// Inlineable
@ -786,63 +803,22 @@ namespace Ryujinx.Graphics.Metal
_currentState.Dirty |= DirtyFlags.StencilRef;
}
public void UpdateTexture(ShaderStage stage, ulong binding, TextureBase texture)
{
if (binding > Constants.MaxTexturesPerStage)
{
Logger.Warning?.Print(LogClass.Gpu, $"Texture binding ({binding}) must be <= {Constants.MaxTexturesPerStage}");
return;
}
switch (stage)
{
case ShaderStage.Fragment:
_currentState.FragmentTextures[binding] = texture;
_currentState.Dirty |= DirtyFlags.FragmentTextures;
break;
case ShaderStage.Vertex:
_currentState.VertexTextures[binding] = texture;
_currentState.Dirty |= DirtyFlags.VertexTextures;
break;
case ShaderStage.Compute:
_currentState.ComputeTextures[binding] = texture;
_currentState.Dirty |= DirtyFlags.ComputeTextures;
break;
}
}
public void UpdateSampler(ShaderStage stage, ulong binding, MTLSamplerState sampler)
{
if (binding > Constants.MaxTexturesPerStage)
{
Logger.Warning?.Print(LogClass.Gpu, $"Sampler binding ({binding}) must be <= {Constants.MaxTexturesPerStage}");
return;
}
switch (stage)
{
case ShaderStage.Fragment:
_currentState.FragmentSamplers[binding] = sampler;
_currentState.Dirty |= DirtyFlags.FragmentTextures;
break;
case ShaderStage.Vertex:
_currentState.VertexSamplers[binding] = sampler;
_currentState.Dirty |= DirtyFlags.VertexTextures;
break;
case ShaderStage.Compute:
_currentState.ComputeSamplers[binding] = sampler;
_currentState.Dirty |= DirtyFlags.ComputeTextures;
break;
}
}
public void UpdateTextureAndSampler(ShaderStage stage, ulong binding, TextureBase texture, Sampler sampler)
{
UpdateTexture(stage, binding, texture);
if (sampler != null)
if (texture is TextureBuffer textureBuffer)
{
UpdateSampler(stage, binding, sampler.GetSampler());
// TODO: Texture buffers
}
else if (texture is Texture view)
{
_currentState.TextureRefs[binding] = new(stage, view, sampler);
}
else
{
_currentState.TextureRefs[binding] = default;
}
_currentState.Dirty |= DirtyFlags.Textures;
}
private readonly void SetDepthStencilState(MTLRenderCommandEncoder renderCommandEncoder)
@ -999,45 +975,40 @@ namespace Ryujinx.Graphics.Metal
renderCommandEncoder.SetVertexBuffer(zeroMtlBuffer, 0, Constants.ZeroBufferIndex);
}
private readonly void SetRenderBuffers(MTLRenderCommandEncoder renderCommandEncoder, BufferRef[] uniformBuffers, BufferRef[] storageBuffers)
private void UpdateAndBind(MTLRenderCommandEncoder renderCommandEncoder, Program program, int setIndex)
{
var uniformArgBufferRange = CreateArgumentBufferForRenderEncoder(renderCommandEncoder, uniformBuffers, true);
var uniformArgBuffer = _bufferManager.GetBuffer(uniformArgBufferRange.Handle, false).Get(_pipeline.Cbs).Value;
var bindingSegments = program.BindingSegments[setIndex];
renderCommandEncoder.SetVertexBuffer(uniformArgBuffer, (ulong)uniformArgBufferRange.Offset, Constants.ConstantBuffersIndex);
renderCommandEncoder.SetFragmentBuffer(uniformArgBuffer, (ulong)uniformArgBufferRange.Offset, Constants.ConstantBuffersIndex);
var storageArgBufferRange = CreateArgumentBufferForRenderEncoder(renderCommandEncoder, storageBuffers, false);
var storageArgBuffer = _bufferManager.GetBuffer(storageArgBufferRange.Handle, true).Get(_pipeline.Cbs).Value;
renderCommandEncoder.SetVertexBuffer(storageArgBuffer, (ulong)storageArgBufferRange.Offset, Constants.StorageBuffersIndex);
renderCommandEncoder.SetFragmentBuffer(storageArgBuffer, (ulong)storageArgBufferRange.Offset, Constants.StorageBuffersIndex);
if (bindingSegments.Length == 0)
{
return;
}
private readonly void SetComputeBuffers(MTLComputeCommandEncoder computeCommandEncoder, BufferRef[] uniformBuffers, BufferRef[] storageBuffers)
var vertArgBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, program.ArgumentBufferSizes[setIndex] * sizeof(ulong));
var fragArgBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, program.FragArgumentBufferSizes[setIndex] * sizeof(ulong));
Span<ulong> vertResourceIds = stackalloc ulong[program.ArgumentBufferSizes[setIndex]];
Span<ulong> fragResourceIds = stackalloc ulong[program.FragArgumentBufferSizes[setIndex]];
var vertResourceIdIndex = 0;
var fragResourceIdIndex = 0;
foreach (ResourceBindingSegment segment in bindingSegments)
{
var uniformArgBufferRange = CreateArgumentBufferForComputeEncoder(computeCommandEncoder, uniformBuffers, true);
var uniformArgBuffer = _bufferManager.GetBuffer(uniformArgBufferRange.Handle, false).Get(_pipeline.Cbs).Value;
int binding = segment.Binding;
int count = segment.Count;
computeCommandEncoder.SetBuffer(uniformArgBuffer, (ulong)uniformArgBufferRange.Offset, Constants.ConstantBuffersIndex);
var storageArgBufferRange = CreateArgumentBufferForComputeEncoder(computeCommandEncoder, storageBuffers, false);
var storageArgBuffer = _bufferManager.GetBuffer(storageArgBufferRange.Handle, true).Get(_pipeline.Cbs).Value;
computeCommandEncoder.SetBuffer(storageArgBuffer, (ulong)storageArgBufferRange.Offset, Constants.StorageBuffersIndex);
}
private readonly BufferRange CreateArgumentBufferForRenderEncoder(MTLRenderCommandEncoder renderCommandEncoder, BufferRef[] buffers, bool constant)
switch (setIndex)
{
var usage = constant ? MTLResourceUsage.Read : MTLResourceUsage.Write;
Span<ulong> resourceIds = stackalloc ulong[buffers.Length];
for (int i = 0; i < buffers.Length; i++)
case MetalRenderer.UniformSetIndex:
for (int i = 0; i < count; i++)
{
var range = buffers[i].Range;
var autoBuffer = buffers[i].Buffer;
int index = binding + i;
ref BufferRef buffer = ref _currentState.UniformBufferRefs[index];
var range = buffer.Range;
var autoBuffer = buffer.Buffer;
var offset = 0;
if (autoBuffer == null)
@ -1058,28 +1029,36 @@ namespace Ryujinx.Graphics.Metal
mtlBuffer = autoBuffer.Get(_pipeline.Cbs).Value;
}
renderCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), usage, MTLRenderStages.RenderStageFragment | MTLRenderStages.RenderStageVertex);
resourceIds[i] = mtlBuffer.GpuAddress + (ulong)offset;
MTLRenderStages renderStages = 0;
if (segment.Stages.HasFlag(ResourceStages.Vertex))
{
vertResourceIds[vertResourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
vertResourceIdIndex++;
renderStages |= MTLRenderStages.RenderStageVertex;
}
var sizeOfArgumentBuffer = sizeof(ulong) * buffers.Length;
if (segment.Stages.HasFlag(ResourceStages.Fragment))
{
fragResourceIds[fragResourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
fragResourceIdIndex++;
var argBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, sizeOfArgumentBuffer);
argBuffer.Holder.SetDataUnchecked(argBuffer.Offset, MemoryMarshal.AsBytes(resourceIds));
return argBuffer.Range;
renderStages |= MTLRenderStages.RenderStageFragment;
}
private readonly BufferRange CreateArgumentBufferForComputeEncoder(MTLComputeCommandEncoder computeCommandEncoder, BufferRef[] buffers, bool constant)
renderCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), MTLResourceUsage.Read, renderStages);
}
break;
case MetalRenderer.StorageSetIndex:
for (int i = 0; i < count; i++)
{
var usage = constant ? MTLResourceUsage.Read : MTLResourceUsage.Write;
int index = binding + i;
Span<ulong> resourceIds = stackalloc ulong[buffers.Length];
ref BufferRef buffer = ref _currentState.StorageBufferRefs[index];
for (int i = 0; i < buffers.Length; i++)
{
var range = buffers[i].Range;
var autoBuffer = buffers[i].Buffer;
var range = buffer.Range;
var autoBuffer = buffer.Buffer;
var offset = 0;
if (autoBuffer == null)
@ -1100,18 +1079,269 @@ namespace Ryujinx.Graphics.Metal
mtlBuffer = autoBuffer.Get(_pipeline.Cbs).Value;
}
computeCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), usage);
resourceIds[i] = mtlBuffer.GpuAddress + (ulong)offset;
MTLRenderStages renderStages = 0;
if (segment.Stages.HasFlag(ResourceStages.Vertex))
{
vertResourceIds[vertResourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
vertResourceIdIndex++;
renderStages |= MTLRenderStages.RenderStageVertex;
}
var sizeOfArgumentBuffer = sizeof(ulong) * buffers.Length;
if (segment.Stages.HasFlag(ResourceStages.Fragment))
{
fragResourceIds[fragResourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
fragResourceIdIndex++;
renderStages |= MTLRenderStages.RenderStageFragment;
}
renderCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), MTLResourceUsage.Read, renderStages);
}
break;
case MetalRenderer.TextureSetIndex:
if (!segment.IsArray)
{
if (segment.Type != ResourceType.BufferTexture)
{
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref var texture = ref _currentState.TextureRefs[index];
var storage = texture.Storage;
if (storage == null)
{
continue;
}
var mtlTexture = storage.GetHandle();
MTLRenderStages renderStages = 0;
if (segment.Stages.HasFlag(ResourceStages.Vertex))
{
vertResourceIds[vertResourceIdIndex] = mtlTexture.GpuResourceID._impl;
vertResourceIdIndex++;
if (texture.Sampler != null)
{
vertResourceIds[vertResourceIdIndex] = texture.Sampler.GetSampler().GpuResourceID._impl;
vertResourceIdIndex++;
}
renderStages |= MTLRenderStages.RenderStageVertex;
}
if (segment.Stages.HasFlag(ResourceStages.Fragment))
{
fragResourceIds[fragResourceIdIndex] = mtlTexture.GpuResourceID._impl;
fragResourceIdIndex++;
if (texture.Sampler != null)
{
fragResourceIds[fragResourceIdIndex] = texture.Sampler.GetSampler().GpuResourceID._impl;
fragResourceIdIndex++;
}
renderStages |= MTLRenderStages.RenderStageFragment;
}
renderCommandEncoder.UseResource(new MTLResource(mtlTexture.NativePtr), MTLResourceUsage.Read, renderStages);
}
}
else
{
// TODO: Buffer textures
}
}
else
{
// TODO: Texture arrays
}
break;
case MetalRenderer.ImageSetIndex:
// TODO: Images
break;
}
}
vertArgBuffer.Holder.SetDataUnchecked(vertArgBuffer.Offset, MemoryMarshal.AsBytes(vertResourceIds));
fragArgBuffer.Holder.SetDataUnchecked(fragArgBuffer.Offset, MemoryMarshal.AsBytes(fragResourceIds));
var mtlVertArgBuffer = _bufferManager.GetBuffer(vertArgBuffer.Handle, false).Get(_pipeline.Cbs).Value;
var mtlFragArgBuffer = _bufferManager.GetBuffer(fragArgBuffer.Handle, false).Get(_pipeline.Cbs).Value;
renderCommandEncoder.SetVertexBuffer(mtlVertArgBuffer, (uint)vertArgBuffer.Range.Offset, SetIndexToBindingIndex(setIndex));
renderCommandEncoder.SetFragmentBuffer(mtlFragArgBuffer, (uint)fragArgBuffer.Range.Offset, SetIndexToBindingIndex(setIndex));
}
private void UpdateAndBind(MTLComputeCommandEncoder computeCommandEncoder, Program program, int setIndex)
{
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
{
return;
}
var argBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, program.ArgumentBufferSizes[setIndex] * sizeof(ulong));
Span<ulong> resourceIds = stackalloc ulong[program.ArgumentBufferSizes[setIndex]];
var resourceIdIndex = 0;
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
switch (setIndex)
{
case MetalRenderer.UniformSetIndex:
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref BufferRef buffer = ref _currentState.UniformBufferRefs[index];
var range = buffer.Range;
var autoBuffer = buffer.Buffer;
var offset = 0;
if (autoBuffer == null)
{
continue;
}
MTLBuffer mtlBuffer;
if (range.HasValue)
{
offset = range.Value.Offset;
mtlBuffer = autoBuffer.Get(_pipeline.Cbs, offset, range.Value.Size, range.Value.Write).Value;
}
else
{
mtlBuffer = autoBuffer.Get(_pipeline.Cbs).Value;
}
if (segment.Stages.HasFlag(ResourceStages.Compute))
{
computeCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), MTLResourceUsage.Read);
resourceIds[resourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
resourceIdIndex++;
}
}
break;
case MetalRenderer.StorageSetIndex:
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref BufferRef buffer = ref _currentState.StorageBufferRefs[index];
var range = buffer.Range;
var autoBuffer = buffer.Buffer;
var offset = 0;
if (autoBuffer == null)
{
continue;
}
MTLBuffer mtlBuffer;
if (range.HasValue)
{
offset = range.Value.Offset;
mtlBuffer = autoBuffer.Get(_pipeline.Cbs, offset, range.Value.Size, range.Value.Write).Value;
}
else
{
mtlBuffer = autoBuffer.Get(_pipeline.Cbs).Value;
}
if (segment.Stages.HasFlag(ResourceStages.Compute))
{
computeCommandEncoder.UseResource(new MTLResource(mtlBuffer.NativePtr), MTLResourceUsage.Read | MTLResourceUsage.Write);
resourceIds[resourceIdIndex] = mtlBuffer.GpuAddress + (ulong)offset;
resourceIdIndex++;
}
}
break;
case MetalRenderer.TextureSetIndex:
if (!segment.IsArray)
{
if (segment.Type != ResourceType.BufferTexture)
{
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref var texture = ref _currentState.TextureRefs[index];
var storage = texture.Storage;
if (storage == null)
{
continue;
}
var mtlTexture = storage.GetHandle();
if (segment.Stages.HasFlag(ResourceStages.Compute))
{
computeCommandEncoder.UseResource(new MTLResource(mtlTexture.NativePtr), MTLResourceUsage.Read);
resourceIds[resourceIdIndex] = mtlTexture.GpuResourceID._impl;
resourceIdIndex++;
if (texture.Sampler != null)
{
resourceIds[resourceIdIndex] = texture.Sampler.GetSampler().GpuResourceID._impl;
resourceIdIndex++;
}
}
}
}
else
{
// TODO: Buffer textures
}
}
else
{
// TODO: Texture arrays
}
break;
case MetalRenderer.ImageSetIndex:
// TODO: Images
break;
}
}
var argBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, sizeOfArgumentBuffer);
argBuffer.Holder.SetDataUnchecked(argBuffer.Offset, MemoryMarshal.AsBytes(resourceIds));
return argBuffer.Range;
var mtlArgBuffer = _bufferManager.GetBuffer(argBuffer.Handle, false).Get(_pipeline.Cbs).Value;
computeCommandEncoder.SetBuffer(mtlArgBuffer, (uint)argBuffer.Range.Offset, SetIndexToBindingIndex(setIndex));
}
private uint SetIndexToBindingIndex(int setIndex)
{
return setIndex switch
{
MetalRenderer.UniformSetIndex => Constants.ConstantBuffersIndex,
MetalRenderer.StorageSetIndex => Constants.StorageBuffersIndex,
MetalRenderer.TextureSetIndex => Constants.TexturesIndex,
MetalRenderer.ImageSetIndex => Constants.ImagessIndex,
};
}
private readonly void SetCullMode(MTLRenderCommandEncoder renderCommandEncoder)
{
renderCommandEncoder.SetCullMode(_currentState.CullMode);
@ -1126,105 +1356,5 @@ namespace Ryujinx.Graphics.Metal
{
renderCommandEncoder.SetStencilReferenceValues((uint)_currentState.FrontRefValue, (uint)_currentState.BackRefValue);
}
private readonly void SetRenderTextures(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, TextureBase[] textures, MTLSamplerState[] samplers)
{
var argBufferRange = CreateArgumentBufferForRenderEncoder(renderCommandEncoder, stage, textures, samplers);
var argBuffer = _bufferManager.GetBuffer(argBufferRange.Handle, false).Get(_pipeline.Cbs).Value;
switch (stage)
{
case ShaderStage.Vertex:
renderCommandEncoder.SetVertexBuffer(argBuffer, (ulong)argBufferRange.Offset, Constants.TexturesIndex);
break;
case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentBuffer(argBuffer, (ulong)argBufferRange.Offset, Constants.TexturesIndex);
break;
}
}
private readonly void SetComputeTextures(MTLComputeCommandEncoder computeCommandEncoder, TextureBase[] textures, MTLSamplerState[] samplers)
{
var argBufferRange = CreateArgumentBufferForComputeEncoder(computeCommandEncoder, textures, samplers);
var argBuffer = _bufferManager.GetBuffer(argBufferRange.Handle, false).Get(_pipeline.Cbs).Value;
computeCommandEncoder.SetBuffer(argBuffer, (ulong)argBufferRange.Offset, Constants.TexturesIndex);
}
private readonly BufferRange CreateArgumentBufferForRenderEncoder(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, TextureBase[] textures, MTLSamplerState[] samplers)
{
var renderStage = stage == ShaderStage.Vertex ? MTLRenderStages.RenderStageVertex : MTLRenderStages.RenderStageFragment;
Span<ulong> resourceIds = stackalloc ulong[textures.Length + samplers.Length];
for (int i = 0; i < textures.Length; i++)
{
if (textures[i] == null)
{
continue;
}
var mtlTexture = textures[i].GetHandle();
renderCommandEncoder.UseResource(new MTLResource(mtlTexture.NativePtr), MTLResourceUsage.Read, renderStage);
resourceIds[i] = mtlTexture.GpuResourceID._impl;
}
for (int i = 0; i < samplers.Length; i++)
{
if (samplers[i].NativePtr == IntPtr.Zero)
{
continue;
}
var sampler = samplers[i];
resourceIds[i + textures.Length] = sampler.GpuResourceID._impl;
}
var sizeOfArgumentBuffer = sizeof(ulong) * (textures.Length + samplers.Length);
var argBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, sizeOfArgumentBuffer);
argBuffer.Holder.SetDataUnchecked(argBuffer.Offset, MemoryMarshal.AsBytes(resourceIds));
return argBuffer.Range;
}
private readonly BufferRange CreateArgumentBufferForComputeEncoder(MTLComputeCommandEncoder computeCommandEncoder, TextureBase[] textures, MTLSamplerState[] samplers)
{
Span<ulong> resourceIds = stackalloc ulong[textures.Length + samplers.Length];
for (int i = 0; i < textures.Length; i++)
{
if (textures[i] == null)
{
continue;
}
var mtlTexture = textures[i].GetHandle();
computeCommandEncoder.UseResource(new MTLResource(mtlTexture.NativePtr), MTLResourceUsage.Read);
resourceIds[i] = mtlTexture.GpuResourceID._impl;
}
for (int i = 0; i < samplers.Length; i++)
{
if (samplers[i].NativePtr == IntPtr.Zero)
{
continue;
}
var sampler = samplers[i];
resourceIds[i + textures.Length] = sampler.GpuResourceID._impl;
}
var sizeOfArgumentBuffer = sizeof(ulong) * (textures.Length + samplers.Length);
var argBuffer = _bufferManager.ReserveOrCreate(_pipeline.Cbs, sizeOfArgumentBuffer);
argBuffer.Holder.SetDataUnchecked(argBuffer.Offset, MemoryMarshal.AsBytes(resourceIds));
return argBuffer.Range;
}
}
}

View file

@ -36,12 +36,19 @@ namespace Ryujinx.Graphics.Metal
_samplerNearest = new Sampler(_device, SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
_samplerLinear = new Sampler(_device, SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
var blitResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build();
var blitSource = ReadMsl("Blit.metal");
_programColorBlit = new Program(
[
new ShaderSource(blitSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(blitSource, ShaderStage.Vertex, TargetLanguage.Msl)
], device);
], blitResourceLayout, device);
var colorClearResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 0).Build();
var colorClearSource = ReadMsl("ColorClear.metal");
for (int i = 0; i < Constants.MaxColorAttachments; i++)
@ -51,7 +58,7 @@ namespace Ryujinx.Graphics.Metal
[
new ShaderSource(crntSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(crntSource, ShaderStage.Vertex, TargetLanguage.Msl)
], device));
], colorClearResourceLayout, device));
}
var depthStencilClearSource = ReadMsl("DepthStencilClear.metal");
@ -59,13 +66,18 @@ namespace Ryujinx.Graphics.Metal
[
new ShaderSource(depthStencilClearSource, ShaderStage.Fragment, TargetLanguage.Msl),
new ShaderSource(depthStencilClearSource, ShaderStage.Vertex, TargetLanguage.Msl)
], device);
], colorClearResourceLayout, device);
var strideChangeResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
var strideChangeSource = ReadMsl("ChangeBufferStride.metal");
_programStrideChange = new Program(
[
new ShaderSource(strideChangeSource, ShaderStage.Compute, TargetLanguage.Msl)
], device, new ComputeSize(64, 1, 1));
], strideChangeResourceLayout, device, new ComputeSize(64, 1, 1));
}
private static string ReadMsl(string fileName)

View file

@ -12,6 +12,13 @@ namespace Ryujinx.Graphics.Metal
[SupportedOSPlatform("macos")]
public sealed class MetalRenderer : IRenderer
{
public const int TotalSets = 4;
public const int UniformSetIndex = 0;
public const int StorageSetIndex = 1;
public const int TextureSetIndex = 2;
public const int ImageSetIndex = 3;
private readonly MTLDevice _device;
private readonly MTLCommandQueue _queue;
private readonly Func<CAMetalLayer> _getMetalLayer;
@ -95,7 +102,7 @@ namespace Ryujinx.Graphics.Metal
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
return new Program(shaders, _device, info.ComputeLocalSize);
return new Program(shaders, info.ResourceLayout, _device, info.ComputeLocalSize);
}
public ISampler CreateSampler(SamplerCreateInfo info)
@ -188,10 +195,10 @@ namespace Ryujinx.Graphics.Metal
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: false,
uniformBufferSetIndex: 0,
storageBufferSetIndex: 1,
textureSetIndex: 2,
imageSetIndex: 3,
uniformBufferSetIndex: UniformSetIndex,
storageBufferSetIndex: StorageSetIndex,
textureSetIndex: TextureSetIndex,
imageSetIndex: ImageSetIndex,
extraSetBaseIndex: 0,
maximumExtraSets: 0,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,

View file

@ -4,6 +4,8 @@ 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
@ -21,7 +23,14 @@ namespace Ryujinx.Graphics.Metal
private MTLComputePipelineState? _computePipelineCache;
private bool _firstBackgroundUse;
public Program(ShaderSource[] shaders, MTLDevice device, ComputeSize computeLocalSize = default)
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;
@ -56,9 +65,155 @@ namespace Ryujinx.Graphics.Metal
}
}
ClearSegments = BuildClearSegments(resourceLayout.Sets);
(BindingSegments, ArgumentBufferSizes, FragArgumentBufferSizes) = BuildBindingSegments(resourceLayout.SetUsages);
_status = ProgramLinkStatus.Success;
}
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
for (int setIndex = 0; setIndex < sets.Count; setIndex++)
{
List<ResourceBindingSegment> 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<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)
{
return _status;

View file

@ -0,0 +1,22 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Metal
{
readonly struct ResourceBindingSegment
{
public readonly int Binding;
public readonly int Count;
public readonly ResourceType Type;
public readonly ResourceStages Stages;
public readonly bool IsArray;
public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray)
{
Binding = binding;
Count = count;
Type = type;
Stages = stages;
IsArray = isArray;
}
}
}

View file

@ -0,0 +1,59 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class ResourceLayoutBuilder
{
private const int TotalSets = MetalRenderer.TotalSets;
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
public ResourceLayoutBuilder()
{
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
_resourceUsages = new List<ResourceUsage>[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
_resourceDescriptors[index] = new();
_resourceUsages[index] = new();
}
}
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
{
int setIndex = type switch
{
ResourceType.UniformBuffer => MetalRenderer.UniformSetIndex,
ResourceType.StorageBuffer => MetalRenderer.StorageSetIndex,
ResourceType.TextureAndSampler or ResourceType.BufferTexture => MetalRenderer.TextureSetIndex,
ResourceType.Image or ResourceType.BufferImage => MetalRenderer.ImageSetIndex,
_ => throw new ArgumentException($"Invalid resource type \"{type}\"."),
};
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
return this;
}
public ResourceLayout Build()
{
var descriptors = new ResourceDescriptorCollection[TotalSets];
var usages = new ResourceUsageCollection[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
}
return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
}
}
}

View file

@ -18,133 +18,7 @@ struct ConstantBuffers {
struct Textures
{
texture2d<float, access::sample> texture;
ulong padding_1;
ulong padding_2;
ulong padding_3;
ulong padding_4;
ulong padding_5;
ulong padding_6;
ulong padding_7;
ulong padding_8;
ulong padding_9;
ulong padding_10;
ulong padding_11;
ulong padding_12;
ulong padding_13;
ulong padding_14;
ulong padding_15;
ulong padding_16;
ulong padding_17;
ulong padding_18;
ulong padding_19;
ulong padding_20;
ulong padding_21;
ulong padding_22;
ulong padding_23;
ulong padding_24;
ulong padding_25;
ulong padding_26;
ulong padding_27;
ulong padding_28;
ulong padding_29;
ulong padding_30;
ulong padding_31;
ulong padding_32;
ulong padding_33;
ulong padding_34;
ulong padding_35;
ulong padding_36;
ulong padding_37;
ulong padding_38;
ulong padding_39;
ulong padding_40;
ulong padding_41;
ulong padding_42;
ulong padding_43;
ulong padding_44;
ulong padding_45;
ulong padding_46;
ulong padding_47;
ulong padding_48;
ulong padding_49;
ulong padding_50;
ulong padding_51;
ulong padding_52;
ulong padding_53;
ulong padding_54;
ulong padding_55;
ulong padding_56;
ulong padding_57;
ulong padding_58;
ulong padding_59;
ulong padding_60;
ulong padding_61;
ulong padding_62;
ulong padding_63;
sampler sampler;
ulong padding_65;
ulong padding_66;
ulong padding_67;
ulong padding_68;
ulong padding_69;
ulong padding_70;
ulong padding_71;
ulong padding_72;
ulong padding_73;
ulong padding_74;
ulong padding_75;
ulong padding_76;
ulong padding_77;
ulong padding_78;
ulong padding_79;
ulong padding_80;
ulong padding_81;
ulong padding_82;
ulong padding_83;
ulong padding_84;
ulong padding_85;
ulong padding_86;
ulong padding_87;
ulong padding_88;
ulong padding_89;
ulong padding_90;
ulong padding_91;
ulong padding_92;
ulong padding_93;
ulong padding_94;
ulong padding_95;
ulong padding_96;
ulong padding_97;
ulong padding_98;
ulong padding_99;
ulong padding_100;
ulong padding_101;
ulong padding_102;
ulong padding_103;
ulong padding_104;
ulong padding_105;
ulong padding_106;
ulong padding_107;
ulong padding_108;
ulong padding_109;
ulong padding_110;
ulong padding_111;
ulong padding_112;
ulong padding_113;
ulong padding_114;
ulong padding_115;
ulong padding_116;
ulong padding_117;
ulong padding_118;
ulong padding_119;
ulong padding_120;
ulong padding_121;
ulong padding_122;
ulong padding_123;
ulong padding_124;
ulong padding_125;
ulong padding_126;
ulong padding_127;
};
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],

View file

@ -19,7 +19,6 @@ struct ConstantBuffers {
};
struct StorageBuffers {
ulong padding;
device InData* in_data;
device OutData* out_data;
};

View file

@ -164,16 +164,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl
private static void DeclareBufferStructures(CodeGenContext context, IEnumerable<BufferDefinition> buffers, bool constant)
{
var name = constant ? "ConstantBuffers" : "StorageBuffers";
var count = constant ? Defaults.MaxUniformBuffersPerStage : Defaults.MaxStorageBuffersPerStage;
var addressSpace = constant ? "constant" : "device";
var argBufferPointers = new string[count];
List<string> argBufferPointers = [];
foreach (BufferDefinition buffer in buffers)
// TODO: Avoid Linq if we can
var sortedBuffers = buffers.OrderBy(x => x.Binding).ToArray();
foreach (BufferDefinition buffer in sortedBuffers)
{
var needsPadding = buffer.Layout == BufferLayout.Std140;
argBufferPointers[buffer.Binding] = $"{addressSpace} {Defaults.StructPrefix}_{buffer.Name}* {buffer.Name};";
argBufferPointers.Add($"{addressSpace} {Defaults.StructPrefix}_{buffer.Name}* {buffer.Name};");
context.AppendLine($"struct {Defaults.StructPrefix}_{buffer.Name}");
context.EnterScope();
@ -211,18 +213,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl
context.AppendLine($"struct {name}");
context.EnterScope();
for (int i = 0; i < argBufferPointers.Length; i++)
foreach (var pointer in argBufferPointers)
{
if (argBufferPointers[i] == null)
{
// We need to pad the struct definition in order to read
// non-contiguous resources correctly.
context.AppendLine($"ulong padding_{i};");
}
else
{
context.AppendLine(argBufferPointers[i]);
}
context.AppendLine(pointer);
}
context.LeaveScope(";");
@ -234,31 +227,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl
context.AppendLine("struct Textures");
context.EnterScope();
var argBufferPointers = new string[Defaults.MaxTexturesPerStage * 2];
List<string> argBufferPointers = [];
foreach (TextureDefinition texture in textures)
// TODO: Avoid Linq if we can
var sortedTextures = textures.OrderBy(x => x.Binding).ToArray();
foreach (TextureDefinition texture in sortedTextures)
{
var textureTypeName = texture.Type.ToMslTextureType();
argBufferPointers[texture.Binding] = $"{textureTypeName} tex_{texture.Name};";
argBufferPointers.Add($"{textureTypeName} tex_{texture.Name};");
if (!texture.Separate && texture.Type != SamplerType.TextureBuffer)
{
argBufferPointers[Defaults.MaxTexturesPerStage + texture.Binding] = $"sampler samp_{texture.Name};";
argBufferPointers.Add($"sampler samp_{texture.Name};");
}
}
for (int i = 0; i < argBufferPointers.Length; i++)
foreach (var pointer in argBufferPointers)
{
if (argBufferPointers[i] == null)
{
// We need to pad the struct definition in order to read
// non-contiguous resources correctly.
context.AppendLine($"ulong padding_{i};");
}
else
{
context.AppendLine(argBufferPointers[i]);
}
context.AppendLine(pointer);
}
context.LeaveScope(";");