Browse Source

Add DMA-BUF Buffer/EGL Image

dev/timill/dma-buf
Timothy Miller 2 weeks ago
parent
commit
694c81d2d0
  1. 42
      src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs
  2. 62
      src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
  3. 42
      src/Avalonia.OpenGL/Egl/EglConsts.cs
  4. 8
      src/Avalonia.OpenGL/Egl/EglContext.cs
  5. 30
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  6. 349
      src/Avalonia.OpenGL/Features/EglDmaBufExternalObjectsFeature.cs
  7. 6
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs
  8. 19
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs
  9. 6
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs
  10. 63
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs
  11. 25
      src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs
  12. 288
      src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs

42
src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs

@ -0,0 +1,42 @@
namespace Avalonia.Platform;
/// <summary>
/// Provides DRM fourcc format constants and mapping to <see cref="PlatformGraphicsExternalImageFormat"/>.
/// </summary>
public static class PlatformGraphicsDrmFormats
{
/// <summary>DRM_FORMAT_ARGB8888: little-endian BGRA in memory = B8G8R8A8UNorm. Most common WebKit output.</summary>
public const uint DRM_FORMAT_ARGB8888 = 0x34325241;
/// <summary>DRM_FORMAT_XRGB8888: little-endian BGRX in memory = B8G8R8A8UNorm (opaque, ignore alpha).</summary>
public const uint DRM_FORMAT_XRGB8888 = 0x34325258;
/// <summary>DRM_FORMAT_ABGR8888: little-endian RGBA in memory = R8G8B8A8UNorm.</summary>
public const uint DRM_FORMAT_ABGR8888 = 0x34324241;
/// <summary>DRM_FORMAT_XBGR8888: little-endian RGBX in memory = R8G8B8A8UNorm (opaque, ignore alpha).</summary>
public const uint DRM_FORMAT_XBGR8888 = 0x34324258;
/// <summary>DRM_FORMAT_MOD_LINEAR: linear (non-tiled) memory layout.</summary>
public const ulong DRM_FORMAT_MOD_LINEAR = 0;
/// <summary>DRM_FORMAT_MOD_INVALID: indicates an invalid or unknown modifier.</summary>
public const ulong DRM_FORMAT_MOD_INVALID = 0x00FFFFFFFFFFFFFF;
/// <summary>
/// Attempts to map a DRM fourcc code to a <see cref="PlatformGraphicsExternalImageFormat"/>.
/// </summary>
/// <param name="fourcc">The DRM format fourcc code.</param>
/// <returns>The corresponding format, or null if the fourcc code is not recognized.</returns>
public static PlatformGraphicsExternalImageFormat? TryMapDrmFormat(uint fourcc)
{
return fourcc switch
{
DRM_FORMAT_ARGB8888 => PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm,
DRM_FORMAT_XRGB8888 => PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm,
DRM_FORMAT_ABGR8888 => PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
DRM_FORMAT_XBGR8888 => PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
_ => null
};
}
}

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

@ -8,6 +8,54 @@ public record struct PlatformGraphicsExternalImageProperties
public ulong MemorySize { get; set; }
public ulong MemoryOffset { get; set; }
public bool TopLeftOrigin { get; set; }
// DMA-BUF specific (ignored for other handle types)
/// <summary>
/// DRM format fourcc code (e.g., DRM_FORMAT_ARGB8888). Used by Vulkan and EGL DMA-BUF import paths.
/// </summary>
public uint DrmFourcc { get; set; }
/// <summary>
/// DRM format modifier (e.g., DRM_FORMAT_MOD_LINEAR). Determines memory layout (linear, tiled, compressed).
/// </summary>
public ulong DrmModifier { get; set; }
/// <summary>
/// Row stride in bytes for plane 0.
/// </summary>
public uint RowPitch { get; set; }
/// <summary>
/// Additional plane information for multi-plane DMA-BUF formats (planes 1-3). Null for single-plane formats.
/// </summary>
public DmaBufPlaneInfo[]? AdditionalPlanes { get; set; }
}
/// <summary>
/// Describes a single plane of a multi-plane DMA-BUF buffer.
/// </summary>
public record struct DmaBufPlaneInfo
{
/// <summary>
/// DMA-BUF file descriptor for this plane.
/// </summary>
public int Fd { get; set; }
/// <summary>
/// Byte offset into the plane.
/// </summary>
public uint Offset { get; set; }
/// <summary>
/// Row stride for this plane.
/// </summary>
public uint Pitch { get; set; }
/// <summary>
/// DRM format modifier (usually same as plane 0).
/// </summary>
public ulong Modifier { get; set; }
}
public enum PlatformGraphicsExternalImageFormat
@ -48,6 +96,13 @@ public static class KnownPlatformGraphicsExternalImageHandleTypes
/// A reference to IOSurface
/// </summary>
public const string IOSurfaceRef = nameof(IOSurfaceRef);
/// <summary>
/// A Linux DMA-BUF file descriptor. Imported via VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT (Vulkan)
/// or EGL_LINUX_DMA_BUF_EXT (EGL). Semantically distinct from VulkanOpaquePosixFileDescriptor as DMA-BUF
/// fds carry DRM format modifier metadata and use a different import path.
/// </summary>
public const string DmaBufFileDescriptor = "DMABUF_FD";
}
/// <summary>
@ -75,4 +130,11 @@ public static class KnownPlatformGraphicsExternalSemaphoreHandleTypes
/// A pointer to MTLSharedEvent object
/// </summary>
public const string MetalSharedEvent = nameof(MetalSharedEvent);
/// <summary>
/// A Linux sync fence file descriptor. Maps to VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT (Vulkan)
/// or EGL_ANDROID_native_fence_sync (EGL). Uses temporary import semantics — the semaphore payload
/// is consumed on the first wait.
/// </summary>
public const string SyncFileDescriptor = "SYNC_FD";
}

