Browse Source

Merge remote-tracking branch 'origin/master' into cleanup-runtime-platform-interface

pull/11930/head
Dan Walmsley 3 years ago
parent
commit
b0a51cc791
  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
      packages/Avalonia/Avalonia.csproj
  22. 2
      samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
  23. 3
      samples/ControlCatalog.NetCore/Program.cs
  24. 3
      samples/ControlCatalog/App.xaml
  25. 4
      samples/ControlCatalog/App.xaml.cs
  26. 74
      src/Android/Avalonia.Android/AndroidPlatform.cs
  27. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  28. 4
      src/Avalonia.Base/CombinedGeometry.cs
  29. 2
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  30. 2
      src/Avalonia.Base/Media/EllipseGeometry.cs
  31. 7
      src/Avalonia.Base/Media/Geometry.cs
  32. 4
      src/Avalonia.Base/Media/GeometryGroup.cs
  33. 2
      src/Avalonia.Base/Media/LineGeometry.cs
  34. 4
      src/Avalonia.Base/Media/PathGeometry.cs
  35. 4
      src/Avalonia.Base/Media/PlatformGeometry.cs
  36. 2
      src/Avalonia.Base/Media/PolylineGeometry.cs
  37. 2
      src/Avalonia.Base/Media/RectangleGeometry.cs
  38. 4
      src/Avalonia.Base/Media/StreamGeometry.cs
  39. 13
      src/Avalonia.Base/Point.cs
  40. 25
      src/Avalonia.Base/RenderTargetNotReadyException.cs
  41. 13
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  42. 14
      src/Avalonia.Base/Vector.cs
  43. 2
      src/Avalonia.Controls/AppBuilder.cs
  44. 2
      src/Avalonia.Controls/ListBox.cs
  45. 29
      src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
  46. 6
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  47. 29
      src/Avalonia.Controls/TextBox.cs
  48. 5
      src/Avalonia.Controls/Window.cs
  49. 15
      src/Avalonia.Metal/Avalonia.Metal.csproj
  50. 34
      src/Avalonia.Metal/IMetalDevice.cs
  51. 5
      src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
  52. 49
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  53. 35
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  54. 18
      src/Avalonia.Native/AvnDispatcher.cs
  55. 49
      src/Avalonia.Native/DeferredFramebuffer.cs
  56. 119
      src/Avalonia.Native/Metal.cs
  57. 12
      src/Avalonia.Native/PopupImpl.cs
  58. 10
      src/Avalonia.Native/WindowImpl.cs
  59. 67
      src/Avalonia.Native/WindowImplBase.cs
  60. 53
      src/Avalonia.Native/avn.idl
  61. 7
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  62. 18
      src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs
  63. 12
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  64. 2
      src/Avalonia.X11/X11Clipboard.cs
  65. 2
      src/Avalonia.X11/X11CursorFactory.cs
  66. 2
      src/Avalonia.X11/X11FramebufferSurface.cs
  67. 2
      src/Avalonia.X11/X11IconLoader.cs
  68. 83
      src/Avalonia.X11/X11Platform.cs
  69. 2
      src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs
  70. 2
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  71. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  72. 11
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  73. 80
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs
  74. 119
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
  75. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  76. 2
      src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
  77. 12
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  78. 4
      src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
  79. 2
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  80. 18
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  81. 59
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  82. 101
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  83. 95
      src/Windows/Avalonia.Win32/Win32Platform.cs
  84. 140
      src/Windows/Avalonia.Win32/Win32PlatformOptions.cs
  85. 2
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
  86. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
  87. 31
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs
  88. 2
      tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs
  89. 45
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  90. 6
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  91. 5
      tests/Avalonia.RenderTests/TestBase.cs
  92. 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
packages/Avalonia/Avalonia.csproj

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.22" />
<PackageReference Include="Avalonia.BuildServices" Version="0.0.27" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets>

2
samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs

@ -39,7 +39,7 @@ internal unsafe class WinApi
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)]
public static extern IntPtr LoadLibrary(string lib);

3
samples/ControlCatalog.NetCore/Program.cs

@ -110,8 +110,7 @@ namespace ControlCatalog.NetCore
{
builder.With(new Win32PlatformOptions()
{
UseLowLatencyDxgiSwapChain = true,
UseWindowsUIComposition = false
CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain }
});
return builder.StartWithClassicDesktopLifetime(args);
}

3
samples/ControlCatalog/App.xaml

@ -28,6 +28,9 @@
</ResourceDictionary.ThemeDictionaries>
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
<FluentTheme x:Key="FluentTheme">
</FluentTheme>
<SimpleTheme x:Key="SimpleTheme" />
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />

4
samples/ControlCatalog/App.xaml.cs

@ -30,8 +30,8 @@ namespace ControlCatalog
AvaloniaXamlLoader.Load(this);
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
_fluentTheme = (FluentTheme)Resources["FluentTheme"]!;
_simpleTheme = (SimpleTheme)Resources["SimpleTheme"]!;
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;

