54 changed files with 1270 additions and 245 deletions
@ -0,0 +1,11 @@ |
|||
#import <AppKit/AppKit.h> |
|||
|
|||
class CppAutoreleasePool |
|||
{ |
|||
void* _pool; |
|||
public: |
|||
CppAutoreleasePool(); |
|||
~CppAutoreleasePool(); |
|||
}; |
|||
|
|||
#define START_ARP_CALL CppAutoreleasePool __autoreleasePool |
|||
@ -0,0 +1,161 @@ |
|||
#import <AppKit/AppKit.h> |
|||
#import <Metal/Metal.h> |
|||
#import <QuartzCore/QuartzCore.h> |
|||
#include "common.h" |
|||
#include "rendertarget.h" |
|||
|
|||
class AvnMetalDevice : public ComSingleObject<IAvnMetalDevice, &IID_IAvnMetalDevice> |
|||
{ |
|||
public: |
|||
id<MTLDevice> device; |
|||
id<MTLCommandQueue> queue; |
|||
FORWARD_IUNKNOWN() |
|||
|
|||
void *GetDevice() override { |
|||
return (__bridge void*) device; |
|||
} |
|||
|
|||
void *GetQueue() override { |
|||
return (__bridge void*) queue; |
|||
} |
|||
|
|||
AvnMetalDevice(id <MTLDevice> device, id <MTLCommandQueue> queue) : device(device), queue(queue) { |
|||
} |
|||
|
|||
}; |
|||
|
|||
|
|||
class AvnMetalRenderSession : public ComSingleObject<IAvnMetalRenderingSession, &IID_IAvnMetalRenderingSession> |
|||
{ |
|||
id<CAMetalDrawable> _drawable; |
|||
id<MTLCommandQueue> _queue; |
|||
id<MTLTexture> _texture; |
|||
CAMetalLayer* _layer; |
|||
AvnPixelSize _size; |
|||
double _scaling; |
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
|
|||
AvnMetalRenderSession(AvnMetalDevice* device, CAMetalLayer* layer, id <CAMetalDrawable> drawable, const AvnPixelSize &size, double scaling) |
|||
: _drawable(drawable), _size(size), _scaling(scaling), _queue(device->queue), |
|||
_texture([drawable texture]) { |
|||
_layer = layer; |
|||
} |
|||
|
|||
HRESULT GetPixelSize(AvnPixelSize *ret) override { |
|||
*ret = _size; |
|||
return 0; |
|||
} |
|||
|
|||
double GetScaling() override { |
|||
return _scaling; |
|||
} |
|||
|
|||
void *GetTexture() override { |
|||
return (__bridge void*) _texture; |
|||
} |
|||
|
|||
~AvnMetalRenderSession() |
|||
{ |
|||
auto buffer = [_queue commandBuffer]; |
|||
[buffer presentDrawable: _drawable]; |
|||
[buffer commit]; |
|||
} |
|||
}; |
|||
|
|||
class AvnMetalRenderTarget : public ComSingleObject<IAvnMetalRenderTarget, &IID_IAvnMetalRenderTarget> |
|||
{ |
|||
CAMetalLayer* _layer; |
|||
double _scaling = 1; |
|||
AvnPixelSize _size = {1,1}; |
|||
ComPtr<AvnMetalDevice> _device; |
|||
public: |
|||
double PendingScaling = 1; |
|||
AvnPixelSize PendingSize = {1,1}; |
|||
FORWARD_IUNKNOWN() |
|||
AvnMetalRenderTarget(CAMetalLayer* layer, ComPtr<AvnMetalDevice> device) |
|||
{ |
|||
_layer = layer; |
|||
_device = device; |
|||
} |
|||
|
|||
HRESULT BeginDrawing(IAvnMetalRenderingSession **ret) override { |
|||
if([NSThread isMainThread]) |
|||
{ |
|||
// Flush all existing rendering |
|||
auto buffer = [_device->queue commandBuffer]; |
|||
[buffer commit]; |
|||
[buffer waitUntilCompleted]; |
|||
_size = PendingSize; |
|||
_scaling= PendingScaling; |
|||
CGSize layerSize = {(CGFloat)_size.Width, (CGFloat)_size.Height}; |
|||
|
|||
[_layer setDrawableSize: layerSize]; |
|||
} |
|||
auto drawable = [_layer nextDrawable]; |
|||
if(drawable == nil) |
|||
{ |
|||
ret = nil; |
|||
return E_FAIL; |
|||
} |
|||
*ret = new AvnMetalRenderSession(_device, _layer, drawable, _size, _scaling); |
|||
return 0; |
|||
} |
|||
}; |
|||
|
|||
@implementation MetalRenderTarget |
|||
{ |
|||
ComPtr<AvnMetalDevice> _device; |
|||
CAMetalLayer* _layer; |
|||
ComPtr<AvnMetalRenderTarget> _target; |
|||
} |
|||
- (MetalRenderTarget *)initWithDevice:(IAvnMetalDevice *)device { |
|||
_device = dynamic_cast<AvnMetalDevice*>(device); |
|||
_layer = [CAMetalLayer new]; |
|||
_layer.device = _device->device; |
|||
_target.setNoAddRef(new AvnMetalRenderTarget(_layer, _device)); |
|||
return self; |
|||
} |
|||
|
|||
|
|||
-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv |
|||
{ |
|||
*ppv = static_cast<IAvnMetalRenderTarget*>(_target.getRetainedReference()); |
|||
} |
|||
|
|||
- (void)resize:(AvnPixelSize)size withScale:(float)scale { |
|||
CGSize layerSize = {(CGFloat)size.Width, (CGFloat)size.Height}; |
|||
_target->PendingScaling = scale; |
|||
_target->PendingSize = size; |
|||
[_layer setNeedsDisplay]; |
|||
} |
|||
|
|||
- (CALayer *)layer { |
|||
return _layer; |
|||
} |
|||
@end |
|||
|
|||
|
|||
class AvnMetalDisplay : public ComSingleObject<IAvnMetalDisplay, &IID_IAvnMetalDisplay> |
|||
{ |
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
HRESULT CreateDevice(IAvnMetalDevice **ret) override { |
|||
|
|||
auto device = MTLCreateSystemDefaultDevice(); |
|||
if(device == nil) { |
|||
ret = nil; |
|||
return E_FAIL; |
|||
} |
|||
auto queue = [device newCommandQueue]; |
|||
*ret = new AvnMetalDevice(device, queue); |
|||
return S_OK; |
|||
} |
|||
}; |
|||
|
|||
static AvnMetalDisplay* _display = new AvnMetalDisplay(); |
|||
|
|||
extern IAvnMetalDisplay* GetMetalDisplay() |
|||
{ |
|||
return _display; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
#include "noarc.h" |
|||
|
|||
CppAutoreleasePool::CppAutoreleasePool() |
|||
{ |
|||
_pool = [[NSAutoreleasePool alloc] init]; |
|||
} |
|||
|
|||
CppAutoreleasePool::~CppAutoreleasePool() { |
|||
auto ptr = (NSAutoreleasePool*)_pool; |
|||
[ptr release]; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia; |
|||
|
|||
public class RenderTargetNotReadyException : Exception |
|||
{ |
|||
public RenderTargetNotReadyException() |
|||
{ |
|||
} |
|||
|
|||
public RenderTargetNotReadyException(string message) |
|||
: base(message) |
|||
{ |
|||
} |
|||
|
|||
public RenderTargetNotReadyException(Exception innerException) |
|||
: base(null, innerException) |
|||
{ |
|||
} |
|||
|
|||
public RenderTargetNotReadyException(string message, Exception innerException) |
|||
: base(message, innerException) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\build\DevAnalyzers.props" /> |
|||
<Import Project="..\..\build\TrimmingEnable.props" /> |
|||
<Import Project="..\..\build\NullableEnable.props" /> |
|||
</Project> |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Metal; |
|||
|
|||
|
|||
[PrivateApi] |
|||
public interface IMetalDevice : IPlatformGraphicsContext |
|||
{ |
|||
IntPtr Device { get; } |
|||
IntPtr CommandQueue { get; } |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalPlatformSurface |
|||
{ |
|||
IMetalPlatformSurfaceRenderTarget CreateMetalRenderTarget(IMetalDevice device); |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalPlatformSurfaceRenderTarget : IDisposable |
|||
{ |
|||
IMetalPlatformSurfaceRenderingSession BeginRendering(); |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalPlatformSurfaceRenderingSession : IDisposable |
|||
{ |
|||
IntPtr Texture { get; } |
|||
PixelSize Size { get; } |
|||
double Scaling { get; } |
|||
bool IsYFlipped { get; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Avalonia.Native.Interop; |
|||
using Avalonia.Threading; |
|||
using MicroCom.Runtime; |
|||
|
|||
namespace Avalonia.Native; |
|||
|
|||
class AvnDispatcher : NativeCallbackBase, IAvnDispatcher |
|||
{ |
|||
public void Post(IAvnActionCallback cb) |
|||
{ |
|||
var callback = cb.CloneReference(); |
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
using (callback) |
|||
callback.Run(); |
|||
}, DispatcherPriority.Send); |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
using System; |
|||
using Avalonia.Metal; |
|||
using Avalonia.Native.Interop; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Native; |
|||
|
|||
class MetalPlatformGraphics : IPlatformGraphics |
|||
{ |
|||
private readonly IAvnMetalDisplay _display; |
|||
|
|||
public MetalPlatformGraphics(IAvaloniaNativeFactory factory) |
|||
{ |
|||
_display = factory.ObtainMetalDisplay(); |
|||
} |
|||
public bool UsesSharedContext => false; |
|||
public IPlatformGraphicsContext CreateContext() => new MetalDevice(_display.CreateDevice()); |
|||
|
|||
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); |
|||
} |
|||
|
|||
class MetalDevice : IMetalDevice |
|||
{ |
|||
public IAvnMetalDevice Native { get; private set; } |
|||
private DisposableLock _syncRoot = new(); |
|||
|
|||
|
|||
public MetalDevice(IAvnMetalDevice native) |
|||
{ |
|||
Native = native; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Native?.Dispose(); |
|||
Native = null; |
|||
} |
|||
|
|||
public object TryGetFeature(Type featureType) => null; |
|||
|
|||
public bool IsLost => false; |
|||
|
|||
public IDisposable EnsureCurrent() => _syncRoot.Lock(); |
|||
|
|||
public IntPtr Device => Native.Device; |
|||
public IntPtr CommandQueue => Native.Queue; |
|||
} |
|||
|
|||
class MetalPlatformSurface : IMetalPlatformSurface |
|||
{ |
|||
private readonly IAvnWindowBase _window; |
|||
|
|||
public MetalPlatformSurface(IAvnWindowBase window) |
|||
{ |
|||
_window = window; |
|||
} |
|||
public IMetalPlatformSurfaceRenderTarget CreateMetalRenderTarget(IMetalDevice device) |
|||
{ |
|||
if (!Dispatcher.UIThread.CheckAccess()) |
|||
throw new RenderTargetNotReadyException(); |
|||
|
|||
var dev = (MetalDevice)device; |
|||
var target = _window.CreateMetalRenderTarget(dev.Native); |
|||
return new MetalRenderTarget(target); |
|||
} |
|||
} |
|||
|
|||
internal class MetalRenderTarget : IMetalPlatformSurfaceRenderTarget |
|||
{ |
|||
private IAvnMetalRenderTarget _native; |
|||
|
|||
public MetalRenderTarget(IAvnMetalRenderTarget native) |
|||
{ |
|||
_native = native; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_native?.Dispose(); |
|||
_native = null; |
|||
} |
|||
|
|||
public IMetalPlatformSurfaceRenderingSession BeginRendering() |
|||
{ |
|||
var session = _native.BeginDrawing(); |
|||
return new MetalDrawingSession(session); |
|||
} |
|||
} |
|||
|
|||
internal class MetalDrawingSession : IMetalPlatformSurfaceRenderingSession |
|||
{ |
|||
private IAvnMetalRenderingSession _session; |
|||
|
|||
public MetalDrawingSession(IAvnMetalRenderingSession session) |
|||
{ |
|||
_session = session; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_session?.Dispose(); |
|||
_session = null; |
|||
} |
|||
|
|||
public IntPtr Texture => _session.Texture; |
|||
public PixelSize Size |
|||
{ |
|||
get |
|||
{ |
|||
var size = _session.PixelSize; |
|||
return new(size.Width, size.Height); |
|||
} |
|||
} |
|||
|
|||
public double Scaling => _session.Scaling; |
|||
public bool IsYFlipped => false; |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Reflection; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Platform.Interop; |
|||
using SkiaSharp; |
|||
using BindingFlags = System.Reflection.BindingFlags; |
|||
|
|||
namespace Avalonia.Skia.Metal; |
|||
|
|||
internal unsafe class SkiaMetalApi |
|||
{ |
|||
delegate* unmanaged[Stdcall] <IntPtr, IntPtr, IntPtr, IntPtr> _gr_direct_context_make_metal_with_options; |
|||
private delegate* unmanaged[Stdcall]<int, int, int, GRMtlTextureInfoNative*, IntPtr> |
|||
_gr_backendrendertarget_new_metal; |
|||
private readonly ConstructorInfo _contextCtor; |
|||
private readonly MethodInfo _contextOptionsToNative; |
|||
private readonly ConstructorInfo _renderTargetCtor; |
|||
|
|||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(GRContext))] |
|||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(GRBackendRenderTarget))] |
|||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, typeof(GRContextOptions))] |
|||
public SkiaMetalApi() |
|||
{ |
|||
// Make sure that skia is loaded
|
|||
GC.KeepAlive(new SKPaint()); |
|||
|
|||
var loader = AvaloniaLocator.Current.GetRequiredService<IDynamicLibraryLoader>(); |
|||
#if NET6_0_OR_GREATER
|
|||
var dll = NativeLibrary.Load("libSkiaSharp", typeof(SKPaint).Assembly, null); |
|||
#else
|
|||
var dll = loader.LoadLibrary("libSkiaSharp"); |
|||
#endif
|
|||
_gr_direct_context_make_metal_with_options = (delegate* unmanaged[Stdcall] <IntPtr, IntPtr, IntPtr, IntPtr>) |
|||
loader.GetProcAddress(dll, "gr_direct_context_make_metal_with_options", false); |
|||
_gr_backendrendertarget_new_metal = |
|||
(delegate* unmanaged[Stdcall]<int, int, int, GRMtlTextureInfoNative*, IntPtr>) |
|||
loader.GetProcAddress(dll, "gr_backendrendertarget_new_metal", false); |
|||
|
|||
_contextCtor = typeof(GRContext).GetConstructor( |
|||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, |
|||
new[] { typeof(IntPtr), typeof(bool) }, null) ?? throw new MissingMemberException("GRContext.ctor(IntPtr,bool)"); |
|||
|
|||
|
|||
_renderTargetCtor = typeof(GRBackendRenderTarget).GetConstructor( |
|||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, |
|||
new[] { typeof(IntPtr), typeof(bool) }, null) ?? throw new MissingMemberException("GRContext.ctor(IntPtr,bool)"); |
|||
|
|||
_contextOptionsToNative = typeof(GRContextOptions).GetMethod("ToNative", |
|||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) |
|||
?? throw new MissingMemberException("GRContextOptions.ToNative()"); |
|||
} |
|||
|
|||
public GRContext CreateContext(IntPtr device, IntPtr queue, GRContextOptions? options) |
|||
{ |
|||
options ??= new(); |
|||
var nativeOptions = _contextOptionsToNative.Invoke(options, null)!; |
|||
var pOptions = Marshal.AllocHGlobal(Marshal.SizeOf(nativeOptions)); |
|||
Marshal.StructureToPtr(nativeOptions, pOptions, false); |
|||
var context = _gr_direct_context_make_metal_with_options(device, queue, pOptions); |
|||
Marshal.FreeHGlobal(pOptions); |
|||
if (context == IntPtr.Zero) |
|||
throw new ArgumentException(); |
|||
return (GRContext)_contextCtor.Invoke(new object[] { context, true }); |
|||
} |
|||
|
|||
internal struct GRMtlTextureInfoNative |
|||
{ |
|||
public IntPtr Texture; |
|||
} |
|||
|
|||
public GRBackendRenderTarget CreateBackendRenderTarget(int width, int height, int samples, IntPtr texture) |
|||
{ |
|||
var info = new GRMtlTextureInfoNative() { Texture = texture }; |
|||
var target = _gr_backendrendertarget_new_metal(width, height, samples, &info); |
|||
if (target == IntPtr.Zero) |
|||
throw new InvalidOperationException("Unable to create GRBackendRenderTarget"); |
|||
return (GRBackendRenderTarget)_renderTargetCtor.Invoke(new object[] { target, true }); |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Metal; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia.Metal; |
|||
|
|||
internal class SkiaMetalGpu : ISkiaGpu |
|||
{ |
|||
private SkiaMetalApi _api = new(); |
|||
private GRContext? _context; |
|||
private readonly IMetalDevice _device; |
|||
|
|||
public SkiaMetalGpu(IMetalDevice device, long? maxResourceBytes) |
|||
{ |
|||
_context = _api.CreateContext(device.Device, device.CommandQueue, |
|||
new GRContextOptions() { AvoidStencilBuffers = true }); |
|||
_device = device; |
|||
if (maxResourceBytes.HasValue) |
|||
_context.SetResourceCacheLimit(maxResourceBytes.Value); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_context?.Dispose(); |
|||
_context = null; |
|||
} |
|||
|
|||
public object? TryGetFeature(Type featureType) => null; |
|||
|
|||
public bool IsLost => false; |
|||
public IDisposable EnsureCurrent() => _device.EnsureCurrent(); |
|||
|
|||
public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces) |
|||
{ |
|||
foreach (var surface in surfaces) |
|||
{ |
|||
if (surface is IMetalPlatformSurface metalSurface) |
|||
{ |
|||
var target = metalSurface.CreateMetalRenderTarget(_device); |
|||
return new SkiaMetalRenderTarget(this, target); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public class SkiaMetalRenderTarget : ISkiaGpuRenderTarget |
|||
{ |
|||
private readonly SkiaMetalGpu _gpu; |
|||
private IMetalPlatformSurfaceRenderTarget? _target; |
|||
|
|||
public SkiaMetalRenderTarget(SkiaMetalGpu gpu, IMetalPlatformSurfaceRenderTarget target) |
|||
{ |
|||
_gpu = gpu; |
|||
_target = target; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_target?.Dispose(); |
|||
_target = null; |
|||
} |
|||
|
|||
public ISkiaGpuRenderSession BeginRenderingSession() |
|||
{ |
|||
var session = (_target ?? throw new ObjectDisposedException(nameof(SkiaMetalRenderTarget))).BeginRendering(); |
|||
var backendTarget = _gpu._api.CreateBackendRenderTarget(session.Size.Width, session.Size.Height, |
|||
1, session.Texture); |
|||
|
|||
var surface = SKSurface.Create(_gpu._context!, backendTarget, |
|||
session.IsYFlipped ? GRSurfaceOrigin.BottomLeft : GRSurfaceOrigin.TopLeft, |
|||
SKColorType.Bgra8888); |
|||
|
|||
return new SkiaMetalRenderSession(_gpu, surface, session); |
|||
} |
|||
|
|||
public bool IsCorrupted => false; |
|||
} |
|||
|
|||
internal class SkiaMetalRenderSession : ISkiaGpuRenderSession |
|||
{ |
|||
private readonly SkiaMetalGpu _gpu; |
|||
private SKSurface? _surface; |
|||
private IMetalPlatformSurfaceRenderingSession? _session; |
|||
|
|||
public SkiaMetalRenderSession(SkiaMetalGpu gpu, |
|||
SKSurface surface, |
|||
IMetalPlatformSurfaceRenderingSession session) |
|||
{ |
|||
_gpu = gpu; |
|||
_surface = surface; |
|||
_session = session; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_surface?.Canvas.Flush(); |
|||
_surface?.Flush(); |
|||
_gpu._context?.Flush(); |
|||
|
|||
_surface?.Dispose(); |
|||
_surface = null; |
|||
_session?.Dispose(); |
|||
_session = null; |
|||
} |
|||
|
|||
public GRContext GrContext => _gpu._context!; |
|||
public SKSurface SkSurface => _surface!; |
|||
public double ScaleFactor => _session!.Scaling; |
|||
|
|||
public GRSurfaceOrigin SurfaceOrigin => |
|||
_session!.IsYFlipped ? GRSurfaceOrigin.BottomLeft : GRSurfaceOrigin.TopLeft; |
|||
} |
|||
|
|||
|
|||
public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession? session) => null; |
|||
} |
|||
Loading…
Reference in new issue