12 changed files with 934 additions and 14 deletions
@ -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 |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue