Browse Source

[OSX] Implemented IOSurface/MTLSharedEvent interop APIs (#18791)

* [OSX] Implemented IOSurface/MTLSharedEvent interop APIs

* Bump Xcode?

* Xcode?

* APIDiff

* Use different XCode versions because of how awesome appium is

* A hack for crapium

* Replace SkiaMetalApi usages

* Update API suppressions

---------

Co-authored-by: Timothy Miller <innerlogic4321@gmail.com>
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/19766/head
Nikita Tsukanov 4 months ago
committed by GitHub
parent
commit
22c4c630ce
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 72
      api/Avalonia.nupkg.xml
  2. 3
      azure-pipelines-integrationtests.yml
  3. 4
      azure-pipelines.yml
  4. 5
      native/Avalonia.Native/inc/noarc.h
  5. 10
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  6. 55
      native/Avalonia.Native/src/OSX/cgl.mm
  7. 2
      native/Avalonia.Native/src/OSX/common.h
  8. 9
      native/Avalonia.Native/src/OSX/crapium.h
  9. 21
      native/Avalonia.Native/src/OSX/crapium.mm
  10. 16
      native/Avalonia.Native/src/OSX/main.mm
  11. 40
      native/Avalonia.Native/src/OSX/memhelp.mm
  12. 169
      native/Avalonia.Native/src/OSX/metal.mm
  13. 16
      native/Avalonia.Native/src/OSX/noarc.mm
  14. 42
      samples/GpuInterop/NativeMethods.cs
  15. 8
      samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
  16. 118
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  17. 155
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  18. 69
      samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
  19. 72
      samples/GpuInterop/VulkanDemo/VulkanTimelineSemaphore.cs
  20. 34
      src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
  21. 10
      src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
  22. 17
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  23. 6
      src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
  24. 30
      src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
  25. 13
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
  26. 36
      src/Avalonia.Metal/IMetalExternalObjectsFeature.cs
  27. 159
      src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
  28. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  29. 55
      src/Avalonia.Native/GpuHandleWrapFeature.cs
  30. 103
      src/Avalonia.Native/Metal.cs
  31. 40
      src/Avalonia.Native/avn.idl
  32. 11
      src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
  33. 6
      src/Avalonia.OpenGL/GlConsts.cs
  34. 12
      src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
  35. 75
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalExternalObjectsFeature.cs
  36. 18
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
  37. 57
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs
  38. 2
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  39. 4
      src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaExternalObjectsFeature.cs
  40. 9
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  41. 2
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs

72
api/Avalonia.nupkg.xml

@ -25,6 +25,30 @@
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType</Target>
<Left>baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
@ -49,6 +73,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
@ -73,4 +121,28 @@
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType</Target>
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right>
</Suppression>
</Suppressions>

3
azure-pipelines-integrationtests.yml

@ -25,12 +25,13 @@ jobs:
arch="arm64"
fi
git clean -ffdx
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
pkill node
pkill testmanagerd
appium > appium.out &
pkill IntegrationTestApp
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
./build.sh CompileNative
sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer
rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
pkill IntegrationTestApp
./samples/IntegrationTestApp/bundle.sh

4
azure-pipelines.yml

@ -95,11 +95,11 @@ jobs:
inputs:
actions: 'build'
scheme: ''
sdk: 'macosx13.0'
sdk: 'macosx14.2'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: 'specifyPath' # Options: 8, 9, default, specifyPath
xcodeDeveloperDir: '/Applications/Xcode_14.1.app/Contents/Developer'
xcodeDeveloperDir: '/Applications/Xcode_15.2.app/Contents/Developer'
args: '-derivedDataPath ./'
- task: CmdLine@2

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

@ -8,4 +8,7 @@ public:
~CppAutoreleasePool();
};
#define START_ARP_CALL CppAutoreleasePool __autoreleasePool
#define START_ARP_CALL CppAutoreleasePool __autoreleasePool
extern void ReleaseNSObject(void* obj);
extern void RetainNSObject(void* obj);
extern uint64_t GetRetainCountForNSObject(void* obj);

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

@ -31,6 +31,8 @@
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
1A465D10246AB61600C5858B /* dnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A465D0F246AB61600C5858B /* dnd.mm */; };
1AC7F1432DCA0C2E003A161B /* crapium.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AC7F1422DCA0C2E003A161B /* crapium.mm */; };
1AE55B8C2DC1060E00FD0BB3 /* memhelp.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AE55B8B2DC1060E00FD0BB3 /* memhelp.mm */; };
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
@ -91,6 +93,9 @@
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
1A465D0F246AB61600C5858B /* dnd.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dnd.mm; sourceTree = "<group>"; };
1AC7F1422DCA0C2E003A161B /* crapium.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objcpp; path = crapium.mm; sourceTree = "<group>"; };
1AC7F1442DCA0D6A003A161B /* crapium.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crapium.h; sourceTree = "<group>"; };
1AE55B8B2DC1060E00FD0BB3 /* memhelp.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = memhelp.mm; sourceTree = "<group>"; };
1AFD334023E03C4F0042899B /* controlhost.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = controlhost.mm; sourceTree = "<group>"; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
@ -171,6 +176,9 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
1AC7F1442DCA0D6A003A161B /* crapium.h */,
1AC7F1422DCA0C2E003A161B /* crapium.mm */,
1AE55B8B2DC1060E00FD0BB3 /* memhelp.mm */,
F10084852BFF1FB40024303E /* TopLevelImpl.mm */,
BC7C33832C066F1100945A48 /* AvnAccessibility.h */,
BC7C33812C066DBF00945A48 /* AvnAutomationNode.h */,
@ -333,6 +341,7 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1AE55B8C2DC1060E00FD0BB3 /* memhelp.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
@ -353,6 +362,7 @@
ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */,
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
1AC7F1432DCA0C2E003A161B /* crapium.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
64B1EA48E308E574685AFB07 /* metal.mm in Sources */,

55
native/Avalonia.Native/src/OSX/cgl.mm

@ -107,6 +107,61 @@ public:
return Context;
}
int texImageIOSurface2D(int target, int internal_format,
int width, int height, int format, int type, void* ioSurface, int plane) override
{
return CGLTexImageIOSurface2D(Context, target, internal_format, width, height, format, type, (IOSurfaceRef)ioSurface, plane);
}
bool GetIOKitRegistryId(uint64_t *value) override {
if (@available(macOS 10.13, *))
{
GLint rendererId;
if(CGLGetParameter(Context, kCGLCPCurrentRendererID, &rendererId) != 0)
return false;
GLint rendererCount = 0;
CGLRendererInfoObj rendererInfo;
if(CGLQueryRendererInfo(0xFFFFFFFF, &rendererInfo, &rendererCount))
return false;
@try
{
for(auto i = 0; i < rendererCount; i++)
{
GLint thisRendererID;
CGLDescribeRenderer(rendererInfo, i, kCGLRPRendererID, &thisRendererID);
if(thisRendererID == rendererId)
{
GLint gpuIDLow = 0;
GLint gpuIDHigh = 0;
if(CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDLow, &gpuIDLow))
return false;
if(CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDHigh, &gpuIDHigh))
return false;
*value = ((uint64_t)gpuIDHigh << 32) | gpuIDLow;
return true;
}
}
return false;
}
@finally
{
CGLDestroyRendererInfo(rendererInfo);
}
}
else
return false;
}
~AvnGlContext()
{
CGLReleaseContext(Context);

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

@ -33,6 +33,7 @@ extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer();
extern IAvnNativeObjectsMemoryManagement* CreateMemoryManagementHelper();
extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();
@ -47,6 +48,7 @@ extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
extern NSSize ToNSSize (AvnSize s);
extern AvnSize FromNSSize (NSSize s);
extern IAvnMTLSharedEvent* ImportMTLSharedEvent(void* object);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
#else

9
native/Avalonia.Native/src/OSX/crapium.h

@ -0,0 +1,9 @@
// The only reason this file exists is Appium which limits our highest Xcode version to 15.2. Please, purge Appium from our codebase
#ifndef crapium_h
#define crapium_h
#import <Foundation/Foundation.h>
@protocol MTLSharedEvent;
API_AVAILABLE(macos(12))
extern BOOL MtlSharedEventWaitUntilSignaledValueHack(id<MTLSharedEvent> ev, uint64_t value, uint64_t milliseconds);
#endif /* crapium_h */

21
native/Avalonia.Native/src/OSX/crapium.mm

@ -0,0 +1,21 @@
// The only reason this file exists is Appium which limits our highest Xcode version to 15.2. Please, purge Appium from our codebase
#import <Foundation/Foundation.h>
#import "crapium.h"
@class MTLSharedEventHandle;
@protocol MTLSharedEvent;
@protocol MTLEvent;
typedef void (^MTLSharedEventNotificationBlock)(id <MTLSharedEvent>, uint64_t value);
API_AVAILABLE(macos(10.14), ios(12.0))
@protocol MTLSharedEvent <MTLEvent>
// Synchronously wait for the signaledValue to be greater than or equal to 'value', with a timeout
// specified in milliseconds. Returns YES if the value was signaled before the timeout, otherwise NO.
- (BOOL)waitUntilSignaledValue:(uint64_t)value timeoutMS:(uint64_t)milliseconds API_AVAILABLE(macos(12.0), ios(15.0));
@end
API_AVAILABLE(macos(12))
extern BOOL MtlSharedEventWaitUntilSignaledValueHack(id<MTLSharedEvent> ev, uint64_t value, uint64_t milliseconds)
{
return [ev waitUntilSignaledValue:value timeoutMS:milliseconds];
}

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

@ -467,6 +467,22 @@ public:
return S_OK;
}
}
virtual HRESULT ImportMTLSharedEvent(void* event, IAvnMTLSharedEvent** ppv) override
{
START_COM_CALL;
*ppv = ::ImportMTLSharedEvent(event);
return *ppv != nullptr ? S_OK : E_FAIL;
}
HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement **ppv) override {
START_COM_CALL;
*ppv = ::CreateMemoryManagementHelper();
return S_OK;
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

40
native/Avalonia.Native/src/OSX/memhelp.mm

@ -0,0 +1,40 @@
#include "common.h"
class MemHelper : public ComSingleObject<IAvnNativeObjectsMemoryManagement, &IID_IAvnNativeObjectsMemoryManagement>
{
FORWARD_IUNKNOWN()
void RetainNSObject(void *object) override
{
::RetainNSObject(object);
}
void ReleaseNSObject(void *object) override
{
::ReleaseNSObject(object);
}
void RetainCFObject(void *object) override
{
CFRetain(object);
}
void ReleaseCFObject(void *object) override
{
CFRelease(object);
}
uint64_t GetRetainCountForNSObject(void *obj) override {
return ::GetRetainCountForNSObject(obj);
}
int64_t GetRetainCountForCFObject(void *obj) override {
return CFGetRetainCount(obj);
}
};
extern IAvnNativeObjectsMemoryManagement* CreateMemoryManagementHelper()
{
return new MemHelper();
}

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

@ -3,6 +3,74 @@
#import <QuartzCore/QuartzCore.h>
#include "common.h"
#include "rendertarget.h"
#import "crapium.h"
class API_AVAILABLE(macos(12.0)) AvnMTLSharedEvent : public ComSingleObject<IAvnMTLSharedEvent, &IID_IAvnMTLSharedEvent>
{
id<MTLSharedEvent> _event;
public:
AvnMTLSharedEvent(id<MTLSharedEvent> ev) : _event(ev)
{
}
FORWARD_IUNKNOWN()
id<MTLSharedEvent> GetEvent()
{
return _event;
}
void *GetNativeHandle() override {
return (__bridge void*)_event;
}
bool Wait(uint64_t value, uint64_t timeoutMS) override {
return MtlSharedEventWaitUntilSignaledValueHack(_event, value, timeoutMS);
}
void SetSignaledValue(uint64_t value) override {
_event.signaledValue = value;
}
uint64_t GetSignaledValue() override {
return _event.signaledValue;
}
};
class AvnMetalTexture : public ComSingleObject<IAvnMetalTexture, &IID_IAvnMetalTexture>
{
id<MTLTexture> _texture;
public:
FORWARD_IUNKNOWN()
AvnMetalTexture(id<MTLTexture> texture) : _texture(texture)
{
}
void *GetNativeHandle() override
{
return (__bridge void*)_texture;
}
int GetWidth() override
{
return (int)_texture.width;
}
int GetHeight() override
{
return (int)_texture.height;
}
int GetSampleCount() override
{
return (int)_texture.sampleCount;
}
};
class AvnMetalDevice : public ComSingleObject<IAvnMetalDevice, &IID_IAvnMetalDevice>
{
@ -18,7 +86,86 @@ public:
void *GetQueue() override {
return (__bridge void*) queue;
}
HRESULT ImportIOSurface(void *handle, AvnPixelFormat pixelFormat, IAvnMetalTexture **ppv) override {
auto surf = (IOSurfaceRef)handle;
auto width = IOSurfaceGetWidth(surf);
auto height = IOSurfaceGetHeight(surf);
auto desc = [MTLTextureDescriptor new];
if(pixelFormat == kAvnRgba8888)
desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
else if(pixelFormat == kAvnBgra8888)
desc.pixelFormat = MTLPixelFormatBGRA8Unorm;
else
return E_INVALIDARG;
desc.textureType = MTLTextureType2D;
desc.width = width;
desc.height = height;
desc.depth = 1;
desc.mipmapLevelCount = 1;
desc.sampleCount = 1;
desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
auto texture = [device newTextureWithDescriptor:desc iosurface:surf plane:0];
if(texture == nullptr)
return E_FAIL;
*ppv = new AvnMetalTexture(texture);
return S_OK;
}
HRESULT ImportSharedEvent(void *mtlSharedEventInstance, IAvnMTLSharedEvent**ppv) override {
if (@available(macOS 12.0, *)) {
auto external = (__bridge id<MTLSharedEvent>)mtlSharedEventInstance;
auto handle = external.newSharedEventHandle;
auto imported = [device newSharedEventWithHandle: handle];
*ppv = new AvnMTLSharedEvent(imported);
return S_OK;
}
else
{
return E_NOTIMPL;
}
}
HRESULT SignalOrWait(IAvnMTLSharedEvent *ev, uint64_t value, bool wait)
{
if (@available(macOS 12.0, *))
{
auto e = dynamic_cast<AvnMTLSharedEvent*>(ev);
if(e == nullptr)
return E_FAIL;;
auto buf = [queue commandBuffer];
if(wait)
[buf encodeWaitForEvent:e->GetEvent() value:value];
else
[buf encodeSignalEvent:e->GetEvent() value:value];
[buf commit];
return S_OK;
}
else
return E_FAIL;
}
HRESULT SubmitWait(IAvnMTLSharedEvent *ev, uint64_t value) override {
return SignalOrWait(ev, value, true);
}
HRESULT SubmitSignal(IAvnMTLSharedEvent *ev, uint64_t value) override {
return SignalOrWait(ev, value, false);
}
bool GetIOKitRegistryId(uint64_t *value) override {
if (@available(macOS 10.13, *)) {
*value = [device registryID];
return true;
} else {
return false;
}
}
AvnMetalDevice(id <MTLDevice> device, id <MTLCommandQueue> queue) : device(device), queue(queue) {
}
@ -160,3 +307,23 @@ extern IAvnMetalDisplay* GetMetalDisplay()
{
return _display;
}
extern IAvnMTLSharedEvent* ImportMTLSharedEvent(void* object)
{
if (@available(macOS 12.0, *)) {
if(object == nullptr)
return nil;
auto evId = (__bridge id<MTLSharedEvent>)object;
if(evId == nil)
return nil;
return new AvnMTLSharedEvent(evId);
}
else
{
return nil;
}
}

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

@ -1,5 +1,5 @@
#include "noarc.h"
#include "avalonia-native.h"
CppAutoreleasePool::CppAutoreleasePool()
{
_pool = [[NSAutoreleasePool alloc] init];
@ -9,3 +9,17 @@ CppAutoreleasePool::~CppAutoreleasePool() {
auto ptr = (NSAutoreleasePool*)_pool;
[ptr release];
}
extern void ReleaseNSObject(void* obj)
{
[(NSObject*)obj release];
}
extern void RetainNSObject(void* obj)
{
[(NSObject*)obj retain];
}
extern uint64_t GetRetainCountForNSObject(void* obj)
{
return [(NSObject*)obj retainCount];
}

42
samples/GpuInterop/NativeMethods.cs

@ -0,0 +1,42 @@
using System;
using System.Runtime.InteropServices;
namespace GpuInterop;
static class NativeMethods
{
[Flags]
public enum IOSurfaceLockOptions : uint
{
None = 0,
ReadOnly = 1 << 0,
AvoidSync = 1 << 1,
}
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern int IOSurfaceLock(IntPtr surface, IOSurfaceLockOptions options, IntPtr seed);
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern nint IOSurfaceGetWidth(IntPtr surface);
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern nint IOSurfaceGetHeight(IntPtr surface);
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern nint IOSurfaceGetBytesPerRow(IntPtr surface);
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern IntPtr IOSurfaceGetBaseAddress(IntPtr surface);
[DllImport("/System/Library/Frameworks/IOSurface.framework/IOSurface")]
public static extern void IOSurfaceUnlock(IntPtr surface, IOSurfaceLockOptions options, IntPtr seed);
[DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
public static extern IntPtr CFRetain(IntPtr cf);
[DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
public static extern void CFRelease(IntPtr cf);
[DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
public static extern nint CFGetRetainCount(IntPtr cf);
}

8
samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs

@ -167,7 +167,8 @@ namespace Avalonia.Vulkan
ReadOnlySpan<PipelineStageFlags> waitDstStageMask = default,
ReadOnlySpan<Semaphore> signalSemaphores = default,
Fence? fence = null,
KeyedMutexSubmitInfo? keyedMutex = null)
KeyedMutexSubmitInfo? keyedMutex = null,
IntPtr pNext = default)
{
EndRecording();
@ -189,7 +190,8 @@ namespace Avalonia.Vulkan
PReleaseKeys = &releaseKey,
PAcquireSyncs = &devMem,
PReleaseSyncs = &devMem,
PAcquireTimeouts = &timeout
PAcquireTimeouts = &timeout,
PNext = (void*)pNext
};
fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
@ -199,7 +201,7 @@ namespace Avalonia.Vulkan
var commandBuffer = InternalHandle;
var submitInfo = new SubmitInfo
{
PNext = keyedMutex != null ? &mutex : null,
PNext = keyedMutex != null ? &mutex : (void*)pNext,
SType = StructureType.SubmitInfo,
WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
PWaitSemaphores = pWaitSemaphores,

118
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -1,17 +1,21 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Silk.NET.Core;
using Silk.NET.Core.Contexts;
using Silk.NET.Core.Loader;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
using D3DDevice = SharpDX.Direct3D11.Device;
#pragma warning disable CS0162 // Unreachable code detected
namespace GpuInterop.VulkanDemo;
@ -24,7 +28,7 @@ public unsafe class VulkanContext : IDisposable
public required Queue Queue { get; init; }
public required uint QueueFamilyIndex { get; init; }
public required VulkanCommandBufferPool Pool { get; init; }
public required GRContext GrContext { get; init; }
public required GRContext? GrContext { get; init; }
public required DescriptorPool DescriptorPool { get; init; }
public required D3DDevice? D3DDevice { get; init; }
@ -44,14 +48,22 @@ public unsafe class VulkanContext : IDisposable
var enabledExtensions = new List<string>()
{
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
"VK_KHR_get_physical_device_properties2"
};
if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
enabledExtensions.Add("VK_KHR_portability_enumeration");
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
enabledExtensions.AddRange([
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
]);
}
var enabledLayers = new List<string>();
Vk api = Vk.GetApi();
Vk api = GetApi();
enabledExtensions.Add("VK_EXT_debug_utils");
if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
enabledLayers.Add("VK_LAYER_KHRONOS_validation");
@ -61,8 +73,10 @@ public unsafe class VulkanContext : IDisposable
DescriptorPool descriptorPool = default;
VulkanCommandBufferPool? pool = null;
GRContext? grContext = null;
bool success = false;
try
{
enabledLayers.Clear();
using var pRequiredExtensions = new ByteStringList(enabledExtensions);
using var pEnabledLayers = new ByteStringList(enabledLayers);
api.CreateInstance(new InstanceCreateInfo
@ -72,7 +86,8 @@ public unsafe class VulkanContext : IDisposable
PpEnabledExtensionNames = pRequiredExtensions,
EnabledExtensionCount = pRequiredExtensions.UCount,
PpEnabledLayerNames = pEnabledLayers,
EnabledLayerCount = pEnabledLayers.UCount
EnabledLayerCount = pEnabledLayers.UCount,
Flags = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? InstanceCreateFlags.EnumeratePortabilityBitKhr : default
}, null, out var vkInstance).ThrowOnError();
@ -93,10 +108,13 @@ public unsafe class VulkanContext : IDisposable
debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out _);
}
var requireDeviceExtensions = new List<string>
var requireDeviceExtensions = new List<string>();
if(!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
"VK_KHR_external_memory",
"VK_KHR_external_semaphore"
requireDeviceExtensions.AddRange([
"VK_KHR_external_memory",
"VK_KHR_external_semaphore"
]);
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -112,6 +130,14 @@ public unsafe class VulkanContext : IDisposable
requireDeviceExtensions.Add("VK_KHR_dedicated_allocation");
requireDeviceExtensions.Add("VK_KHR_get_memory_requirements2");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
.IOSurfaceRef)
)
return (null, "Image sharing is not supported by the current backend");
requireDeviceExtensions.AddRange(["VK_EXT_metal_objects", "VK_KHR_timeline_semaphore"]);
}
else
{
if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
@ -223,26 +249,29 @@ public unsafe class VulkanContext : IDisposable
.ThrowOnError();
pool = new VulkanCommandBufferPool(api, device, queue, queueFamilyIndex);
grContext = GRContext.CreateVulkan(new GRVkBackendContext
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
VkInstance = vkInstance.Handle,
VkDevice = device.Handle,
VkQueue = queue.Handle,
GraphicsQueueIndex = queueFamilyIndex,
VkPhysicalDevice = physicalDevice.Handle,
GetProcedureAddress = (proc, _, _) =>
grContext = GRContext.CreateVulkan(new GRVkBackendContext
{
var rv = api.GetDeviceProcAddr(device, proc);
if (rv != IntPtr.Zero)
return rv;
rv = api.GetInstanceProcAddr(vkInstance, proc);
if (rv != IntPtr.Zero)
return rv;
return api.GetInstanceProcAddr(default, proc);
}
});
VkInstance = vkInstance.Handle,
VkDevice = device.Handle,
VkQueue = queue.Handle,
GraphicsQueueIndex = queueFamilyIndex,
VkPhysicalDevice = physicalDevice.Handle,
GetProcedureAddress = (proc, _, _) =>
{
var rv = api.GetDeviceProcAddr(device, proc);
if (rv != IntPtr.Zero)
return rv;
rv = api.GetInstanceProcAddr(vkInstance, proc);
if (rv != IntPtr.Zero)
return rv;
return api.GetInstanceProcAddr(default, proc);
}
});
if (grContext == null)
return (null, "Can't create Skia GrContext, device is likely broken");
}
D3DDevice? d3dDevice = null;
if (physicalDeviceIDProperties.DeviceLuidvalid &&
@ -251,7 +280,7 @@ public unsafe class VulkanContext : IDisposable
)
d3dDevice = D3DMemoryHelper.CreateDeviceByLuid(
new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8));
success = true;
return (new VulkanContext
{
Api = api,
@ -278,7 +307,7 @@ public unsafe class VulkanContext : IDisposable
}
finally
{
if (grContext == null && api != null)
if (!success)
{
pool?.Dispose();
if (descriptorPool.Handle != default)
@ -322,11 +351,40 @@ public unsafe class VulkanContext : IDisposable
return Vk.False;
}
private const string MacVulkanSdkGlobalPath = "/usr/local/lib/libvulkan.dylib";
class MacLibraryNameContainer : SearchPathContainer
{
public override string Windows64 { get; }
public override string Windows86 { get; }
public override string Linux { get; }
public override string MacOS { get; } = MacVulkanSdkGlobalPath;
}
private static Vk GetApi()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || !File.Exists(MacVulkanSdkGlobalPath))
return Vk.GetApi();
var ctx = new MultiNativeContext(new INativeContext[2]
{
Vk.CreateDefaultContext(new MacLibraryNameContainer().GetLibraryName()),
null!
});
var ret = new Vk(ctx);
ctx.Contexts[1] = new LamdaNativeContext((Func<string, IntPtr>) ((x) =>
{
if (x.EndsWith("ProcAddr"))
return IntPtr.Zero;
IntPtr deviceProcAddr = (IntPtr) ret.GetDeviceProcAddr(ret.CurrentDevice.GetValueOrDefault(), x);
return deviceProcAddr != IntPtr.Zero ? deviceProcAddr : (IntPtr) ret.GetInstanceProcAddr(ret.CurrentInstance.GetValueOrDefault(), x);
}));
return ret;
}
public void Dispose()
{
D3DDevice?.Dispose();
GrContext.Dispose();
GrContext?.Dispose();
Pool.Dispose();
Api.DestroyDescriptorPool(Device, DescriptorPool, null);
Api.DestroyDevice(Device, null);

155
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@ -8,6 +8,7 @@ using Avalonia.Platform;
using Avalonia.Vulkan;
using SharpDX.DXGI;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
@ -45,6 +46,8 @@ public unsafe class VulkanImage : IDisposable
public ulong MemorySize { get; }
public uint CurrentLayout => (uint) _currentLayout;
private bool _hasIOSurface;
public VulkanImage(VulkanContext vk, uint format, PixelSize size,
bool exportable, IReadOnlyList<string> supportedHandleTypes)
{
@ -75,10 +78,22 @@ public unsafe class VulkanImage : IDisposable
SType = StructureType.ExternalMemoryImageCreateInfo,
HandleTypes = handleType
};
var ioSurfaceCreateInfo = new ExportMetalObjectCreateInfoEXT
{
SType = StructureType.ExportMetalObjectCreateInfoExt,
ExportObjectType = ExportMetalObjectTypeFlagsEXT.IosurfaceBitExt
};
_hasIOSurface = exportable && RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
var imageCreateInfo = new ImageCreateInfo
{
PNext = exportable ? &externalMemoryCreateInfo : null,
PNext = exportable ?
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
&ioSurfaceCreateInfo :
&externalMemoryCreateInfo : null,
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.Type2D,
Format = Format,
@ -98,59 +113,64 @@ public unsafe class VulkanImage : IDisposable
Api
.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
InternalHandle = image;
Api.GetImageMemoryRequirements(_device, InternalHandle,
out var memoryRequirements);
var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR
{
SType = StructureType.MemoryDedicatedAllocateInfoKhr,
Image = image
};
var fdExport = new ExportMemoryAllocateInfo
if (!exportable || !RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
HandleTypes = handleType, SType = StructureType.ExportMemoryAllocateInfo,
PNext = &dedicatedAllocation
};
ImportMemoryWin32HandleInfoKHR handleImport = default;
if (handleType == ExternalMemoryHandleTypeFlags.D3D11TextureBit && exportable)
{
var d3dDevice = vk.D3DDevice ?? throw new NotSupportedException("Vulkan D3DDevice wasn't created");
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(d3dDevice, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
Api.GetImageMemoryRequirements(_device, InternalHandle,
out var memoryRequirements);
handleImport = new ImportMemoryWin32HandleInfoKHR
var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
SType = StructureType.MemoryDedicatedAllocateInfoKhr, Image = image
};
}
var memoryAllocateInfo = new MemoryAllocateInfo
{
PNext =
exportable ? handleImport.Handle != IntPtr.Zero ? &handleImport : &fdExport : null,
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
Api,
_physicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
};
var fdExport = new ExportMemoryAllocateInfo
{
HandleTypes = handleType,
SType = StructureType.ExportMemoryAllocateInfo,
PNext = &dedicatedAllocation
};
Api.AllocateMemory(_device, memoryAllocateInfo, null,
out var imageMemory).ThrowOnError();
ImportMemoryWin32HandleInfoKHR handleImport = default;
if (handleType == ExternalMemoryHandleTypeFlags.D3D11TextureBit && exportable)
{
var d3dDevice = vk.D3DDevice ?? throw new NotSupportedException("Vulkan D3DDevice wasn't created");
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(d3dDevice, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
_imageMemory = imageMemory;
MemorySize = memoryRequirements.Size;
handleImport = new ImportMemoryWin32HandleInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
};
}
var memoryAllocateInfo = new MemoryAllocateInfo
{
PNext =
exportable ? handleImport.Handle != IntPtr.Zero ? &handleImport : &fdExport : null,
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
Api,
_physicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
};
Api.AllocateMemory(_device, memoryAllocateInfo, null,
out var imageMemory).ThrowOnError();
_imageMemory = imageMemory;
MemorySize = memoryRequirements.Size;
Api.BindImageMemory(_device, InternalHandle, _imageMemory, 0).ThrowOnError();
}
Api.BindImageMemory(_device, InternalHandle, _imageMemory, 0).ThrowOnError();
var componentMapping = new ComponentMapping(
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
@ -209,6 +229,26 @@ public unsafe class VulkanImage : IDisposable
ext.GetMemoryWin32Handle(_device, info, out var fd).ThrowOnError();
return fd;
}
public IntPtr ExportIOSurface()
{
if (!Api.TryGetDeviceExtension<ExtMetalObjects>(_instance, _device, out var ext))
throw new InvalidOperationException();
var surfaceExport = new ExportMetalIOSurfaceInfoEXT
{
SType = StructureType.ExportMetalIOSurfaceInfoExt,
Image = InternalHandle
};
var export = new ExportMetalObjectsInfoEXT()
{
SType = StructureType.ExportMetalObjectsInfoExt,
PNext = &surfaceExport
};
ext.ExportMetalObjects(_device, ref export);
if (surfaceExport.IoSurface == IntPtr.Zero)
throw new Exception("Unable to export IOSurfaceRef");
return surfaceExport.IoSurface;
}
public IPlatformHandle Export()
{
@ -225,6 +265,9 @@ public unsafe class VulkanImage : IDisposable
return new PlatformHandle(ExportOpaqueNtHandle(),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return new PlatformHandle(ExportIOSurface(),
KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef);
else
return new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
@ -281,6 +324,30 @@ public unsafe class VulkanImage : IDisposable
public void SaveTexture(string path)
{
if (_vk.GrContext == null)
{
if (_hasIOSurface)
{
var surf = ExportIOSurface();
if (NativeMethods.IOSurfaceLock(surf, 0, IntPtr.Zero) != 0)
throw new Exception("IOSurfaceLock failed");
var w = (int)NativeMethods.IOSurfaceGetWidth(surf);
var h = (int)NativeMethods.IOSurfaceGetHeight(surf);
var sstride = NativeMethods.IOSurfaceGetBytesPerRow(surf);
var pSurface = NativeMethods.IOSurfaceGetBaseAddress(surf);
using var b = new Avalonia.Media.Imaging.Bitmap(PixelFormat.Bgra8888,
AlphaFormat.Premul, pSurface, new PixelSize(w, h),
new Vector(96, 96), (int)sstride);
b.Save(path);
NativeMethods.IOSurfaceUnlock(surf, 0, IntPtr.Zero);
return;
}
else
throw new NotSupportedException("Need skia to dump textures, sorry");
}
_vk.GrContext.ResetContext();
var _image = this;
var imageInfo = new GRVkImageInfo()

69
samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs

@ -39,8 +39,10 @@ class VulkanSwapchainImage : ISwapchainImage
private readonly ICompositionGpuInterop _interop;
private readonly CompositionDrawingSurface _target;
private readonly VulkanImage _image;
private readonly VulkanSemaphorePair _semaphorePair;
private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore;
private readonly VulkanSemaphorePair? _semaphorePair;
private readonly VulkanTimelineSemaphore? _timelineSemaphore;
private ulong _timelineCounter;
private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore, _importedTimelineSemaphore;
private ICompositionImportedGpuImage? _importedImage;
private Task? _lastPresent;
public VulkanImage Image => _image;
@ -52,8 +54,12 @@ class VulkanSwapchainImage : ISwapchainImage
_interop = interop;
_target = target;
Size = size;
_image = new VulkanImage(vk, (uint)Format.R8G8B8A8Unorm, size, true, interop.SupportedImageHandleTypes);
_semaphorePair = new VulkanSemaphorePair(vk, true);
var format = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm;
_image = new VulkanImage(vk, (uint)format, size, true, interop.SupportedImageHandleTypes);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
_timelineSemaphore = new(vk);
else
_semaphorePair = new VulkanSemaphorePair(vk, true);
}
public async ValueTask DisposeAsync()
@ -62,11 +68,13 @@ class VulkanSwapchainImage : ISwapchainImage
await LastPresent;
if (_importedImage != null)
await _importedImage.DisposeAsync();
if (_availableSemaphore != null)
await _availableSemaphore.DisposeAsync();
if (_renderCompletedSemaphore != null)
await _renderCompletedSemaphore.DisposeAsync();
_semaphorePair.Dispose();
_semaphorePair?.Dispose();
_timelineSemaphore?.Dispose();
_image.Dispose();
}
@ -89,6 +97,22 @@ class VulkanSwapchainImage : ISwapchainImage
AcquireKey = 0,
DeviceMemory = _image.DeviceMemory
});
else if (_timelineSemaphore != null)
{
unsafe
{
var wait = _timelineCounter;
var submitInfo = new TimelineSemaphoreSubmitInfo
{
PWaitSemaphoreValues = &wait,
WaitSemaphoreValueCount = 1,
SType = StructureType.TimelineSemaphoreSubmitInfo
};
var waitSemaphores = new[] { _timelineSemaphore.Handle };
buffer.Submit(waitSemaphores, pNext: (IntPtr)(&submitInfo));
}
}
else if (_initial)
{
_initial = false;
@ -110,7 +134,7 @@ class VulkanSwapchainImage : ISwapchainImage
buffer.BeginRecording();
_image.TransitionLayout(buffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferWriteBit);
if (_image.IsDirectXBacked)
{
buffer.Submit(null, null, null, null,
@ -119,10 +143,30 @@ class VulkanSwapchainImage : ISwapchainImage
DeviceMemory = _image.DeviceMemory, ReleaseKey = 1
});
}
else if (_timelineSemaphore != null)
{
unsafe
{
var signal = _timelineCounter + 1;
var submitInfo = new TimelineSemaphoreSubmitInfo
{
PSignalSemaphoreValues = &signal,
SignalSemaphoreValueCount = 1,
SType = StructureType.TimelineSemaphoreSubmitInfo
};
var signalSemaphores = new[] { _timelineSemaphore.Handle };
buffer.Submit(default, signalSemaphores: signalSemaphores, pNext: (IntPtr)(&submitInfo));
}
}
else
buffer.Submit(null, null, new[] { _semaphorePair.RenderFinishedSemaphore });
if (!_image.IsDirectXBacked)
if (_timelineSemaphore != null)
{
_importedTimelineSemaphore ??= _interop.ImportSemaphore(_timelineSemaphore.Export());
}
else if (!_image.IsDirectXBacked)
{
_availableSemaphore ??= _interop.ImportSemaphore(_semaphorePair.Export(false));
@ -132,13 +176,18 @@ class VulkanSwapchainImage : ISwapchainImage
_importedImage ??= _interop.ImportImage(_image.Export(),
new PlatformGraphicsExternalImageProperties
{
Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
Format = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm : PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
Width = Size.Width,
Height = Size.Height,
MemorySize = _image.MemorySize
});
if (_image.IsDirectXBacked)
if (_importedTimelineSemaphore != null)
{
_lastPresent = _target.UpdateWithTimelineSemaphoresAsync(_importedImage,
_importedTimelineSemaphore, _timelineCounter + 1, _importedTimelineSemaphore, _timelineCounter + 2);
_timelineCounter += 2;
}
else if (_image.IsDirectXBacked)
_lastPresent = _target.UpdateWithKeyedMutexAsync(_importedImage, 1, 0);
else
_lastPresent = _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore!, _availableSemaphore!);

72
samples/GpuInterop/VulkanDemo/VulkanTimelineSemaphore.cs

@ -0,0 +1,72 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using SilkNetDemo;
namespace GpuInterop.VulkanDemo;
class VulkanTimelineSemaphore : IDisposable
{
private VulkanContext _resources;
public unsafe VulkanTimelineSemaphore(VulkanContext resources)
{
_resources = resources;
var mtlEvent = new ExportMetalObjectCreateInfoEXT
{
SType = StructureType.ExportMetalObjectCreateInfoExt,
ExportObjectType = ExportMetalObjectTypeFlagsEXT.SharedEventBitExt
};
var semaphoreTypeInfo = new SemaphoreTypeCreateInfoKHR()
{
SType = StructureType.SemaphoreTypeCreateInfo,
SemaphoreType = SemaphoreType.Timeline,
PNext = &mtlEvent
};
var semaphoreCreateInfo = new SemaphoreCreateInfo
{
SType = StructureType.SemaphoreCreateInfo,
PNext = &semaphoreTypeInfo,
};
resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
Handle = semaphore;
}
public Semaphore Handle { get; }
public unsafe void Dispose()
{
_resources.Api.DestroySemaphore(_resources.Device, Handle, null);
}
public unsafe IntPtr ExportSharedEvent()
{
if (!_resources.Api.TryGetDeviceExtension<ExtMetalObjects>(_resources.Instance, _resources.Device, out var ext))
throw new InvalidOperationException();
var eventExport = new ExportMetalSharedEventInfoEXT()
{
SType = StructureType.ExportMetalSharedEventInfoExt,
Semaphore = Handle,
};
var export = new ExportMetalObjectsInfoEXT()
{
SType = StructureType.ExportMetalObjectsInfoExt,
PNext = &eventExport
};
ext.ExportMetalObjects(_resources.Device, ref export);
if (eventExport.MtlSharedEvent == IntPtr.Zero)
throw new Exception("Unable to export IOSurfaceRef");
return eventExport.MtlSharedEvent;
}
public IPlatformHandle Export()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return new PlatformHandle(ExportSharedEvent(),
KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent);
throw new PlatformNotSupportedException();
}
}

34
src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs

@ -24,11 +24,41 @@ public interface IExternalObjectsRenderInterfaceContextFeature
IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image);
IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle);
CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
public byte[]? DeviceUuid { get; }
public byte[]? DeviceLuid { get; }
}
/// <summary>
/// This interface allows proper management of ref-counted platform handles.
/// If we immediately wrap the handle, the caller can destroy its copy immediately after the call
/// This is needed for MoltenVK-based users that can e.g. get an MTLSharedEvent from a VkSemaphore.
/// This does NOT actually increase the ref-counter of MTLSharedEvent, since it's declared as
/// __unsafe_unretained in vulkan headers.
/// Same happens with exporting an IOSurfaceRef from a VkImage.
/// So in a case when the VkSemaphore or VkImage is destroyed, the "handle" which is actually a pointer
/// will be pointing to a dead object.
/// To prevent this we need to increase the reference counter in a handle-specific means
/// synchronously before returning control back to the user.
///
/// This is not needed for fds or DXGI handles, since those are _created_ on demand as proper NT handles
/// </summary>
[Unstable, NotClientImplementable]
public interface IExternalObjectsHandleWrapRenderInterfaceContextFeature
{
IExternalObjectsWrappedGpuHandle? WrapImageHandleOnAnyThread(IPlatformHandle handle,
PlatformGraphicsExternalImageProperties properties);
IExternalObjectsWrappedGpuHandle? WrapSemaphoreHandleOnAnyThread(IPlatformHandle handle);
}
[Unstable, NotClientImplementable]
public interface IExternalObjectsWrappedGpuHandle : IPlatformHandle, IDisposable
{
}
[Unstable]
public interface IPlatformRenderInterfaceImportedObject : IDisposable
{
@ -43,6 +73,10 @@ public interface IPlatformRenderInterfaceImportedImage : IPlatformRenderInterfac
IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
IPlatformRenderInterfaceImportedSemaphore signalSemaphore);
IBitmapImpl SnapshotWithTimelineSemaphores(
IPlatformRenderInterfaceImportedSemaphore waitForSemaphore, ulong waitForValue,
IPlatformRenderInterfaceImportedSemaphore signalSemaphore, ulong signalValue);
IBitmapImpl SnapshotWithAutomaticSync();
}