74
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Android;
using Avalonia.Android.Platform;
@ -23,6 +25,39 @@ namespace Avalonia
.UseSkia();
}
}
/// <summary>
/// Represents the rendering mode for platform graphics.
/// </summary>
public enum AndroidRenderingMode
{
/// <summary>
/// Avalonia is rendered into a framebuffer.
/// </summary>
Software = 1,
/// <summary>
/// Enables android EGL rendering.
/// </summary>
Egl = 2
}
public sealed class AndroidPlatformOptions
{
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="AndroidRenderingMode.Egl"/>, <see cref="AndroidRenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="AndroidRenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<AndroidRenderingMode> RenderingMode { get; set; } = new[]
{
AndroidRenderingMode.Egl, AndroidRenderingMode.Software
};
}
}
namespace Avalonia.Android
@ -48,18 +83,39 @@ namespace Avalonia.Android
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
if (Options.UseGpu)
var graphics = InitializeGraphics(Options);
if (graphics is not null)
{
EglPlatformGraphics.TryInitialize();
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(graphics);
}
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
Compositor = new Compositor(graphics);
}
}
private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)
{
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} must not be empty or null");
}
public sealed class AndroidPlatformOptions
{
public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true;
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == AndroidRenderingMode.Software)
{
return null;
}
if (renderingMode == AndroidRenderingMode.Egl)
{
if (EglPlatformGraphics.TryCreate() is { } egl)
{
return egl;
}
}
}
throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
}
}

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

4
src/Avalonia.Base/CombinedGeometry.cs

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
public enum GeometryCombineMode
@ -147,7 +145,7 @@ namespace Avalonia.Media
return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var g1 = Geometry1;
var g2 = Geometry2;

2
src/Avalonia.Base/Input/TextInputEventArgs.cs

@ -4,6 +4,6 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
public string? Text { get; init; }
public string? Text { get; set; }
}
}

2
src/Avalonia.Base/Media/EllipseGeometry.cs

@ -135,7 +135,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

7
src/Avalonia.Base/Media/Geometry.cs

@ -28,6 +28,11 @@ namespace Avalonia.Media
TransformProperty.Changed.AddClassHandler<Geometry>((x,e) => x.TransformChanged(e));
}
internal Geometry()
{
}
/// <summary>
/// Raised when the geometry changes.
/// </summary>
@ -134,7 +139,7 @@ namespace Avalonia.Media
/// Creates the platform implementation of the geometry, without the transform applied.
/// </summary>
/// <returns></returns>
protected abstract IGeometryImpl? CreateDefiningGeometry();
private protected abstract IGeometryImpl? CreateDefiningGeometry();
/// <summary>
/// Invalidates the platform implementation of the geometry.

4
src/Avalonia.Base/Media/GeometryGroup.cs

@ -1,8 +1,6 @@
using Avalonia.Metadata;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Media
{
/// <summary>
@ -72,7 +70,7 @@ namespace Avalonia.Media
newChildren.Parent = this;
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
if (_children.Count > 0)
{

2
src/Avalonia.Base/Media/LineGeometry.cs

@ -68,7 +68,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

4
src/Avalonia.Base/Media/PathGeometry.cs

@ -43,7 +43,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="pathData">The s.</param>
/// <returns></returns>
public static new PathGeometry Parse(string pathData)
public new static PathGeometry Parse(string pathData)
{
var pathGeometry = new PathGeometry();
@ -81,7 +81,7 @@ namespace Avalonia.Media
set { SetValue(FillRuleProperty, value); }
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var figures = Figures;

4
src/Avalonia.Base/Media/PlatformGeometry.cs

@ -2,7 +2,7 @@
namespace Avalonia.Media
{
internal class PlatformGeometry : Geometry
internal sealed class PlatformGeometry : Geometry
{
private readonly IGeometryImpl _geometryImpl;
@ -16,7 +16,7 @@ namespace Avalonia.Media
return new PlatformGeometry(_geometryImpl);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected override IGeometryImpl? CreateDefiningGeometry()
{
return _geometryImpl;
}

2
src/Avalonia.Base/Media/PolylineGeometry.cs

@ -74,7 +74,7 @@ namespace Avalonia.Media
return new PolylineGeometry(Points, IsFilled);
}
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var geometry = factory.CreateStreamGeometry();

2
src/Avalonia.Base/Media/RectangleGeometry.cs

@ -47,7 +47,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
public override Geometry Clone() => new RectangleGeometry(Rect);
protected override IGeometryImpl? CreateDefiningGeometry()
private protected sealed override IGeometryImpl? CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();

4
src/Avalonia.Base/Media/StreamGeometry.cs

@ -31,7 +31,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="s">The string.</param>
/// <returns>A <see cref="StreamGeometry"/>.</returns>
public static new StreamGeometry Parse(string s)
public new static StreamGeometry Parse(string s)
{
var streamGeometry = new StreamGeometry();
@ -62,7 +62,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override IGeometryImpl? CreateDefiningGeometry()
private protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_impl == null)
{

13
src/Avalonia.Base/Point.cs

@ -164,6 +164,19 @@ namespace Avalonia
/// <returns>The resulting point.</returns>
public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point);
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance.</returns>
public static double Distance(Point value1, Point value2)
{
double distanceSquared = ((value2.X - value1.X) * (value2.X - value1.X)) +
((value2.Y - value1.Y) * (value2.Y - value1.Y));
return Math.Sqrt(distanceSquared);
}
/// <summary>
/// Parses a <see cref="Point"/> string.
/// </summary>

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;

14
src/Avalonia.Base/Vector.cs

@ -359,7 +359,7 @@ namespace Avalonia
internal Vector(Vector2 v) : this(v.X, v.Y)
{
}
/// <summary>
@ -379,21 +379,27 @@ namespace Avalonia
/// </summary>
public static Vector Max(Vector left, Vector right) =>
new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y));
/// <summary>
/// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors
/// </summary>
public static Vector Min(Vector left, Vector right) =>
new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y));
/// <summary>
/// Computes the Euclidean distance between the two given points.
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance.</returns>
public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2));
/// <summary>
/// Returns the Euclidean distance squared between two specified points
/// </summary>
/// <param name="value1">The first point.</param>
/// <param name="value2">The second point.</param>
/// <returns>The Euclidean distance squared.</returns>
public static double DistanceSquared(Vector value1, Vector value2)
{
var difference = value1 - value2;

2
src/Avalonia.Controls/AppBuilder.cs

@ -139,7 +139,7 @@ namespace Avalonia
}
throw new InvalidOperationException(
$"Unable to create AppBuilder from type {entryPointType.Name}." +
$"Unable to create AppBuilder from type \"{entryPointType.FullName}\". " +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}

2
src/Avalonia.Controls/ListBox.cs

@ -148,7 +148,7 @@ namespace Avalonia.Controls
}
else if (e.Key == Key.Space || e.Key == Key.Enter)
{
e.Handled |= UpdateSelectionFromEventSource(
UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasFlag(KeyModifiers.Shift),

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

29
src/Avalonia.Controls/TextBox.cs

@ -812,9 +812,14 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
if (IsFocused)
if (_presenter != null)
{
_presenter?.ShowCaret();
if (IsFocused)
{
_presenter.ShowCaret();
}
_presenter.PropertyChanged += PresenterPropertyChanged;
}
}
@ -822,9 +827,29 @@ namespace Avalonia.Controls
{
base.OnDetachedFromVisualTree(e);
if (_presenter != null)
{
_presenter.HideCaret();
_presenter.PropertyChanged -= PresenterPropertyChanged;
}
_imClient.SetPresenter(null, null);
}
private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if(e.Property == TextPresenter.PreeditTextProperty)
{
if(string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string))
{
PseudoClasses.Set(":empty", false);
DeleteSelection();
}
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

5
src/Avalonia.Controls/Window.cs

@ -715,8 +715,8 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner);
PlatformImpl?.Show(ShowActivated, false);
StartRendering();
PlatformImpl?.Show(ShowActivated, false);
OnOpened(EventArgs.Empty);
}
}
@ -791,9 +791,8 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner);
PlatformImpl?.Show(ShowActivated, true);
StartRendering();
PlatformImpl?.Show(ShowActivated, true);
Observable.FromEventPattern(
x => Closed += x,

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

49
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)
{
@ -123,23 +123,46 @@ namespace Avalonia.Native
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(hotkeys);
if (_options.UseGpu)
foreach (var mode in _options.RenderingMode)
{
try
if (mode == AvaloniaNativeRenderingMode.OpenGl)
{
_platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGl);
try
{
_platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
break;
}
catch (Exception)
{
// ignored
}
}
catch (Exception)
#pragma warning disable CS0618
else if (mode == AvaloniaNativeRenderingMode.Metal)
#pragma warning restore CS0618
{
// ignored
try
{
var metal = new MetalPlatformGraphics(_factory);
metal.CreateContext().Dispose();
_platformGraphics = metal;
}
catch
{
// Ignored
}
}
else if (mode == AvaloniaNativeRenderingMode.Software)
break;
}
if (_platformGraphics != null)
AvaloniaLocator.CurrentMutable
.Bind<IPlatformGraphics>().ToConstant(_platformGraphics);
Compositor = new Compositor(_platformGl, true);
Compositor = new Compositor(_platformGraphics, true);
}
public ITrayIconImpl CreateTrayIcon()
@ -149,7 +172,7 @@ namespace Avalonia.Native
public IWindowImpl CreateWindow()
{
return new WindowImpl(_factory, _options, _platformGl);
return new WindowImpl(_factory, _options);
}
public IWindowImpl CreateEmbeddableWindow()

35
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Native;
@ -27,16 +28,44 @@ namespace Avalonia
}
}
public enum AvaloniaNativeRenderingMode
{
/// <summary>
/// Avalonia would try to use native OpenGL with GPU rendering.
/// </summary>
OpenGl = 1,
/// <summary>
/// Avalonia is rendered into a framebuffer.
/// </summary>
Software = 2,
/// <summary>
/// Avalonia would try to use Metal with GPU rendering.
/// </summary>
[Obsolete("Experimental, unstable, not for production usage")]
Metal = 3
}
/// <summary>
/// OSX backend options.
/// </summary>
public class AvaloniaNativePlatformOptions
{
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="AvaloniaNativeRenderingMode.OpenGl"/>, <see cref="AvaloniaNativeRenderingMode.Software"/>.
/// </summary>
public bool UseGpu { get; set; } = true;
/// <remarks>
/// If application should work on as wide range of devices as possible,
/// at least add <see cref="AvaloniaNativeRenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<AvaloniaNativeRenderingMode> RenderingMode { get; set; } = new[]
{
AvaloniaNativeRenderingMode.OpenGl,
AvaloniaNativeRenderingMode.Software
};
/// <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
{

7
src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs

@ -16,13 +16,6 @@ namespace Avalonia.OpenGL.Egl
_display = display;
}
public static void TryInitialize()
{
var feature = TryCreate();
if (feature != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
}
public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
{
Egl = new EglInterface(),

18
src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs

@ -10,12 +10,18 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant
public ColorPaletteResourcesCollection() : base(2)
{
this.ForEachItem(
(_, x) =>
(key, x) =>
{
if (Owner is not null)
{
x.PropertyChanged += Palette_PropertyChanged;
}
if (key != ThemeVariant.Dark && key != ThemeVariant.Light)
{
throw new InvalidOperationException(
$"{nameof(FluentTheme)}.{nameof(FluentTheme.Palettes)} only supports Light and Dark variants.");
}
},
(_, x) =>
{
@ -30,9 +36,13 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant
public bool HasResources => Count > 0;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
theme ??= ThemeVariant.Default;
if (base.TryGetValue(theme, out var paletteResources)
&& paletteResources.TryGetResource(key, theme, out value))
if (theme == null || theme == ThemeVariant.Default)
{
theme = ThemeVariant.Light;
}
if (base.TryGetValue(theme, out var themePaletteResources)
&& themePaletteResources.TryGetResource(key, theme, out value))
{
return true;
}

12
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -15,18 +15,6 @@ namespace Avalonia.X11.Glx
IPlatformGraphicsContext IPlatformGraphics.CreateContext() => Display.CreateContext();
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
public static bool TryInitialize(X11Info x11, IList<GlVersion> glProfiles)
{
var feature = TryCreate(x11, glProfiles);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
return true;
}
return false;
}
public static GlxPlatformGraphics TryCreate(X11Info x11, IList<GlVersion> glProfiles)
{

2
src/Avalonia.X11/X11Clipboard.cs

@ -224,7 +224,7 @@ namespace Avalonia.X11
private Task<object> SendDataRequest(IntPtr format)
{
if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
if (_requestedDataTcs == null || _requestedDataTcs.Task.IsCompleted)
_requestedDataTcs = new TaskCompletionSource<object>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedDataTcs.Task;

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

83
src/Avalonia.X11/X11Platform.cs

@ -93,17 +93,13 @@ namespace Avalonia.X11
XI2 = xi2;
}
if (options.UseGpu)
var graphics = InitializeGraphics(options, Info);
if (graphics is not null)
{
if (options.UseEGL)
EglPlatformGraphics.TryInitialize();
else
GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles);
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(graphics);
}
var gl = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
Compositor = new Compositor(gl);
Compositor = new Compositor(graphics);
}
public IntPtr DeferredDisplay { get; set; }
@ -185,25 +181,84 @@ namespace Avalonia.X11
return false;
}
private static IPlatformGraphics InitializeGraphics(X11PlatformOptions opts, X11Info info)
{
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} must not be empty or null");
}
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == X11RenderingMode.Software)
{
return null;
}
if (renderingMode == X11RenderingMode.Glx)
{
if (GlxPlatformGraphics.TryCreate(info, opts.GlProfiles) is { } glx)
{
return glx;
}
}
if (renderingMode == X11RenderingMode.Egl)
{
if (EglPlatformGraphics.TryCreate() is { } egl)
{
return egl;
}
}
}
throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
}
}
namespace Avalonia
{
/// <summary>
/// Platform-specific options which apply to Linux.
/// Represents the rendering mode for platform graphics.
/// </summary>
public class X11PlatformOptions
public enum X11RenderingMode
{
/// <summary>
/// Enables native Linux EGL when set to true. The default value is false.
/// Avalonia is rendered into a framebuffer.
/// </summary>
public bool UseEGL { get; set; }
Software = 1,
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// Enables Glx rendering.
/// </summary>
public bool UseGpu { get; set; } = true;
Glx = 2,
/// <summary>
/// Enables native Linux EGL rendering.
/// </summary>
Egl = 3
}
/// <summary>
/// Platform-specific options which apply to Linux.
/// </summary>
public class X11PlatformOptions
{
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="X11RenderingMode.Glx"/>, <see cref="X11RenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="X11RenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<X11RenderingMode> RenderingMode { get; set; } = new[]
{
X11RenderingMode.Glx, X11RenderingMode.Software
};
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.

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();

4
src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs

@ -28,16 +28,18 @@ namespace Avalonia.Win32.DirectX
_syncLock = syncLock;
}
public static void TryCreateAndRegister()
public static bool TryCreateAndRegister()
{
try
{
TryCreateAndRegisterCore();
return true;
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(null, "Unable to establish Dxgi: {0}", ex);
return false;
}
}

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

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

18
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -324,6 +324,11 @@ namespace Avalonia.Win32.Input
if (IsActive)
{
Client.SetPreeditText(null);
if (Client.SupportsSurroundingText && Client.SurroundingText.AnchorOffset != Client.SurroundingText.CursorOffset)
{
KeyPress(Key.Delete);
}
}
IsComposing = true;
@ -393,6 +398,19 @@ namespace Avalonia.Win32.Input
return (int)(ptr.ToInt64() & 0xffffffff);
}
private void KeyPress(Key key)
{
if (_parent?.Input != null)
{
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyDown, key, RawInputModifiers.None));
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyUp, key, RawInputModifiers.None));
}
}
~Imm32InputMethod()
{
_caretManager.TryDestroy();

59
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1161,7 +1161,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW", ExactSpelling = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
uint lpClassName,
@ -1215,16 +1215,16 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern int GetMessageTime();
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetModuleHandleW", ExactSpelling = true)]
public static extern IntPtr GetModuleHandle(string? lpModuleName);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)]
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLong")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongW", ExactSpelling = true)]
public static extern uint GetWindowLong32b(IntPtr hWnd, int nIndex);
public static uint GetWindowLong(IntPtr hWnd, int nIndex)
@ -1239,10 +1239,10 @@ namespace Avalonia.Win32.Interop
}
}
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongW", ExactSpelling = true)]
private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)]
private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value);
public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
@ -1310,7 +1310,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
[DllImport("user32.dll")]
[DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)]
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
[DllImport("user32.dll")]
@ -1319,7 +1319,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("user32.dll")]
[DllImport("user32.dll", EntryPoint = "PeekMessageW", ExactSpelling = true)]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32")]
@ -1334,7 +1334,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterWindowMessageW", ExactSpelling = true)]
public static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll")]
@ -1415,7 +1415,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "UnregisterClassW", ExactSpelling = true)]
public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")]
@ -1439,10 +1439,10 @@ namespace Avalonia.Win32.Interop
[DllImport("shell32", CharSet = CharSet.Auto)]
public static extern int Shell_NotifyIcon(NIM dwMessage, NOTIFYICONDATA lpData);
[DllImport("user32.dll", EntryPoint = "SetClassLongPtr")]
[DllImport("user32.dll", EntryPoint = "SetClassLongPtrW", ExactSpelling = true)]
private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetClassLong")]
[DllImport("user32.dll", EntryPoint = "SetClassLongW", ExactSpelling = true)]
private static extern IntPtr SetClassLong32(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
public static IntPtr SetClassLong(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong)
@ -1463,10 +1463,10 @@ namespace Avalonia.Win32.Interop
return new IntPtr(GetClassLongPtr32(hWnd, nIndex));
}
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
[DllImport("user32.dll", EntryPoint = "GetClassLongW", ExactSpelling = true)]
public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
[DllImport("user32.dll", EntryPoint = "GetClassLongPtrW", ExactSpelling = true)]
public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetCursor")]
@ -1527,10 +1527,10 @@ namespace Avalonia.Win32.Interop
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)]
public static extern IntPtr LoadLibrary(string fileName);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryExW", ExactSpelling = true)]
public static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, int flags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
@ -1645,7 +1645,7 @@ namespace Avalonia.Win32.Interop
[DllImport("opengl32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr wglGetProcAddress(string name);
[DllImport("kernel32.dll", SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateFileMappingW", ExactSpelling = true)]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
@ -1668,16 +1668,16 @@ namespace Avalonia.Win32.Interop
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern void ReleaseStgMedium(ref STGMEDIUM medium);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetClipboardFormatNameW", ExactSpelling = true)]
public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "RegisterClipboardFormatW", ExactSpelling = true)]
public static extern int RegisterClipboardFormat(string format);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GlobalSize(IntPtr hGlobal);
[DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
[DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, EntryPoint = "DragQueryFileW", ExactSpelling = true)]
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder? lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
@ -1827,8 +1827,21 @@ namespace Avalonia.Win32.Interop
return result;
}
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
internal static int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data)
{
var user32 = LoadLibrary("user32.dll");
var pfnSetWindowCompositionAttribute = (delegate* unmanaged[Stdcall]<IntPtr, WindowCompositionAttributeData*, int>)GetProcAddress(user32, nameof(SetWindowCompositionAttribute));
if (pfnSetWindowCompositionAttribute == null)
{
// This preserves the same behavior as using the DllImport attribute.
throw new EntryPointNotFoundException("The unsupported SetWindowCompositionAttribute-function has been removed from the operating system.");
}
fixed (WindowCompositionAttributeData* pData = &data)
{
return pfnSetWindowCompositionAttribute(hwnd, pData);
}
}
[Flags]
public enum GCS : uint
@ -1893,7 +1906,7 @@ namespace Avalonia.Win32.Interop
[DllImport("imm32.dll")]
public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf);
[DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
[DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "ImmGetCompositionStringW", ExactSpelling = true)]
public static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, [Out, Optional] IntPtr lpBuf, uint dwBufLen);
public static string? ImmGetCompositionString(IntPtr hIMC, GCS dwIndex)

101
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -1,3 +1,6 @@
using System;
using System.Diagnostics.Tracing;
using System.Linq;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Win32.DirectX;
@ -5,56 +8,88 @@ using Avalonia.Win32.OpenGl;
using Avalonia.Win32.OpenGl.Angle;
using Avalonia.Win32.WinRT.Composition;
namespace Avalonia.Win32
namespace Avalonia.Win32;
static class Win32GlManager
{
static class Win32GlManager
public static IPlatformGraphics? Initialize()
{
public static IPlatformGraphics? Initialize()
{
var gl = InitializeCore();
if (gl is not null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl);
}
var gl = InitializeCore();
return gl;
if (gl is not null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl);
}
return gl;
}
private static IPlatformGraphics? InitializeCore()
private static IPlatformGraphics? InitializeCore()
{
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} must not be empty or null");
}
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
if (opts.UseWgl)
foreach (var renderingMode in opts.RenderingMode)
{
if (renderingMode == Win32RenderingMode.Software)
{
var wgl = WglPlatformOpenGlInterface.TryCreate();
return wgl;
return null;
}
if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7)
if (renderingMode == Win32RenderingMode.AngleEgl)
{
var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService<AngleOptions>() ??
new());
var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService<AngleOptions>() ?? new());
if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphicsOpenGlContextFactory>()
.ToConstant(egl);
if (opts.UseWindowsUIComposition)
{
WinUiCompositorConnection.TryCreateAndRegister();
}
else if (opts.UseLowLatencyDxgiSwapChain)
{
DxgiConnection.TryCreateAndRegister();
}
TryRegisterComposition(opts);
return egl;
}
}
if (renderingMode == Win32RenderingMode.Wgl)
{
if (WglPlatformOpenGlInterface.TryCreate() is { } wgl)
{
return wgl;
}
}
}
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied.");
}
private static void TryRegisterComposition(Win32PlatformOptions opts)
{
if (opts.CompositionMode is null || !opts.CompositionMode.Any())
{
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} must not be empty or null");
}
foreach (var compositionMode in opts.CompositionMode)
{
if (compositionMode == Win32CompositionMode.RedirectionSurface)
{
return;
}
return egl;
if (compositionMode == Win32CompositionMode.WinUIComposition
&& WinUiCompositorConnection.IsSupported()
&& WinUiCompositorConnection.TryCreateAndRegister())
{
return;
}
return null;
if (compositionMode == Win32CompositionMode.LowLatencyDxgiSwapChain
&& DxgiConnection.TryCreateAndRegister())
{
return;
}
}
throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} has a value of \"{string.Join(", ", opts.CompositionMode)}\", but no options were applied.");
}
}

