diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index b64405de54..fcbb6e8534 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -85,6 +85,12 @@ baseline/netstandard2.0/Avalonia.Base.dll target/netstandard2.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Platform.IPlatformRenderInterfaceImportedImage.SnapshotWithTimelineSemaphores(Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64,Avalonia.Platform.IPlatformRenderInterfaceImportedSemaphore,System.UInt64) + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.Storage.IStorageFolder.GetFileAsync(System.String) @@ -139,6 +145,24 @@ baseline/netstandard2.0/Avalonia.Controls.dll target/netstandard2.0/Avalonia.Controls.dll + + CP0006 + M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/netstandard2.0/Avalonia.OpenGL.dll + target/netstandard2.0/Avalonia.OpenGL.dll + + + CP0006 + M:Avalonia.OpenGL.IGlExternalSemaphore.WaitTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) + baseline/netstandard2.0/Avalonia.OpenGL.dll + target/netstandard2.0/Avalonia.OpenGL.dll + + + CP0006 + P:Avalonia.OpenGL.IGlExternalImageTexture.TextureType + baseline/netstandard2.0/Avalonia.OpenGL.dll + target/netstandard2.0/Avalonia.OpenGL.dll + CP0009 T:Avalonia.Diagnostics.StyleDiagnostics 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 dc23500374..01b614cf13 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..2fd8e68459 --- /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 = gpu.SkiaMetalApi.CreateBackendRenderTarget(texture.Width, texture.Height, texture.Samples, 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 bfd6e1aa59..7e86ef5d20 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; @@ -12,12 +11,18 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext private SkiaMetalApi _api = new(); private GRContext? _context; private readonly IMetalDevice _device; + private readonly SkiaMetalExternalObjectsFeature? _externalObjects; + + internal GRContext GrContext => _context ?? throw new ObjectDisposedException(nameof(SkiaMetalGpu)); + internal SkiaMetalApi SkiaMetalApi => _api; public SkiaMetalGpu(IMetalDevice device, long? maxResourceBytes) { _context = _api.CreateContext(device.Device, device.CommandQueue, new GRContextOptions() { AvoidStencilBuffers = true }); _device = device; + if (device.TryGetFeature() is { } externalObjects) + _externalObjects = new SkiaMetalExternalObjectsFeature(this, externalObjects); if (maxResourceBytes.HasValue) _context.SetResourceCacheLimit(maxResourceBytes.Value); } @@ -28,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(SkiaMetalApi)), - EnsureCurrent().Dispose); + ScopedResource.Create(GrContext, EnsureCurrent().Dispose); public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable surfaces) { @@ -78,7 +89,7 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext var surface = SKSurface.Create(_gpu._context!, backendTarget, session.IsYFlipped ? GRSurfaceOrigin.BottomLeft : GRSurfaceOrigin.TopLeft, SKColorType.Bgra8888); - + return new SkiaMetalRenderSession(_gpu, surface, session); } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs index 2b6caf34dc..48d83f38ea 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs +++ b/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()) diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index d87ff52c2e..7336e7672b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/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; } diff --git a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaExternalObjectsFeature.cs b/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaExternalObjectsFeature.cs index 5c5427b8d9..d0b3c28d8e 100644 --- a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaExternalObjectsFeature.cs +++ b/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(); } diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index ec645692b2..1879fa6f14 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/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; /// /// 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); } @@ -154,7 +156,10 @@ namespace Avalonia.Skia /// public void Dispose() { - _image.Dispose(); + if (_customImageDispose != null) + _customImageDispose(); + else + _image.Dispose(); _bitmap?.Dispose(); } diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs index a37e178991..e98facb4fd 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs +++ b/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; } }