10
src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs

@ -43,6 +43,11 @@ public static class KnownPlatformGraphicsExternalImageHandleTypes
// A global shared handle that's been exported by Vulkan using VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT or in a compatible way
public const string VulkanOpaqueKmtHandle = nameof(VulkanOpaqueKmtHandle);
/// <summary>
/// A reference to IOSurface
/// </summary>
public const string IOSurfaceRef = nameof(IOSurfaceRef);
}
/// <summary>
@ -65,4 +70,9 @@ public static class KnownPlatformGraphicsExternalSemaphoreHandleTypes
/// A DXGI NT handle returned by ID3D12Device::CreateSharedHandle or ID3D11Fence::CreateSharedHandle
public const string Direct3D12FenceNtHandle = nameof(Direct3D12FenceNtHandle);
/// <summary>
/// A pointer to MTLSharedEvent object
/// </summary>
public const string MetalSharedEvent = nameof(MetalSharedEvent);
}

17
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@ -41,6 +41,23 @@ public sealed class CompositionDrawingSurface : CompositionSurface, IDisposable
var signal = (CompositionImportedGpuSemaphore)signalSemaphore;
return Compositor.InvokeServerJobAsync(() => Server.UpdateWithSemaphores(img, wait, signal));
}
/// <summary>
/// Updates the surface contents using an imported memory image using a semaphore pair as the means of synchronization
/// </summary>
/// <param name="image">GPU image with new surface contents</param>
/// <param name="waitForSemaphore">The semaphore to wait for before accessing the image</param>
/// <param name="signalSemaphore">The semaphore to signal after accessing the image</param>
/// <returns>A task that completes when update operation is completed and user code is free to destroy or dispose the image</returns>
public Task UpdateWithTimelineSemaphoresAsync(ICompositionImportedGpuImage image,
ICompositionImportedGpuSemaphore waitForSemaphore, ulong waitForValue,
ICompositionImportedGpuSemaphore signalSemaphore, ulong signalValue)
{
var img = (CompositionImportedGpuImage)image;
var wait = (CompositionImportedGpuSemaphore)waitForSemaphore;
var signal = (CompositionImportedGpuSemaphore)signalSemaphore;
return Compositor.InvokeServerJobAsync(() => Server.UpdateWithTimelineSemaphores(img, wait, waitForValue, signal, signalValue));
}
/// <summary>
/// Updates the surface contents using an unspecified automatic means of synchronization

6
src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs

@ -81,7 +81,11 @@ public enum CompositionGpuImportedImageSynchronizationCapabilities
/// <summary>
/// Synchronization and ordering is somehow handled by the underlying platform
/// </summary>
Automatic = 4
Automatic = 4,
/// <summary>
/// Pre-render and after-render timeline semaphores must be provided alongside with the image
/// </summary>
TimelineSemaphores = 8
}
/// <summary>

30
src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs

@ -10,7 +10,8 @@ internal class CompositionInterop : ICompositionGpuInterop
private readonly Compositor _compositor;
private readonly IPlatformRenderInterfaceContext _context;
private readonly IExternalObjectsRenderInterfaceContextFeature _externalObjects;
private readonly IExternalObjectsHandleWrapRenderInterfaceContextFeature? _externalObjectsWithHandleWrap;
public CompositionInterop(
Compositor compositor,
@ -21,6 +22,7 @@ internal class CompositionInterop : ICompositionGpuInterop
DeviceLuid = externalObjects.DeviceLuid;
DeviceUuid = externalObjects.DeviceUuid;
_externalObjects = externalObjects;
_externalObjectsWithHandleWrap = _context.TryGetFeature<IExternalObjectsHandleWrapRenderInterfaceContextFeature>();
}
public IReadOnlyList<string> SupportedImageHandleTypes => _externalObjects.SupportedImageHandleTypes;
@ -31,17 +33,23 @@ internal class CompositionInterop : ICompositionGpuInterop
public ICompositionImportedGpuImage ImportImage(IPlatformHandle handle,
PlatformGraphicsExternalImageProperties properties)
=> new CompositionImportedGpuImage(_compositor, _context, _externalObjects,
() => _externalObjects.ImportImage(handle, properties));
{
handle = _externalObjectsWithHandleWrap?.WrapImageHandleOnAnyThread(handle, properties) ?? handle;
return new CompositionImportedGpuImage(_compositor, _context, _externalObjects,
() => _externalObjects.ImportImage(handle, properties), handle);
}
public ICompositionImportedGpuImage ImportImage(ICompositionImportableSharedGpuContextImage image)
{
return new CompositionImportedGpuImage(_compositor, _context, _externalObjects,
() => _externalObjects.ImportImage(image));
() => _externalObjects.ImportImage(image), null);
}
public ICompositionImportedGpuSemaphore ImportSemaphore(IPlatformHandle handle)
=> new CompositionImportedGpuSemaphore(handle, _compositor, _context, _externalObjects);
{
handle = _externalObjectsWithHandleWrap?.WrapSemaphoreHandleOnAnyThread(handle) ?? handle;
return new CompositionImportedGpuSemaphore(handle, _compositor, _context, _externalObjects);
}
public ICompositionImportedGpuImage ImportSemaphore(ICompositionImportableSharedGpuContextSemaphore image)
{
@ -61,13 +69,17 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject
public CompositionGpuImportedObjectBase(Compositor compositor,
IPlatformRenderInterfaceContext context,
IExternalObjectsRenderInterfaceContextFeature feature)
IExternalObjectsRenderInterfaceContextFeature feature, IPlatformHandle? handle)
{
Compositor = compositor;
Context = context;
Feature = feature;
ImportCompleted = Compositor.InvokeServerJobAsync(Import);
ImportCompleted = Compositor.InvokeServerJobAsync(() =>
{
using var _ = handle as IExternalObjectsWrappedGpuHandle;
Import();
});
}
protected abstract void Import();
@ -93,7 +105,7 @@ class CompositionImportedGpuImage : CompositionGpuImportedObjectBase, ICompositi
public CompositionImportedGpuImage(Compositor compositor,
IPlatformRenderInterfaceContext context,
IExternalObjectsRenderInterfaceContextFeature feature,
Func<IPlatformRenderInterfaceImportedImage> importer): base(compositor, context, feature)
Func<IPlatformRenderInterfaceImportedImage> importer, IPlatformHandle? handle): base(compositor, context, feature, handle)
{
_importer = importer;
}
@ -128,7 +140,7 @@ class CompositionImportedGpuSemaphore : CompositionGpuImportedObjectBase, ICompo
public CompositionImportedGpuSemaphore(IPlatformHandle handle,
Compositor compositor, IPlatformRenderInterfaceContext context,
IExternalObjectsRenderInterfaceContextFeature feature) : base(compositor, context, feature)
IExternalObjectsRenderInterfaceContextFeature feature) : base(compositor, context, feature, handle)
{
_handle = handle;
}

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

@ -75,6 +75,19 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp
Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context);
}
}
public void UpdateWithTimelineSemaphores(CompositionImportedGpuImage image,
CompositionImportedGpuSemaphore wait, ulong waitForValue,
CompositionImportedGpuSemaphore signal, ulong signalValue)
{
using (Compositor.RenderInterface.EnsureCurrent())
{
PerformSanityChecks(image);
if (!wait.IsUsable || !signal.IsUsable)
throw new PlatformGraphicsContextLostException();
Update(image.Image.SnapshotWithTimelineSemaphores(wait.Semaphore, waitForValue, signal.Semaphore, signalValue), image.Context);
}
}
public void Dispose()
{

36
src/Avalonia.Metal/IMetalExternalObjectsFeature.cs

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
namespace Avalonia.Metal;
[PrivateApi]
public interface IMetalExternalObjectsFeature
{
IReadOnlyList<string> SupportedImageHandleTypes { get; }
IReadOnlyList<string> SupportedSemaphoreTypes { get; }
byte[]? DeviceLuid { get; }
CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
IMetalExternalTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties);
IMetalSharedEvent ImportSharedEvent(IPlatformHandle handle);
void SubmitWait(IMetalSharedEvent @event, ulong waitForValue);
void SubmitSignal(IMetalSharedEvent @event, ulong signalValue);
}
[PrivateApi]
public interface IMetalExternalTexture : IDisposable
{
int Width { get; }
int Height { get; }
int Samples { get; }
IntPtr Handle { get; }
}
[PrivateApi]
public interface IMetalSharedEvent : IDisposable
{
IntPtr Handle { get; }
}

159
src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs

@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.OpenGL;
using Avalonia.Native.Interop;
using System.Drawing;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Native
@ -13,7 +14,7 @@ namespace Avalonia.Native
{
private readonly IAvnGlDisplay _display;
public AvaloniaNativeGlPlatformGraphics(IAvnGlDisplay display)
public AvaloniaNativeGlPlatformGraphics(IAvnGlDisplay display, IAvaloniaNativeFactory factory)
{
_display = display;
var context = display.CreateContext(null);
@ -36,7 +37,7 @@ namespace Avalonia.Native
});
}
GlDisplay = new GlDisplay(display, glInterface, context.SampleCount, context.StencilSize);
GlDisplay = new GlDisplay(display, glInterface, context.SampleCount, context.StencilSize, factory);
SharedContext =(GlContext)CreateContext();
}
@ -59,12 +60,15 @@ namespace Avalonia.Native
{
private readonly IAvnGlDisplay _display;
public GlDisplay(IAvnGlDisplay display, GlInterface glInterface, int sampleCount, int stencilSize)
public GlDisplay(IAvnGlDisplay display, GlInterface glInterface, int sampleCount, int stencilSize,
IAvaloniaNativeFactory factory)
{
_display = display;
SampleCount = sampleCount;
StencilSize = stencilSize;
Factory = factory;
GlInterface = glInterface;
MemoryHelper = factory.CreateMemoryManagementHelper();
}
public GlInterface GlInterface { get; }
@ -72,6 +76,9 @@ namespace Avalonia.Native
public int SampleCount { get; }
public int StencilSize { get; }
public IAvaloniaNativeFactory Factory { get; }
public IAvnNativeObjectsMemoryManagement MemoryHelper { get; }
public void ClearContext() => _display.LegacyClearCurrentContext();
@ -84,6 +91,8 @@ namespace Avalonia.Native
{
private readonly GlDisplay _display;
private readonly GlContext _sharedWith;
private readonly GpuHandleWrapFeature _handleWrapFeature;
private readonly GlExternalObjectsFeature _externalObjects;
public IAvnGlContext Context { get; private set; }
public GlContext(GlDisplay display, GlContext sharedWith, IAvnGlContext context, GlVersion version)
@ -92,6 +101,8 @@ namespace Avalonia.Native
_sharedWith = sharedWith;
Context = context;
Version = version;
_handleWrapFeature = new GpuHandleWrapFeature(display.Factory);
_externalObjects = new GlExternalObjectsFeature(this, display);
}
public GlVersion Version { get; }
@ -128,7 +139,14 @@ namespace Avalonia.Native
Context = null;
}
public object TryGetFeature(Type featureType) => null;
public object TryGetFeature(Type featureType)
{
if (featureType == typeof(IExternalObjectsHandleWrapRenderInterfaceContextFeature))
return _handleWrapFeature;
if (featureType == typeof(IGlContextExternalObjectsFeature))
return _externalObjects;
return null;
}
}
@ -203,6 +221,137 @@ namespace Avalonia.Native
var avnContext = (GlContext)context;
return new GlPlatformSurfaceRenderTarget(_topLevel.CreateGlRenderTarget(avnContext.Context), avnContext);
}
}
class GlExternalObjectsFeature : IGlContextExternalObjectsFeature
{
private readonly GlContext _context;
private readonly GlDisplay _display;
public unsafe GlExternalObjectsFeature(GlContext context, GlDisplay display)
{
_context = context;
_display = display;
ulong registryId = 0;
if (context.Context.GetIOKitRegistryId(&registryId) != 0)
{
// We are reversing bytes to match MoltenVK (LUID is a Vulkan term after all)
DeviceLuid = BitConverter.GetBytes(registryId).Reverse().ToArray();
}
}
public IReadOnlyList<string> SupportedImportableExternalImageTypes { get; } =
[KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef];
public IReadOnlyList<string> SupportedExportableExternalImageTypes { get; } = [];
public IReadOnlyList<string> SupportedImportableExternalSemaphoreTypes { get; } =
[KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent];
public IReadOnlyList<string> SupportedExportableExternalSemaphoreTypes { get; } = [];
public IReadOnlyList<PlatformGraphicsExternalImageFormat> GetSupportedFormatsForExternalMemoryType(string type)
{
return [PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm];
}
public IGlExportableExternalImageTexture CreateImage(string type, PixelSize size, PlatformGraphicsExternalImageFormat format) =>
throw new NotSupportedException();
public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
public IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
{
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef)
{
if (properties.Format != PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm)
throw new OpenGlException("Only B8G8R8A8UNorm format is supported for IOSurfaceRef");
using (_context.EnsureCurrent())
{
_context.GlInterface.GetIntegerv(GlConsts.GL_TEXTURE_BINDING_RECTANGLE, out var oldTexture);
var textureId = _context.GlInterface.GenTexture();
_context.GlInterface.BindTexture(GlConsts.GL_TEXTURE_RECTANGLE, textureId);
var error = _context.Context.texImageIOSurface2D(GlConsts.GL_TEXTURE_RECTANGLE, GlConsts.GL_RGBA8,
properties.Width, properties.Height, GlConsts.GL_BGRA, GlConsts.GL_UNSIGNED_INT_8_8_8_8_REV,
handle.Handle, 0);
//var error = 0;
_context.GlInterface.BindTexture(GlConsts.GL_TEXTURE_RECTANGLE, oldTexture);
if(error != 0)
{
_context.GlInterface.DeleteTexture(textureId);
throw new OpenGlException("CGLTexImageIOSurface2D returned " + error);
}
return new ImportedTexture(_context, GlConsts.GL_TEXTURE_RECTANGLE, textureId,
GlConsts.GL_RGBA8, properties);
}
}
throw new NotSupportedException("This handle type is not supported");
}
public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle)
{
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent)
{
var imported = _display.Factory.ImportMTLSharedEvent(handle.Handle);
return new MtlEventSemaphore(imported);
}
throw new NotSupportedException("This handle type is not supported");
}
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
{
return CompositionGpuImportedImageSynchronizationCapabilities.Automatic |
CompositionGpuImportedImageSynchronizationCapabilities.TimelineSemaphores;
}
public byte[] DeviceLuid { get; }
public byte[] DeviceUuid { get; }
}
class MtlEventSemaphore(IAvnMTLSharedEvent inner) : IGlExternalSemaphore
{
public void WaitSemaphore(IGlExternalImageTexture texture) =>
throw new NotSupportedException("This is a timeline semaphore");
public void SignalSemaphore(IGlExternalImageTexture texture) =>
throw new NotSupportedException("This is a timeline semaphore");
public void WaitTimelineSemaphore(IGlExternalImageTexture texture, ulong value) =>
inner.Wait(value, 1000);
public void SignalTimelineSemaphore(IGlExternalImageTexture texture, ulong value) =>
inner.SetSignaledValue(value);
public void Dispose() => inner.Dispose();
}
class ImportedTexture(GlContext context, int type, int id, int internalFormat,
PlatformGraphicsExternalImageProperties properties) : IGlExternalImageTexture
{
private bool _disposed;
public void Dispose()
{
if(_disposed)
return;
try
{
_disposed = true;
using (context.EnsureCurrent())
context.GlInterface.DeleteTexture(id);
}
catch
{
// Ignore, context is likely broken
}
}
public void AcquireKeyedMutex(uint key) => throw new NotSupportedException();
public void ReleaseKeyedMutex(uint key) => throw new NotSupportedException();
public int TextureId => id;
public int InternalFormat => internalFormat;
public int TextureType => type;
public PlatformGraphicsExternalImageProperties Properties => properties;
}
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -149,7 +149,7 @@ namespace Avalonia.Native
{
try
{
_platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
_platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay(), _factory);
break;
}
catch (Exception)

55
src/Avalonia.Native/GpuHandleWrapFeature.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Native.Interop;
using Avalonia.Platform;
namespace Avalonia.Native;
class GpuHandleWrapFeature : IExternalObjectsHandleWrapRenderInterfaceContextFeature
{
private readonly IAvnNativeObjectsMemoryManagement _helper;
public GpuHandleWrapFeature(IAvaloniaNativeFactory factory)
{
_helper = factory.CreateMemoryManagementHelper();
}
public IExternalObjectsWrappedGpuHandle? WrapImageHandleOnAnyThread(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
{
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef)
{
_helper.RetainCFObject(handle.Handle);
return new CFObjectWrapper(_helper, handle.Handle, handle.HandleDescriptor);
}
return null;
}
public IExternalObjectsWrappedGpuHandle? WrapSemaphoreHandleOnAnyThread(IPlatformHandle handle)
{
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent)
{
_helper.RetainNSObject(handle.Handle);
return new NSObjectWrapper(_helper, handle.Handle, handle.HandleDescriptor);
}
return null;
}
class NSObjectWrapper(IAvnNativeObjectsMemoryManagement helper, IntPtr handle, string descriptor) : IExternalObjectsWrappedGpuHandle
{
public void Dispose() => helper.ReleaseNSObject(handle);
public IntPtr Handle => handle;
public string HandleDescriptor => descriptor;
}
class CFObjectWrapper(IAvnNativeObjectsMemoryManagement helper, IntPtr handle, string descriptor) : IExternalObjectsWrappedGpuHandle
{
public void Dispose()
{
helper.ReleaseCFObject(handle);
}
public IntPtr Handle => handle;
public string HandleDescriptor => descriptor;
}
}

