From 694c81d2d03b9fde818b22a855c5ee63853e780b Mon Sep 17 00:00:00 2001 From: Timothy Miller Date: Fri, 13 Mar 2026 15:03:51 +0900 Subject: [PATCH] Add DMA-BUF Buffer/EGL Image --- .../Platform/PlatformGraphicsDrmFormats.cs | 42 +++ .../PlatformGraphicsExternalMemory.cs | 62 ++++ src/Avalonia.OpenGL/Egl/EglConsts.cs | 42 +++ src/Avalonia.OpenGL/Egl/EglContext.cs | 8 + src/Avalonia.OpenGL/Egl/EglInterface.cs | 30 ++ .../EglDmaBufExternalObjectsFeature.cs | 349 ++++++++++++++++++ .../UnmanagedInterop/VulkanDeviceApi.cs | 8 +- .../UnmanagedInterop/VulkanEnums.cs | 19 + .../UnmanagedInterop/VulkanInstanceApi.cs | 6 + .../UnmanagedInterop/VulkanStructs.cs | 63 ++++ src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs | 25 ++ .../VulkanExternalObjectsFeature.cs | 294 ++++++++++++++- 12 files changed, 934 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.Base/Platform/PlatformGraphicsDrmFormats.cs create mode 100644 src/Avalonia.OpenGL/Features/EglDmaBufExternalObjectsFeature.cs create mode 100644 src/Avalonia.Vulkan/VulkanDrmFormatMapping.cs 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