Browse Source
* [OSX] Implemented IOSurface/MTLSharedEvent interop APIs * Bump Xcode? * Xcode? * APIDiff * Use different XCode versions because of how awesome appium is * A hack for crapium * Replace SkiaMetalApi usages * Update API suppressions --------- Co-authored-by: Timothy Miller <innerlogic4321@gmail.com> Co-authored-by: Julien Lebosquain <julien@lebosquain.net>pull/19766/head
committed by
GitHub
41 changed files with 1458 additions and 129 deletions
@ -0,0 +1,9 @@ |
|||
// The only reason this file exists is Appium which limits our highest Xcode version to 15.2. Please, purge Appium from our codebase
|
|||
#ifndef crapium_h |
|||
#define crapium_h |
|||
#import <Foundation/Foundation.h> |
|||
@protocol MTLSharedEvent; |
|||
|
|||
API_AVAILABLE(macos(12)) |
|||
extern BOOL MtlSharedEventWaitUntilSignaledValueHack(id<MTLSharedEvent> ev, uint64_t value, uint64_t milliseconds); |
|||
#endif /* crapium_h */ |
|||
@ -0,0 +1,21 @@ |
|||
// The only reason this file exists is Appium which limits our highest Xcode version to 15.2. Please, purge Appium from our codebase |
|||
#import <Foundation/Foundation.h> |
|||
#import "crapium.h" |
|||
@class MTLSharedEventHandle; |
|||
@protocol MTLSharedEvent; |
|||
@protocol MTLEvent; |
|||
|
|||
typedef void (^MTLSharedEventNotificationBlock)(id <MTLSharedEvent>, uint64_t value); |
|||
|
|||
API_AVAILABLE(macos(10.14), ios(12.0)) |
|||
@protocol MTLSharedEvent <MTLEvent> |
|||
// Synchronously wait for the signaledValue to be greater than or equal to 'value', with a timeout |
|||
// specified in milliseconds. Returns YES if the value was signaled before the timeout, otherwise NO. |
|||
- (BOOL)waitUntilSignaledValue:(uint64_t)value timeoutMS:(uint64_t)milliseconds API_AVAILABLE(macos(12.0), ios(15.0)); |
|||
@end |
|||
|
|||
API_AVAILABLE(macos(12)) |
|||
extern BOOL MtlSharedEventWaitUntilSignaledValueHack(id<MTLSharedEvent> ev, uint64_t value, uint64_t milliseconds) |
|||
{ |
|||
return [ev waitUntilSignaledValue:value timeoutMS:milliseconds]; |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
#include "common.h" |
|||
class MemHelper : public ComSingleObject<IAvnNativeObjectsMemoryManagement, &IID_IAvnNativeObjectsMemoryManagement> |
|||
{ |
|||
FORWARD_IUNKNOWN() |
|||
void RetainNSObject(void *object) override |
|||
{ |
|||
::RetainNSObject(object); |
|||
} |
|||
|
|||
void ReleaseNSObject(void *object) override |
|||
{ |
|||
::ReleaseNSObject(object); |
|||
} |
|||
|
|||
void RetainCFObject(void *object) override |
|||
{ |
|||
CFRetain(object); |
|||
} |
|||
|
|||
void ReleaseCFObject(void *object) override |
|||
{ |
|||
CFRelease(object); |
|||
} |
|||
|
|||
uint64_t GetRetainCountForNSObject(void *obj) override { |
|||
return ::GetRetainCountForNSObject(obj); |
|||
} |
|||
|
|||
int64_t GetRetainCountForCFObject(void *obj) override { |
|||
return CFGetRetainCount(obj); |
|||
} |
|||
|
|||
|
|||
}; |
|||
|
|||
|
|||
extern IAvnNativeObjectsMemoryManagement* CreateMemoryManagementHelper() |
|||
{ |
|||
return new MemHelper(); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Platform; |
|||
using Silk.NET.Vulkan; |
|||
using Silk.NET.Vulkan.Extensions.EXT; |
|||
using SilkNetDemo; |
|||
|
|||
namespace GpuInterop.VulkanDemo; |
|||
|
|||
class VulkanTimelineSemaphore : IDisposable |
|||
{ |
|||
private VulkanContext _resources; |
|||
|
|||
public unsafe VulkanTimelineSemaphore(VulkanContext resources) |
|||
{ |
|||
_resources = resources; |
|||
var mtlEvent = new ExportMetalObjectCreateInfoEXT |
|||
{ |
|||
SType = StructureType.ExportMetalObjectCreateInfoExt, |
|||
ExportObjectType = ExportMetalObjectTypeFlagsEXT.SharedEventBitExt |
|||
}; |
|||
|
|||
var semaphoreTypeInfo = new SemaphoreTypeCreateInfoKHR() |
|||
{ |
|||
SType = StructureType.SemaphoreTypeCreateInfo, |
|||
SemaphoreType = SemaphoreType.Timeline, |
|||
PNext = &mtlEvent |
|||
}; |
|||
|
|||
var semaphoreCreateInfo = new SemaphoreCreateInfo |
|||
{ |
|||
SType = StructureType.SemaphoreCreateInfo, |
|||
PNext = &semaphoreTypeInfo, |
|||
}; |
|||
resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out var semaphore).ThrowOnError(); |
|||
Handle = semaphore; |
|||
} |
|||
|
|||
public Semaphore Handle { get; } |
|||
public unsafe void Dispose() |
|||
{ |
|||
_resources.Api.DestroySemaphore(_resources.Device, Handle, null); |
|||
} |
|||
|
|||
|
|||
public unsafe IntPtr ExportSharedEvent() |
|||
{ |
|||
if (!_resources.Api.TryGetDeviceExtension<ExtMetalObjects>(_resources.Instance, _resources.Device, out var ext)) |
|||
throw new InvalidOperationException(); |
|||
var eventExport = new ExportMetalSharedEventInfoEXT() |
|||
{ |
|||
SType = StructureType.ExportMetalSharedEventInfoExt, |
|||
Semaphore = Handle, |
|||
}; |
|||
var export = new ExportMetalObjectsInfoEXT() |
|||
{ |
|||
SType = StructureType.ExportMetalObjectsInfoExt, |
|||
PNext = &eventExport |
|||
}; |
|||
ext.ExportMetalObjects(_resources.Device, ref export); |
|||
if (eventExport.MtlSharedEvent == IntPtr.Zero) |
|||
throw new Exception("Unable to export IOSurfaceRef"); |
|||
return eventExport.MtlSharedEvent; |
|||
} |
|||
public IPlatformHandle Export() |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
return new PlatformHandle(ExportSharedEvent(), |
|||
KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent); |
|||
throw new PlatformNotSupportedException(); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.Metal; |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalExternalObjectsFeature |
|||
{ |
|||
IReadOnlyList<string> SupportedImageHandleTypes { get; } |
|||
IReadOnlyList<string> SupportedSemaphoreTypes { get; } |
|||
byte[]? DeviceLuid { get; } |
|||
CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType); |
|||
IMetalExternalTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties); |
|||
IMetalSharedEvent ImportSharedEvent(IPlatformHandle handle); |
|||
|
|||
void SubmitWait(IMetalSharedEvent @event, ulong waitForValue); |
|||
void SubmitSignal(IMetalSharedEvent @event, ulong signalValue); |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalExternalTexture : IDisposable |
|||
{ |
|||
int Width { get; } |
|||
int Height { get; } |
|||
int Samples { get; } |
|||
IntPtr Handle { get; } |
|||
} |
|||
|
|||
[PrivateApi] |
|||
public interface IMetalSharedEvent : IDisposable |
|||
{ |
|||
IntPtr Handle { get; } |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Metal; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering.Composition; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia.Metal; |
|||
|
|||
class SkiaMetalExternalObjectsFeature(SkiaMetalGpu gpu, IMetalExternalObjectsFeature inner) : IExternalObjectsRenderInterfaceContextFeature |
|||
{ |
|||
public IReadOnlyList<string> SupportedImageHandleTypes => inner.SupportedImageHandleTypes; |
|||
|
|||
public IReadOnlyList<string> SupportedSemaphoreTypes => inner.SupportedSemaphoreTypes; |
|||
|
|||
class ImportedSemaphore(IMetalSharedEvent ev) : IPlatformRenderInterfaceImportedSemaphore |
|||
{ |
|||
public IMetalSharedEvent Event => ev; |
|||
public void Dispose() => ev.Dispose(); |
|||
} |
|||
|
|||
class ImportedImage(SkiaMetalGpu gpu, IMetalExternalObjectsFeature feature, IMetalExternalTexture texture, |
|||
SKColorType colorType, bool topLeftOrigin) : IPlatformRenderInterfaceImportedImage |
|||
{ |
|||
public void Dispose() => texture.Dispose(); |
|||
|
|||
public IBitmapImpl SnapshotWithKeyedMutex(uint acquireIndex, uint releaseIndex) => throw new System.NotSupportedException(); |
|||
|
|||
public IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore, |
|||
IPlatformRenderInterfaceImportedSemaphore signalSemaphore) => |
|||
throw new System.NotSupportedException(); |
|||
|
|||
public IBitmapImpl SnapshotWithTimelineSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore, |
|||
ulong waitForValue, IPlatformRenderInterfaceImportedSemaphore signalSemaphore, ulong signalValue) |
|||
{ |
|||
gpu.GrContext.Flush(true, false); |
|||
feature.SubmitWait(((ImportedSemaphore)waitForSemaphore).Event, waitForValue); |
|||
using var backendTarget = new GRBackendRenderTarget(texture.Width, texture.Height, new GRMtlTextureInfo(texture.Handle)); |
|||
|
|||
using var surface = SKSurface.Create(gpu.GrContext, backendTarget, |
|||
topLeftOrigin ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft, |
|||
colorType); |
|||
var rv = new ImmutableBitmap(surface.Snapshot()); |
|||
gpu.GrContext.Flush(); |
|||
feature.SubmitSignal(((ImportedSemaphore)signalSemaphore).Event, signalValue); |
|||
return rv; |
|||
} |
|||
|
|||
public IBitmapImpl SnapshotWithAutomaticSync() => throw new System.NotSupportedException(); |
|||
} |
|||
|
|||
public IPlatformRenderInterfaceImportedImage ImportImage(IPlatformHandle handle, |
|||
PlatformGraphicsExternalImageProperties properties) |
|||
{ |
|||
var format = properties.Format switch |
|||
{ |
|||
PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => SKColorType.Rgba8888, |
|||
PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => SKColorType.Bgra8888, |
|||
_ => throw new NotSupportedException("Pixel format is not supported") |
|||
}; |
|||
|
|||
return new ImportedImage(gpu, inner, inner.ImportImage(handle, properties), format, |
|||
properties.TopLeftOrigin); |
|||
} |
|||
|
|||
public IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image) => throw new System.NotSupportedException(); |
|||
|
|||
public IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle) => new ImportedSemaphore(inner.ImportSharedEvent(handle)); |
|||
|
|||
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType) |
|||
=> inner.GetSynchronizationCapabilities(imageHandleType); |
|||
|
|||
public byte[]? DeviceUuid { get; } = null; |
|||
public byte[]? DeviceLuid => inner.DeviceLuid; |
|||
} |
|||
Loading…
Reference in new issue