103
src/Avalonia.Native/Metal.cs

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Metal;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Utilities;
@ -9,14 +12,16 @@ namespace Avalonia.Native;
class MetalPlatformGraphics : IPlatformGraphics
{
private readonly IAvaloniaNativeFactory _factory;
private readonly IAvnMetalDisplay _display;
public MetalPlatformGraphics(IAvaloniaNativeFactory factory)
{
_factory = factory;
_display = factory.ObtainMetalDisplay();
}
public bool UsesSharedContext => false;
public IPlatformGraphicsContext CreateContext() => new MetalDevice(_display.CreateDevice());
public IPlatformGraphicsContext CreateContext() => new MetalDevice(_factory, _display.CreateDevice());
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
}
@ -25,11 +30,15 @@ class MetalDevice : IMetalDevice
{
public IAvnMetalDevice Native { get; private set; }
private DisposableLock _syncRoot = new();
private readonly GpuHandleWrapFeature _handleWrapFeature;
private readonly MetalExternalObjectsFeature _externalObjectsFeature;
public MetalDevice(IAvnMetalDevice native)
public MetalDevice(IAvaloniaNativeFactory factory, IAvnMetalDevice native)
{
Native = native;
_handleWrapFeature = new GpuHandleWrapFeature(factory);
_externalObjectsFeature = new MetalExternalObjectsFeature(native);
}
public void Dispose()
@ -38,7 +47,14 @@ class MetalDevice : IMetalDevice
Native = null;
}
public object TryGetFeature(Type featureType) => null;
public object TryGetFeature(Type featureType)
{
if (featureType == typeof(IExternalObjectsHandleWrapRenderInterfaceContextFeature))
return _handleWrapFeature;
if (featureType == typeof(IMetalExternalObjectsFeature))
return _externalObjectsFeature;
return null;
}
public bool IsLost => false;
@ -67,6 +83,85 @@ class MetalPlatformSurface : IMetalPlatformSurface
}
}
internal class MetalExternalObjectsFeature : IMetalExternalObjectsFeature
{
private readonly IAvnMetalDevice _device;
public unsafe MetalExternalObjectsFeature(IAvnMetalDevice device)
{
_device = device;
ulong registryId;
if (_device.GetIOKitRegistryId(&registryId) != 0)
{
DeviceLuid = BitConverter.GetBytes(registryId).Reverse().ToArray();
}
}
public IReadOnlyList<string> SupportedImageHandleTypes { get; } =
[KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef];
public IReadOnlyList<string> SupportedSemaphoreTypes { get; } =
[KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent];
public byte[] DeviceLuid { get; }
public CompositionGpuImportedImageSynchronizationCapabilities
GetSynchronizationCapabilities(string imageHandleType) =>
CompositionGpuImportedImageSynchronizationCapabilities.TimelineSemaphores;
public IMetalExternalTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
{
var format = properties.Format switch
{
PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => AvnPixelFormat.kAvnRgba8888,
PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => AvnPixelFormat.kAvnBgra8888,
_ => throw new NotSupportedException("Pixel format is not supported")
};
if (handle.HandleDescriptor != KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef)
throw new NotSupportedException();
return new ImportedTexture(_device.ImportIOSurface(handle.Handle, format));
}
public IMetalSharedEvent ImportSharedEvent(IPlatformHandle handle)
{
if (handle.HandleDescriptor != KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent)
throw new NotSupportedException();
return new SharedEvent(_device.ImportSharedEvent(handle.Handle));
}
class ImportedTexture(IAvnMetalTexture texture) : IMetalExternalTexture
{
public void Dispose() => texture.Dispose();
public int Width => texture.Width;
public int Height => texture.Height;
public int Samples => texture.SampleCount;
public IntPtr Handle => texture.NativeHandle;
}
class SharedEvent(IAvnMTLSharedEvent inner) : IMetalSharedEvent
{
public IAvnMTLSharedEvent Native => inner;
public void Dispose()
{
inner.Dispose();
}
public IntPtr Handle => inner.NativeHandle;
}
public void SubmitWait(IMetalSharedEvent @event, ulong waitForValue) =>
_device.SubmitWait(((SharedEvent)@event).Native, waitForValue);
public void SubmitSignal(IMetalSharedEvent @event, ulong signalValue) =>
_device.SubmitSignal(((SharedEvent)@event).Native, signalValue);
}
internal class MetalRenderTarget : IMetalPlatformSurfaceRenderTarget
{
private IAvnMetalRenderTarget _native;

40
src/Avalonia.Native/avn.idl

@ -2,6 +2,7 @@
@clr-access internal
@clr-map bool int
@clr-map u_int64_t ulong
@clr-map uint64_t ulong
@clr-map int64_t long
@clr-map long IntPtr
@cpp-preamble @@
@ -700,6 +701,8 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv);
HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv);
HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv);
HRESULT ImportMTLSharedEvent([intptr]void* idMtlSharedEvent, IAvnMTLSharedEvent** ppv);
HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -1043,6 +1046,9 @@ interface IAvnGlContext : IUnknown
int GetSampleCount();
int GetStencilSize();
[intptr]void* GetNativeHandle();
int texImageIOSurface2D(int target, int internal_format,
int width, int height, int format, int type, [intptr]void* ioSurface, int plane);
bool GetIOKitRegistryId(uint64_t* value);
}
[uuid(931062d2-5bc8-4062-8588-83dd8deb99c2)]
@ -1069,6 +1075,11 @@ interface IAvnMetalDevice : IUnknown
{
[intptr]void* GetDevice();
[intptr]void* GetQueue();
bool GetIOKitRegistryId(uint64_t* value);
HRESULT ImportIOSurface([intptr]void* handle, AvnPixelFormat pixelFormat, IAvnMetalTexture** ppv);
HRESULT ImportSharedEvent([intptr]void*mtlSharedEventInstance, IAvnMTLSharedEvent**ppv);
HRESULT SubmitWait(IAvnMTLSharedEvent* ev, uint64_t value);
HRESULT SubmitSignal(IAvnMTLSharedEvent* ev, uint64_t value);
}
[uuid(f1306b71-eca0-426e-8700-105192693b1a)]
@ -1076,6 +1087,35 @@ interface IAvnMetalRenderTarget : IUnknown
{
HRESULT BeginDrawing(IAvnMetalRenderingSession** ret);
}
[uuid(a1f4fcde-9152-48bd-bf8a-b1b651134a69)]
interface IAvnMTLSharedEvent : IUnknown
{
[intptr]void* GetNativeHandle();
bool Wait(uint64_t value, uint64_t timeoutMS);
void SetSignaledValue(uint64_t value);
uint64_t GetSignaledValue();
}
[uuid(722aad20-a87b-4ce5-b50f-f05cfa4cda39)]
interface IAvnMetalTexture
{
[intptr]void* GetNativeHandle();
int GetWidth();
int GetHeight();
int GetSampleCount();
}
[uuid(74027aa2-5262-45a5-a74a-5a53373dcc17)]
interface IAvnNativeObjectsMemoryManagement
{
void RetainNSObject([intptr] void* obj);
void ReleaseNSObject([intptr] void* obj);
uint64_t GetRetainCountForNSObject([intptr] void* obj);
void RetainCFObject([intptr] void* obj);
void ReleaseCFObject([intptr] void* obj);
int64_t GetRetainCountForCFObject([intptr] void* obj);
}
[uuid(e625b406-f04c-484e-946a-4abd2c6015ad)]
interface IAvnMetalRenderingSession : IUnknown

11
src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs

@ -249,6 +249,16 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
var dstLayout = 0;
_ext.SignalSemaphoreEXT(_semaphore, 0, null, 1, &texId, &dstLayout);
}
public void WaitTimelineSemaphore(IGlExternalImageTexture texture, ulong value)
{
throw new NotSupportedException("This semaphore type doesn't support value-based wait");
}
public void SignalTimelineSemaphore(IGlExternalImageTexture texture, ulong value)
{
throw new NotSupportedException("This semaphore type doesn't support value-based signaling");
}
}
private class ExternalImageTexture : IGlExternalImageTexture
@ -286,6 +296,7 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
public int TextureId { get; }
public int InternalFormat => GL_RGBA8;
public int TextureType => GL_TEXTURE_2D;
public PlatformGraphicsExternalImageProperties Properties { get; }
}
}

6
src/Avalonia.OpenGL/GlConsts.cs

@ -544,7 +544,7 @@ namespace Avalonia.OpenGL
// public const int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034;
// public const int GL_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366;
// public const int GL_UNSIGNED_INT_8_8_8_8 = 0x8035;
// public const int GL_UNSIGNED_INT_8_8_8_8_REV = 0x8367;
public const int GL_UNSIGNED_INT_8_8_8_8_REV = 0x8367;
// public const int GL_UNSIGNED_INT_10_10_10_2 = 0x8036;
// public const int GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368;
// public const int GL_LIGHT_MODEL_COLOR_CONTROL = 0x81F8;
@ -1231,8 +1231,8 @@ namespace Avalonia.OpenGL
// public const int GL_MAX_TEXTURE_BUFFER_SIZE = 0x8C2B;
// public const int GL_TEXTURE_BINDING_BUFFER = 0x8C2C;
// public const int GL_TEXTURE_BUFFER_DATA_STORE_BINDING = 0x8C2D;
// public const int GL_TEXTURE_RECTANGLE = 0x84F5;
// public const int GL_TEXTURE_BINDING_RECTANGLE = 0x84F6;
public const int GL_TEXTURE_RECTANGLE = 0x84F5;
public const int GL_TEXTURE_BINDING_RECTANGLE = 0x84F6;
// public const int GL_PROXY_TEXTURE_RECTANGLE = 0x84F7;
// public const int GL_MAX_RECTANGLE_TEXTURE_SIZE = 0x84F8;
// public const int GL_R8_SNORM = 0x8F94;

12
src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
namespace Avalonia.OpenGL;
//TODO12: Make private and expose IBitmapImpl-based import API for composition visuals
[NotClientImplementable]
public interface IGlContextExternalObjectsFeature
{
IReadOnlyList<string> SupportedImportableExternalImageTypes { get; }
@ -23,10 +26,14 @@ public interface IGlContextExternalObjectsFeature
public byte[]? DeviceUuid { get; }
}
//TODO12: Make private and expose IBitmapImpl-based import API for composition visuals
[NotClientImplementable]
public interface IGlExternalSemaphore : IDisposable
{
void WaitSemaphore(IGlExternalImageTexture texture);
void SignalSemaphore(IGlExternalImageTexture texture);
void WaitTimelineSemaphore(IGlExternalImageTexture texture, ulong value);
void SignalTimelineSemaphore(IGlExternalImageTexture texture, ulong value);
}
public interface IGlExportableExternalSemaphore : IGlExternalSemaphore
@ -34,12 +41,17 @@ public interface IGlExportableExternalSemaphore : IGlExternalSemaphore
IPlatformHandle GetHandle();
}
[NotClientImplementable]
public interface IGlExternalImageTexture : IDisposable
{
void AcquireKeyedMutex(uint key);
void ReleaseKeyedMutex(uint key);
int TextureId { get; }
int InternalFormat { get; }
/// <summary>
/// GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE
/// </summary>
public int TextureType { get; }
PlatformGraphicsExternalImageProperties Properties { get; }
}

