diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 6cf22df074..5d8cdee507 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -25,6 +25,30 @@
baseline/Avalonia/lib/net6.0/Avalonia.Base.dll
current/Avalonia/lib/net6.0/Avalonia.Base.dll
+
+ CP0006
+ M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)
+ baseline/Avalonia/lib/net6.0/Avalonia.Base.dll
+ current/Avalonia/lib/net6.0/Avalonia.Base.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType
+ baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll
+
CP0006
M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)
@@ -49,6 +73,30 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0006
+ M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType
+ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
CP0006
M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)
@@ -73,4 +121,28 @@
baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
+
+ CP0006
+ M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64)
+ baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
+ current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
+ baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+
+
+ CP0006
+ P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType
+ baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+ current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll
+
diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml
index fa83e8bccd..b34def779f 100644
--- a/azure-pipelines-integrationtests.yml
+++ b/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
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index d9a4c80fdf..956bcde6d7 100644
--- a/azure-pipelines.yml
+++ b/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
diff --git a/native/Avalonia.Native/inc/noarc.h b/native/Avalonia.Native/inc/noarc.h
index c49d975ade..7d338bf05d 100644
--- a/native/Avalonia.Native/inc/noarc.h
+++ b/native/Avalonia.Native/inc/noarc.h
@@ -8,4 +8,7 @@ public:
~CppAutoreleasePool();
};
-#define START_ARP_CALL CppAutoreleasePool __autoreleasePool
\ No newline at end of file
+#define START_ARP_CALL CppAutoreleasePool __autoreleasePool
+extern void ReleaseNSObject(void* obj);
+extern void RetainNSObject(void* obj);
+extern uint64_t GetRetainCountForNSObject(void* obj);
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
index 868551eac1..70bcfc500a 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
+++ b/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 = ""; };
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 = ""; };
+ 1AC7F1422DCA0C2E003A161B /* crapium.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objcpp; path = crapium.mm; sourceTree = ""; };
+ 1AC7F1442DCA0D6A003A161B /* crapium.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crapium.h; sourceTree = ""; };
+ 1AE55B8B2DC1060E00FD0BB3 /* memhelp.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = memhelp.mm; sourceTree = ""; };
1AFD334023E03C4F0042899B /* controlhost.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = controlhost.mm; sourceTree = ""; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; };
@@ -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 */,
diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm
index 085037978e..4fc4064df1 100644
--- a/native/Avalonia.Native/src/OSX/cgl.mm
+++ b/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);
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 819254b841..fae03984fd 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/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
diff --git a/native/Avalonia.Native/src/OSX/crapium.h b/native/Avalonia.Native/src/OSX/crapium.h
new file mode 100644
index 0000000000..0b27b68c79
--- /dev/null
+++ b/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
+@protocol MTLSharedEvent;
+
+API_AVAILABLE(macos(12))
+extern BOOL MtlSharedEventWaitUntilSignaledValueHack(id ev, uint64_t value, uint64_t milliseconds);
+#endif /* crapium_h */
diff --git a/native/Avalonia.Native/src/OSX/crapium.mm b/native/Avalonia.Native/src/OSX/crapium.mm
new file mode 100644
index 0000000000..962d7e2eda
--- /dev/null
+++ b/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
+#import "crapium.h"
+@class MTLSharedEventHandle;
+@protocol MTLSharedEvent;
+@protocol MTLEvent;
+
+typedef void (^MTLSharedEventNotificationBlock)(id , uint64_t value);
+
+API_AVAILABLE(macos(10.14), ios(12.0))
+@protocol MTLSharedEvent
+// 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 ev, uint64_t value, uint64_t milliseconds)
+{
+ return [ev waitUntilSignaledValue:value timeoutMS:milliseconds];
+}
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index f4e244efdd..2a92eb3bcf 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/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()
diff --git a/native/Avalonia.Native/src/OSX/memhelp.mm b/native/Avalonia.Native/src/OSX/memhelp.mm
new file mode 100644
index 0000000000..1efee567b4
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/memhelp.mm
@@ -0,0 +1,40 @@
+#include "common.h"
+class MemHelper : public ComSingleObject
+{
+ 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();
+}
diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm
index 5622f2040e..33aa2aeb53 100644
--- a/native/Avalonia.Native/src/OSX/metal.mm
+++ b/native/Avalonia.Native/src/OSX/metal.mm
@@ -3,6 +3,74 @@
#import
#include "common.h"
#include "rendertarget.h"
+#import "crapium.h"
+
+
+class API_AVAILABLE(macos(12.0)) AvnMTLSharedEvent : public ComSingleObject
+{
+ id _event;
+public:
+
+ AvnMTLSharedEvent(id ev) : _event(ev)
+ {
+
+ }
+
+ FORWARD_IUNKNOWN()
+
+ id 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
+{
+ id _texture;
+public:
+ FORWARD_IUNKNOWN()
+ AvnMetalTexture(id 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
{
@@ -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)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(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 device, id 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)object;
+
+ if(evId == nil)
+ return nil;
+
+
+ return new AvnMTLSharedEvent(evId);
+ }
+ else
+ {
+ return nil;
+ }
+}
diff --git a/native/Avalonia.Native/src/OSX/noarc.mm b/native/Avalonia.Native/src/OSX/noarc.mm
index 82378ce84c..6a87fc78d0 100644
--- a/native/Avalonia.Native/src/OSX/noarc.mm
+++ b/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];
+}
diff --git a/samples/GpuInterop/NativeMethods.cs b/samples/GpuInterop/NativeMethods.cs
new file mode 100644
index 0000000000..6ba8ab2223
--- /dev/null
+++ b/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);
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
index f89de4719f..08df0fe7be 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
+++ b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
@@ -167,7 +167,8 @@ namespace Avalonia.Vulkan
ReadOnlySpan waitDstStageMask = default,
ReadOnlySpan 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,
diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs
index 6983a69d86..e3957b1fea 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanContext.cs
+++ b/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()
{
- "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();
- 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
+ var requireDeviceExtensions = new List();
+ 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(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) ((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);
diff --git a/samples/GpuInterop/VulkanDemo/VulkanImage.cs b/samples/GpuInterop/VulkanDemo/VulkanImage.cs
index 0c5c2e3511..3f3c0b212c 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanImage.cs
+++ b/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 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();
+ 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();
- _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(_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()
diff --git a/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs b/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
index a748ba4bb6..323d9b4091 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
+++ b/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!);
diff --git a/samples/GpuInterop/VulkanDemo/VulkanTimelineSemaphore.cs b/samples/GpuInterop/VulkanDemo/VulkanTimelineSemaphore.cs
new file mode 100644
index 0000000000..3ce8ab307e
--- /dev/null
+++ b/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(_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();
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs b/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
index 5b06651b5f..bc610db71b 100644
--- a/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
+++ b/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; }
}
+///
+/// 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
+///
+[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();
}
diff --git a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs b/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
index b9be1f7dc9..0e6125de2f 100644
--- a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
+++ b/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);
+
+ ///
+ /// A reference to IOSurface
+ ///
+ public const string IOSurfaceRef = nameof(IOSurfaceRef);
}
///
@@ -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);
+
+ ///
+ /// A pointer to MTLSharedEvent object
+ ///
+ public const string MetalSharedEvent = nameof(MetalSharedEvent);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
index 91cafd562e..6d8562bb4a 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
+++ b/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));
}
+
+ ///
+ /// Updates the surface contents using an imported memory image using a semaphore pair as the means of synchronization
+ ///
+ /// GPU image with new surface contents
+ /// The semaphore to wait for before accessing the image
+ /// The semaphore to signal after accessing the image
+ /// A task that completes when update operation is completed and user code is free to destroy or dispose the image
+ 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));
+ }
///
/// Updates the surface contents using an unspecified automatic means of synchronization
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
index 31cbaf2a29..17fc5c8595 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
@@ -81,7 +81,11 @@ public enum CompositionGpuImportedImageSynchronizationCapabilities
///
/// Synchronization and ordering is somehow handled by the underlying platform
///
- Automatic = 4
+ Automatic = 4,
+ ///
+ /// Pre-render and after-render timeline semaphores must be provided alongside with the image
+ ///
+ TimelineSemaphores = 8
}
///
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
index 12a252ed96..2cf6288c02 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
+++ b/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();
}
public IReadOnlyList 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 importer): base(compositor, context, feature)
+ Func 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;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
index f58403f0bc..04350902a6 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
+++ b/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()
{
diff --git a/src/Avalonia.Metal/IMetalExternalObjectsFeature.cs b/src/Avalonia.Metal/IMetalExternalObjectsFeature.cs
new file mode 100644
index 0000000000..8d67d34291
--- /dev/null
+++ b/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 SupportedImageHandleTypes { get; }
+ IReadOnlyList 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; }
+}
diff --git a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
index b1caa3b757..d5787533de 100644
--- a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
+++ b/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(®istryId) != 0)
+ {
+ // We are reversing bytes to match MoltenVK (LUID is a Vulkan term after all)
+ DeviceLuid = BitConverter.GetBytes(registryId).Reverse().ToArray();
+ }
+ }
+
+ public IReadOnlyList SupportedImportableExternalImageTypes { get; } =
+ [KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef];
+ public IReadOnlyList SupportedExportableExternalImageTypes { get; } = [];
+
+ public IReadOnlyList SupportedImportableExternalSemaphoreTypes { get; } =
+ [KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent];
+
+ public IReadOnlyList SupportedExportableExternalSemaphoreTypes { get; } = [];
+ public IReadOnlyList 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;
}
}
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index fa4ae7522f..99edfd9d29 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/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)
diff --git a/src/Avalonia.Native/GpuHandleWrapFeature.cs b/src/Avalonia.Native/GpuHandleWrapFeature.cs
new file mode 100644
index 0000000000..feb394f460
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/src/Avalonia.Native/Metal.cs b/src/Avalonia.Native/Metal.cs
index ef7a1f7d85..804f63c4f7 100644
--- a/src/Avalonia.Native/Metal.cs
+++ b/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(®istryId) != 0)
+ {
+ DeviceLuid = BitConverter.GetBytes(registryId).Reverse().ToArray();
+ }
+ }
+
+ public IReadOnlyList SupportedImageHandleTypes { get; } =
+ [KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef];
+
+ public IReadOnlyList 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;
diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl
index f3dd266fb0..f0ef042493 100644
--- a/src/Avalonia.Native/avn.idl
+++ b/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
diff --git a/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs b/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
index 6778726f84..438283dc2f 100644
--- a/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
+++ b/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; }
}
}
diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs
index a841a19591..55be3da5f0 100644
--- a/src/Avalonia.OpenGL/GlConsts.cs
+++ b/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;
diff --git a/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs b/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
index 676f8eaf86..3cf26cf9a3 100644
--- a/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
+++ b/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 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; }
+ ///
+ /// GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE
+ ///
+ public int TextureType { get; }
PlatformGraphicsExternalImageProperties Properties { get; }
}
diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalExternalObjectsFeature.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalExternalObjectsFeature.cs
new file mode 100644
index 0000000000..71402e3d8d
--- /dev/null
+++ b/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 SupportedImageHandleTypes => inner.SupportedImageHandleTypes;
+
+ public IReadOnlyList 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;
+}
diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
index 8faa754ef8..ad5f624a03 100644
--- a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
+++ b/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() 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 TryGetGrContext() =>
- ScopedResource.Create(_context ?? throw new ObjectDisposedException(nameof(SkiaMetalGpu)),
- EnsureCurrent().Dispose);
+ ScopedResource.Create(GrContext, EnsureCurrent().Dispose);
public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable