Skip to content

Commit

Permalink
Metal: Advanced Present (#6)
Browse files Browse the repository at this point in the history
* Initial DrawTexture support & Advanced Present

* TODO: Get Scissors Working

* Chnage scissor state management

* Rebase problems…

* Rebase fixes again

* Update DrawTexture + Fix Topology

* Fix flipping

* Add clear action support

* Cleanup
  • Loading branch information
IsaacMarovitz authored and GreemDev committed Dec 24, 2024
1 parent 6cc4d46 commit f7941a0
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 63 deletions.
10 changes: 10 additions & 0 deletions src/Ryujinx.Graphics.Metal/Effects/IPostProcessingEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Ryujinx.Graphics.Metal.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
Texture Run(Texture view, int width, int height);
}
}
18 changes: 18 additions & 0 deletions src/Ryujinx.Graphics.Metal/Effects/IScalingFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Ryujinx.Graphics.GAL;
using System;

namespace Ryujinx.Graphics.Metal.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
Texture view,
Texture destinationTexture,
Format format,
int width,
int height,
Extents2D source,
Extents2D destination);
}
}
3 changes: 3 additions & 0 deletions src/Ryujinx.Graphics.Metal/EncoderState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ struct EncoderState
// Dirty flags
public DirtyFlags Dirty = new();

// Only to be used for present
public bool ClearLoadAction = false;

public EncoderState() { }

public EncoderState Clone()
Expand Down
26 changes: 18 additions & 8 deletions src/Ryujinx.Graphics.Metal/EncoderStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ struct EncoderStateManager : IDisposable
public readonly MTLIndexType IndexType => _currentState.IndexType;
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
public readonly PrimitiveTopology Topology => _currentState.Topology;
public readonly Texture[] RenderTargets => _currentState.RenderTargets;
public readonly Texture DepthStencil => _currentState.DepthStencil;

public EncoderStateManager(MTLDevice device, Pipeline pipeline)
{
Expand Down Expand Up @@ -82,6 +80,11 @@ public void RestoreState()
}
}

public void SetClearLoadAction(bool clear)
{
_currentState.ClearLoadAction = clear;
}

public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{
// Initialise Pass & State
Expand All @@ -93,7 +96,7 @@ public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{
var passAttachment = renderPassDescriptor.ColorAttachments.Object((ulong)i);
passAttachment.Texture = _currentState.RenderTargets[i].MTLTexture;
passAttachment.LoadAction = MTLLoadAction.Load;
passAttachment.LoadAction = _currentState.ClearLoadAction ? MTLLoadAction.Clear : MTLLoadAction.Load;
passAttachment.StoreAction = MTLStoreAction.Store;
}
}
Expand Down Expand Up @@ -661,11 +664,18 @@ private readonly MTLVertexDescriptor BuildVertexDescriptor(VertexBufferDescripto
// TODO: Handle 'zero' buffers
for (int i = 0; i < attribDescriptors.Length; i++)
{
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = attribDescriptors[i].Format.Convert();
indexMask |= 1u << attribDescriptors[i].BufferIndex;
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
attrib.Offset = (ulong)attribDescriptors[i].Offset;
if (!attribDescriptors[i].IsZero)
{
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = attribDescriptors[i].Format.Convert();
indexMask |= 1u << attribDescriptors[i].BufferIndex;
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
attrib.Offset = (ulong)attribDescriptors[i].Offset;
}
else
{
// Logger.Warning?.PrintMsg(LogClass.Gpu, "Unhandled IsZero buffer!");
}
}

for (int i = 0; i < bufferDescriptors.Length; i++)
Expand Down
149 changes: 134 additions & 15 deletions src/Ryujinx.Graphics.Metal/HelperShader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class HelperShader : IDisposable
private readonly Pipeline _pipeline;
private MTLDevice _device;

private readonly ISampler _samplerLinear;
private readonly ISampler _samplerNearest;
private readonly IProgram _programColorBlit;
private readonly List<IProgram> _programsColorClear = new();
private readonly IProgram _programDepthStencilClear;
Expand All @@ -25,6 +27,9 @@ public HelperShader(MTLDevice device, Pipeline pipeline)
_device = device;
_pipeline = pipeline;

_samplerNearest = new Sampler(_device, SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
_samplerLinear = new Sampler(_device, SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));

var blitSource = ReadMsl("Blit.metal");
_programColorBlit = new Program(
[
Expand Down Expand Up @@ -56,28 +61,140 @@ private static string ReadMsl(string fileName)
return EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
}

public void BlitColor(
ITexture source,
ITexture destination)
public unsafe void BlitColor(
ITexture src,
ITexture dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter)
{
var sampler = _device.NewSamplerState(new MTLSamplerDescriptor
const int RegionBufferSize = 16;

var sampler = linearFilter ? _samplerLinear : _samplerNearest;

Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];

region[0] = srcRegion.X1 / src.Width;
region[1] = srcRegion.X2 / src.Width;
region[2] = srcRegion.Y1 / src.Height;
region[3] = srcRegion.Y2 / src.Height;

if (dstRegion.X1 > dstRegion.X2)
{
MinFilter = MTLSamplerMinMagFilter.Nearest,
MagFilter = MTLSamplerMinMagFilter.Nearest,
MipFilter = MTLSamplerMipFilter.NotMipmapped
});
(region[0], region[1]) = (region[1], region[0]);
}

if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}

var rect = new Rectangle<float>(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));

Span<Viewport> viewports = stackalloc Viewport[1];

viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);

int dstWidth = dst.Width;
int dstHeight = dst.Height;

// Save current state
_pipeline.SaveAndResetState();

