Browse Source

OSX rendering refactoring

pull/11914/head
Nikita Tsukanov 3 years ago
parent
commit
8680c612fd
  1. 1
      Avalonia.Desktop.slnf
  2. 6
      Avalonia.sln
  3. 1
      build/CoreLibraries.props
  4. 1
      native/Avalonia.Native/inc/com.h
  5. 19
      native/Avalonia.Native/inc/comimpl.h
  6. 11
      native/Avalonia.Native/inc/noarc.h
  7. 16
      native/Avalonia.Native/inc/rendertarget.h
  8. 20
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  9. 5
      native/Avalonia.Native/src/OSX/AvnView.h
  10. 46
      native/Avalonia.Native/src/OSX/AvnView.mm
  11. 6
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  12. 13
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  13. 58
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  14. 2
      native/Avalonia.Native/src/OSX/WindowImpl.h
  15. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  16. 31
      native/Avalonia.Native/src/OSX/common.h
  17. 36
      native/Avalonia.Native/src/OSX/main.mm
  18. 161
      native/Avalonia.Native/src/OSX/metal.mm
  19. 11
      native/Avalonia.Native/src/OSX/noarc.mm
  20. 245
      native/Avalonia.Native/src/OSX/rendertarget.mm
  21. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  22. 25
      src/Avalonia.Base/RenderTargetNotReadyException.cs
  23. 13
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  24. 29
      src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
  25. 6
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  26. 15
      src/Avalonia.Metal/Avalonia.Metal.csproj
  27. 34
      src/Avalonia.Metal/IMetalDevice.cs
  28. 5
      src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
  29. 42
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  30. 2
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  31. 18
      src/Avalonia.Native/AvnDispatcher.cs
  32. 49
      src/Avalonia.Native/DeferredFramebuffer.cs
  33. 119
      src/Avalonia.Native/Metal.cs
  34. 12
      src/Avalonia.Native/PopupImpl.cs
  35. 10
      src/Avalonia.Native/WindowImpl.cs
  36. 67
      src/Avalonia.Native/WindowImplBase.cs
  37. 53
      src/Avalonia.Native/avn.idl
  38. 2
      src/Avalonia.X11/X11CursorFactory.cs
  39. 2
      src/Avalonia.X11/X11FramebufferSurface.cs
  40. 2
      src/Avalonia.X11/X11IconLoader.cs
  41. 2
      src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs
  42. 2
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  43. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  44. 11
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  45. 80
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs
  46. 119
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
  47. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  48. 2
      src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
  49. 12
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  50. 2
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  51. 6
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  52. 5
      tests/Avalonia.RenderTests/TestBase.cs
  53. 2
      tests/Avalonia.UnitTests/CompositorTestServices.cs

1
Avalonia.Desktop.slnf

@ -24,6 +24,7 @@
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Metal\\Avalonia.Metal.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",

6
Avalonia.sln

@ -274,6 +274,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.Uni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Browser", "samples\MobileSandbox.Browser\MobileSandbox.Browser.csproj", "{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Metal", "src\Avalonia.Metal\Avalonia.Metal.csproj", "{60B4ED1F-ECFA-453B-8A70-1788261C8355}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -661,6 +663,10 @@ Global
{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.Build.0 = Release|Any CPU
{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

1
build/CoreLibraries.props

@ -4,6 +4,7 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Metal/Avalonia.Metal.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />

1
native/Avalonia.Native/inc/com.h

@ -28,6 +28,7 @@ typedef DWORD ULONG;
#define E_UNEXPECTED 0x8000FFFFL
#define E_HANDLE 0x80070006L
#define E_INVALIDARG 0x80070057L
#define COR_E_INVALIDOPERATION 0x80131509L
struct IUnknown
{

19
native/Avalonia.Native/inc/comimpl.h

@ -43,6 +43,18 @@ public:
_obj->AddRef();
}
}
ComPtr(TInterface* pObj, bool ownsHandle)
{
_obj = 0;
if (pObj)
{
_obj = pObj;
if(!ownsHandle)
_obj->AddRef();
}
}
ComPtr(const ComPtr& ptr)
{
@ -92,6 +104,13 @@ public:
{
return &_obj;
}
void setNoAddRef(TInterface* value)
{
if(_obj != nullptr)
_obj->Release();
_obj = value;
}
operator TInterface*() const
{

11
native/Avalonia.Native/inc/noarc.h

@ -0,0 +1,11 @@
#import <AppKit/AppKit.h>
class CppAutoreleasePool
{
void* _pool;
public:
CppAutoreleasePool();
~CppAutoreleasePool();
};
#define START_ARP_CALL CppAutoreleasePool __autoreleasePool

16
native/Avalonia.Native/inc/rendertarget.h

@ -5,13 +5,21 @@
#include "avalonia-native.h"
@protocol IRenderTarget
-(void) setNewLayer: (CALayer*) layer;
-(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
-(void) resize: (AvnPixelSize) size withScale: (float) scale;
-(AvnPixelSize) pixelSize;
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
-(CALayer*) layer;
@end
@interface IOSurfaceRenderTarget : NSObject<IRenderTarget>
-(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget;
-(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
-(void)consumeSurfaces;
@end
@interface MetalRenderTarget : NSObject<IRenderTarget>
-(MetalRenderTarget*) initWithDevice: (IAvnMetalDevice*) device;
-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv;
@end

20
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -43,6 +43,11 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
64B1EA48E308E574685AFB07 /* metal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1EBEECBE13D8616D7C934 /* metal.mm */; };
64B1ECA861163C0EFF0E502B /* noarc.h in Headers */ = {isa = PBXBuildFile; fileRef = 64B1E26F2B1B9C577BF52F06 /* noarc.h */; };
64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */; };
64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */; };
64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */; };
8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */; };
@ -99,6 +104,11 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
64B1E23CAFE5FEC7AD981B1B /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
64B1E26F2B1B9C577BF52F06 /* noarc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = noarc.h; path = ../../inc/noarc.h; sourceTree = "<group>"; };
64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = noarc.mm; sourceTree = "<group>"; };
64B1E677F7791226F783ACE4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
64B1EBEECBE13D8616D7C934 /* metal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = metal.mm; sourceTree = "<group>"; };
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = "<group>"; };
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethodDelegate.h; sourceTree = "<group>"; };
8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethod.h; sourceTree = "<group>"; };
@ -126,6 +136,8 @@
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */,
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */,
64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -141,6 +153,8 @@
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
AB1E522B217613570091CD71 /* OpenGL.framework */,
AB661C1D2148230F00291242 /* AppKit.framework */,
64B1E23CAFE5FEC7AD981B1B /* Metal.framework */,
64B1E677F7791226F783ACE4 /* CoreGraphics.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -198,6 +212,9 @@
18391DB45C7D892E61BF388C /* WindowInterfaces.h */,
18391BB698579F40F1783F31 /* PopupImpl.mm */,
183910513F396141938832B5 /* PopupImpl.h */,
64B1EBEECBE13D8616D7C934 /* metal.mm */,
64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */,
64B1E26F2B1B9C577BF52F06 /* noarc.h */,
);
sourceTree = "<group>";
};
@ -230,6 +247,7 @@
18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */,
183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */,
18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */,
64B1ECA861163C0EFF0E502B /* noarc.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -319,6 +337,8 @@
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
64B1EA48E308E574685AFB07 /* metal.mm in Sources */,
64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