42
src/Avalonia.OpenGL/Egl/EglConsts.cs

@ -218,5 +218,47 @@ namespace Avalonia.OpenGL.Egl
public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6;
public const int EGL_TEXTURE_INTERNAL_FORMAT_ANGLE = 0x345D;
// EGL_KHR_image_base
public const int EGL_NO_IMAGE_KHR = 0;
// EGL_EXT_image_dma_buf_import
public const int EGL_LINUX_DMA_BUF_EXT = 0x3270;
public const int EGL_LINUX_DRM_FOURCC_EXT = 0x3271;
public const int EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272;
public const int EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273;
public const int EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274;
public const int EGL_DMA_BUF_PLANE1_FD_EXT = 0x3275;
public const int EGL_DMA_BUF_PLANE1_OFFSET_EXT = 0x3276;
public const int EGL_DMA_BUF_PLANE1_PITCH_EXT = 0x3277;
public const int EGL_DMA_BUF_PLANE2_FD_EXT = 0x3278;
public const int EGL_DMA_BUF_PLANE2_OFFSET_EXT = 0x3279;
public const int EGL_DMA_BUF_PLANE2_PITCH_EXT = 0x327A;
// EGL_EXT_image_dma_buf_import_modifiers
public const int EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT = 0x3443;
public const int EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT = 0x3444;
public const int EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT = 0x3445;
public const int EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT = 0x3446;
public const int EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT = 0x3447;
public const int EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT = 0x3448;
public const int EGL_DMA_BUF_PLANE3_FD_EXT = 0x3440;
public const int EGL_DMA_BUF_PLANE3_OFFSET_EXT = 0x3441;
public const int EGL_DMA_BUF_PLANE3_PITCH_EXT = 0x3442;
public const int EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449;
public const int EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A;
// EGL_ANDROID_native_fence_sync
public const int EGL_SYNC_NATIVE_FENCE_ANDROID = 0x3144;
public const int EGL_SYNC_NATIVE_FENCE_FD_ANDROID = 0x3145;
public const int EGL_NO_NATIVE_FENCE_FD_ANDROID = -1;
public const int EGL_SYNC_NATIVE_FENCE_SIGNALED_ANDROID = 0x3146;
// EGL_KHR_fence_sync
public const int EGL_SYNC_FENCE_KHR = 0x30F9;
public const int EGL_SYNC_FLUSH_COMMANDS_BIT_KHR = 0x0001;
public const long EGL_FOREVER_KHR = unchecked((long)0xFFFFFFFFFFFFFFFF);
public const int EGL_CONDITION_SATISFIED_KHR = 0x30F6;
public const int EGL_TIMEOUT_EXPIRED_KHR = 0x30F5;
}
}

8
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Avalonia.OpenGL.Features;
using Avalonia.Platform;
using Avalonia.Reactive;
using static Avalonia.OpenGL.Egl.EglConsts;
@ -37,6 +38,13 @@ namespace Avalonia.OpenGL.Egl
{
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
_features = features.ToDictionary(x => x.Key, x => x.Value(this));
// Auto-register DMA-BUF external objects feature if no external objects feature is registered
if (!_features.ContainsKey(typeof(IGlContextExternalObjectsFeature)))
{
if (EglDmaBufExternalObjectsFeature.TryCreate(this) is { } dmaBufFeature)
_features[typeof(IGlContextExternalObjectsFeature)] = dmaBufFeature;
}
}
}

30
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -135,5 +135,35 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglQueryDeviceAttribEXT", true)]
public partial bool QueryDeviceAttribExt(IntPtr display, int attr, out IntPtr res);
// EGL_KHR_image_base
[GetProcAddress("eglCreateImageKHR", true)]
public partial IntPtr CreateImageKHR(IntPtr dpy, IntPtr ctx, int target, IntPtr buffer, int[] attribs);
[GetProcAddress("eglDestroyImageKHR", true)]
public partial bool DestroyImageKHR(IntPtr dpy, IntPtr image);
// EGL_EXT_image_dma_buf_import_modifiers
[GetProcAddress("eglQueryDmaBufFormatsEXT", true)]
public partial bool QueryDmaBufFormatsEXT(IntPtr dpy, int maxFormats, int[]? formats, out int numFormats);
[GetProcAddress("eglQueryDmaBufModifiersEXT", true)]
public partial bool QueryDmaBufModifiersEXT(IntPtr dpy, int format, int maxModifiers, long[]? modifiers, int[]? externalOnly, out int numModifiers);
// EGL_KHR_fence_sync / EGL_ANDROID_native_fence_sync
[GetProcAddress("eglCreateSyncKHR", true)]
public partial IntPtr CreateSyncKHR(IntPtr dpy, int type, int[] attribs);
[GetProcAddress("eglDestroySyncKHR", true)]
public partial bool DestroySyncKHR(IntPtr dpy, IntPtr sync);
[GetProcAddress("eglClientWaitSyncKHR", true)]
public partial int ClientWaitSyncKHR(IntPtr dpy, IntPtr sync, int flags, long timeout);
[GetProcAddress("eglWaitSyncKHR", true)]
public partial int WaitSyncKHR(IntPtr dpy, IntPtr sync, int flags);
[GetProcAddress("eglDupNativeFenceFDANDROID", true)]
public partial int DupNativeFenceFDANDROID(IntPtr dpy, IntPtr sync);
}
}

349
src/Avalonia.OpenGL/Features/EglDmaBufExternalObjectsFeature.cs