75
src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalExternalObjectsFeature.cs

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Avalonia.Metal;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using SkiaSharp;
namespace Avalonia.Skia.Metal;
class SkiaMetalExternalObjectsFeature(SkiaMetalGpu gpu, IMetalExternalObjectsFeature inner) : IExternalObjectsRenderInterfaceContextFeature
{
public IReadOnlyList<string> SupportedImageHandleTypes => inner.SupportedImageHandleTypes;
public IReadOnlyList<string> SupportedSemaphoreTypes => inner.SupportedSemaphoreTypes;
class ImportedSemaphore(IMetalSharedEvent ev) : IPlatformRenderInterfaceImportedSemaphore
{
public IMetalSharedEvent Event => ev;
public void Dispose() => ev.Dispose();
}
class ImportedImage(SkiaMetalGpu gpu, IMetalExternalObjectsFeature feature, IMetalExternalTexture texture,
SKColorType colorType, bool topLeftOrigin) : IPlatformRenderInterfaceImportedImage
{
public void Dispose() => texture.Dispose();
public IBitmapImpl SnapshotWithKeyedMutex(uint acquireIndex, uint releaseIndex) => throw new System.NotSupportedException();
public IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
IPlatformRenderInterfaceImportedSemaphore signalSemaphore) =>
throw new System.NotSupportedException();
public IBitmapImpl SnapshotWithTimelineSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
ulong waitForValue, IPlatformRenderInterfaceImportedSemaphore signalSemaphore, ulong signalValue)
{
gpu.GrContext.Flush(true, false);
feature.SubmitWait(((ImportedSemaphore)waitForSemaphore).Event, waitForValue);
using var backendTarget = new GRBackendRenderTarget(texture.Width, texture.Height, new GRMtlTextureInfo(texture.Handle));
using var surface = SKSurface.Create(gpu.GrContext, backendTarget,
topLeftOrigin ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
colorType);
var rv = new ImmutableBitmap(surface.Snapshot());
gpu.GrContext.Flush();
feature.SubmitSignal(((ImportedSemaphore)signalSemaphore).Event, signalValue);
return rv;
}
public IBitmapImpl SnapshotWithAutomaticSync() => throw new System.NotSupportedException();
}
public IPlatformRenderInterfaceImportedImage ImportImage(IPlatformHandle handle,
PlatformGraphicsExternalImageProperties properties)
{
var format = properties.Format switch
{
PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => SKColorType.Rgba8888,
PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => SKColorType.Bgra8888,
_ => throw new NotSupportedException("Pixel format is not supported")
};
return new ImportedImage(gpu, inner, inner.ImportImage(handle, properties), format,
properties.TopLeftOrigin);
}
public IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image) => throw new System.NotSupportedException();
public IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle) => new ImportedSemaphore(inner.ImportSharedEvent(handle));
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
=> inner.GetSynchronizationCapabilities(imageHandleType);
public byte[]? DeviceUuid { get; } = null;
public byte[]? DeviceLuid => inner.DeviceLuid;
}

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

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Metal;
using Avalonia.Platform;
using SkiaSharp;
@ -11,6 +10,9 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext
{
private GRContext? _context;
private readonly IMetalDevice _device;
private readonly SkiaMetalExternalObjectsFeature? _externalObjects;
internal GRContext GrContext => _context ?? throw new ObjectDisposedException(nameof(SkiaMetalGpu));
public SkiaMetalGpu(IMetalDevice device, long? maxResourceBytes)
{
@ -19,6 +21,8 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext
new GRContextOptions { AvoidStencilBuffers = true })
?? throw new InvalidOperationException("Unable to create GRContext from Metal device.");
_device = device;
if (device.TryGetFeature<IMetalExternalObjectsFeature>() is { } externalObjects)
_externalObjects = new SkiaMetalExternalObjectsFeature(this, externalObjects);
if (maxResourceBytes.HasValue)
_context.SetResourceCacheLimit(maxResourceBytes.Value);
}
@ -29,15 +33,21 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext
_context = null;
}
public object? TryGetFeature(Type featureType) => null;
public object? TryGetFeature(Type featureType)
{
if (featureType == typeof(IExternalObjectsHandleWrapRenderInterfaceContextFeature))
return _device.TryGetFeature(featureType);
if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
return _externalObjects;
return null;
}
public bool IsLost => false;
public IDisposable EnsureCurrent() => _device.EnsureCurrent();
public IPlatformGraphicsContext? PlatformGraphicsContext => _device;
public IScopedResource<GRContext> TryGetGrContext() =>
ScopedResource<GRContext>.Create(_context ?? throw new ObjectDisposedException(nameof(SkiaMetalGpu)),
EnsureCurrent().Dispose);
ScopedResource<GRContext>.Create(GrContext, EnsureCurrent().Dispose);
public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces)
{

57
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
@ -108,17 +109,17 @@ internal class GlSkiaImportedImage : IPlatformRenderInterfaceImportedImage
_ => SKColorType.Rgba8888
};
SKSurface? TryCreateSurface(int textureId, int format, int width, int height, bool topLeft)
SKSurface? TryCreateSurface(int target, int textureId, int format, int width, int height, bool topLeft)
{
var origin = topLeft ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft;
using var texture = new GRBackendTexture(width, height, false,
new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)format));
new GRGlTextureInfo((uint)target, (uint)textureId, (uint)format));
var surf = SKSurface.Create(_gpu.GrContext, texture, origin, SKColorType.Rgba8888);
if (surf != null)
return surf;
using var unformatted = new GRBackendTexture(width, height, false,
new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId));
new GRGlTextureInfo((uint)GlConsts.GL_TEXTURE_2D, (uint)textureId));
return SKSurface.Create(_gpu.GrContext, unformatted, origin, SKColorType.Rgba8888);
}
@ -130,16 +131,34 @@ internal class GlSkiaImportedImage : IPlatformRenderInterfaceImportedImage
var internalFormat = _image?.InternalFormat ?? _sharedTexture!.InternalFormat;
var textureId = _image?.TextureId ?? _sharedTexture!.TextureId;
var topLeft = _image?.Properties.TopLeftOrigin ?? false;
var textureType = _image?.TextureType ?? GlConsts.GL_TEXTURE_2D;
using var texture = new GRBackendTexture(width, height, false,
new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)internalFormat));
IBitmapImpl rv;
using (var surf = TryCreateSurface(textureId, internalFormat, width, height, topLeft))
using (var surf = TryCreateSurface(textureType, textureId, internalFormat, width, height, topLeft))
{
if (surf == null)
throw new OpenGlException("Unable to consume provided texture");
rv = new ImmutableBitmap(surf.Snapshot());
var snapshot = surf.Snapshot();
var context = _gpu.GlContext;
rv = new ImmutableBitmap(snapshot, () =>
{
IDisposable? restoreContext = null;
try
{
restoreContext = context.EnsureCurrent();
}
catch
{
// Ignore, context is likely dead
}
using (restoreContext)
{
snapshot.Dispose();
}
});
}
_gpu.GrContext.Flush();
@ -192,6 +211,30 @@ internal class GlSkiaImportedImage : IPlatformRenderInterfaceImportedImage
}
}
public IBitmapImpl SnapshotWithTimelineSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
ulong waitForValue, IPlatformRenderInterfaceImportedSemaphore signalSemaphore, ulong signalValue)
{
if (_image is null)
{
throw new NotSupportedException("Only supported with an external image");
}
var wait = (GlSkiaImportedSemaphore)waitForSemaphore;
var signal = (GlSkiaImportedSemaphore)signalSemaphore;
using (_gpu.EnsureCurrent())
{
wait.Semaphore.WaitTimelineSemaphore(_image, waitForValue);
try
{
return TakeSnapshot();
}
finally
{
signal.Semaphore.SignalTimelineSemaphore(_image, signalValue);
}
}
}
public IBitmapImpl SnapshotWithAutomaticSync()
{
using (_gpu.EnsureCurrent())

2
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -173,6 +173,8 @@ namespace Avalonia.Skia
return this;
if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
return _externalObjectsFeature;
if (featureType == typeof(IExternalObjectsHandleWrapRenderInterfaceContextFeature))
return _glContext.TryGetFeature(featureType);
return null;
}

4
src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaExternalObjectsFeature.cs

@ -108,6 +108,10 @@ internal class VulkanSkiaExternalObjectsFeature : IExternalObjectsRenderInterfac
return new ImmutableBitmap(image);
}
public IBitmapImpl SnapshotWithTimelineSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
ulong waitForValue, IPlatformRenderInterfaceImportedSemaphore signalSemaphore, ulong signalValue) =>
throw new NotSupportedException();
public IBitmapImpl SnapshotWithAutomaticSync() => throw new NotSupportedException();
}

9
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -14,6 +14,7 @@ namespace Avalonia.Skia
{
private readonly SKImage _image;
private readonly SKBitmap? _bitmap;
private readonly Action? _customImageDispose = null;
/// <summary>
/// Create immutable bitmap from given stream.
@ -39,9 +40,10 @@ namespace Avalonia.Skia
}
}
public ImmutableBitmap(SKImage image)
public ImmutableBitmap(SKImage image, Action? customImageDispose = null)
{
_image = image;
_customImageDispose = customImageDispose;
PixelSize = new PixelSize(image.Width, image.Height);
Dpi = new Vector(96, 96);
}
@ -156,7 +158,10 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void Dispose()
{
_image.Dispose();
if (_customImageDispose != null)
_customImageDispose();
else
_image.Dispose();
_bitmap?.Dispose();
}

2
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs

@ -68,6 +68,8 @@ internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture
public int TextureId { get; private set; }
public int InternalFormat { get; }
public int TextureType => GL_TEXTURE_2D;
public PlatformGraphicsExternalImageProperties Properties { get; }
}

Loading…
Cancel
Save