5
native/Avalonia.Native/src/OSX/AvnView.h

@ -11,15 +11,16 @@
#include "KeyTransform.h"
@class AvnAccessibilityElement;
@protocol IRenderTarget;
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination, AvnTextInputMethodDelegate>
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination, AvnTextInputMethodDelegate, CALayerDelegate>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
-(void) setRenderTarget:(NSObject<IRenderTarget>*)target;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end

46
native/Avalonia.Native/src/OSX/AvnView.mm

@ -17,7 +17,7 @@
NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget;
NSObject<IRenderTarget>* _currentRenderTarget;
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
NSRect _cursorRect;
@ -41,15 +41,39 @@
- (void) updateRenderTarget
{
[_renderTarget resize:_lastPixelSize withScale:static_cast<float>([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
if(_currentRenderTarget) {
[_currentRenderTarget resize:_lastPixelSize withScale:static_cast<float>([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
}
}
-(void) setRenderTarget:(NSObject<IRenderTarget>*)target
{
if([self layer])
{
[self layer].delegate = nil;
}
_currentRenderTarget = target;
auto layer = [target layer];
[self setLayer: layer];
[layer setDelegate: self];
layer.needsDisplayOnBoundsChange = YES;
[self updateRenderTarget];
}
-(void)displayLayer: (CALayer*)layer
{
[self updateLayer];
}
-(AvnView*) initWithParent: (WindowBaseImpl*) parent
{
self = [super init];
_renderTarget = parent->renderTarget;
[self setWantsLayer:YES];
[self setLayerContentsPlacement: NSViewLayerContentsPlacementTopLeft];
[self setCanDrawSubviewsIntoLayer: NO];
[self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
_parent = parent;
@ -77,12 +101,6 @@
return YES;
}
- (void)setLayer:(CALayer *)layer
{
[_renderTarget setNewLayer: layer];
[super setLayer: layer];
}
- (BOOL)isOpaque
{
return YES;
@ -164,14 +182,6 @@
return;
}
-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
{
@autoreleasepool {
[_renderTarget setSwFrame:fb];
dispose->Release();
}
}
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self bounds].size.height - pt.Y;

6
native/Avalonia.Native/src/OSX/PopupImpl.mm

@ -23,7 +23,7 @@ private:
END_INTERFACE_MAP()
virtual ~PopupImpl(){}
ComPtr<IAvnWindowEvents> WindowEvents;
PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
@ -47,11 +47,11 @@ public:
};
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events)
{
@autoreleasepool
{
IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events));
return ptr;
}
}

13
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -27,7 +27,7 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl();
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel = false);
virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -81,13 +81,15 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override;
virtual HRESULT SetCursor(IAvnCursor *cursor) override;
virtual void UpdateCursor();
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override;
virtual HRESULT CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) override;
virtual HRESULT CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) override;
virtual HRESULT CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) override;
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override;
@ -118,7 +120,6 @@ private:
void CleanNSWindow ();
NSCursor *cursor;
ComPtr<IAvnGlContext> _glContext;
bool hasPosition;
NSSize lastSize;
NSSize lastMinSize;
@ -132,7 +133,7 @@ protected:
bool _shown;
public:
NSObject <IRenderTarget> *renderTarget;
NSObject <IRenderTarget> *currentRenderTarget;
NSWindow * Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
ComPtr<AvnTextInputMethod> InputMethod;

58
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -16,6 +16,7 @@
#import "WindowInterfaces.h"
#include "WindowBaseImpl.h"
#include "AvnTextInputMethod.h"
#include "AvnView.h"
WindowBaseImpl::~WindowBaseImpl() {
@ -23,12 +24,10 @@ WindowBaseImpl::~WindowBaseImpl() {
Window = nullptr;
}
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel) {
_shown = false;
_inResize = false;
BaseEvents = events;
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
View = [[AvnView alloc] initWithParent:this];
InputMethod = new AvnTextInputMethod(View);
StandardContainer = [[AutoFitContentView new] initWithContent:View];
@ -448,13 +447,6 @@ HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) {
}
}
HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) {
START_COM_CALL;
[View setSwRenderedFrame:fb dispose:dispose];
return S_OK;
}
HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) {
START_COM_CALL;
@ -479,13 +471,49 @@ void WindowBaseImpl::UpdateCursor() {
}
}
HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) {
HRESULT WindowBaseImpl::CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) {
START_COM_CALL;
if(![NSThread isMainThread])
return COR_E_INVALIDOPERATION;
if (View == NULL)
return E_FAIL;
*ppv = [renderTarget createSurfaceRenderTarget];
return static_cast<HRESULT>(*ppv == nil ? E_FAIL : S_OK);
auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: nil];
*ppv = [target createSoftwareRenderTarget];
[View setRenderTarget: target];
return S_OK;
}
HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) {
START_COM_CALL;
if(![NSThread isMainThread])
return COR_E_INVALIDOPERATION;
if (View == NULL)
return E_FAIL;
auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: glContext];
*ppv = [target createSurfaceRenderTarget];
[View setRenderTarget: target];
return S_OK;
}
HRESULT WindowBaseImpl::CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) {
START_COM_CALL;
if(![NSThread isMainThread])
return COR_E_INVALIDOPERATION;
if (View == NULL)
return E_FAIL;
auto target = [[MetalRenderTarget alloc] initWithDevice: device];
[View setRenderTarget: target];
[target getRenderTarget: ppv];
return S_OK;
}
HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) {
@ -615,11 +643,11 @@ HRESULT WindowBaseImpl::GetInputMethod(IAvnTextInputMethod **retOut) {
return S_OK;
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events)
{
@autoreleasepool
{
IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events);
return ptr;
}
}

2
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -39,7 +39,7 @@ BEGIN_INTERFACE_MAP()
ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
WindowImpl(IAvnWindowEvents* events);
virtual HRESULT Show (bool activate, bool isDialog) override;

2
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -9,7 +9,7 @@
#include "automation.h"
#include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
WindowImpl::WindowImpl(IAvnWindowEvents *events) : WindowBaseImpl(events) {
_isEnabled = true;
_children = std::list<WindowImpl*>();
_isClientAreaExtended = false;

31
native/Avalonia.Native/src/OSX/common.h

@ -6,11 +6,13 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <pthread.h>
#include "noarc.h"
extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
extern void FreeAvnGCHandle(void* handle);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern void PostDispatcherCallback(IAvnActionCallback* cb);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
@ -21,6 +23,7 @@ extern NSString* GetAvnCustomDataType();
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMetalDisplay* GetMetalDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
@ -56,6 +59,27 @@ template<typename T> inline T* objc_cast(id from) {
return nil;
}
template<typename T> class ObjCWrapper {
public:
T* Value;
ObjCWrapper(T* value)
{
Value = value;
}
operator T*() const
{
return Value;
}
T* operator->() const
{
return Value;
}
~ObjCWrapper()
{
Value = nil;
}
};
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@ -79,5 +103,8 @@ public:
virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override;
};
#define NSApp [NSApplication sharedApplication]
#define START_COM_ARP_CALL START_ARP_CALL; START_COM_CALL
#endif

36
native/Avalonia.Native/src/OSX/main.mm

@ -191,16 +191,20 @@ public:
@end
static ComPtr<IAvnGCHandleDeallocatorCallback> _deallocator;
static ComPtr<IAvnDispatcher> _dispatcher;
class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAvaloniaNativeFactory>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator,
IAvnApplicationEvents* events,
IAvnDispatcher* dispatcher) override
{
START_COM_CALL;
_deallocator = deallocator;
_dispatcher = dispatcher;
@autoreleasepool{
[[ThreadingInitializer new] do];
}
@ -213,7 +217,7 @@ public:
return (IAvnMacOptions*)new MacOptions();
}
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) override
{
START_COM_CALL;
@ -221,12 +225,12 @@ public:
{
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
*ppv = CreateAvnWindow(cb, gl);
*ppv = CreateAvnWindow(cb);
return S_OK;
}
};
virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override
virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) override
{
START_COM_CALL;
@ -235,7 +239,7 @@ public:
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
*ppv = CreateAvnPopup(cb, gl);
*ppv = CreateAvnPopup(cb);
return S_OK;
}
}
@ -320,7 +324,22 @@ public:
return S_OK;
}
}
virtual HRESULT ObtainMetalDisplay(IAvnMetalDisplay** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
auto rv = ::GetMetalDisplay();
if(rv == NULL)
return E_FAIL;
rv->AddRef();
*ppv = rv;
return S_OK;
}
}
virtual HRESULT CreateTrayIcon (IAvnTrayIcon** ppv) override
{
START_COM_CALL;
@ -432,6 +451,11 @@ extern void FreeAvnGCHandle(void* handle)
_deallocator->FreeGCHandle(handle);
}
extern void PostDispatcherCallback(IAvnActionCallback* cb)
{
_dispatcher->Post(cb);
}
NSSize ToNSSize (AvnSize s)
{
NSSize result;

161
native/Avalonia.Native/src/OSX/metal.mm

@ -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;
}

11
native/Avalonia.Native/src/OSX/noarc.mm

@ -0,0 +1,11 @@
#include "noarc.h"
CppAutoreleasePool::CppAutoreleasePool()
{
_pool = [[NSAutoreleasePool alloc] init];
}
CppAutoreleasePool::~CppAutoreleasePool() {
auto ptr = (NSAutoreleasePool*)_pool;
[ptr release];
}