_pipeline.SetProgram(_programColorBlit);
// Viewport and scissor needs to be set before render pass begin so as not to bind the old ones
_pipeline.SetViewports([]);
_pipeline.SetScissors([]);
_pipeline.SetRenderTargets([destination], null);
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, source, new Sampler(sampler));
_pipeline.SetPrimitiveTopology(PrimitiveTopology.Triangles);
_pipeline.Draw(6, 1, 0, 0);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
_pipeline.SetRenderTargets([dst], null);
_pipeline.SetClearLoadAction(true);
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);

fixed (float* ptr = region)
{
_pipeline.GetOrCreateRenderEncoder().SetVertexBytes((IntPtr)ptr, RegionBufferSize, 0);
}

_pipeline.Draw(4, 1, 0, 0);

// Restore previous state
_pipeline.RestoreState();
}

public unsafe void DrawTexture(
ITexture src,
ISampler srcSampler,
Extents2DF srcRegion,
Extents2DF dstRegion)
{
const int RegionBufferSize = 16;

Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];

region[0] = srcRegion.X1 / src.Width;
region[1] = srcRegion.X2 / src.Width;
region[2] = srcRegion.Y1 / src.Height;
region[3] = srcRegion.Y2 / src.Height;

if (dstRegion.X1 > dstRegion.X2)
{
(region[0], region[1]) = (region[1], region[0]);
}

if (dstRegion.Y1 > dstRegion.Y2)
{
(region[2], region[3]) = (region[3], region[2]);
}

Span<Viewport> viewports = stackalloc Viewport[1];
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];

var rect = new Rectangle<float>(
MathF.Min(dstRegion.X1, dstRegion.X2),
MathF.Min(dstRegion.Y1, dstRegion.Y2),
MathF.Abs(dstRegion.X2 - dstRegion.X1),
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));

viewports[0] = new Viewport(
rect,
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);

scissors[0] = new Rectangle<int>(0, 0, 0xFFFF, 0xFFFF);

// Save current state
_pipeline.SaveState();

_pipeline.SetProgram(_programColorBlit);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(scissors);
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetFaceCulling(false, Face.FrontAndBack);
// For some reason this results in a SIGSEGV
// _pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));

fixed (float* ptr = region)
{
_pipeline.GetOrCreateRenderEncoder().SetVertexBytes((IntPtr)ptr, RegionBufferSize, 0);
}

_pipeline.Draw(4, 1, 0, 0);

// Restore previous state
_pipeline.RestoreState();
Expand Down Expand Up @@ -169,6 +286,8 @@ public void Dispose()
}
_programDepthStencilClear.Dispose();
_pipeline.Dispose();
_samplerLinear.Dispose();
_samplerNearest.Dispose();
}
}
}
22 changes: 10 additions & 12 deletions src/Ryujinx.Graphics.Metal/Pipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public void RestoreState()
_encoderStateManager.RestoreState();
}

public void SetClearLoadAction(bool clear)
{
_encoderStateManager.SetClearLoadAction(clear);
}

public MTLRenderCommandEncoder GetOrCreateRenderEncoder()
{
MTLRenderCommandEncoder renderCommandEncoder;
Expand Down Expand Up @@ -167,22 +172,17 @@ public MTLComputeCommandEncoder BeginComputePass()
return computeCommandEncoder;
}

public void Present(CAMetalDrawable drawable, ITexture texture)
public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear)
{
if (texture is not Texture tex)
{
return;
}

EndCurrentPass();

SaveState();

// TODO: Clean this up
var textureInfo = new TextureCreateInfo((int)drawable.Texture.Width, (int)drawable.Texture.Height, (int)drawable.Texture.Depth, (int)drawable.Texture.MipmapLevelCount, (int)drawable.Texture.SampleCount, 0, 0, 0, Format.B8G8R8A8Unorm, 0, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha);
var dest = new Texture(_device, this, textureInfo, drawable.Texture, 0, 0);
var dst = new Texture(_device, this, textureInfo, drawable.Texture, 0, 0);

_helperShader.BlitColor(tex, dest);
_helperShader.BlitColor(src, dst, srcRegion, dstRegion, isLinear);

EndCurrentPass();

Expand All @@ -194,7 +194,7 @@ public void Present(CAMetalDrawable drawable, ITexture texture)
RestoreState();

// Cleanup
dest.Dispose();
dst.Dispose();
}

public void Barrier()
Expand Down Expand Up @@ -338,9 +338,7 @@ public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterB

public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
{
// var renderCommandEncoder = GetOrCreateRenderEncoder();

Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
_helperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
}

public void SetAlphaTest(bool enable, float reference, CompareOp op)
Expand Down
29 changes: 10 additions & 19 deletions src/Ryujinx.Graphics.Metal/Shaders/Blit.metal
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,23 @@

using namespace metal;

// ------------------
// Simple Blit Shader
// ------------------

constant float2 quadVertices[] = {
float2(-1, -1),
float2(-1, 1),
float2( 1, 1),
float2(-1, -1),
float2( 1, 1),
float2( 1, -1)
};

struct CopyVertexOut {
float4 position [[position]];
float2 uv;
};

vertex CopyVertexOut vertexMain(unsigned short vid [[vertex_id]]) {
float2 position = quadVertices[vid];

vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
const device float* texCoord [[buffer(0)]]) {
CopyVertexOut out;

out.position = float4(position, 0, 1);
out.position.y = -out.position.y;
out.uv = position * 0.5f + 0.5f;
int low = vid & 1;
int high = vid >> 1;
out.uv.x = texCoord[low];
out.uv.y = texCoord[2 + high];
out.position.x = (float(low) - 0.5f) * 2.0f;
out.position.y = (float(high) - 0.5f) * 2.0f;
out.position.z = 0.0f;
out.position.w = 1.0f;

return out;
}
Expand Down
Loading

0 comments on commit f7941a0

Please sign in to comment.