95
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Avalonia.Reactive;
using System.Runtime.InteropServices;
using System.Threading;
@ -10,7 +10,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
@ -33,78 +32,6 @@ namespace Avalonia
"Win32");
}
}
/// <summary>
/// Platform-specific options which apply to Windows.
/// </summary>
public class Win32PlatformOptions
{
/// <summary>
/// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false.
/// </summary>
/// <remarks>
/// GPU rendering will not be enabled if this is set to false.
/// </remarks>
public bool? AllowEglInitialization { get; set; }
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// Avalonia would try to use native Widows OpenGL when set to true. The default value is false.
/// </summary>
public bool UseWgl { get; set; }
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
new GlVersion(GlProfileType.OpenGL, 3, 2),
};
/// <summary>
/// Render Avalonia to a Texture inside the Windows.UI.Composition tree.
/// This setting is true by default.
/// </summary>
/// <remarks>
/// Supported on Windows 10 build 16299 and above. Ignored on other versions.
/// This is recommended if you need to use AcrylicBlur or acrylic in your applications.
/// </remarks>
public bool UseWindowsUIComposition { get; set; } = true;
/// <summary>
/// When <see cref="UseWindowsUIComposition"/> enabled, create rounded corner blur brushes
/// If set to null the brushes will be created using default settings (sharp corners)
/// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app
/// </summary>
public float? CompositionBackdropCornerRadius { get; set; }
/// <summary>
/// When <see cref="UseLowLatencyDxgiSwapChain"/> is active, renders Avalonia through a low-latency Dxgi Swapchain.
/// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion.
/// This is only recommended if low input latency is desirable, and there is no need for the transparency
/// and stylings / blurrings offered by <see cref="UseWindowsUIComposition"/><br/>
/// This is mutually exclusive with
/// <see cref="UseWindowsUIComposition"/> which if active will override this setting.
/// This setting is false by default.
/// </summary>
public bool UseLowLatencyDxgiSwapChain { get; set; }
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// Only applicable if both <see cref="UseWindowsUIComposition"/> and <see cref="UseLowLatencyDxgiSwapChain"/>
/// are false.
/// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get; set; }
/// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu
/// </summary>
public IPlatformGraphics? CustomPlatformGraphics { get; set; }
}
}
namespace Avalonia.Win32
@ -175,9 +102,23 @@ namespace Avalonia.Win32
.Bind<NonPumpingLockHelper.IHelperImpl>().ToConstant(NonPumpingWaitHelperImpl.Instance)
.Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(s_instance);
var platformGraphics = options.CustomPlatformGraphics
?? Win32GlManager.Initialize();
IPlatformGraphics? platformGraphics;
if (options.CustomPlatformGraphics is not null)
{
if (options.CompositionMode?.Contains(Win32CompositionMode.RedirectionSurface) == false)
{
throw new InvalidOperationException(
$"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CustomPlatformGraphics)} is only " +
$"compatible with {nameof(Win32CompositionMode)}.{nameof(Win32CompositionMode.RedirectionSurface)}");
}
platformGraphics = options.CustomPlatformGraphics;
}
else
{
platformGraphics = Win32GlManager.Initialize();
}
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();