245
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -5,15 +5,12 @@
#include <OpenGL/glext.h>
#include <OpenGL/gl3.h>
#include <queue>
@interface IOSurfaceHolder : NSObject
@end
@implementation IOSurfaceHolder
@implementation IOSurfaceHolder : NSObject
{
@public IOSurfaceRef surface;
@public AvnPixelSize size;
@public bool hasContent;
@public float scale;
ComPtr<IAvnGlContext> _context;
GLuint _framebuffer, _texture, _renderbuffer;
@ -42,7 +39,6 @@
self->scale = scale;
self->size = size;
self->_context = context;
self->hasContent = false;
return self;
}
@ -76,7 +72,7 @@
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0);
}
if(_renderbuffer == 0)
{
glGenRenderbuffers(1, &_renderbuffer);
@ -84,48 +80,72 @@
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer);
}
return S_OK;
}
-(void) finishDraw
{
ComPtr<IUnknown> release;
_context->MakeCurrent(release.getPPV());
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
if(_framebuffer != 0) {
glDeleteFramebuffers(1, &_framebuffer);
_framebuffer = 0;
}
if(_texture != 0) {
glDeleteTextures(1, &_texture);
_texture = 0;
}
if(_renderbuffer != 0) {
glDeleteRenderbuffers(1, &_renderbuffer);
_renderbuffer = 0;
}
glFlush();
self->hasContent = true;
}
-(void) dealloc
{
if(_framebuffer != 0)
{
ComPtr<IUnknown> release;
_context->MakeCurrent(release.getPPV());
glDeleteFramebuffers(1, &_framebuffer);
if(_texture != 0)
glDeleteTextures(1, &_texture);
if(_renderbuffer != 0)
glDeleteRenderbuffers(1, &_renderbuffer);
}
if(surface != nullptr)
{
CFRelease(surface);
surface = nil;
}
}
@end
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target);
static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target);
static bool SizeEquals(AvnPixelSize& left, AvnPixelSize& right)
{
return left.Width == right.Width && right.Height == left.Height;
}
class ConsumeSurfacesCallback : public ComSingleObject<IAvnActionCallback, &IID_IAvnActionCallback>
{
IOSurfaceRenderTarget* _target;
public:
FORWARD_IUNKNOWN()
ConsumeSurfacesCallback(IOSurfaceRenderTarget* target)
{
_target = target;
}
void Run() override {
[_target consumeSurfaces];
}
};
@implementation IOSurfaceRenderTarget
{
CALayer* _layer;
@public IOSurfaceHolder* surface;
@public NSObject* lock;
ComPtr<IAvnGlContext> _glContext;
bool _consumeSurfacesScheduled;
std::queue<ObjCWrapper<IOSurfaceHolder>> _surfaces;
IOSurfaceHolder* _activeSurface;
AvnPixelSize _size;
float _scale;
}
- (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
@ -133,16 +153,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
self = [super init];
_glContext = context;
lock = [NSObject new];
surface = nil;
_layer = [CALayer new];
[self resize:{1,1} withScale: 1];
return self;
}
- (AvnPixelSize) pixelSize {
return {1, 1};
}
- (CALayer *)layer {
return _layer;
}
@ -155,57 +171,110 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
size.Width = 1;
@synchronized (lock) {
if(surface == nil
|| surface->size.Width != size.Width
|| surface->size.Height != size.Height
|| surface->scale != scale)
{
surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()];
[self updateLayer];
}
_size = size;
_scale = scale;
}
}
- (void)updateLayer {
if ([NSThread isMainThread])
- (void)setSurfaceInUiThreadContext: (IOSurfaceHolder*) surface
{
[CATransaction begin];
if(surface == _activeSurface)
[_layer setContents: nil];
_activeSurface = surface;
[_layer setContentsScale: _activeSurface->scale];
[_layer setContents: (__bridge IOSurface*) _activeSurface->surface];
[CATransaction commit];
}
- (void)consumeSurfaces {
@synchronized (lock) {
_consumeSurfacesScheduled = false;
while(_surfaces.size() > 1)
_surfaces.pop();
if(_surfaces.size() == 0)
return;
auto targetSurface = _surfaces.front();
_surfaces.pop();
[self setSurfaceInUiThreadContext: targetSurface];
}
// This can trigger event processing on the main thread
// which might need to lock the renderer
// which can cause a deadlock. So flush call is outside of the lock
[CATransaction flush];
}
- (IOSurfaceHolder*) getNextSurfaceInSafeContext
{
IOSurfaceHolder* targetSurface = nil;
if([NSThread isMainThread])
{
@synchronized (lock) {
if(_layer == nil)
return;
if(!surface->hasContent)
return;
[CATransaction begin];
[_layer setContents: nil];
if(surface != nil)
// Drain the surface queue and try to find a surface usable for rendering
while(_surfaces.size() > 0)
{
auto front = _surfaces.front();
_surfaces.pop();
if(targetSurface == nil && SizeEquals(front.Value->size, _size))
{
[_layer setContentsScale: surface->scale];
[_layer setContents: (__bridge IOSurface*) surface->surface];
targetSurface = front;
}
[CATransaction commit];
}
// This can trigger event processing on the main thread
// which might need to lock the renderer
// which can cause a deadlock. So flush call is outside of the lock
[CATransaction flush];
if(targetSurface == nil && _activeSurface != nil && SizeEquals(_activeSurface->size, _size))
targetSurface = _activeSurface;
}
else
{
// Try to reuse an outdated surface that is still not picked up by the UI thread
while(_surfaces.size() > 1)
{
auto front = _surfaces.front();
_surfaces.pop();
// Simply discard the surface on size mismatch
if(SizeEquals(front.Value->size, _size))
targetSurface = front;
}
}
if(targetSurface == nil)
targetSurface = [[IOSurfaceHolder alloc] initWithSize: _size withScale: _scale withOpenGlContext: _glContext];
return targetSurface;
}
- (void) presentSurfaceInSafeContext: (IOSurfaceHolder*) surface
{
if([NSThread isMainThread])
[self setSurfaceInUiThreadContext: surface];
else
dispatch_async(dispatch_get_main_queue(), ^{
[self updateLayer];
});
{
_surfaces.push(surface);
if(_consumeSurfacesScheduled)
return;
_consumeSurfacesScheduled = true;
__block auto strongSelf = self;
ComPtr<ConsumeSurfacesCallback> cb(new ConsumeSurfacesCallback(self), true);
PostDispatcherCallback(cb);
}
}
- (void) setNewLayer:(CALayer *)layer {
_layer = layer;
[self updateLayer];
- (void) presentSurface: (IOSurfaceHolder*) surface
{
@synchronized(lock)
{
[self presentSurfaceInSafeContext: surface];
}
}
- (HRESULT)setSwFrame:(AvnFramebuffer *)fb {
@synchronized (lock) {
if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565)
return E_INVALIDARG;
if(surface == nil)
return E_FAIL;
auto surface = [self getNextSurfaceInSafeContext];
IOSurfaceRef surf = surface->surface;
if(IOSurfaceLock(surf, 0, nil))
return E_FAIL;
@ -221,8 +290,7 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes);
}
IOSurfaceUnlock(surf, 0, nil);
surface->hasContent = true;
[self updateLayer];
[self presentSurfaceInSafeContext: surface];
return S_OK;
}
}
@ -232,6 +300,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
return CreateGlRenderTarget(self);
}
-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget
{
return CreateSoftwareRenderTarget(self);
}
@end
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
@ -241,12 +314,12 @@ class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSessi
IOSurfaceHolder* _surface;
public:
FORWARD_IUNKNOWN()
AvnGlRenderingSession(IOSurfaceRenderTarget* target, ComPtr<IUnknown> releaseContext)
AvnGlRenderingSession(IOSurfaceRenderTarget* target, IOSurfaceHolder* surface, ComPtr<IUnknown> releaseContext)
{
_target = target;
// This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this
// particular render session
_surface = _target->surface;
_surface = surface;
_releaseContext = releaseContext;
}
@ -272,8 +345,9 @@ public:
virtual ~AvnGlRenderingSession()
{
START_ARP_CALL;
[_surface finishDraw];
[_target updateLayer];
[_target presentSurface: _surface];
_releaseContext = nil;
}
};
@ -290,17 +364,15 @@ public:
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
{
START_COM_CALL;
START_COM_ARP_CALL;
ComPtr<IUnknown> releaseContext;
@synchronized (_target->lock) {
if(_target->surface == nil)
return E_FAIL;
auto surface = [_target getNextSurfaceInSafeContext];
_target->_glContext->MakeCurrent(releaseContext.getPPV());
HRESULT res = [_target->surface prepareForGlRender];
HRESULT res = [surface prepareForGlRender];
if(res)
return res;
*ret = new AvnGlRenderingSession(_target, releaseContext);
*ret = new AvnGlRenderingSession(_target, surface, releaseContext);
return S_OK;
}
}
@ -310,4 +382,27 @@ public:
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target)
{
return new AvnGlRenderTarget(target);
}
};
class AvnSoftwareRenderTarget : public ComSingleObject<IAvnSoftwareRenderTarget, &IID_IAvnSoftwareRenderTarget>
{
IOSurfaceRenderTarget* _target;
public:
FORWARD_IUNKNOWN()
AvnSoftwareRenderTarget(IOSurfaceRenderTarget *target) {
_target = target;
}
HRESULT SetFrame(AvnFramebuffer *fb) override {
START_COM_ARP_CALL;
[_target setSwFrame: fb];
return 0;
}
};
static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target)
{
return new AvnSoftwareRenderTarget(target);
};

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@ -13,5 +13,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface, _topLevel.RenderScaling);
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