@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.SourceGenerator;
using static Avalonia.OpenGL.Egl.EglConsts;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL.Features;
/// <summary>
/// GL extension interface for GL_OES_EGL_image (binding EGL images as GL textures).
/// </summary>
unsafe partial class EglImageGlInterface
{
public EglImageGlInterface(Func<string, IntPtr> getProcAddress)
{
Initialize(getProcAddress);
}
[GetProcAddress("glEGLImageTargetTexture2DOES")]
public partial void EGLImageTargetTexture2DOES(int target, IntPtr image);
}
/// <summary>
/// Implements <see cref="IGlContextExternalObjectsFeature"/> for DMA-BUF import via the EGL image path.
/// This uses <c>EGL_EXT_image_dma_buf_import</c> and <c>GL_OES_EGL_image</c> rather than the
/// <c>GL_EXT_memory_object_fd</c> path used by <see cref="ExternalObjectsOpenGlExtensionFeature"/>.
/// </summary>
public class EglDmaBufExternalObjectsFeature : IGlContextExternalObjectsFeature
{
private readonly EglContext _context;
private readonly EglInterface _egl;
private readonly EglImageGlInterface _glExt;
private readonly IntPtr _eglDisplay;
private readonly bool _hasModifiers;
private readonly bool _hasSyncFence;
public static EglDmaBufExternalObjectsFeature? TryCreate(EglContext context)
{
var egl = context.EglInterface;
var display = context.Display.Handle;
var extensions = egl.QueryString(display, EGL_EXTENSIONS);
if (extensions == null)
return null;
if (!extensions.Contains("EGL_EXT_image_dma_buf_import"))
return null;
if (!extensions.Contains("EGL_KHR_image_base"))
return null;
var glExtensions = context.GlInterface.GetExtensions();
if (!glExtensions.Contains("GL_OES_EGL_image"))
return null;
try
{
return new EglDmaBufExternalObjectsFeature(context, extensions);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(nameof(EglDmaBufExternalObjectsFeature),
"Unable to initialize EGL DMA-BUF import: " + e);
return null;
}
}
private EglDmaBufExternalObjectsFeature(EglContext context, string extensions)
{
_context = context;
_egl = context.EglInterface;
_eglDisplay = context.Display.Handle;
_glExt = new EglImageGlInterface(context.GlInterface.GetProcAddress);
_hasModifiers = extensions.Contains("EGL_EXT_image_dma_buf_import_modifiers");
_hasSyncFence = extensions.Contains("EGL_ANDROID_native_fence_sync");
}
public IReadOnlyList<string> SupportedImportableExternalImageTypes { get; } =
new[] { KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor };
public IReadOnlyList<string> SupportedExportableExternalImageTypes { get; } = Array.Empty<string>();
public IReadOnlyList<string> SupportedImportableExternalSemaphoreTypes =>
_hasSyncFence
? new[] { KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor }
: Array.Empty<string>();
public IReadOnlyList<string> SupportedExportableExternalSemaphoreTypes { get; } = Array.Empty<string>();
public IReadOnlyList<PlatformGraphicsExternalImageFormat> GetSupportedFormatsForExternalMemoryType(string type)
{
return new[]
{
PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm
};
}
public IGlExportableExternalImageTexture CreateImage(string type, PixelSize size,
PlatformGraphicsExternalImageFormat format) =>
throw new NotSupportedException();
public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
public unsafe IGlExternalImageTexture ImportImage(IPlatformHandle handle,
PlatformGraphicsExternalImageProperties properties)
{
if (handle.HandleDescriptor != KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor)
throw new ArgumentException($"Handle type {handle.HandleDescriptor} is not supported; expected DMABUF_FD");
// Build EGL attribute list for DMA-BUF import
var attribs = new List<int>
{
EGL_WIDTH, properties.Width,
EGL_HEIGHT, properties.Height,
EGL_LINUX_DRM_FOURCC_EXT, (int)properties.DrmFourcc,
EGL_DMA_BUF_PLANE0_FD_EXT, handle.Handle.ToInt32(),
EGL_DMA_BUF_PLANE0_OFFSET_EXT, (int)properties.MemoryOffset,
EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)properties.RowPitch,
};
// Add modifier if available
if (_hasModifiers && properties.DrmModifier != PlatformGraphicsDrmFormats.DRM_FORMAT_MOD_INVALID)
{
attribs.Add(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT);
attribs.Add((int)(properties.DrmModifier & 0xFFFFFFFF));
attribs.Add(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT);
attribs.Add((int)(properties.DrmModifier >> 32));
}
// Add additional planes if present
if (properties.AdditionalPlanes != null)
{
for (int i = 0; i < properties.AdditionalPlanes.Length && i < 3; i++)
{
var plane = properties.AdditionalPlanes[i];
switch (i)
{
case 0:
attribs.Add(EGL_DMA_BUF_PLANE1_FD_EXT);
attribs.Add(plane.Fd);
attribs.Add(EGL_DMA_BUF_PLANE1_OFFSET_EXT);
attribs.Add((int)plane.Offset);
attribs.Add(EGL_DMA_BUF_PLANE1_PITCH_EXT);
attribs.Add((int)plane.Pitch);
if (_hasModifiers)
{
attribs.Add(EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT);
attribs.Add((int)(plane.Modifier & 0xFFFFFFFF));
attribs.Add(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT);
attribs.Add((int)(plane.Modifier >> 32));
}
break;
case 1:
attribs.Add(EGL_DMA_BUF_PLANE2_FD_EXT);
attribs.Add(plane.Fd);
attribs.Add(EGL_DMA_BUF_PLANE2_OFFSET_EXT);
attribs.Add((int)plane.Offset);
attribs.Add(EGL_DMA_BUF_PLANE2_PITCH_EXT);
attribs.Add((int)plane.Pitch);
if (_hasModifiers)
{
attribs.Add(EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT);
attribs.Add((int)(plane.Modifier & 0xFFFFFFFF));
attribs.Add(EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT);
attribs.Add((int)(plane.Modifier >> 32));
}
break;
case 2:
attribs.Add(EGL_DMA_BUF_PLANE3_FD_EXT);
attribs.Add(plane.Fd);
attribs.Add(EGL_DMA_BUF_PLANE3_OFFSET_EXT);
attribs.Add((int)plane.Offset);
attribs.Add(EGL_DMA_BUF_PLANE3_PITCH_EXT);
attribs.Add((int)plane.Pitch);
if (_hasModifiers)
{
attribs.Add(EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT);
attribs.Add((int)(plane.Modifier & 0xFFFFFFFF));
attribs.Add(EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT);
attribs.Add((int)(plane.Modifier >> 32));
}
break;
}
}
}
attribs.Add(EGL_NONE);
// Create EGL image from DMA-BUF
var eglImage = _egl.CreateImageKHR(_eglDisplay, IntPtr.Zero, EGL_LINUX_DMA_BUF_EXT,
IntPtr.Zero, attribs.ToArray());
if (eglImage == IntPtr.Zero)
{
var error = _egl.GetError();
throw OpenGlException.GetFormattedEglException("eglCreateImageKHR (DMA-BUF)", error);
}
// Bind as GL texture
var gl = _context.GlInterface;
gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
var texture = gl.GenTexture();
gl.BindTexture(GL_TEXTURE_2D, texture);
_glExt.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
var err = gl.GetError();
gl.BindTexture(GL_TEXTURE_2D, oldTexture);
if (err != 0)
{
gl.DeleteTexture(texture);
_egl.DestroyImageKHR(_eglDisplay, eglImage);
throw OpenGlException.GetFormattedException("glEGLImageTargetTexture2DOES", err);
}
return new DmaBufImageTexture(_context, _egl, _eglDisplay, properties, texture, eglImage);
}
public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle)
{
if (handle.HandleDescriptor != KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor)
throw new ArgumentException($"Handle type {handle.HandleDescriptor} is not supported");
if (!_hasSyncFence)
throw new NotSupportedException("EGL_ANDROID_native_fence_sync is not available");
return new SyncFenceSemaphore(_context, _egl, _eglDisplay, handle.Handle.ToInt32());
}
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(
string imageHandleType)
{
if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor)
{
var caps = CompositionGpuImportedImageSynchronizationCapabilities.Automatic;
if (_hasSyncFence)
caps |= CompositionGpuImportedImageSynchronizationCapabilities.Semaphores;
return caps;
}
return default;
}
public byte[]? DeviceLuid => null;
public byte[]? DeviceUuid => null;
private class DmaBufImageTexture : IGlExternalImageTexture
{
private readonly IGlContext _context;
private readonly EglInterface _egl;
private readonly IntPtr _eglDisplay;
private IntPtr _eglImage;
public DmaBufImageTexture(IGlContext context, EglInterface egl, IntPtr eglDisplay,
PlatformGraphicsExternalImageProperties properties, int textureId, IntPtr eglImage)
{
_context = context;
_egl = egl;
_eglDisplay = eglDisplay;
_eglImage = eglImage;
TextureId = textureId;
Properties = properties;
}
public void Dispose()
{
if (_context.IsLost)
return;
using (_context.EnsureCurrent())
{
_context.GlInterface.DeleteTexture(TextureId);
if (_eglImage != IntPtr.Zero)
{
_egl.DestroyImageKHR(_eglDisplay, _eglImage);
_eglImage = IntPtr.Zero;
}
}
}
public void AcquireKeyedMutex(uint key) => throw new NotSupportedException();
public void ReleaseKeyedMutex(uint key) => throw new NotSupportedException();
public int TextureId { get; }
public int InternalFormat => GL_RGBA8;
public int TextureType => GL_TEXTURE_2D;
public PlatformGraphicsExternalImageProperties Properties { get; }
}
/// <summary>
/// Wraps an EGL sync fence (EGL_ANDROID_native_fence_sync) as an <see cref="IGlExternalSemaphore"/>.
/// The sync fd is imported on construction and waited on GPU-side when WaitSemaphore is called.
/// </summary>
private class SyncFenceSemaphore : IGlExternalSemaphore
{
private readonly IGlContext _context;
private readonly EglInterface _egl;
private readonly IntPtr _eglDisplay;
private IntPtr _sync;
public SyncFenceSemaphore(IGlContext context, EglInterface egl, IntPtr eglDisplay, int fd)
{
_context = context;
_egl = egl;
_eglDisplay = eglDisplay;
// Import the sync fd as an EGL native fence sync
var attribs = new[]
{
EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd,
EGL_NONE
};
_sync = egl.CreateSyncKHR(eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
if (_sync == IntPtr.Zero)
throw OpenGlException.GetFormattedEglException("eglCreateSyncKHR (SYNC_FD import)", egl.GetError());
}
public void Dispose()
{
if (_sync != IntPtr.Zero && !_context.IsLost)
{
_egl.DestroySyncKHR(_eglDisplay, _sync);
_sync = IntPtr.Zero;
}
}
public void WaitSemaphore(IGlExternalImageTexture texture)
{
if (_sync == IntPtr.Zero)
return;
// GPU-side wait, no CPU block
_egl.WaitSyncKHR(_eglDisplay, _sync, 0);
}
public void SignalSemaphore(IGlExternalImageTexture texture)
{
// Signal is a no-op for imported sync fences — they are consumed on wait
}
public void WaitTimelineSemaphore(IGlExternalImageTexture texture, ulong value) =>
throw new NotSupportedException("SYNC_FD does not support timeline semaphores");
public void SignalTimelineSemaphore(IGlExternalImageTexture texture, ulong value) =>
throw new NotSupportedException("SYNC_FD does not support timeline semaphores");
}
}

6
src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs

@ -150,5 +150,9 @@ internal unsafe partial class VulkanDeviceApi
public partial VkResult ImportSemaphoreWin32HandleKHR(VkDevice device,
VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo);
[GetProcAddress("vkGetMemoryFdPropertiesKHR", true)]
public partial VkResult GetMemoryFdPropertiesKHR(VkDevice device,
VkExternalMemoryHandleTypeFlagBits handleType,
int fd,
VkMemoryFdPropertiesKHR* pMemoryFdProperties);
}

19
src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs

@ -2,6 +2,16 @@ using System;
namespace Avalonia.Vulkan.UnmanagedInterop
{
internal static class VkQueueFamilyConstants
{
// VK_QUEUE_FAMILY_EXTERNAL (0xFFFFFFFE) — for ownership transfers to/from external APIs
public const uint VK_QUEUE_FAMILY_EXTERNAL = ~1u;
// VK_QUEUE_FAMILY_FOREIGN_EXT (0xFFFFFFFF) — for ownership transfers to/from foreign queues
// Provided by VK_EXT_queue_family_foreign
public const uint VK_QUEUE_FAMILY_FOREIGN_EXT = ~0u;
}
internal enum VkResult
{
VK_SUCCESS = 0,
@ -1903,6 +1913,15 @@ namespace Avalonia.Vulkan.UnmanagedInterop
VK_COMPONENT_SWIZZLE_A = 6,
}
[Flags]
enum VkFormatFeatureFlags : uint
{
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT = 0x00000001,
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0x00000080,
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT = 0x00004000,
VK_FORMAT_FEATURE_TRANSFER_DST_BIT = 0x00008000,
}
enum VkDependencyFlags
{
VK_DEPENDENCY_BY_REGION_BIT = 0x00000001,

6
src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs

@ -82,4 +82,10 @@ internal unsafe partial class VulkanInstanceApi
public partial void GetPhysicalDeviceProperties2(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceProperties2* pProperties);
[GetProcAddress("vkGetPhysicalDeviceFormatProperties2", true)]
public partial void GetPhysicalDeviceFormatProperties2(
VkPhysicalDevice physicalDevice,
VkFormat format,
VkFormatProperties2* pFormatProperties);
}

63
src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs

@ -764,4 +764,67 @@ namespace Avalonia.Vulkan.UnmanagedInterop
public void* pNext;
public VkExternalMemoryHandleTypeFlagBits handleTypes;
}
// DRM format modifier support (VK_EXT_image_drm_format_modifier)
struct VkSubresourceLayout
{
public VkDeviceSize offset;
public VkDeviceSize size;
public VkDeviceSize rowPitch;
public VkDeviceSize arrayPitch;
public VkDeviceSize depthPitch;
}
unsafe struct VkImageDrmFormatModifierExplicitCreateInfoEXT
{
public VkStructureType sType;
public void* pNext;
public ulong drmFormatModifier;
public uint32_t drmFormatModifierPlaneCount;
public VkSubresourceLayout* pPlaneLayouts;
}
unsafe struct VkDrmFormatModifierPropertiesEXT
{
public ulong drmFormatModifier;
public uint32_t drmFormatModifierPlaneCount;
public VkFormatFeatureFlags drmFormatModifierTilingFeatures;
}
unsafe struct VkDrmFormatModifierPropertiesListEXT
{
public VkStructureType sType;
public void* pNext;
public uint32_t drmFormatModifierCount;
public VkDrmFormatModifierPropertiesEXT* pDrmFormatModifierProperties;
}
unsafe struct VkFormatProperties2
{
public VkStructureType sType;
public void* pNext;
public VkFormatProperties formatProperties;
}
struct VkFormatProperties
{
public VkFormatFeatureFlags linearTilingFeatures;
public VkFormatFeatureFlags optimalTilingFeatures;
public VkFormatFeatureFlags bufferFeatures;
}
unsafe struct VkMemoryFdPropertiesKHR
{
public VkStructureType sType;
public void* pNext;
public uint32_t memoryTypeBits;
}
unsafe struct VkPhysicalDeviceExternalImageFormatInfo
{
public VkStructureType sType;
public void* pNext;
public VkExternalMemoryHandleTypeFlagBits handleType;
}
}

25
src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs

@ -0,0 +1,25 @@
using Avalonia.Platform;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
/// <summary>
/// Maps DRM fourcc format codes to Vulkan VkFormat values.
/// </summary>
internal static class VulkanDrmFormatMapping
{
/// <summary>
/// Maps a DRM fourcc code to its corresponding VkFormat.
/// </summary>
public static VkFormat? TryMapDrmFourccToVkFormat(uint fourcc)
{
return fourcc switch
{
PlatformGraphicsDrmFormats.DRM_FORMAT_ARGB8888 => VkFormat.VK_FORMAT_B8G8R8A8_UNORM,
PlatformGraphicsDrmFormats.DRM_FORMAT_XRGB8888 => VkFormat.VK_FORMAT_B8G8R8A8_UNORM,
PlatformGraphicsDrmFormats.DRM_FORMAT_ABGR8888 => VkFormat.VK_FORMAT_R8G8B8A8_UNORM,
PlatformGraphicsDrmFormats.DRM_FORMAT_XBGR8888 => VkFormat.VK_FORMAT_R8G8B8A8_UNORM,
_ => null
};
}
}

288
src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs

@ -25,6 +25,13 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
"VK_KHR_dedicated_allocation",
};
private static string[] s_optionalDmaBufDeviceExtensions =
{
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
"VK_EXT_queue_family_foreign"
};
private static string[] s_requiredLinuxDeviceExtensions =
s_requiredCommonDeviceExtensions.Concat(new[]
{
@ -40,14 +47,22 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
public static string[] RequiredDeviceExtensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? s_requiredWin32DeviceExtensions
: s_requiredLinuxDeviceExtensions;
: s_requiredLinuxDeviceExtensions.Concat(s_optionalDmaBufDeviceExtensions).ToArray();
private readonly VulkanContext _context;
private readonly VulkanCommandBufferPool _pool;
private readonly bool _hasDmaBufSupport;
private readonly bool _hasQueueFamilyForeign;
public VulkanExternalObjectsFeature(VulkanContext context)
{
_context = context;
_hasDmaBufSupport = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
&& context.Device.EnabledExtensions.Contains("VK_EXT_external_memory_dma_buf")
&& context.Device.EnabledExtensions.Contains("VK_EXT_image_drm_format_modifier");
_hasQueueFamilyForeign = context.Device.EnabledExtensions.Contains("VK_EXT_queue_family_foreign");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
//TODO: keyed muted
@ -64,14 +79,22 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
}
else
{
SupportedImageHandleTypes = new[]
var imageHandleTypes = new List<string>
{
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor
};
SupportedSemaphoreTypes = new[]
if (_hasDmaBufSupport)
imageHandleTypes.Add(KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor);
SupportedImageHandleTypes = imageHandleTypes;
var semaphoreTypes = new List<string>
{
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor,
};
semaphoreTypes.Add(KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor);
SupportedSemaphoreTypes = semaphoreTypes;
}
var physicalDeviceIDProperties = new VkPhysicalDeviceIDProperties()
@ -106,6 +129,11 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
if (!SupportedImageHandleTypes.Contains(imageHandleType))
throw new ArgumentException();
//TODO: keyed muted
if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor)
{
return CompositionGpuImportedImageSynchronizationCapabilities.Semaphores
| CompositionGpuImportedImageSynchronizationCapabilities.Automatic;
}
return CompositionGpuImportedImageSynchronizationCapabilities.Semaphores;
}
@ -114,6 +142,9 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
if (!SupportedImageHandleTypes.Contains(handle.HandleDescriptor))
throw new NotSupportedException();
if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor)
return new ImportedDmaBufImage(_context, _pool, handle, properties, _hasQueueFamilyForeign);
return new ImportedImage(_context, _pool, handle, properties);
}
@ -126,6 +157,8 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
{
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueKmtHandle =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueNtHandle =>
@ -135,18 +168,21 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
var semaphore = new VulkanSemaphore(_context);
if (handle.HandleDescriptor ==
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor
|| handle.HandleDescriptor ==
KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor)
{
var info = new VkImportSemaphoreFdInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
fd = handle.Handle.ToInt32(),
handleType = typeBit,
semaphore = semaphore.Handle
semaphore = semaphore.Handle,
// SYNC_FD requires temporary import semantics — payload is consumed on first wait
flags = handle.HandleDescriptor == KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor
? VkSemaphoreImportFlags.VK_SEMAPHORE_IMPORT_TEMPORARY_BIT
: 0
};
var addr = _context.Instance.GetDeviceProcAddress(_context.Device.Handle, "vkImportSemaphoreFdKHR");
if (addr == IntPtr.Zero)
addr = _context.Instance.GetInstanceProcAddress(_context.Instance.Handle, "vkImportSemaphoreFdKHR");
_context.DeviceApi.ImportSemaphoreFdKHR(_context.DeviceHandle, &info)
.ThrowOnError("vkImportSemaphoreFdKHR");
return new ImportedSemaphore(_context, _pool, semaphore);
@ -306,4 +342,238 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
return imageMemory;
}
}
/// <summary>
/// DMA-BUF image import using VK_EXT_external_memory_dma_buf and VK_EXT_image_drm_format_modifier.
/// This is a separate path from the opaque fd import because DMA-BUF requires modifier-aware
/// image creation and uses different memory handle types.
/// </summary>
class ImportedDmaBufImage : IVulkanExternalImage
{
private readonly IVulkanPlatformGraphicsContext _context;
private VkImage _image;
private VkDeviceMemory _memory;
private VkImageView _imageView;
private readonly VkFormat _format;
private readonly PixelSize _size;
private VkImageLayout _currentLayout;
public VulkanImageInfo Info { get; }
public ImportedDmaBufImage(IVulkanPlatformGraphicsContext context, VulkanCommandBufferPool commandBufferPool,
IPlatformHandle importHandle, PlatformGraphicsExternalImageProperties properties, bool hasQueueFamilyForeign)
{
_context = context;
_size = new PixelSize(properties.Width, properties.Height);
// Map DRM fourcc to VkFormat
_format = VulkanDrmFormatMapping.TryMapDrmFourccToVkFormat(properties.DrmFourcc)
?? throw new ArgumentException($"Unsupported DRM fourcc: 0x{properties.DrmFourcc:X8}");
// Step 1: Create VkImage with DRM format modifier tiling
var planeLayout = new VkSubresourceLayout
{
offset = properties.MemoryOffset,
rowPitch = properties.RowPitch,
size = 0,
arrayPitch = 0,
depthPitch = 0
};
var drmModifierInfo = new VkImageDrmFormatModifierExplicitCreateInfoEXT
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT,
drmFormatModifier = properties.DrmModifier,
drmFormatModifierPlaneCount = 1,
pPlaneLayouts = &planeLayout,
};
var externalMemoryInfo = new VkExternalMemoryImageCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
handleTypes = VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
pNext = &drmModifierInfo
};
var createInfo = new VkImageCreateInfo
{
pNext = &externalMemoryInfo,
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
imageType = VkImageType.VK_IMAGE_TYPE_2D,
format = _format,
extent = new VkExtent3D
{
width = (uint)properties.Width,
height = (uint)properties.Height,
depth = 1
},
mipLevels = 1,
arrayLayers = 1,
samples = VkSampleCountFlags.VK_SAMPLE_COUNT_1_BIT,
tiling = VkImageTiling.VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
usage = VkImageUsageFlags.VK_IMAGE_USAGE_SAMPLED_BIT
| VkImageUsageFlags.VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
sharingMode = VkSharingMode.VK_SHARING_MODE_EXCLUSIVE,
initialLayout = VkImageLayout.VK_IMAGE_LAYOUT_UNDEFINED,
};
context.DeviceApi.CreateImage(context.DeviceHandle, ref createInfo, IntPtr.Zero, out _image)
.ThrowOnError("vkCreateImage (DMA-BUF)");
try
{
// Step 2: Query memory requirements
context.DeviceApi.GetImageMemoryRequirements(context.DeviceHandle, _image, out var memReqs);
// Step 3: Query memory type bits from the DMA-BUF fd
var fdProperties = new VkMemoryFdPropertiesKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR
};
context.DeviceApi.GetMemoryFdPropertiesKHR(context.DeviceHandle,
VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
importHandle.Handle.ToInt32(), &fdProperties).ThrowOnError("vkGetMemoryFdPropertiesKHR");
var combinedMemoryTypeBits = memReqs.memoryTypeBits & fdProperties.memoryTypeBits;
if (combinedMemoryTypeBits == 0)
combinedMemoryTypeBits = fdProperties.memoryTypeBits;
// Step 4: Import memory
var dedicated = new VkMemoryDedicatedAllocateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
image = _image,
};
var importInfo = new VkImportMemoryFdInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
handleType = VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
fd = importHandle.Handle.ToInt32(),
pNext = &dedicated,
};
var memoryAllocateInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
pNext = new IntPtr(&importInfo),
allocationSize = memReqs.size,
memoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(context,
combinedMemoryTypeBits,
VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
context.DeviceApi.AllocateMemory(context.DeviceHandle, ref memoryAllocateInfo, IntPtr.Zero,
out _memory).ThrowOnError("vkAllocateMemory (DMA-BUF)");
// Step 5: Bind memory
context.DeviceApi.BindImageMemory(context.DeviceHandle, _image, _memory, 0)
.ThrowOnError("vkBindImageMemory (DMA-BUF)");
// Step 6: Transition layout with foreign queue ownership transfer
var barrier = new VkImageMemoryBarrier
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
srcAccessMask = 0,
dstAccessMask = VkAccessFlags.VK_ACCESS_TRANSFER_READ_BIT,
oldLayout = VkImageLayout.VK_IMAGE_LAYOUT_UNDEFINED,
newLayout = VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcQueueFamilyIndex = hasQueueFamilyForeign
? VkQueueFamilyConstants.VK_QUEUE_FAMILY_FOREIGN_EXT
: VkQueueFamilyConstants.VK_QUEUE_FAMILY_EXTERNAL,
dstQueueFamilyIndex = context.GraphicsQueueFamilyIndex,
image = _image,
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel = 0,
levelCount = 1,
baseArrayLayer = 0,
layerCount = 1,
}
};
var cmdBuf = commandBufferPool.CreateCommandBuffer();
cmdBuf.BeginRecording();
context.DeviceApi.CmdPipelineBarrier(
cmdBuf.Handle,
VkPipelineStageFlags.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VkPipelineStageFlags.VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, IntPtr.Zero, 0, IntPtr.Zero,
1, &barrier);
cmdBuf.EndRecording();
cmdBuf.Submit();
_currentLayout = VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
// Step 7: Create image view
var viewCreateInfo = new VkImageViewCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
image = _image,
viewType = VkImageViewType.VK_IMAGE_VIEW_TYPE_2D,
format = _format,
components = new VkComponentMapping(),
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel = 0,
levelCount = 1,
baseArrayLayer = 0,
layerCount = 1,
}
};
context.DeviceApi.CreateImageView(context.DeviceHandle, ref viewCreateInfo, IntPtr.Zero, out _imageView)
.ThrowOnError("vkCreateImageView (DMA-BUF)");
Info = new VulkanImageInfo
{
Handle = _image.Handle,
PixelSize = _size,
Format = (uint)_format,
MemoryHandle = _memory.Handle,
MemorySize = memReqs.size,
ViewHandle = _imageView.Handle,
UsageFlags = (uint)(VkImageUsageFlags.VK_IMAGE_USAGE_SAMPLED_BIT | VkImageUsageFlags.VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
Layout = (uint)_currentLayout,
Tiling = (uint)VkImageTiling.VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
LevelCount = 1,
SampleCount = 1,
IsProtected = false
};
}
catch
{
if (_imageView.Handle != 0)
context.DeviceApi.DestroyImageView(context.DeviceHandle, _imageView, IntPtr.Zero);
if (_memory.Handle != 0)
context.DeviceApi.FreeMemory(context.DeviceHandle, _memory, IntPtr.Zero);
if (_image.Handle != 0)
context.DeviceApi.DestroyImage(context.DeviceHandle, _image, IntPtr.Zero);
throw;
}
}
public void Dispose()
{
var api = _context.DeviceApi;
var d = _context.DeviceHandle;
if (_imageView.Handle != 0)
{
api.DestroyImageView(d, _imageView, IntPtr.Zero);
_imageView = default;
}
if (_image.Handle != 0)
{
api.DestroyImage(d, _image, IntPtr.Zero);
_image = default;
}
if (_memory.Handle != 0)
{
api.FreeMemory(d, _memory, IntPtr.Zero);
_memory = default;
}
}
}
}
Loading…
Cancel
Save