140
src/Windows/Avalonia.Win32/Win32PlatformOptions.cs

@ -0,0 +1,140 @@
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.Platform;
namespace Avalonia;
/// <summary>
/// Represents the rendering mode for platform graphics.
/// </summary>
public enum Win32RenderingMode
{
/// <summary>
/// Avalonia is rendered into a framebuffer.
/// </summary>
Software = 1,
/// <summary>
/// Enables ANGLE EGL for Windows with GPU rendering.
/// </summary>
AngleEgl = 2,
/// <summary>
/// Avalonia would try to use native Widows OpenGL with GPU rendering.
/// </summary>
Wgl = 3
}
/// <summary>
/// Represents the Win32 window composition mode.
/// </summary>
public enum Win32CompositionMode
{
/// <summary>
/// Render Avalonia to a texture inside the Windows.UI.Composition tree.
/// </summary>
/// <remarks>
/// Supported on Windows 10 build 17134 and above. Ignored on other versions.
/// This is recommended option, as it allows window acrylic effects and high refresh rate rendering.<br/>
/// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
/// </remarks>
WinUIComposition = 1,
// /// <summary>
// /// Render Avalonia to a texture inside the DirectComposition tree.
// /// </summary>
// /// <remarks>
// /// Supported on Windows 8 and above. Ignored on other versions.<br/>
// /// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
// /// </remarks>
// DirectComposition = 2,
/// <summary>
/// When <see cref="LowLatencyDxgiSwapChain"/> is active, renders Avalonia through a low-latency Dxgi Swapchain.
/// </summary>
/// <remarks>
/// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion.
/// This is only recommended if low input latency is desirable, and there is no need for the transparency
/// and styling / blurring offered by <see cref="WinUIComposition"/>.<br/>
/// Can only be applied with <see cref="Win32PlatformOptions.RenderingMode"/>=<see cref="Win32RenderingMode.AngleEgl"/>.
/// </remarks>
LowLatencyDxgiSwapChain = 3,
/// <summary>
/// The window renders to a redirection surface.
/// </summary>
/// <remarks>
/// This option is kept only for compatibility with older systems. Some Avalonia features might not work.
/// </remarks>
RedirectionSurface,
}
/// <summary>
/// Platform-specific options which apply to Windows.
/// </summary>
public class Win32PlatformOptions
{
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// Gets or sets Avalonia rendering modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="Win32RenderingMode.AngleEgl"/>, <see cref="Win32RenderingMode.Software"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="Win32RenderingMode.Software"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<Win32RenderingMode> RenderingMode { get; set; } = new[]
{
Win32RenderingMode.AngleEgl, Win32RenderingMode.Software
};
/// <summary>
/// Gets or sets Avalonia composition modes with fallbacks.
/// The first element in the array has the highest priority.
/// The default value is: <see cref="Win32CompositionMode.WinUIComposition"/>, <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary>
/// <remarks>
/// If application should work on as wide range of devices as possible, at least add <see cref="Win32CompositionMode.RedirectionSurface"/> as a fallback value.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown if no values were matched.</exception>
public IReadOnlyList<Win32CompositionMode> CompositionMode { get; set; } = new[]
{
Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface
};
/// <summary>
/// When <see cref="CompositionMode"/> is set to <see cref="Win32CompositionMode.WinUIComposition"/>, create rounded corner blur brushes
/// If set to null the brushes will be created using default settings (sharp corners)
/// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app.
/// </summary>
public float? WinUICompositionBackdropCornerRadius { get; set; }
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// Only applicable if <see cref="CompositionMode"/> is set to <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get; set; }
/// <summary>
/// Windows OpenGL profiles used when <see cref="RenderingMode"/> is set to <see cref="Win32RenderingMode.Wgl"/>.
/// This setting is 4.0 and 3.2 by default.
/// </summary>
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new(GlProfileType.OpenGL, 4, 0), new(GlProfileType.OpenGL, 3, 2)
};
/// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu.
/// When this property set <see cref="RenderingMode"/> is ignored
/// and <see cref="CompositionMode"/> only accepts null or <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary>
public IPlatformGraphics? CustomPlatformGraphics { get; set; }
}