25
src/Avalonia.Base/RenderTargetNotReadyException.cs

@ -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)
{
}
}

13
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -135,7 +135,18 @@ namespace Avalonia.Rendering.Composition.Server
_redrawRequested = true;
}
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
try
{
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
}
catch (RenderTargetNotReadyException)
{
return;
}
catch (RenderTargetCorruptedException)
{
return;
}
if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
return;

29
src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs

@ -1,10 +1,17 @@
using Avalonia.Metadata;
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform.Surfaces
{
[Unstable]
public interface IFramebufferPlatformSurface
{
IFramebufferRenderTarget CreateFramebufferRenderTarget();
}
[Unstable]
public interface IFramebufferRenderTarget : IDisposable
{
/// <summary>
/// Provides a framebuffer descriptor for drawing.
@ -14,4 +21,24 @@ namespace Avalonia.Controls.Platform.Surfaces
/// </remarks>
ILockedFramebuffer Lock();
}
/// <summary>
/// For simple cases when framebuffer is always available
/// </summary>
public class FuncFramebufferRenderTarget : IFramebufferRenderTarget
{
private readonly Func<ILockedFramebuffer> _lockFramebuffer;
public FuncFramebufferRenderTarget(Func<ILockedFramebuffer> lockFramebuffer)
{
_lockFramebuffer = lockFramebuffer;
}
public void Dispose()
{
// No-op
}
public ILockedFramebuffer Lock() => _lockFramebuffer();
}
}

6
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -282,9 +282,6 @@ namespace Avalonia.Controls.Remote.Server
}
}
public ILockedFramebuffer Lock()
=> GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded);
private void SendLastFrameIfNeeded()
{
if (IsDisposed)
@ -328,5 +325,8 @@ namespace Avalonia.Controls.Remote.Server
public override IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; }
public IFramebufferRenderTarget CreateFramebufferRenderTarget() =>
new FuncFramebufferRenderTarget(() => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded));
}
}

15
src/Avalonia.Metal/Avalonia.Metal.csproj

@ -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>

34
src/Avalonia.Metal/IMetalDevice.cs

@ -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; }
}

5
src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs

@ -192,10 +192,13 @@ namespace Avalonia.Native
{
_window = window;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
if (!Dispatcher.UIThread.CheckAccess())
throw new RenderTargetNotReadyException();
var avnContext = (GlContext)context;
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), avnContext);
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(avnContext.Context), avnContext);
}
}

42
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -19,7 +19,7 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions? _options;
private AvaloniaNativeGlPlatformGraphics? _platformGl;
private IPlatformGraphics? _platformGraphics;
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
@ -93,7 +93,7 @@ namespace Avalonia.Native
if (_factory.MacOptions != null)
_factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0);
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform);
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform, new AvnDispatcher());
if (_factory.MacOptions != null)
{
@ -126,20 +126,38 @@ namespace Avalonia.Native
if (_options.UseGpu)
{
try
if (_options.UseMetal)
{
_platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGl);
try
{
var metal = new MetalPlatformGraphics(_factory);
metal.CreateContext().Dispose();
_platformGraphics = metal;
}
catch
{
// Ignored
}
}
catch (Exception)
if (_platformGraphics == null)
{
// ignored
try
{
_platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
}
catch (Exception)
{
// ignored
}
}
if(_platformGraphics != null)
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGraphics);
}
Compositor = new Compositor(_platformGl, true);
Compositor = new Compositor(_platformGraphics, true);
}
public ITrayIconImpl CreateTrayIcon()
@ -149,7 +167,7 @@ namespace Avalonia.Native
public IWindowImpl CreateWindow()
{
return new WindowImpl(_factory, _options, _platformGl);
return new WindowImpl(_factory, _options);
}
public IWindowImpl CreateEmbeddableWindow()

2
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -35,6 +35,8 @@ namespace Avalonia
/// </summary>
public bool UseGpu { get; set; } = true;
public bool UseMetal { get; set; }
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>

18
src/Avalonia.Native/AvnDispatcher.cs

@ -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);
}
}

