diff --git a/src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs b/src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs
new file mode 100644
index 0000000000..5efa0c149a
--- /dev/null
+++ b/src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs
@@ -0,0 +1,42 @@
+namespace Avalonia.Platform;
+
+///
+/// Provides DRM fourcc format constants and mapping to .
+///
+public static class PlatformGraphicsDrmFormats
+{
+ /// DRM_FORMAT_ARGB8888: little-endian BGRA in memory = B8G8R8A8UNorm. Most common WebKit output.
+ public const uint DRM_FORMAT_ARGB8888 = 0x34325241;
+
+ /// DRM_FORMAT_XRGB8888: little-endian BGRX in memory = B8G8R8A8UNorm (opaque, ignore alpha).
+ public const uint DRM_FORMAT_XRGB8888 = 0x34325258;
+
+ /// DRM_FORMAT_ABGR8888: little-endian RGBA in memory = R8G8B8A8UNorm.
+ public const uint DRM_FORMAT_ABGR8888 = 0x34324241;
+
+ /// DRM_FORMAT_XBGR8888: little-endian RGBX in memory = R8G8B8A8UNorm (opaque, ignore alpha).
+ public const uint DRM_FORMAT_XBGR8888 = 0x34324258;
+
+ /// DRM_FORMAT_MOD_LINEAR: linear (non-tiled) memory layout.
+ public const ulong DRM_FORMAT_MOD_LINEAR = 0;
+
+ /// DRM_FORMAT_MOD_INVALID: indicates an invalid or unknown modifier.
+ public const ulong DRM_FORMAT_MOD_INVALID = 0x00FFFFFFFFFFFFFF;
+
+ ///
+ /// Attempts to map a DRM fourcc code to a .
+ ///
+ /// The DRM format fourcc code.
+ /// The corresponding format, or null if the fourcc code is not recognized.
+ 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
+ };
+ }
+}
diff --git a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs b/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
index 0e6125de2f..bdd9dd5930 100644
--- a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
+++ b/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)
+
+ ///
+ /// DRM format fourcc code (e.g., DRM_FORMAT_ARGB8888). Used by Vulkan and EGL DMA-BUF import paths.
+ ///
+ public uint DrmFourcc { get; set; }
+
+ ///
+ /// DRM format modifier (e.g., DRM_FORMAT_MOD_LINEAR). Determines memory layout (linear, tiled, compressed).
+ ///
+ public ulong DrmModifier { get; set; }
+
+ ///
+ /// Row stride in bytes for plane 0.
+ ///
+ public uint RowPitch { get; set; }
+
+ ///
+ /// Additional plane information for multi-plane DMA-BUF formats (planes 1-3). Null for single-plane formats.
+ ///
+ public DmaBufPlaneInfo[]? AdditionalPlanes { get; set; }
+}
+
+///
+/// Describes a single plane of a multi-plane DMA-BUF buffer.
+///
+public record struct DmaBufPlaneInfo
+{
+ ///
+ /// DMA-BUF file descriptor for this plane.
+ ///
+ public int Fd { get; set; }
+
+ ///
+ /// Byte offset into the plane.
+ ///
+ public uint Offset { get; set; }
+
+ ///
+ /// Row stride for this plane.
+ ///
+ public uint Pitch { get; set; }
+
+ ///
+ /// DRM format modifier (usually same as plane 0).
+ ///
+ public ulong Modifier { get; set; }
}
public enum PlatformGraphicsExternalImageFormat
@@ -48,6 +96,13 @@ public static class KnownPlatformGraphicsExternalImageHandleTypes
/// A reference to IOSurface
///
public const string IOSurfaceRef = nameof(IOSurfaceRef);
+
+ ///
+ /// 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.
+ ///
+ public const string DmaBufFileDescriptor = "DMABUF_FD";
}
///
@@ -75,4 +130,11 @@ public static class KnownPlatformGraphicsExternalSemaphoreHandleTypes
/// A pointer to MTLSharedEvent object
///
public const string MetalSharedEvent = nameof(MetalSharedEvent);
+
+ ///
+ /// 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.
+ ///
+ public const string SyncFileDescriptor = "SYNC_FD";
}
diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs
index aff56ecfe0..9a83ec6b42 100644
--- a/src/Avalonia.OpenGL/Egl/EglConsts.cs
+++ b/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;
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs
index ae729968ba..e51a1aff35 100644
--- a/src/Avalonia.OpenGL/Egl/EglContext.cs
+++ b/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;
+ }
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs
index 5c93d2084b..9b606d4900 100644
--- a/src/Avalonia.OpenGL/Egl/EglInterface.cs
+++ b/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);
}
}
diff --git a/src/Avalonia.OpenGL/Features/EglDmaBufExternalObjectsFeature.cs b/src/Avalonia.OpenGL/Features/EglDmaBufExternalObjectsFeature.cs
new file mode 100644
index 0000000000..c561bcd970
--- /dev/null
+++ b/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;
+
+///
+/// GL extension interface for GL_OES_EGL_image (binding EGL images as GL textures).
+///
+unsafe partial class EglImageGlInterface
+{
+ public EglImageGlInterface(Func getProcAddress)
+ {
+ Initialize(getProcAddress);
+ }
+
+ [GetProcAddress("glEGLImageTargetTexture2DOES")]
+ public partial void EGLImageTargetTexture2DOES(int target, IntPtr image);
+}
+
+///
+/// Implements for DMA-BUF import via the EGL image path.
+/// This uses EGL_EXT_image_dma_buf_import and GL_OES_EGL_image rather than the
+/// GL_EXT_memory_object_fd path used by .
+///
+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 SupportedImportableExternalImageTypes { get; } =
+ new[] { KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor };
+
+ public IReadOnlyList SupportedExportableExternalImageTypes { get; } = Array.Empty();
+
+ public IReadOnlyList SupportedImportableExternalSemaphoreTypes =>
+ _hasSyncFence
+ ? new[] { KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor }
+ : Array.Empty();
+
+ public IReadOnlyList SupportedExportableExternalSemaphoreTypes { get; } = Array.Empty();
+
+ public IReadOnlyList 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
+ {
+ 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; }
+ }
+
+ ///
+ /// Wraps an EGL sync fence (EGL_ANDROID_native_fence_sync) as an .
+ /// The sync fd is imported on construction and waited on GPU-side when WaitSemaphore is called.
+ ///
+ 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");
+ }
+}
diff --git a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs b/src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs
index f3f3e5f824..95ae24d9fc 100644
--- a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs
+++ b/src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs
@@ -149,6 +149,10 @@ internal unsafe partial class VulkanDeviceApi
[GetProcAddress("vkImportSemaphoreWin32HandleKHR", true)]
public partial VkResult ImportSemaphoreWin32HandleKHR(VkDevice device,
VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo);
-
-
+
+ [GetProcAddress("vkGetMemoryFdPropertiesKHR", true)]
+ public partial VkResult GetMemoryFdPropertiesKHR(VkDevice device,
+ VkExternalMemoryHandleTypeFlagBits handleType,
+ int fd,
+ VkMemoryFdPropertiesKHR* pMemoryFdProperties);
}
\ No newline at end of file
diff --git a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs b/src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs
index e3530489f4..f88d79a090 100644
--- a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs
+++ b/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,
diff --git a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs b/src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs
index 9a64319b00..1cfb3eb407 100644
--- a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs
+++ b/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);
}
\ No newline at end of file
diff --git a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs b/src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs
index bce6aa06bd..8030152317 100644
--- a/src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs
+++ b/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;
+ }
}
\ No newline at end of file
diff --git a/src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs b/src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs
new file mode 100644
index 0000000000..286cf851d2
--- /dev/null
+++ b/src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs
@@ -0,0 +1,25 @@
+using Avalonia.Platform;
+using Avalonia.Vulkan.UnmanagedInterop;
+
+namespace Avalonia.Vulkan;
+
+///
+/// Maps DRM fourcc format codes to Vulkan VkFormat values.
+///
+internal static class VulkanDrmFormatMapping
+{
+ ///
+ /// Maps a DRM fourcc code to its corresponding VkFormat.
+ ///
+ 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
+ };
+ }
+}
diff --git a/src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs b/src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs
index 2b31a7311e..8096b8a3c9 100644
--- a/src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs
+++ b/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
{
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor
};
- SupportedSemaphoreTypes = new[]
+ if (_hasDmaBufSupport)
+ imageHandleTypes.Add(KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor);
+
+ SupportedImageHandleTypes = imageHandleTypes;
+
+ var semaphoreTypes = new List
{
- 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);
@@ -267,13 +303,13 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
if (_properties.MemoryOffset != 0 || _properties.MemorySize != size)
throw new Exception("Invalid memory size");
-
+
var dedicated = new VkMemoryDedicatedAllocateInfo()
{
image = image,
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
};
-
+
var isPosixHandle = handle.HandleDescriptor ==
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor;
var win32Info = new VkImportMemoryWin32HandleInfoKHR
@@ -290,7 +326,7 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
fd = isPosixHandle ? handle.Handle.ToInt32() : 0,
pNext = &dedicated
};
-
+
var memoryAllocateInfo = new VkMemoryAllocateInfo
{
pNext = new IntPtr(isPosixHandle ? &posixInfo : &win32Info),
@@ -306,4 +342,238 @@ internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjec
return imageMemory;
}
}
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+ }
+ }
}
\ No newline at end of file