2
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs

@ -23,7 +23,7 @@ namespace Avalonia.Win32.WinRT.Composition
public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice)
{
var cornerRadius = AvaloniaLocator.Current.GetService<Win32PlatformOptions>()
?.CompositionBackdropCornerRadius;
?.WinUICompositionBackdropCornerRadius;
_window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius);
_window.SetBlur(_blurEffect);

1
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs

@ -12,6 +12,7 @@ internal class WinUiCompositionShared : IDisposable
public ICompositionBrush? MicaBrush { get; }
public object SyncRoot { get; } = new();
public static readonly Version MinWinCompositionVersion = new(10, 0, 17134);
public static readonly Version MinAcrylicVersion = new(10, 0, 15063);
public static readonly Version MinHostBackdropVersion = new(10, 0, 22000);

31
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs

@ -112,35 +112,36 @@ internal class WinUiCompositorConnection : IRenderTimer
}
}
public static void TryCreateAndRegister()
public static bool IsSupported()
{
const int majorRequired = 10;
const int buildRequired = 17134;
var majorInstalled = Win32Platform.WindowsVersion.Major;
var buildInstalled = Win32Platform.WindowsVersion.Build;
if (majorInstalled >= majorRequired &&
buildInstalled >= buildRequired)
return Win32Platform.WindowsVersion >= WinUiCompositionShared.MinWinCompositionVersion;
}
public static bool TryCreateAndRegister()
{
if (IsSupported())
{
try
{
TryCreateAndRegisterCore();
return;
return true;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "WinUIComposition")
?.Log(null, "Unable to initialize WinUI compositor: {0}", e);
}
}
else
{
var osVersionNotice =
$"Windows {WinUiCompositionShared.MinWinCompositionVersion} is required. Your machine has Windows {Win32Platform.WindowsVersion} installed.";
var osVersionNotice =
$"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed.";
Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
$"Unable to initialize WinUI compositor: {osVersionNotice}");
}
Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
$"Unable to initialize WinUI compositor: {osVersionNotice}");
return false;
}
public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info);

2
tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs

@ -103,7 +103,7 @@ namespace Avalonia.Base.UnitTests.Media
throw new NotImplementedException();
}
protected override IGeometryImpl CreateDefiningGeometry()
private protected sealed override IGeometryImpl CreateDefiningGeometry()
{
return Mock.Of<IGeometryImpl>(
x => x.WithTransform(It.IsAny<Matrix>()) ==

45
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -1247,6 +1247,51 @@ namespace Avalonia.Controls.UnitTests
data.GetRealizedItems());
}
[Fact]
public void Should_Not_Handle_Space_When_TextBox_Inside_ListBoxItem()
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var target = new TextBox
{
Focusable = true
};
var listbox = new ListBox()
{
Template = ListBoxTemplate(),
Items =
{
new ListBoxItem()
{
Template = ListBoxItemTemplate(),
Content = target,
}
}
};
var nKeyDown = 0;
var root = new TestRoot()
{
Width = 1000,
Height = 1000,
Child = listbox,
};
root.KeyDown += (s, e) => nKeyDown++;
listbox.ApplyTemplate();
root.LayoutManager.ExecuteInitialLayoutPass();
target.Focus();
RaiseKeyEvent(target, Key.Space, KeyModifiers.None);
Assert.Equal(1, nKeyDown);
}
}
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
{
target.RaiseEvent(new KeyEventArgs

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