49
src/Avalonia.Native/DeferredFramebuffer.cs

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@ -7,11 +8,13 @@ namespace Avalonia.Native
{
internal unsafe class DeferredFramebuffer : ILockedFramebuffer
{
private readonly Func<Action<IAvnWindowBase>, bool> _lockWindow;
public DeferredFramebuffer(Func<Action<IAvnWindowBase>, bool> lockWindow,
private readonly IAvnSoftwareRenderTarget _renderTarget;
private readonly Action<Action<IAvnWindowBase>> _lockWindow;
public DeferredFramebuffer(IAvnSoftwareRenderTarget renderTarget, Action<Action<IAvnWindowBase>> lockWindow,
int width, int height, Vector dpi)
{
_renderTarget = renderTarget;
_lockWindow = lockWindow;
Address = Marshal.AllocHGlobal(width * height * 4);
Size = new PixelSize(width, height);
@ -27,54 +30,28 @@ namespace Avalonia.Native
public Vector Dpi { get; set; }
public PixelFormat Format { get; set; }
class Disposer : NativeCallbackBase
{
private IntPtr _ptr;
public Disposer(IntPtr ptr)
{
_ptr = ptr;
}
protected override void Destroyed()
{
if(_ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(_ptr);
_ptr = IntPtr.Zero;
}
}
}
public void Dispose()
{
if (Address == IntPtr.Zero)
return;
if (!_lockWindow(win =>
_lockWindow(win =>
{
var fb = new AvnFramebuffer
{
Data = Address.ToPointer(),
Dpi = new AvnVector
{
X = Dpi.X,
Y = Dpi.Y
},
Dpi = new AvnVector { X = Dpi.X, Y = Dpi.Y },
Width = Size.Width,
Height = Size.Height,
PixelFormat = (AvnPixelFormat)Format.FormatEnum,
Stride = RowBytes
};
using (var d = new Disposer(Address))
{
win.ThreadSafeSetSwRenderedFrame(&fb, d);
}
}))
{
Marshal.FreeHGlobal(Address);
}
_renderTarget.SetFrame(&fb);
});
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}

119
src/Avalonia.Native/Metal.cs

@ -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;
}

12
src/Avalonia.Native/PopupImpl.cs

@ -8,21 +8,15 @@ namespace Avalonia.Native
{
class PopupImpl : WindowBaseImpl, IPopupImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature,
IWindowBaseImpl parent) : base(factory, opts, glFeature)
IWindowBaseImpl parent) : base(factory)
{
_opts = opts;
_glFeature = glFeature;
_parent = parent;
using (var e = new PopupEvents(this))
{
Init(factory.CreatePopup(e, _glFeature.SharedContext.Context), factory.CreateScreens());
Init(factory.CreatePopup(e), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
@ -68,7 +62,7 @@ namespace Avalonia.Native
base.Show(false, isDialog);
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, this);
public void SetWindowManagerAddShadowHint(bool enabled)
{

10
src/Avalonia.Native/WindowImpl.cs

@ -15,7 +15,7 @@ namespace Avalonia.Native
internal class WindowImpl : WindowBaseImpl, IWindowImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
private readonly AvaloniaNativeGlPlatformGraphics _graphics;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
@ -23,16 +23,14 @@ namespace Avalonia.Native
private readonly AvaloniaNativeTextInputMethod _inputMethod;
private bool _canResize = true;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(factory)
{
_opts = opts;
_glFeature = glFeature;
_doubleClickHelper = new DoubleClickHelper();
using (var e = new WindowEvents(this))
{
Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens());
Init(_native = factory.CreateWindow(e), factory.CreateScreens());
}
_nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
@ -215,7 +213,7 @@ namespace Avalonia.Native
public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() =>
_opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, _glFeature, this);
_opts.OverlayPopups ? null : new PopupImpl(_factory, this);
public Action GotInputWhenDisabled { get; set; }

67
src/Avalonia.Native/WindowImplBase.cs

@ -54,24 +54,20 @@ namespace Avalonia.Native
protected IInputRoot _inputRoot;
IAvnWindowBase _native;
private object _syncRoot = new object();
private bool _gpu = false;
private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private readonly ICursorFactory _cursorFactory;
private Size _savedLogicalSize;
private Size _lastRenderedLogicalSize;
private double _savedScaling;
private GlPlatformSurface _glSurface;
private NativeControlHostImpl _nativeControlHost;
private IStorageProvider _storageProvider;
private PlatformBehaviorInhibition _platformBehaviorInhibition;
private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature)
internal WindowBaseImpl(IAvaloniaNativeFactory factory)
{
_factory = factory;
_gpu = opts.UseGpu && glFeature != null;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();
@ -82,9 +78,8 @@ namespace Avalonia.Native
{
_native = window;
Surfaces = new object[] { new GlPlatformSurface(window), new MetalPlatformSurface(window), this };
Handle = new MacOSTopLevelWindowHandle(window);
if (_gpu)
_glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
@ -133,29 +128,55 @@ namespace Avalonia.Native
}
}
public IEnumerable<object> Surfaces => new[] {
(_gpu ? _glSurface : (object)null),
this
};
public IEnumerable<object> Surfaces { get; private set; }
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
public ILockedFramebuffer Lock()
IFramebufferRenderTarget IFramebufferPlatformSurface.CreateFramebufferRenderTarget()
{
var w = _savedLogicalSize.Width * _savedScaling;
var h = _savedLogicalSize.Height * _savedScaling;
var dpi = _savedScaling * 96;
return new DeferredFramebuffer(cb =>
if (!Dispatcher.UIThread.CheckAccess())
throw new RenderTargetNotReadyException();
return new FramebufferRenderTarget(this, _native.CreateSoftwareRenderTarget());
}
class FramebufferRenderTarget : IFramebufferRenderTarget
{
private readonly WindowBaseImpl _parent;
private IAvnSoftwareRenderTarget? _target;
public FramebufferRenderTarget(WindowBaseImpl parent, IAvnSoftwareRenderTarget target)
{
lock (_syncRoot)
_parent = parent;
_target = target;
}
public void Dispose()
{
lock (_parent._syncRoot)
{
if (_native == null)
return false;
cb(_native);
_lastRenderedLogicalSize = _savedLogicalSize;
return true;
_target?.Dispose();
_target = null;
}
}, (int)w, (int)h, new Vector(dpi, dpi));
}
public ILockedFramebuffer Lock()
{
var w = _parent._savedLogicalSize.Width * _parent._savedScaling;
var h = _parent._savedLogicalSize.Height * _parent._savedScaling;
var dpi = _parent._savedScaling * 96;
return new DeferredFramebuffer(_target, cb =>
{
lock (_parent._syncRoot)
{
if (_parent._native != null && _target != null)
{
cb(_parent._native);
_parent._lastRenderedLogicalSize = _parent._savedLogicalSize;
}
}
}, (int)w, (int)h, new Vector(dpi, dpi));
}
}
public Action LostFocus { get; set; }

53
src/Avalonia.Native/avn.idl

@ -485,10 +485,11 @@ enum AvnPlatformThemeVariant
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
interface IAvaloniaNativeFactory : IUnknown
{
HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* appCb);
HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* appCb,
IAvnDispatcher* dispatcher);
IAvnMacOptions* GetMacOptions();
HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv);
HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv);
HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv);
HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv);
HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv);
HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv);
HRESULT CreateScreens(IAvnScreens** ppv);
@ -496,6 +497,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateDndClipboard(IAvnClipboard** ppv);
HRESULT CreateCursorFactory(IAvnCursorFactory** ppv);
HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv);
HRESULT ObtainMetalDisplay(IAvnMetalDisplay** ppv);
HRESULT SetAppMenu(IAvnMenu* menu);
HRESULT SetServicesMenu(IAvnMenu* menu);
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
@ -533,10 +535,11 @@ interface IAvnWindowBase : IUnknown
HRESULT SetPosition(AvnPoint point);
HRESULT PointToClient(AvnPoint point, AvnPoint*ret);
HRESULT PointToScreen(AvnPoint point, AvnPoint*ret);
HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose);
HRESULT SetTopMost(bool value);
HRESULT SetCursor(IAvnCursor* cursor);
HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret);
HRESULT CreateGlRenderTarget(IAvnGlContext* context, IAvnGlSurfaceRenderTarget** ret);
HRESULT CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget** ret);
HRESULT CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget** ret);
HRESULT SetMainMenu(IAvnMenu* menu);
HRESULT ObtainNSWindowHandle([intptr]void** retOut);
HRESULT ObtainNSWindowHandleRetained([intptr]void** retOut);
@ -734,6 +737,13 @@ interface IAvnCursorFactory : IUnknown
HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut);
}
[uuid(931062d2-5bc8-4062-8588-83dd8deb99c2)]
interface IAvnSoftwareRenderTarget : IUnknown
{
HRESULT SetFrame(AvnFramebuffer* fb);
}
[uuid(60452465-8616-40af-bc00-042e69828ce7)]
interface IAvnGlDisplay : IUnknown
{
@ -766,6 +776,33 @@ interface IAvnGlSurfaceRenderingSession : IUnknown
HRESULT GetScaling(double* ret);
}
[uuid(da291767-4db3-4598-893d-09ecaa23893f)]
interface IAvnMetalDisplay : IUnknown
{
HRESULT CreateDevice(IAvnMetalDevice** ret);
}
[uuid(969fa914-b74a-4c9f-8725-5160dc63579e)]
interface IAvnMetalDevice : IUnknown
{
[intptr]void* GetDevice();
[intptr]void* GetQueue();
}
[uuid(f1306b71-eca0-426e-8700-105192693b1a)]
interface IAvnMetalRenderTarget : IUnknown
{
HRESULT BeginDrawing(IAvnMetalRenderingSession** ret);
}
[uuid(e625b406-f04c-484e-946a-4abd2c6015ad)]
interface IAvnMetalRenderingSession : IUnknown
{
HRESULT GetPixelSize(AvnPixelSize* ret);
double GetScaling();
[intptr]void* GetTexture();
}
[uuid(60992d19-38f0-4141-a0a9-76ac303801f3)]
interface IAvnTrayIcon : IUnknown
{
@ -828,6 +865,12 @@ interface IAvnGCHandleDeallocatorCallback : IUnknown
void FreeGCHandle([intptr]void* handle);
}
[uuid(96688589-5dc7-41ec-9ce3-d481942454ee)]
interface IAvnDispatcher : IUnknown
{
void Post(IAvnActionCallback* cb);
}
[uuid(91c7f677-f26b-4ff3-93cc-cf15aa966ffa)]
interface IAvnNativeControlHost : IUnknown
{

2
src/Avalonia.X11/X11CursorFactory.cs

@ -139,6 +139,8 @@ namespace Avalonia.X11
_pixelSize, _pixelSize.Width * 4,
new Vector(96, 96), PixelFormat.Bgra8888, null);
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

2
src/Avalonia.X11/X11FramebufferSurface.cs

@ -27,5 +27,7 @@ namespace Avalonia.X11
XUnlockDisplay(_display);
return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling());
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

2
src/Avalonia.X11/X11IconLoader.cs

@ -86,5 +86,7 @@ namespace Avalonia.X11
new Vector(96, 96), PixelFormat.Bgra8888,
() => h.Free());
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

2
src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs

@ -84,5 +84,7 @@ namespace Avalonia.Browser.Skia
Marshal.FreeHGlobal(Address);
}
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

2
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -218,6 +218,8 @@ namespace Avalonia.Headless
});
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
public WriteableBitmap? GetLastRenderedFrame()
{
lock (_sync)

2
src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@ -152,6 +152,8 @@ namespace Avalonia.LinuxFramebuffer
new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress))
.Lock(new Vector(96, 96) * Scaling);
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
private void ReleaseUnmanagedResources()

11
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -12,12 +12,12 @@ namespace Avalonia.Skia
/// </summary>
internal class FramebufferRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _platformSurface;
private SKImageInfo _currentImageInfo;
private IntPtr _currentFramebufferAddress;
private SKSurface? _framebufferSurface;
private PixelFormatConversionShim? _conversionShim;
private IDisposable? _preFramebufferCopyHandler;
private IFramebufferRenderTarget? _renderTarget;
/// <summary>
/// Create new framebuffer render target using a target surface.
@ -25,19 +25,24 @@ namespace Avalonia.Skia
/// <param name="platformSurface">Target surface.</param>
public FramebufferRenderTarget(IFramebufferPlatformSurface platformSurface)
{
_platformSurface = platformSurface;
_renderTarget = platformSurface.CreateFramebufferRenderTarget();
}
/// <inheritdoc />
public void Dispose()
{
_renderTarget?.Dispose();
_renderTarget = null;
FreeSurface();
}
/// <inheritdoc />
public IDrawingContextImpl CreateDrawingContext()
{
var framebuffer = _platformSurface.Lock();
if (_renderTarget == null)
throw new ObjectDisposedException(nameof(FramebufferRenderTarget));
var framebuffer = _renderTarget.Lock();
var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height,
framebuffer.Format.ToSkColorType(),
framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul);

80
src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs

@ -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 });
}
}

119
src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs

@ -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;
}

4
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -7,6 +7,8 @@ using Avalonia.Platform;
using Avalonia.Media.Imaging;
using SkiaSharp;
using Avalonia.Media.TextFormatting;
using Avalonia.Metal;
using Avalonia.Skia.Metal;
namespace Avalonia.Skia
{
@ -32,6 +34,8 @@ namespace Avalonia.Skia
return new SkiaContext(skiaGpu);
if (graphicsContext is IGlContext gl)
return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes));
if (graphicsContext is IMetalDevice metal)
return new SkiaContext(new SkiaMetalGpu(metal, _maxResourceBytes));
throw new ArgumentException("Graphics context of type is not supported");
}

2
src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs

@ -28,4 +28,6 @@ internal class RenderTargetBitmapImpl : WriteableBitmapImpl,
_renderTarget.Dispose();
base.Dispose();
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}

12
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -11,20 +11,24 @@ namespace Avalonia.Direct2D1
{
class FramebufferShimRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _surface;
private IFramebufferRenderTarget? _target;
public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface)
{
_surface = surface;
_target = surface.CreateFramebufferRenderTarget();
}
public void Dispose()
{
{
_target?.Dispose();
_target = null;
}
public IDrawingContextImpl CreateDrawingContext()
{
var locked = _surface.Lock();
if (_target == null)
throw new ObjectDisposedException(nameof(FramebufferShimRenderTarget));
var locked = _target.Lock();
if (locked.Format == PixelFormat.Rgb565)
{
locked.Dispose();

2
src/Windows/Avalonia.Win32/FramebufferManager.cs

@ -60,6 +60,8 @@ namespace Avalonia.Win32
}
}
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
public void Dispose()
{

6
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -53,12 +53,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
//no-op
}
public ILockedFramebuffer Lock()
{
return this;
}
public void Deallocate() => Marshal.FreeHGlobal(Address);
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(() => this);
}

5
tests/Avalonia.RenderTests/TestBase.cs

@ -135,7 +135,10 @@ namespace Avalonia.Direct2D1.RenderTests
_bitmap = bitmap;
}
public ILockedFramebuffer Lock() => _bitmap.Lock();
public IFramebufferRenderTarget CreateFramebufferRenderTarget()
{
return new FuncFramebufferRenderTarget(() => _bitmap.Lock());
}
}
protected void CompareImages([CallerMemberName] string testName = "",

2
tests/Avalonia.UnitTests/CompositorTestServices.cs

@ -161,6 +161,8 @@ public class CompositorTestServices : IDisposable
return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96),
PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr));
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
public Compositor Compositor => _compositor;

Loading…
Cancel
Save