Metal: Advanced Present (#6)
* 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
This commit is contained in:
parent
1f91c74a95
commit
1f29a76ea3
8 changed files with 358 additions and 63 deletions
10
src/Ryujinx.Graphics.Metal/Effects/IPostProcessingEffect.cs
Normal file
10
src/Ryujinx.Graphics.Metal/Effects/IPostProcessingEffect.cs
Normal file
|
@ -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
src/Ryujinx.Graphics.Metal/Effects/IScalingFilter.cs
Normal file
18
src/Ryujinx.Graphics.Metal/Effects/IScalingFilter.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,6 +73,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
// Dirty flags
|
// Dirty flags
|
||||||
public DirtyFlags Dirty = new();
|
public DirtyFlags Dirty = new();
|
||||||
|
|
||||||
|
// Only to be used for present
|
||||||
|
public bool ClearLoadAction = false;
|
||||||
|
|
||||||
public EncoderState() { }
|
public EncoderState() { }
|
||||||
|
|
||||||
public EncoderState Clone()
|
public EncoderState Clone()
|
||||||
|
|
|
@ -24,8 +24,6 @@ namespace Ryujinx.Graphics.Metal
|
||||||
public readonly MTLIndexType IndexType => _currentState.IndexType;
|
public readonly MTLIndexType IndexType => _currentState.IndexType;
|
||||||
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
|
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
|
||||||
public readonly PrimitiveTopology Topology => _currentState.Topology;
|
public readonly PrimitiveTopology Topology => _currentState.Topology;
|
||||||
public readonly Texture[] RenderTargets => _currentState.RenderTargets;
|
|
||||||
public readonly Texture DepthStencil => _currentState.DepthStencil;
|
|
||||||
|
|
||||||
public EncoderStateManager(MTLDevice device, Pipeline pipeline)
|
public EncoderStateManager(MTLDevice device, Pipeline pipeline)
|
||||||
{
|
{
|
||||||
|
@ -82,6 +80,11 @@ namespace Ryujinx.Graphics.Metal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetClearLoadAction(bool clear)
|
||||||
|
{
|
||||||
|
_currentState.ClearLoadAction = clear;
|
||||||
|
}
|
||||||
|
|
||||||
public MTLRenderCommandEncoder CreateRenderCommandEncoder()
|
public MTLRenderCommandEncoder CreateRenderCommandEncoder()
|
||||||
{
|
{
|
||||||
// Initialise Pass & State
|
// Initialise Pass & State
|
||||||
|
@ -93,7 +96,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
var passAttachment = renderPassDescriptor.ColorAttachments.Object((ulong)i);
|
var passAttachment = renderPassDescriptor.ColorAttachments.Object((ulong)i);
|
||||||
passAttachment.Texture = _currentState.RenderTargets[i].MTLTexture;
|
passAttachment.Texture = _currentState.RenderTargets[i].MTLTexture;
|
||||||
passAttachment.LoadAction = MTLLoadAction.Load;
|
passAttachment.LoadAction = _currentState.ClearLoadAction ? MTLLoadAction.Clear : MTLLoadAction.Load;
|
||||||
passAttachment.StoreAction = MTLStoreAction.Store;
|
passAttachment.StoreAction = MTLStoreAction.Store;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -660,6 +663,8 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
// TODO: Handle 'zero' buffers
|
// TODO: Handle 'zero' buffers
|
||||||
for (int i = 0; i < attribDescriptors.Length; i++)
|
for (int i = 0; i < attribDescriptors.Length; i++)
|
||||||
|
{
|
||||||
|
if (!attribDescriptors[i].IsZero)
|
||||||
{
|
{
|
||||||
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
|
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
|
||||||
attrib.Format = attribDescriptors[i].Format.Convert();
|
attrib.Format = attribDescriptors[i].Format.Convert();
|
||||||
|
@ -667,6 +672,11 @@ namespace Ryujinx.Graphics.Metal
|
||||||
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
|
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
|
||||||
attrib.Offset = (ulong)attribDescriptors[i].Offset;
|
attrib.Offset = (ulong)attribDescriptors[i].Offset;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Logger.Warning?.PrintMsg(LogClass.Gpu, "Unhandled IsZero buffer!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < bufferDescriptors.Length; i++)
|
for (int i = 0; i < bufferDescriptors.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.Metal
|
||||||
private readonly Pipeline _pipeline;
|
private readonly Pipeline _pipeline;
|
||||||
private MTLDevice _device;
|
private MTLDevice _device;
|
||||||
|
|
||||||
|
private readonly ISampler _samplerLinear;
|
||||||
|
private readonly ISampler _samplerNearest;
|
||||||
private readonly IProgram _programColorBlit;
|
private readonly IProgram _programColorBlit;
|
||||||
private readonly List<IProgram> _programsColorClear = new();
|
private readonly List<IProgram> _programsColorClear = new();
|
||||||
private readonly IProgram _programDepthStencilClear;
|
private readonly IProgram _programDepthStencilClear;
|
||||||
|
@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
_device = device;
|
_device = device;
|
||||||
_pipeline = pipeline;
|
_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");
|
var blitSource = ReadMsl("Blit.metal");
|
||||||
_programColorBlit = new Program(
|
_programColorBlit = new Program(
|
||||||
[
|
[
|
||||||
|
@ -56,28 +61,140 @@ namespace Ryujinx.Graphics.Metal
|
||||||
return EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
|
return EmbeddedResources.ReadAllText(string.Join('/', ShadersSourcePath, fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BlitColor(
|
public unsafe void BlitColor(
|
||||||
ITexture source,
|
ITexture src,
|
||||||
ITexture destination)
|
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,
|
(region[0], region[1]) = (region[1], region[0]);
|
||||||
MagFilter = MTLSamplerMinMagFilter.Nearest,
|
}
|
||||||
MipFilter = MTLSamplerMipFilter.NotMipmapped
|
|
||||||
});
|
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
|
// Save current state
|
||||||
_pipeline.SaveAndResetState();
|
_pipeline.SaveAndResetState();
|
||||||
|
|
||||||
_pipeline.SetProgram(_programColorBlit);
|
_pipeline.SetProgram(_programColorBlit);
|
||||||
// Viewport and scissor needs to be set before render pass begin so as not to bind the old ones
|
_pipeline.SetViewports(viewports);
|
||||||
_pipeline.SetViewports([]);
|
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
|
||||||
_pipeline.SetScissors([]);
|
_pipeline.SetRenderTargets([dst], null);
|
||||||
_pipeline.SetRenderTargets([destination], null);
|
_pipeline.SetClearLoadAction(true);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, source, new Sampler(sampler));
|
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
|
||||||
_pipeline.SetPrimitiveTopology(PrimitiveTopology.Triangles);
|
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||||
_pipeline.Draw(6, 1, 0, 0);
|
|
||||||
|
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
|
// Restore previous state
|
||||||
_pipeline.RestoreState();
|
_pipeline.RestoreState();
|
||||||
|
@ -169,6 +286,8 @@ namespace Ryujinx.Graphics.Metal
|
||||||
}
|
}
|
||||||
_programDepthStencilClear.Dispose();
|
_programDepthStencilClear.Dispose();
|
||||||
_pipeline.Dispose();
|
_pipeline.Dispose();
|
||||||
|
_samplerLinear.Dispose();
|
||||||
|
_samplerNearest.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,11 @@ namespace Ryujinx.Graphics.Metal
|
||||||
_encoderStateManager.RestoreState();
|
_encoderStateManager.RestoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetClearLoadAction(bool clear)
|
||||||
|
{
|
||||||
|
_encoderStateManager.SetClearLoadAction(clear);
|
||||||
|
}
|
||||||
|
|
||||||
public MTLRenderCommandEncoder GetOrCreateRenderEncoder()
|
public MTLRenderCommandEncoder GetOrCreateRenderEncoder()
|
||||||
{
|
{
|
||||||
MTLRenderCommandEncoder renderCommandEncoder;
|
MTLRenderCommandEncoder renderCommandEncoder;
|
||||||
|
@ -167,22 +172,17 @@ namespace Ryujinx.Graphics.Metal
|
||||||
return computeCommandEncoder;
|
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();
|
EndCurrentPass();
|
||||||
|
|
||||||
SaveState();
|
SaveState();
|
||||||
|
|
||||||
// TODO: Clean this up
|
// 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 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();
|
EndCurrentPass();
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
RestoreState();
|
RestoreState();
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
dest.Dispose();
|
dst.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Barrier()
|
public void Barrier()
|
||||||
|
@ -338,9 +338,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
|
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
|
||||||
{
|
{
|
||||||
// var renderCommandEncoder = GetOrCreateRenderEncoder();
|
_helperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||||
|
|
|
@ -2,32 +2,23 @@
|
||||||
|
|
||||||
using namespace metal;
|
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 {
|
struct CopyVertexOut {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float2 uv;
|
float2 uv;
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex CopyVertexOut vertexMain(unsigned short vid [[vertex_id]]) {
|
vertex CopyVertexOut vertexMain(uint vid [[vertex_id]],
|
||||||
float2 position = quadVertices[vid];
|
const device float* texCoord [[buffer(0)]]) {
|
||||||
|
|
||||||
CopyVertexOut out;
|
CopyVertexOut out;
|
||||||
|
|
||||||
out.position = float4(position, 0, 1);
|
int low = vid & 1;
|
||||||
out.position.y = -out.position.y;
|
int high = vid >> 1;
|
||||||
out.uv = position * 0.5f + 0.5f;
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Metal.Effects;
|
||||||
using SharpMetal.ObjectiveCCore;
|
using SharpMetal.ObjectiveCCore;
|
||||||
using SharpMetal.QuartzCore;
|
using SharpMetal.QuartzCore;
|
||||||
using System;
|
using System;
|
||||||
|
@ -10,51 +11,196 @@ namespace Ryujinx.Graphics.Metal
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
class Window : IWindow, IDisposable
|
class Window : IWindow, IDisposable
|
||||||
{
|
{
|
||||||
|
public bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
private readonly MetalRenderer _renderer;
|
private readonly MetalRenderer _renderer;
|
||||||
private readonly CAMetalLayer _metalLayer;
|
private readonly CAMetalLayer _metalLayer;
|
||||||
|
|
||||||
|
private int _width;
|
||||||
|
private int _height;
|
||||||
|
private bool _vsyncEnabled;
|
||||||
|
private AntiAliasing _currentAntiAliasing;
|
||||||
|
private bool _updateEffect;
|
||||||
|
private IPostProcessingEffect _effect;
|
||||||
|
private IScalingFilter _scalingFilter;
|
||||||
|
private bool _isLinear;
|
||||||
|
private float _scalingFilterLevel;
|
||||||
|
private bool _updateScalingFilter;
|
||||||
|
private ScalingFilter _currentScalingFilter;
|
||||||
|
private bool _colorSpacePassthroughEnabled;
|
||||||
|
|
||||||
public Window(MetalRenderer renderer, CAMetalLayer metalLayer)
|
public Window(MetalRenderer renderer, CAMetalLayer metalLayer)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
_metalLayer = metalLayer;
|
_metalLayer = metalLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle ImageCrop
|
|
||||||
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
if (_renderer.Pipeline is Pipeline pipeline && texture is Texture tex)
|
if (_renderer.Pipeline is Pipeline pipeline && texture is Texture tex)
|
||||||
{
|
{
|
||||||
var drawable = new CAMetalDrawable(ObjectiveC.IntPtr_objc_msgSend(_metalLayer, "nextDrawable"));
|
var drawable = new CAMetalDrawable(ObjectiveC.IntPtr_objc_msgSend(_metalLayer, "nextDrawable"));
|
||||||
pipeline.Present(drawable, tex);
|
|
||||||
|
_width = (int)drawable.Texture.Width;
|
||||||
|
_height = (int)drawable.Texture.Height;
|
||||||
|
|
||||||
|
UpdateEffect();
|
||||||
|
|
||||||
|
if (_effect != null)
|
||||||
|
{
|
||||||
|
// TODO: Run Effects
|
||||||
|
// view = _effect.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
int srcX0, srcX1, srcY0, srcY1;
|
||||||
|
|
||||||
|
if (crop.Left == 0 && crop.Right == 0)
|
||||||
|
{
|
||||||
|
srcX0 = 0;
|
||||||
|
srcX1 = tex.Width;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
srcX0 = crop.Left;
|
||||||
|
srcX1 = crop.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crop.Top == 0 && crop.Bottom == 0)
|
||||||
|
{
|
||||||
|
srcY0 = 0;
|
||||||
|
srcY1 = tex.Height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
srcY0 = crop.Top;
|
||||||
|
srcY1 = crop.Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ScreenCaptureRequested)
|
||||||
|
{
|
||||||
|
// TODO: Support screen captures
|
||||||
|
|
||||||
|
ScreenCaptureRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
|
||||||
|
float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
|
||||||
|
|
||||||
|
int dstWidth = (int)(_width * ratioX);
|
||||||
|
int dstHeight = (int)(_height * ratioY);
|
||||||
|
|
||||||
|
int dstPaddingX = (_width - dstWidth) / 2;
|
||||||
|
int dstPaddingY = (_height - dstHeight) / 2;
|
||||||
|
|
||||||
|
int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
|
||||||
|
int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
|
||||||
|
|
||||||
|
int dstY0 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
||||||
|
int dstY1 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
||||||
|
|
||||||
|
if (_scalingFilter != null)
|
||||||
|
{
|
||||||
|
// TODO: Run scaling filter
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.Present(
|
||||||
|
drawable,
|
||||||
|
tex,
|
||||||
|
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||||
|
new Extents2D(dstX0, dstY0, dstX1, dstY1),
|
||||||
|
_isLinear);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled)
|
public void ChangeVSyncMode(bool vsyncEnabled)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
_vsyncEnabled = vsyncEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAntiAliasing(AntiAliasing antialiasing)
|
public void SetAntiAliasing(AntiAliasing effect)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
if (_currentAntiAliasing == effect && _effect != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentAntiAliasing = effect;
|
||||||
|
|
||||||
|
_updateEffect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetScalingFilter(ScalingFilter type)
|
public void SetScalingFilter(ScalingFilter type)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
if (_currentScalingFilter == type && _effect != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentScalingFilter = type;
|
||||||
|
|
||||||
|
_updateScalingFilter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetScalingFilterLevel(float level)
|
public void SetScalingFilterLevel(float level)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
_scalingFilterLevel = level;
|
||||||
|
_updateScalingFilter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled) { }
|
public void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled)
|
||||||
|
{
|
||||||
|
_colorSpacePassthroughEnabled = colorSpacePassThroughEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEffect()
|
||||||
|
{
|
||||||
|
if (_updateEffect)
|
||||||
|
{
|
||||||
|
_updateEffect = false;
|
||||||
|
|
||||||
|
switch (_currentAntiAliasing)
|
||||||
|
{
|
||||||
|
case AntiAliasing.Fxaa:
|
||||||
|
_effect?.Dispose();
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Gpu, "FXAA not implemented for Metal backend!");
|
||||||
|
break;
|
||||||
|
case AntiAliasing.None:
|
||||||
|
_effect?.Dispose();
|
||||||
|
_effect = null;
|
||||||
|
break;
|
||||||
|
case AntiAliasing.SmaaLow:
|
||||||
|
case AntiAliasing.SmaaMedium:
|
||||||
|
case AntiAliasing.SmaaHigh:
|
||||||
|
case AntiAliasing.SmaaUltra:
|
||||||
|
var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Gpu, "SMAA not implemented for Metal backend!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_updateScalingFilter)
|
||||||
|
{
|
||||||
|
_updateScalingFilter = false;
|
||||||
|
|
||||||
|
switch (_currentScalingFilter)
|
||||||
|
{
|
||||||
|
case ScalingFilter.Bilinear:
|
||||||
|
case ScalingFilter.Nearest:
|
||||||
|
_scalingFilter?.Dispose();
|
||||||
|
_scalingFilter = null;
|
||||||
|
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
|
||||||
|
break;
|
||||||
|
case ScalingFilter.Fsr:
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Gpu, "FSR not implemented for Metal backend!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue