8 changed files with 1837 additions and 0 deletions
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
<IsTestProject>false</IsTestProject> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Vulkan\Avalonia.Vulkan.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.OpenGL\Avalonia.OpenGL.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\build\SharedVersion.props" /> |
|||
<Import Project="..\..\build\NullableEnable.props" /> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,208 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.InteropServices; |
|||
using static Avalonia.DmaBufInteropTests.NativeInterop; |
|||
|
|||
namespace Avalonia.DmaBufInteropTests; |
|||
|
|||
/// <summary>
|
|||
/// Allocates DMA-BUF buffers with known pixel content for testing.
|
|||
/// Prefers GBM when available, falls back to udmabuf.
|
|||
/// </summary>
|
|||
internal sealed unsafe class DmaBufAllocator : IDisposable |
|||
{ |
|||
private readonly int _drmFd; |
|||
private readonly IntPtr _gbmDevice; |
|||
|
|||
public bool IsAvailable => _gbmDevice != IntPtr.Zero; |
|||
|
|||
public DmaBufAllocator() |
|||
{ |
|||
// Try render nodes in order
|
|||
foreach (var path in new[] { "/dev/dri/renderD128", "/dev/dri/renderD129" }) |
|||
{ |
|||
if (!File.Exists(path)) |
|||
continue; |
|||
_drmFd = Open(path, O_RDWR); |
|||
if (_drmFd < 0) |
|||
continue; |
|||
_gbmDevice = GbmCreateDevice(_drmFd); |
|||
if (_gbmDevice != IntPtr.Zero) |
|||
return; |
|||
Close(_drmFd); |
|||
_drmFd = -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allocates a DMA-BUF with the specified format and fills it with a solid ARGB color.
|
|||
/// </summary>
|
|||
public DmaBufAllocation? AllocateLinear(uint width, uint height, uint format, uint color) |
|||
{ |
|||
if (_gbmDevice == IntPtr.Zero) |
|||
return AllocateViaUdmabuf(width, height, format, color); |
|||
|
|||
var bo = GbmBoCreate(_gbmDevice, width, height, format, |
|||
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); |
|||
if (bo == IntPtr.Zero) |
|||
return null; |
|||
|
|||
var fd = GbmBoGetFd(bo); |
|||
var stride = GbmBoGetStride(bo); |
|||
var modifier = GbmBoGetModifier(bo); |
|||
|
|||
// Map and fill with color
|
|||
uint mapStride; |
|||
IntPtr mapData = IntPtr.Zero; |
|||
var mapped = GbmBoMap(bo, 0, 0, width, height, GBM_BO_TRANSFER_WRITE, &mapStride, &mapData); |
|||
if (mapped != IntPtr.Zero) |
|||
{ |
|||
var pixels = (uint*)mapped; |
|||
var pixelsPerRow = mapStride / 4; |
|||
for (uint y = 0; y < height; y++) |
|||
for (uint x = 0; x < width; x++) |
|||
pixels[y * pixelsPerRow + x] = color; |
|||
GbmBoUnmap(bo, mapData); |
|||
} |
|||
|
|||
return new DmaBufAllocation(fd, stride, modifier, width, height, format, bo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allocates a DMA-BUF with tiled modifier (if the GPU supports it).
|
|||
/// </summary>
|
|||
public DmaBufAllocation? AllocateTiled(uint width, uint height, uint format, uint color) |
|||
{ |
|||
if (_gbmDevice == IntPtr.Zero) |
|||
return null; |
|||
|
|||
// Use GBM_BO_USE_RENDERING without LINEAR to let the driver choose tiling
|
|||
var bo = GbmBoCreate(_gbmDevice, width, height, format, GBM_BO_USE_RENDERING); |
|||
if (bo == IntPtr.Zero) |
|||
return null; |
|||
|
|||
var modifier = GbmBoGetModifier(bo); |
|||
// If the driver gave us linear anyway, this isn't a useful tiled test
|
|||
if (modifier == 0) // DRM_FORMAT_MOD_LINEAR
|
|||
{ |
|||
GbmBoDestroy(bo); |
|||
return null; |
|||
} |
|||
|
|||
var fd = GbmBoGetFd(bo); |
|||
var stride = GbmBoGetStride(bo); |
|||
|
|||
// Map and fill — for tiled BOs, GBM handles the detiling in map
|
|||
uint mapStride; |
|||
IntPtr mapData = IntPtr.Zero; |
|||
var mapped = GbmBoMap(bo, 0, 0, width, height, GBM_BO_TRANSFER_WRITE, &mapStride, &mapData); |
|||
if (mapped != IntPtr.Zero) |
|||
{ |
|||
var pixels = (uint*)mapped; |
|||
var pixelsPerRow = mapStride / 4; |
|||
for (uint y = 0; y < height; y++) |
|||
for (uint x = 0; x < width; x++) |
|||
pixels[y * pixelsPerRow + x] = color; |
|||
GbmBoUnmap(bo, mapData); |
|||
} |
|||
|
|||
return new DmaBufAllocation(fd, stride, modifier, width, height, format, bo); |
|||
} |
|||
|
|||
private static DmaBufAllocation? AllocateViaUdmabuf(uint width, uint height, uint format, uint color) |
|||
{ |
|||
if (!File.Exists("/dev/udmabuf")) |
|||
return null; |
|||
|
|||
var stride = width * 4; |
|||
var size = (long)(stride * height); |
|||
|
|||
var memfd = MemfdCreate("dmabuf-test", MFD_ALLOW_SEALING); |
|||
if (memfd < 0) |
|||
return null; |
|||
|
|||
if (Ftruncate(memfd, size) != 0) |
|||
{ |
|||
Close(memfd); |
|||
return null; |
|||
} |
|||
|
|||
// Map and fill
|
|||
var mapped = Mmap(IntPtr.Zero, (nuint)size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0); |
|||
if (mapped != IntPtr.Zero && mapped != new IntPtr(-1)) |
|||
{ |
|||
var pixels = (uint*)mapped; |
|||
var count = width * height; |
|||
for (uint i = 0; i < count; i++) |
|||
pixels[i] = color; |
|||
Munmap(mapped, (nuint)size); |
|||
} |
|||
|
|||
// Create DMA-BUF via udmabuf
|
|||
var udmabufFd = Open("/dev/udmabuf", O_RDWR); |
|||
if (udmabufFd < 0) |
|||
{ |
|||
Close(memfd); |
|||
return null; |
|||
} |
|||
|
|||
var createParams = new UdmabufCreate |
|||
{ |
|||
Memfd = memfd, |
|||
Flags = 0, |
|||
Offset = 0, |
|||
Size = (ulong)size |
|||
}; |
|||
|
|||
var dmaBufFd = Ioctl(udmabufFd, UDMABUF_CREATE, &createParams); |
|||
Close(udmabufFd); |
|||
Close(memfd); |
|||
|
|||
if (dmaBufFd < 0) |
|||
return null; |
|||
|
|||
return new DmaBufAllocation(dmaBufFd, stride, 0 /* LINEAR */, width, height, format, IntPtr.Zero); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_gbmDevice != IntPtr.Zero) |
|||
GbmDeviceDestroy(_gbmDevice); |
|||
if (_drmFd >= 0) |
|||
Close(_drmFd); |
|||
} |
|||
} |
|||
|
|||
internal sealed class DmaBufAllocation : IDisposable |
|||
{ |
|||
public int Fd { get; } |
|||
public uint Stride { get; } |
|||
public ulong Modifier { get; } |
|||
public uint Width { get; } |
|||
public uint Height { get; } |
|||
public uint DrmFourcc { get; } |
|||
private IntPtr _gbmBo; |
|||
|
|||
public DmaBufAllocation(int fd, uint stride, ulong modifier, uint width, uint height, uint drmFourcc, |
|||
IntPtr gbmBo) |
|||
{ |
|||
Fd = fd; |
|||
Stride = stride; |
|||
Modifier = modifier; |
|||
Width = width; |
|||
Height = height; |
|||
DrmFourcc = drmFourcc; |
|||
_gbmBo = gbmBo; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_gbmBo != IntPtr.Zero) |
|||
{ |
|||
NativeInterop.GbmBoDestroy(_gbmBo); |
|||
_gbmBo = IntPtr.Zero; |
|||
} |
|||
if (Fd >= 0) |
|||
NativeInterop.Close(Fd); |
|||
} |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.DmaBufInteropTests; |
|||
|
|||
/// <summary>
|
|||
/// P/Invoke bindings for GBM, DRM, and Linux kernel interfaces used for test buffer allocation.
|
|||
/// </summary>
|
|||
internal static unsafe partial class NativeInterop |
|||
{ |
|||
// GBM
|
|||
private const string LibGbm = "libgbm.so.1"; |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_create_device")] |
|||
public static partial IntPtr GbmCreateDevice(int fd); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_device_destroy")] |
|||
public static partial void GbmDeviceDestroy(IntPtr gbm); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_create")] |
|||
public static partial IntPtr GbmBoCreate(IntPtr gbm, uint width, uint height, uint format, uint flags); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_create_with_modifiers")] |
|||
public static partial IntPtr GbmBoCreateWithModifiers(IntPtr gbm, uint width, uint height, uint format, |
|||
ulong* modifiers, uint count); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_destroy")] |
|||
public static partial void GbmBoDestroy(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_fd")] |
|||
public static partial int GbmBoGetFd(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_stride")] |
|||
public static partial uint GbmBoGetStride(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_modifier")] |
|||
public static partial ulong GbmBoGetModifier(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_width")] |
|||
public static partial uint GbmBoGetWidth(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_height")] |
|||
public static partial uint GbmBoGetHeight(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_get_format")] |
|||
public static partial uint GbmBoGetFormat(IntPtr bo); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_map")] |
|||
public static partial IntPtr GbmBoMap(IntPtr bo, uint x, uint y, uint width, uint height, |
|||
uint flags, uint* stride, IntPtr* mapData); |
|||
|
|||
[LibraryImport(LibGbm, EntryPoint = "gbm_bo_unmap")] |
|||
public static partial void GbmBoUnmap(IntPtr bo, IntPtr mapData); |
|||
|
|||
// GBM flags
|
|||
public const uint GBM_BO_USE_RENDERING = 1 << 2; |
|||
public const uint GBM_BO_USE_LINEAR = 1 << 4; |
|||
|
|||
// GBM_BO_TRANSFER flags for gbm_bo_map
|
|||
public const uint GBM_BO_TRANSFER_WRITE = 2; |
|||
public const uint GBM_BO_TRANSFER_READ_WRITE = 3; |
|||
|
|||
// DRM format codes
|
|||
public const uint GBM_FORMAT_ARGB8888 = 0x34325241; // DRM_FORMAT_ARGB8888
|
|||
public const uint GBM_FORMAT_XRGB8888 = 0x34325258; |
|||
public const uint GBM_FORMAT_ABGR8888 = 0x34324241; |
|||
|
|||
// libc
|
|||
private const string LibC = "libc"; |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "open", StringMarshalling = StringMarshalling.Utf8)] |
|||
public static partial int Open(string path, int flags); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "close")] |
|||
public static partial int Close(int fd); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "mmap")] |
|||
public static partial IntPtr Mmap(IntPtr addr, nuint length, int prot, int flags, int fd, long offset); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "munmap")] |
|||
public static partial int Munmap(IntPtr addr, nuint length); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "memfd_create", StringMarshalling = StringMarshalling.Utf8)] |
|||
public static partial int MemfdCreate(string name, uint flags); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "ftruncate")] |
|||
public static partial int Ftruncate(int fd, long length); |
|||
|
|||
[LibraryImport(LibC, EntryPoint = "ioctl")] |
|||
public static partial int Ioctl(int fd, ulong request, void* arg); |
|||
|
|||
public const int O_RDWR = 0x02; |
|||
public const int PROT_READ = 0x1; |
|||
public const int PROT_WRITE = 0x2; |
|||
public const int MAP_SHARED = 0x01; |
|||
public const uint MFD_ALLOW_SEALING = 0x0002; |
|||
|
|||
// udmabuf
|
|||
public const ulong UDMABUF_CREATE = 0x40187542; // _IOW('u', 0x42, struct udmabuf_create)
|
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct UdmabufCreate |
|||
{ |
|||
public int Memfd; |
|||
public uint Flags; |
|||
public ulong Offset; |
|||
public ulong Size; |
|||
} |
|||
|
|||
// EGL
|
|||
private const string LibEgl = "libEGL.so.1"; |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglGetProcAddress", StringMarshalling = StringMarshalling.Utf8)] |
|||
public static partial IntPtr EglGetProcAddress(string procname); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglGetDisplay")] |
|||
public static partial IntPtr EglGetDisplay(IntPtr nativeDisplay); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglInitialize")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglInitialize(IntPtr display, out int major, out int minor); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglTerminate")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglTerminate(IntPtr display); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglQueryString")] |
|||
public static partial IntPtr EglQueryStringNative(IntPtr display, int name); |
|||
|
|||
public static string? EglQueryString(IntPtr display, int name) |
|||
{ |
|||
var ptr = EglQueryStringNative(display, name); |
|||
return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr); |
|||
} |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglBindAPI")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglBindApi(int api); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglChooseConfig")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglChooseConfig(IntPtr display, int[] attribs, IntPtr* configs, int configSize, |
|||
out int numConfig); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglCreateContext")] |
|||
public static partial IntPtr EglCreateContext(IntPtr display, IntPtr config, IntPtr shareContext, int[] attribs); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglDestroyContext")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglDestroyContext(IntPtr display, IntPtr context); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglMakeCurrent")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglCreatePbufferSurface")] |
|||
public static partial IntPtr EglCreatePbufferSurface(IntPtr display, IntPtr config, int[] attribs); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglDestroySurface")] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public static partial bool EglDestroySurface(IntPtr display, IntPtr surface); |
|||
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglGetError")] |
|||
public static partial int EglGetError(); |
|||
|
|||
// EGL_KHR_platform_gbm
|
|||
[LibraryImport(LibEgl, EntryPoint = "eglGetPlatformDisplayEXT")] |
|||
public static partial IntPtr EglGetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[]? attribs); |
|||
|
|||
// EGL constants
|
|||
public const int EGL_OPENGL_ES_API = 0x30A0; |
|||
public const int EGL_OPENGL_ES2_BIT = 0x0004; |
|||
public const int EGL_OPENGL_ES3_BIT = 0x0040; |
|||
public const int EGL_RENDERABLE_TYPE = 0x3040; |
|||
public const int EGL_SURFACE_TYPE = 0x3033; |
|||
public const int EGL_PBUFFER_BIT = 0x0001; |
|||
public const int EGL_RED_SIZE = 0x3024; |
|||
public const int EGL_GREEN_SIZE = 0x3023; |
|||
public const int EGL_BLUE_SIZE = 0x3022; |
|||
public const int EGL_ALPHA_SIZE = 0x3021; |
|||
public const int EGL_NONE = 0x3038; |
|||
public const int EGL_CONTEXT_MAJOR_VERSION = 0x3098; |
|||
public const int EGL_CONTEXT_MINOR_VERSION = 0x30FB; |
|||
public const int EGL_WIDTH = 0x3057; |
|||
public const int EGL_HEIGHT = 0x3056; |
|||
public const int EGL_EXTENSIONS = 0x3055; |
|||
public const int EGL_NO_IMAGE_KHR = 0; |
|||
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_PLANE0_MODIFIER_LO_EXT = 0x3443; |
|||
public const int EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT = 0x3444; |
|||
public const int EGL_PLATFORM_GBM_KHR = 0x31D7; |
|||
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_FLUSH_COMMANDS_BIT_KHR = 0x0001; |
|||
public const long EGL_FOREVER_KHR = unchecked((long)0xFFFFFFFFFFFFFFFF); |
|||
public const int EGL_CONDITION_SATISFIED_KHR = 0x30F6; |
|||
|
|||
// GL
|
|||
private const string LibGl = "libGLESv2.so.2"; |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glGetError")] |
|||
public static partial int GlGetError(); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glGenTextures")] |
|||
public static partial void GlGenTextures(int n, int* textures); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glDeleteTextures")] |
|||
public static partial void GlDeleteTextures(int n, int* textures); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glBindTexture")] |
|||
public static partial void GlBindTexture(int target, int texture); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glGenFramebuffers")] |
|||
public static partial void GlGenFramebuffers(int n, int* framebuffers); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glDeleteFramebuffers")] |
|||
public static partial void GlDeleteFramebuffers(int n, int* framebuffers); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glBindFramebuffer")] |
|||
public static partial void GlBindFramebuffer(int target, int framebuffer); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glFramebufferTexture2D")] |
|||
public static partial void GlFramebufferTexture2D(int target, int attachment, int texTarget, int texture, |
|||
int level); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glCheckFramebufferStatus")] |
|||
public static partial int GlCheckFramebufferStatus(int target); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glReadPixels")] |
|||
public static partial void GlReadPixels(int x, int y, int width, int height, int format, int type, void* pixels); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glFlush")] |
|||
public static partial void GlFlush(); |
|||
|
|||
[LibraryImport(LibGl, EntryPoint = "glFinish")] |
|||
public static partial void GlFinish(); |
|||
|
|||
public const int GL_TEXTURE_2D = 0x0DE1; |
|||
public const int GL_FRAMEBUFFER = 0x8D40; |
|||
public const int GL_COLOR_ATTACHMENT0 = 0x8CE0; |
|||
public const int GL_FRAMEBUFFER_COMPLETE = 0x8CD5; |
|||
public const int GL_RGBA = 0x1908; |
|||
public const int GL_BGRA = 0x80E1; |
|||
public const int GL_UNSIGNED_BYTE = 0x1401; |
|||
|
|||
// Function pointer types for EGL extensions loaded via eglGetProcAddress
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate IntPtr EglCreateImageKHRDelegate(IntPtr dpy, IntPtr ctx, int target, IntPtr buffer, |
|||
int[] attribs); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public delegate bool EglDestroyImageKHRDelegate(IntPtr dpy, IntPtr image); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public delegate bool EglQueryDmaBufFormatsEXTDelegate(IntPtr dpy, int maxFormats, |
|||
[Out] int[]? formats, out int numFormats); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public delegate bool EglQueryDmaBufModifiersEXTDelegate(IntPtr dpy, int format, int maxModifiers, |
|||
[Out] long[]? modifiers, [Out] int[]? externalOnly, out int numModifiers); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate void GlEGLImageTargetTexture2DOESDelegate(int target, IntPtr image); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate IntPtr EglCreateSyncKHRDelegate(IntPtr dpy, int type, int[] attribs); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
[return: MarshalAs(UnmanagedType.Bool)] |
|||
public delegate bool EglDestroySyncKHRDelegate(IntPtr dpy, IntPtr sync); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate int EglClientWaitSyncKHRDelegate(IntPtr dpy, IntPtr sync, int flags, long timeout); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate int EglWaitSyncKHRDelegate(IntPtr dpy, IntPtr sync, int flags); |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
public delegate int EglDupNativeFenceFDANDROIDDelegate(IntPtr dpy, IntPtr sync); |
|||
|
|||
public static T? LoadEglExtension<T>(string name) where T : Delegate |
|||
{ |
|||
var ptr = EglGetProcAddress(name); |
|||
return ptr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer<T>(ptr); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using Avalonia.DmaBufInteropTests; |
|||
using Avalonia.DmaBufInteropTests.Tests; |
|||
|
|||
// Parse args
|
|||
var mode = "both"; |
|||
bool verbose = false; |
|||
foreach (var arg in args) |
|||
{ |
|||
switch (arg) |
|||
{ |
|||
case "--egl": mode = "egl"; break; |
|||
case "--vulkan": mode = "vulkan"; break; |
|||
case "--both": mode = "both"; break; |
|||
case "--logic": mode = "logic"; break; |
|||
case "-v" or "--verbose": verbose = true; break; |
|||
case "-h" or "--help": |
|||
PrintUsage(); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
Console.WriteLine($"DMA-BUF Interop Tests — mode: {mode}"); |
|||
Console.WriteLine(new string('=', 60)); |
|||
|
|||
var allResults = new List<TestResult>(); |
|||
var sw = Stopwatch.StartNew(); |
|||
|
|||
// Pure logic tests (always run)
|
|||
RunSuite("DRM Format Mapping", DrmFormatMappingTests.Run(), allResults); |
|||
|
|||
if (mode is "egl" or "both") |
|||
RunSuite("EGL DMA-BUF Import", EglDmaBufImportTests.Run(), allResults); |
|||
|
|||
if (mode is "vulkan" or "both") |
|||
RunSuite("Vulkan DMA-BUF Import", VulkanDmaBufImportTests.Run(), allResults); |
|||
|
|||
sw.Stop(); |
|||
|
|||
// Summary
|
|||
Console.WriteLine(); |
|||
Console.WriteLine(new string('=', 60)); |
|||
var passed = allResults.Count(r => r.Status == TestStatus.Passed); |
|||
var failed = allResults.Count(r => r.Status == TestStatus.Failed); |
|||
var skipped = allResults.Count(r => r.Status == TestStatus.Skipped); |
|||
Console.WriteLine($"Total: {allResults.Count} | Passed: {passed} | Failed: {failed} | Skipped: {skipped} | Time: {sw.ElapsedMilliseconds}ms"); |
|||
|
|||
if (failed > 0) |
|||
{ |
|||
Console.WriteLine(); |
|||
Console.WriteLine("FAILURES:"); |
|||
foreach (var f in allResults.Where(r => r.Status == TestStatus.Failed)) |
|||
Console.WriteLine($" {f}"); |
|||
} |
|||
|
|||
return failed > 0 ? 1 : 0; |
|||
|
|||
void RunSuite(string name, IEnumerable<TestResult> results, List<TestResult> accumulator) |
|||
{ |
|||
Console.WriteLine(); |
|||
Console.WriteLine($"--- {name} ---"); |
|||
foreach (var result in results) |
|||
{ |
|||
accumulator.Add(result); |
|||
if (verbose || result.Status != TestStatus.Passed) |
|||
Console.WriteLine($" {result}"); |
|||
else |
|||
Console.Write("."); |
|||
} |
|||
if (!verbose) |
|||
Console.WriteLine(); |
|||
} |
|||
|
|||
void PrintUsage() |
|||
{ |
|||
Console.WriteLine("Usage: Avalonia.DmaBufInteropTests [options]"); |
|||
Console.WriteLine(); |
|||
Console.WriteLine("Options:"); |
|||
Console.WriteLine(" --egl Run EGL tests only"); |
|||
Console.WriteLine(" --vulkan Run Vulkan tests only"); |
|||
Console.WriteLine(" --both Run both EGL and Vulkan tests (default)"); |
|||
Console.WriteLine(" --logic Run only pure logic tests (no GPU)"); |
|||
Console.WriteLine(" -v Verbose output (show passing tests)"); |
|||
Console.WriteLine(" -h Show this help"); |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
namespace Avalonia.DmaBufInteropTests; |
|||
|
|||
public enum TestStatus |
|||
{ |
|||
Passed, |
|||
Failed, |
|||
Skipped |
|||
} |
|||
|
|||
public record TestResult(string Name, TestStatus Status, string? Message = null) |
|||
{ |
|||
public override string ToString() |
|||
{ |
|||
var tag = Status switch |
|||
{ |
|||
TestStatus.Passed => "PASS", |
|||
TestStatus.Failed => "FAIL", |
|||
TestStatus.Skipped => "SKIP", |
|||
_ => "????" |
|||
}; |
|||
var suffix = Message != null ? $" — {Message}" : ""; |
|||
return $"[{tag}] {Name}{suffix}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.DmaBufInteropTests.Tests; |
|||
|
|||
/// <summary>
|
|||
/// Pure logic tests — no GPU needed.
|
|||
/// </summary>
|
|||
internal static class DrmFormatMappingTests |
|||
{ |
|||
public static IEnumerable<TestResult> Run() |
|||
{ |
|||
yield return Test("DrmFormatMapping_Argb8888_Maps_To_B8G8R8A8", |
|||
PlatformGraphicsDrmFormats.TryMapDrmFormat(PlatformGraphicsDrmFormats.DRM_FORMAT_ARGB8888) |
|||
== PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm); |
|||
|
|||
yield return Test("DrmFormatMapping_Xrgb8888_Maps_To_B8G8R8A8", |
|||
PlatformGraphicsDrmFormats.TryMapDrmFormat(PlatformGraphicsDrmFormats.DRM_FORMAT_XRGB8888) |
|||
== PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm); |
|||
|
|||
yield return Test("DrmFormatMapping_Abgr8888_Maps_To_R8G8B8A8", |
|||
PlatformGraphicsDrmFormats.TryMapDrmFormat(PlatformGraphicsDrmFormats.DRM_FORMAT_ABGR8888) |
|||
== PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm); |
|||
|
|||
yield return Test("DrmFormatMapping_Xbgr8888_Maps_To_R8G8B8A8", |
|||
PlatformGraphicsDrmFormats.TryMapDrmFormat(PlatformGraphicsDrmFormats.DRM_FORMAT_XBGR8888) |
|||
== PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm); |
|||
|
|||
yield return Test("DrmFormatMapping_Unknown_Returns_Null", |
|||
PlatformGraphicsDrmFormats.TryMapDrmFormat(0x00000000) == null); |
|||
|
|||
yield return Test("DmaBufFileDescriptor_Is_DMABUF_FD", |
|||
KnownPlatformGraphicsExternalImageHandleTypes.DmaBufFileDescriptor == "DMABUF_FD"); |
|||
|
|||
yield return Test("SyncFileDescriptor_Is_SYNC_FD", |
|||
KnownPlatformGraphicsExternalSemaphoreHandleTypes.SyncFileDescriptor == "SYNC_FD"); |
|||
|
|||
yield return Test("DrmModLinear_Is_Zero", |
|||
PlatformGraphicsDrmFormats.DRM_FORMAT_MOD_LINEAR == 0); |
|||
} |
|||
|
|||
private static TestResult Test(string name, bool condition) |
|||
{ |
|||
return condition |
|||
? new TestResult(name, TestStatus.Passed) |
|||
: new TestResult(name, TestStatus.Failed, "assertion failed"); |
|||
} |
|||
} |
|||
@ -0,0 +1,366 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using static Avalonia.DmaBufInteropTests.NativeInterop; |
|||
|
|||
namespace Avalonia.DmaBufInteropTests.Tests; |
|||
|
|||
/// <summary>
|
|||
/// Tests EGL DMA-BUF import path directly without Avalonia.
|
|||
/// </summary>
|
|||
internal static unsafe class EglDmaBufImportTests |
|||
{ |
|||
public static List<TestResult> Run() |
|||
{ |
|||
var results = new List<TestResult>(); |
|||
|
|||
using var allocator = new DmaBufAllocator(); |
|||
if (!allocator.IsAvailable) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_All", TestStatus.Skipped, "no render node available")); |
|||
return results; |
|||
} |
|||
|
|||
var display = IntPtr.Zero; |
|||
var context = IntPtr.Zero; |
|||
var surface = IntPtr.Zero; |
|||
|
|||
try |
|||
{ |
|||
// Try GBM platform display first, fall back to default
|
|||
var getPlatformDisplay = LoadEglExtension<EglGetPlatformDisplayDelegate>("eglGetPlatformDisplayEXT"); |
|||
if (getPlatformDisplay != null) |
|||
{ |
|||
int drmFd = -1; |
|||
IntPtr gbm = IntPtr.Zero; |
|||
foreach (var path in new[] { "/dev/dri/renderD128", "/dev/dri/renderD129" }) |
|||
{ |
|||
if (!System.IO.File.Exists(path)) continue; |
|||
drmFd = Open(path, O_RDWR); |
|||
if (drmFd >= 0) |
|||
{ |
|||
gbm = GbmCreateDevice(drmFd); |
|||
if (gbm != IntPtr.Zero) break; |
|||
Close(drmFd); |
|||
drmFd = -1; |
|||
} |
|||
} |
|||
if (gbm != IntPtr.Zero) |
|||
display = getPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm, null); |
|||
} |
|||
|
|||
if (display == IntPtr.Zero) |
|||
display = EglGetDisplay(IntPtr.Zero); |
|||
|
|||
if (display == IntPtr.Zero) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_All", TestStatus.Skipped, "cannot get EGL display")); |
|||
return results; |
|||
} |
|||
|
|||
if (!EglInitialize(display, out _, out _)) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_All", TestStatus.Skipped, "eglInitialize failed")); |
|||
return results; |
|||
} |
|||
|
|||
var extensions = EglQueryString(display, EGL_EXTENSIONS) ?? ""; |
|||
|
|||
results.Add(extensions.Contains("EGL_EXT_image_dma_buf_import") |
|||
? new TestResult("Egl_DmaBuf_Extension_Availability", TestStatus.Passed) |
|||
: new TestResult("Egl_DmaBuf_Extension_Availability", TestStatus.Skipped, |
|||
"EGL_EXT_image_dma_buf_import not available")); |
|||
|
|||
if (!extensions.Contains("EGL_EXT_image_dma_buf_import")) |
|||
{ |
|||
EglTerminate(display); |
|||
return results; |
|||
} |
|||
|
|||
// Create GL context — try pbuffer first, then surfaceless
|
|||
EglBindApi(EGL_OPENGL_ES_API); |
|||
IntPtr config; |
|||
int numConfig; |
|||
bool useSurfaceless = false; |
|||
|
|||
// Try pbuffer config first
|
|||
var configAttribs = new[] |
|||
{ |
|||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, |
|||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, |
|||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, |
|||
EGL_NONE |
|||
}; |
|||
EglChooseConfig(display, configAttribs, &config, 1, out numConfig); |
|||
if (numConfig == 0) |
|||
{ |
|||
configAttribs[3] = EGL_OPENGL_ES2_BIT; |
|||
EglChooseConfig(display, configAttribs, &config, 1, out numConfig); |
|||
} |
|||
|
|||
// Fall back to surfaceless (no surface type requirement)
|
|||
if (numConfig == 0) |
|||
{ |
|||
configAttribs = new[] |
|||
{ |
|||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, |
|||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, |
|||
EGL_NONE |
|||
}; |
|||
EglChooseConfig(display, configAttribs, &config, 1, out numConfig); |
|||
if (numConfig == 0) |
|||
{ |
|||
configAttribs[1] = EGL_OPENGL_ES2_BIT; |
|||
EglChooseConfig(display, configAttribs, &config, 1, out numConfig); |
|||
} |
|||
useSurfaceless = numConfig > 0; |
|||
} |
|||
|
|||
if (numConfig == 0) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_All", TestStatus.Skipped, "no suitable EGL config")); |
|||
EglTerminate(display); |
|||
return results; |
|||
} |
|||
|
|||
var ctxAttribs = new[] { EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE }; |
|||
context = EglCreateContext(display, config, IntPtr.Zero, ctxAttribs); |
|||
if (context == IntPtr.Zero) |
|||
{ |
|||
ctxAttribs = new[] { EGL_CONTEXT_MAJOR_VERSION, 2, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE }; |
|||
context = EglCreateContext(display, config, IntPtr.Zero, ctxAttribs); |
|||
} |
|||
|
|||
if (context == IntPtr.Zero) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_All", TestStatus.Skipped, "cannot create EGL context")); |
|||
EglTerminate(display); |
|||
return results; |
|||
} |
|||
|
|||
if (useSurfaceless) |
|||
{ |
|||
// EGL_KHR_surfaceless_context: pass EGL_NO_SURFACE
|
|||
EglMakeCurrent(display, IntPtr.Zero, IntPtr.Zero, context); |
|||
} |
|||
else |
|||
{ |
|||
surface = EglCreatePbufferSurface(display, config, new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); |
|||
EglMakeCurrent(display, surface, surface, context); |
|||
} |
|||
|
|||
var eglCreateImageKHR = LoadEglExtension<EglCreateImageKHRDelegate>("eglCreateImageKHR"); |
|||
var eglDestroyImageKHR = LoadEglExtension<EglDestroyImageKHRDelegate>("eglDestroyImageKHR"); |
|||
var glEGLImageTargetTexture2DOES = |
|||
LoadEglExtension<GlEGLImageTargetTexture2DOESDelegate>("glEGLImageTargetTexture2DOES"); |
|||
|
|||
if (eglCreateImageKHR == null || eglDestroyImageKHR == null || glEGLImageTargetTexture2DOES == null) |
|||
{ |
|||
results.Add(new TestResult("Egl_DmaBuf_Image_Import", TestStatus.Skipped, |
|||
"missing eglCreateImageKHR or glEGLImageTargetTexture2DOES")); |
|||
} |
|||
else |
|||
{ |
|||
results.AddRange(TestFormatQuery(display, extensions)); |
|||
results.Add(TestImageImportAndReadback(display, allocator, eglCreateImageKHR, |
|||
eglDestroyImageKHR, glEGLImageTargetTexture2DOES)); |
|||
} |
|||
|
|||
results.Add(TestSyncFence(display, extensions)); |
|||
} |
|||
finally |
|||
{ |
|||
if (context != IntPtr.Zero) |
|||
{ |
|||
EglMakeCurrent(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); |
|||
if (surface != IntPtr.Zero) |
|||
EglDestroySurface(display, surface); |
|||
EglDestroyContext(display, context); |
|||
} |
|||
|
|||
if (display != IntPtr.Zero) |
|||
EglTerminate(display); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
private static List<TestResult> TestFormatQuery(IntPtr display, string extensions) |
|||
{ |
|||
if (!extensions.Contains("EGL_EXT_image_dma_buf_import_modifiers")) |
|||
return [new TestResult("Egl_DmaBuf_Format_Query", TestStatus.Skipped, |
|||
"EGL_EXT_image_dma_buf_import_modifiers not available")]; |
|||
|
|||
var queryFormats = LoadEglExtension<EglQueryDmaBufFormatsEXTDelegate>("eglQueryDmaBufFormatsEXT"); |
|||
var queryModifiers = LoadEglExtension<EglQueryDmaBufModifiersEXTDelegate>("eglQueryDmaBufModifiersEXT"); |
|||
if (queryFormats == null || queryModifiers == null) |
|||
return [new TestResult("Egl_DmaBuf_Format_Query", TestStatus.Skipped, "query functions not available")]; |
|||
|
|||
queryFormats(display, 0, null, out var numFormats); |
|||
if (numFormats == 0) |
|||
return [new TestResult("Egl_DmaBuf_Format_Query", TestStatus.Failed, |
|||
"eglQueryDmaBufFormatsEXT returned 0 formats")]; |
|||
|
|||
var formats = new int[numFormats]; |
|||
queryFormats(display, numFormats, formats, out _); |
|||
|
|||
bool hasArgb8888 = false; |
|||
foreach (var fmt in formats) |
|||
if ((uint)fmt == GBM_FORMAT_ARGB8888) |
|||
hasArgb8888 = true; |
|||
|
|||
return [hasArgb8888 |
|||
? new TestResult("Egl_DmaBuf_Format_Query", TestStatus.Passed, |
|||
$"{numFormats} formats, ARGB8888 supported") |
|||
: new TestResult("Egl_DmaBuf_Format_Query", TestStatus.Failed, |
|||
$"{numFormats} formats but DRM_FORMAT_ARGB8888 not found")]; |
|||
} |
|||
|
|||
private static TestResult TestImageImportAndReadback(IntPtr display, DmaBufAllocator allocator, |
|||
EglCreateImageKHRDelegate eglCreateImageKHR, EglDestroyImageKHRDelegate eglDestroyImageKHR, |
|||
GlEGLImageTargetTexture2DOESDelegate glEGLImageTargetTexture2DOES) |
|||
{ |
|||
const uint width = 64, height = 64; |
|||
const uint greenColor = 0xFF00FF00; // ARGB: full green
|
|||
|
|||
using var alloc = allocator.AllocateLinear(width, height, GBM_FORMAT_ARGB8888, greenColor); |
|||
if (alloc == null) |
|||
return new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Skipped, |
|||
"could not allocate DMA-BUF"); |
|||
|
|||
var attribs = new[] |
|||
{ |
|||
EGL_WIDTH, (int)width, |
|||
EGL_HEIGHT, (int)height, |
|||
EGL_LINUX_DRM_FOURCC_EXT, (int)GBM_FORMAT_ARGB8888, |
|||
EGL_DMA_BUF_PLANE0_FD_EXT, alloc.Fd, |
|||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, |
|||
EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)alloc.Stride, |
|||
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (int)(alloc.Modifier & 0xFFFFFFFF), |
|||
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (int)(alloc.Modifier >> 32), |
|||
EGL_NONE |
|||
}; |
|||
|
|||
var eglImage = eglCreateImageKHR(display, IntPtr.Zero, EGL_LINUX_DMA_BUF_EXT, IntPtr.Zero, attribs); |
|||
if (eglImage == IntPtr.Zero) |
|||
{ |
|||
var err = EglGetError(); |
|||
return new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Failed, |
|||
$"eglCreateImageKHR failed with 0x{err:X}"); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
int texId; |
|||
GlGenTextures(1, &texId); |
|||
GlBindTexture(GL_TEXTURE_2D, texId); |
|||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage); |
|||
|
|||
var glErr = GlGetError(); |
|||
if (glErr != 0) |
|||
{ |
|||
GlDeleteTextures(1, &texId); |
|||
return new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Failed, |
|||
$"glEGLImageTargetTexture2DOES error: 0x{glErr:X}"); |
|||
} |
|||
|
|||
int fbo; |
|||
GlGenFramebuffers(1, &fbo); |
|||
GlBindFramebuffer(GL_FRAMEBUFFER, fbo); |
|||
GlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0); |
|||
|
|||
var status = GlCheckFramebufferStatus(GL_FRAMEBUFFER); |
|||
if (status != GL_FRAMEBUFFER_COMPLETE) |
|||
{ |
|||
GlBindFramebuffer(GL_FRAMEBUFFER, 0); |
|||
GlDeleteFramebuffers(1, &fbo); |
|||
GlDeleteTextures(1, &texId); |
|||
return new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Failed, |
|||
$"framebuffer incomplete: 0x{status:X}"); |
|||
} |
|||
|
|||
var pixels = new byte[width * height * 4]; |
|||
fixed (byte* pPixels = pixels) |
|||
GlReadPixels(0, 0, (int)width, (int)height, GL_RGBA, GL_UNSIGNED_BYTE, pPixels); |
|||
|
|||
GlBindFramebuffer(GL_FRAMEBUFFER, 0); |
|||
GlDeleteFramebuffers(1, &fbo); |
|||
GlDeleteTextures(1, &texId); |
|||
|
|||
// Verify: DRM_FORMAT_ARGB8888 with green=0xFF → G=0xFF, A=0xFF after import
|
|||
int correctPixels = 0; |
|||
for (int i = 0; i < width * height; i++) |
|||
{ |
|||
var g = pixels[i * 4 + 1]; |
|||
var a = pixels[i * 4 + 3]; |
|||
if (g == 0xFF && a == 0xFF) |
|||
correctPixels++; |
|||
} |
|||
|
|||
var correctRatio = (double)correctPixels / (width * height); |
|||
return correctRatio > 0.95 |
|||
? new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Passed, |
|||
$"{correctRatio:P0} pixels correct") |
|||
: new TestResult("Egl_DmaBuf_Image_Import_And_Readback", TestStatus.Failed, |
|||
$"only {correctRatio:P0} pixels correct (expected >95%)"); |
|||
} |
|||
finally |
|||
{ |
|||
eglDestroyImageKHR(display, eglImage); |
|||
} |
|||
} |
|||
|
|||
private static TestResult TestSyncFence(IntPtr display, string extensions) |
|||
{ |
|||
if (!extensions.Contains("EGL_ANDROID_native_fence_sync")) |
|||
return new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Skipped, |
|||
"EGL_ANDROID_native_fence_sync not available"); |
|||
|
|||
var createSync = LoadEglExtension<EglCreateSyncKHRDelegate>("eglCreateSyncKHR"); |
|||
var destroySync = LoadEglExtension<EglDestroySyncKHRDelegate>("eglDestroySyncKHR"); |
|||
var clientWait = LoadEglExtension<EglClientWaitSyncKHRDelegate>("eglClientWaitSyncKHR"); |
|||
var dupFence = LoadEglExtension<EglDupNativeFenceFDANDROIDDelegate>("eglDupNativeFenceFDANDROID"); |
|||
|
|||
if (createSync == null || destroySync == null || clientWait == null || dupFence == null) |
|||
return new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Skipped, |
|||
"sync fence functions not available"); |
|||
|
|||
GlFlush(); |
|||
|
|||
var sync = createSync(display, EGL_SYNC_NATIVE_FENCE_ANDROID, |
|||
new[] { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, EGL_NO_NATIVE_FENCE_FD_ANDROID, EGL_NONE }); |
|||
if (sync == IntPtr.Zero) |
|||
{ |
|||
var err = EglGetError(); |
|||
return new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Failed, |
|||
$"eglCreateSyncKHR (export) failed with 0x{err:X}"); |
|||
} |
|||
|
|||
var fd = dupFence(display, sync); |
|||
destroySync(display, sync); |
|||
|
|||
if (fd < 0) |
|||
return new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Failed, |
|||
$"eglDupNativeFenceFDANDROID returned {fd}"); |
|||
|
|||
var importSync = createSync(display, EGL_SYNC_NATIVE_FENCE_ANDROID, |
|||
new[] { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd, EGL_NONE }); |
|||
if (importSync == IntPtr.Zero) |
|||
{ |
|||
var err = EglGetError(); |
|||
return new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Failed, |
|||
$"eglCreateSyncKHR (import) failed with 0x{err:X}"); |
|||
} |
|||
|
|||
var waitResult = clientWait(display, importSync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR); |
|||
destroySync(display, importSync); |
|||
|
|||
return waitResult == EGL_CONDITION_SATISFIED_KHR |
|||
? new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Passed) |
|||
: new TestResult("Egl_SyncFence_RoundTrip", TestStatus.Failed, |
|||
$"eglClientWaitSyncKHR returned 0x{waitResult:X}"); |
|||
} |
|||
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
private delegate IntPtr EglGetPlatformDisplayDelegate(int platform, IntPtr nativeDisplay, int[]? attribs); |
|||
} |
|||
@ -0,0 +1,790 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using static Avalonia.DmaBufInteropTests.NativeInterop; |
|||
|
|||
namespace Avalonia.DmaBufInteropTests.Tests; |
|||
|
|||
/// <summary>
|
|||
/// Tests Vulkan DMA-BUF import path using raw Vulkan API via P/Invoke.
|
|||
/// </summary>
|
|||
internal static unsafe partial class VulkanDmaBufImportTests |
|||
{ |
|||
// Vulkan constants
|
|||
private const uint VK_API_VERSION_1_1 = (1u << 22) | (1u << 12); |
|||
private const int VK_SUCCESS = 0; |
|||
private const int VK_STRUCTURE_TYPE_APPLICATION_INFO = 0; |
|||
private const int VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1; |
|||
private const int VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3; |
|||
private const int VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2; |
|||
private const int VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO = 5; |
|||
private const int VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO = 14; |
|||
private const int VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO = 15; |
|||
private const int VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR = 1000074000; |
|||
private const int VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR = 1000074001; |
|||
private const int VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO = 1000127001; |
|||
private const int VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT = 1000158004; |
|||
private const int VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT = 1000158005; |
|||
private const int VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT = 1000158003; |
|||
private const int VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO = 1000072001; |
|||
private const int VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO = 1000071000; |
|||
private const int VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2 = 1000059003; |
|||
private const int VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2 = 1000059003; |
|||
private const int VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT = 1000158000; |
|||
private const int VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2 = 1000059005; |
|||
|
|||
private const int VK_IMAGE_TYPE_2D = 1; |
|||
private const int VK_FORMAT_B8G8R8A8_UNORM = 44; |
|||
private const int VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT = 1000158000; |
|||
private const int VK_IMAGE_TILING_OPTIMAL = 0; |
|||
private const int VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004; |
|||
private const int VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001; |
|||
private const int VK_SHARING_MODE_EXCLUSIVE = 0; |
|||
private const int VK_SAMPLE_COUNT_1_BIT = 0x00000001; |
|||
private const int VK_IMAGE_LAYOUT_UNDEFINED = 0; |
|||
private const int VK_IMAGE_VIEW_TYPE_2D = 1; |
|||
private const int VK_COMPONENT_SWIZZLE_IDENTITY = 0; |
|||
private const int VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001; |
|||
private const uint VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT = 0x00000200; |
|||
private const uint VK_QUEUE_FAMILY_FOREIGN_EXT = ~0u; |
|||
private const uint VK_QUEUE_FAMILY_IGNORED = ~0u; |
|||
|
|||
private const string LibVulkan = "libvulkan.so.1"; |
|||
|
|||
// Minimal Vulkan P/Invoke declarations
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkCreateInstance")] |
|||
private static partial int VkCreateInstance(VkInstanceCreateInfo* createInfo, void* allocator, IntPtr* instance); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkDestroyInstance")] |
|||
private static partial void VkDestroyInstance(IntPtr instance, void* allocator); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkEnumeratePhysicalDevices")] |
|||
private static partial int VkEnumeratePhysicalDevices(IntPtr instance, uint* count, IntPtr* devices); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetPhysicalDeviceQueueFamilyProperties")] |
|||
private static partial void VkGetPhysicalDeviceQueueFamilyProperties(IntPtr physicalDevice, uint* count, |
|||
VkQueueFamilyProperties* properties); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkCreateDevice")] |
|||
private static partial int VkCreateDevice(IntPtr physicalDevice, VkDeviceCreateInfo* createInfo, void* allocator, |
|||
IntPtr* device); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkDestroyDevice")] |
|||
private static partial void VkDestroyDevice(IntPtr device, void* allocator); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetDeviceProcAddr", StringMarshalling = StringMarshalling.Utf8)] |
|||
private static partial IntPtr VkGetDeviceProcAddr(IntPtr device, string name); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)] |
|||
private static partial IntPtr VkGetInstanceProcAddr(IntPtr instance, string name); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkEnumerateDeviceExtensionProperties")] |
|||
private static partial int VkEnumerateDeviceExtensionProperties(IntPtr physicalDevice, byte* layerName, |
|||
uint* count, VkExtensionProperties* properties); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetPhysicalDeviceMemoryProperties")] |
|||
private static partial void VkGetPhysicalDeviceMemoryProperties(IntPtr physicalDevice, |
|||
VkPhysicalDeviceMemoryProperties* memProperties); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetPhysicalDeviceFormatProperties2")] |
|||
private static partial void VkGetPhysicalDeviceFormatProperties2(IntPtr physicalDevice, int format, |
|||
VkFormatProperties2Native* formatProperties); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkCreateImage")] |
|||
private static partial int VkCreateImage(IntPtr device, VkImageCreateInfo* createInfo, void* allocator, |
|||
ulong* image); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkDestroyImage")] |
|||
private static partial void VkDestroyImage(IntPtr device, ulong image, void* allocator); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkGetImageMemoryRequirements")] |
|||
private static partial void VkGetImageMemoryRequirements(IntPtr device, ulong image, |
|||
VkMemoryRequirements* memRequirements); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkAllocateMemory")] |
|||
private static partial int VkAllocateMemory(IntPtr device, VkMemoryAllocateInfo* allocateInfo, void* allocator, |
|||
ulong* memory); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkFreeMemory")] |
|||
private static partial void VkFreeMemory(IntPtr device, ulong memory, void* allocator); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkBindImageMemory")] |
|||
private static partial int VkBindImageMemory(IntPtr device, ulong image, ulong memory, ulong offset); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkCreateImageView")] |
|||
private static partial int VkCreateImageView(IntPtr device, VkImageViewCreateInfo* createInfo, void* allocator, |
|||
ulong* imageView); |
|||
|
|||
[LibraryImport(LibVulkan, EntryPoint = "vkDestroyImageView")] |
|||
private static partial void VkDestroyImageView(IntPtr device, ulong imageView, void* allocator); |
|||
|
|||
// Function pointer delegate for vkGetMemoryFdPropertiesKHR
|
|||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
|||
private delegate int VkGetMemoryFdPropertiesKHRDelegate(IntPtr device, uint handleType, int fd, |
|||
VkMemoryFdPropertiesKHRNative* memoryFdProperties); |
|||
|
|||
// Structures
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkApplicationInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public byte* pApplicationName; |
|||
public uint applicationVersion; |
|||
public byte* pEngineName; |
|||
public uint engineVersion; |
|||
public uint apiVersion; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkInstanceCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint flags; |
|||
public VkApplicationInfo* pApplicationInfo; |
|||
public uint enabledLayerCount; |
|||
public byte** ppEnabledLayerNames; |
|||
public uint enabledExtensionCount; |
|||
public byte** ppEnabledExtensionNames; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkDeviceQueueCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint flags; |
|||
public uint queueFamilyIndex; |
|||
public uint queueCount; |
|||
public float* pQueuePriorities; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkDeviceCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint flags; |
|||
public uint queueCreateInfoCount; |
|||
public VkDeviceQueueCreateInfo* pQueueCreateInfos; |
|||
public uint enabledLayerCount; |
|||
public byte** ppEnabledLayerNames; |
|||
public uint enabledExtensionCount; |
|||
public byte** ppEnabledExtensionNames; |
|||
public void* pEnabledFeatures; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkExtensionProperties |
|||
{ |
|||
public fixed byte extensionName[256]; |
|||
public uint specVersion; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkQueueFamilyProperties |
|||
{ |
|||
public uint queueFlags; |
|||
public uint queueCount; |
|||
public uint timestampValidBits; |
|||
public uint minImageTransferGranularity_width; |
|||
public uint minImageTransferGranularity_height; |
|||
public uint minImageTransferGranularity_depth; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkPhysicalDeviceMemoryProperties |
|||
{ |
|||
public uint memoryTypeCount; |
|||
public VkMemoryType memoryTypes_0; |
|||
public VkMemoryType memoryTypes_1; |
|||
public VkMemoryType memoryTypes_2; |
|||
public VkMemoryType memoryTypes_3; |
|||
public VkMemoryType memoryTypes_4; |
|||
public VkMemoryType memoryTypes_5; |
|||
public VkMemoryType memoryTypes_6; |
|||
public VkMemoryType memoryTypes_7; |
|||
public VkMemoryType memoryTypes_8; |
|||
public VkMemoryType memoryTypes_9; |
|||
public VkMemoryType memoryTypes_10; |
|||
public VkMemoryType memoryTypes_11; |
|||
public VkMemoryType memoryTypes_12; |
|||
public VkMemoryType memoryTypes_13; |
|||
public VkMemoryType memoryTypes_14; |
|||
public VkMemoryType memoryTypes_15; |
|||
public VkMemoryType memoryTypes_16; |
|||
public VkMemoryType memoryTypes_17; |
|||
public VkMemoryType memoryTypes_18; |
|||
public VkMemoryType memoryTypes_19; |
|||
public VkMemoryType memoryTypes_20; |
|||
public VkMemoryType memoryTypes_21; |
|||
public VkMemoryType memoryTypes_22; |
|||
public VkMemoryType memoryTypes_23; |
|||
public VkMemoryType memoryTypes_24; |
|||
public VkMemoryType memoryTypes_25; |
|||
public VkMemoryType memoryTypes_26; |
|||
public VkMemoryType memoryTypes_27; |
|||
public VkMemoryType memoryTypes_28; |
|||
public VkMemoryType memoryTypes_29; |
|||
public VkMemoryType memoryTypes_30; |
|||
public VkMemoryType memoryTypes_31; |
|||
public uint memoryHeapCount; |
|||
public VkMemoryHeap memoryHeaps_0; |
|||
public VkMemoryHeap memoryHeaps_1; |
|||
public VkMemoryHeap memoryHeaps_2; |
|||
public VkMemoryHeap memoryHeaps_3; |
|||
public VkMemoryHeap memoryHeaps_4; |
|||
public VkMemoryHeap memoryHeaps_5; |
|||
public VkMemoryHeap memoryHeaps_6; |
|||
public VkMemoryHeap memoryHeaps_7; |
|||
public VkMemoryHeap memoryHeaps_8; |
|||
public VkMemoryHeap memoryHeaps_9; |
|||
public VkMemoryHeap memoryHeaps_10; |
|||
public VkMemoryHeap memoryHeaps_11; |
|||
public VkMemoryHeap memoryHeaps_12; |
|||
public VkMemoryHeap memoryHeaps_13; |
|||
public VkMemoryHeap memoryHeaps_14; |
|||
public VkMemoryHeap memoryHeaps_15; |
|||
|
|||
public VkMemoryType GetMemoryType(int index) |
|||
{ |
|||
// Use pointer arithmetic since we can't use fixed buffer of structs
|
|||
fixed (VkMemoryType* p = &memoryTypes_0) |
|||
return p[index]; |
|||
} |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryType |
|||
{ |
|||
public uint propertyFlags; |
|||
public uint heapIndex; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryHeap |
|||
{ |
|||
public ulong size; |
|||
public uint flags; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkImageCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint flags; |
|||
public int imageType; |
|||
public int format; |
|||
public uint extent_width; |
|||
public uint extent_height; |
|||
public uint extent_depth; |
|||
public uint mipLevels; |
|||
public uint arrayLayers; |
|||
public int samples; |
|||
public int tiling; |
|||
public int usage; |
|||
public int sharingMode; |
|||
public uint queueFamilyIndexCount; |
|||
public uint* pQueueFamilyIndices; |
|||
public int initialLayout; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkExternalMemoryImageCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint handleTypes; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkImageDrmFormatModifierExplicitCreateInfoEXT |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public ulong drmFormatModifier; |
|||
public uint drmFormatModifierPlaneCount; |
|||
public VkSubresourceLayout* pPlaneLayouts; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkSubresourceLayout |
|||
{ |
|||
public ulong offset; |
|||
public ulong size; |
|||
public ulong rowPitch; |
|||
public ulong arrayPitch; |
|||
public ulong depthPitch; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryRequirements |
|||
{ |
|||
public ulong size; |
|||
public ulong alignment; |
|||
public uint memoryTypeBits; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryAllocateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public ulong allocationSize; |
|||
public uint memoryTypeIndex; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkImportMemoryFdInfoKHR |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint handleType; |
|||
public int fd; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryDedicatedAllocateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public ulong image; |
|||
public ulong buffer; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkMemoryFdPropertiesKHRNative |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint memoryTypeBits; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkImageViewCreateInfo |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint flags; |
|||
public ulong image; |
|||
public int viewType; |
|||
public int format; |
|||
public int components_r; |
|||
public int components_g; |
|||
public int components_b; |
|||
public int components_a; |
|||
public int subresourceRange_aspectMask; |
|||
public uint subresourceRange_baseMipLevel; |
|||
public uint subresourceRange_levelCount; |
|||
public uint subresourceRange_baseArrayLayer; |
|||
public uint subresourceRange_layerCount; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkDrmFormatModifierPropertiesEXT |
|||
{ |
|||
public ulong drmFormatModifier; |
|||
public uint drmFormatModifierPlaneCount; |
|||
public uint drmFormatModifierTilingFeatures; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkDrmFormatModifierPropertiesListEXT |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint drmFormatModifierCount; |
|||
public VkDrmFormatModifierPropertiesEXT* pDrmFormatModifierProperties; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
private struct VkFormatProperties2Native |
|||
{ |
|||
public int sType; |
|||
public void* pNext; |
|||
public uint linearTilingFeatures; |
|||
public uint optimalTilingFeatures; |
|||
public uint bufferFeatures; |
|||
} |
|||
|
|||
public static List<TestResult> Run() |
|||
{ |
|||
var results = new List<TestResult>(); |
|||
IntPtr instance = IntPtr.Zero; |
|||
IntPtr device = IntPtr.Zero; |
|||
IntPtr physicalDevice = IntPtr.Zero; |
|||
|
|||
try |
|||
{ |
|||
var appInfo = new VkApplicationInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, |
|||
apiVersion = VK_API_VERSION_1_1 |
|||
}; |
|||
|
|||
var instanceCreateInfo = new VkInstanceCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
|||
pApplicationInfo = &appInfo |
|||
}; |
|||
|
|||
var result = VkCreateInstance(&instanceCreateInfo, null, &instance); |
|||
if (result != VK_SUCCESS) |
|||
{ |
|||
results.Add(new TestResult("Vulkan_DmaBuf_All", TestStatus.Skipped, |
|||
$"vkCreateInstance failed: {result}")); |
|||
return results; |
|||
} |
|||
|
|||
uint deviceCount = 0; |
|||
VkEnumeratePhysicalDevices(instance, &deviceCount, null); |
|||
if (deviceCount == 0) |
|||
{ |
|||
results.Add(new TestResult("Vulkan_DmaBuf_All", TestStatus.Skipped, "no Vulkan devices")); |
|||
return results; |
|||
} |
|||
|
|||
var physDevices = stackalloc IntPtr[(int)deviceCount]; |
|||
VkEnumeratePhysicalDevices(instance, &deviceCount, physDevices); |
|||
physicalDevice = physDevices[0]; |
|||
|
|||
uint extCount = 0; |
|||
VkEnumerateDeviceExtensionProperties(physicalDevice, null, &extCount, null); |
|||
var extensions = new VkExtensionProperties[extCount]; |
|||
fixed (VkExtensionProperties* pExt = extensions) |
|||
VkEnumerateDeviceExtensionProperties(physicalDevice, null, &extCount, pExt); |
|||
|
|||
var extNames = new HashSet<string>(); |
|||
fixed (VkExtensionProperties* pExts = extensions) |
|||
{ |
|||
for (int i = 0; i < extCount; i++) |
|||
extNames.Add(Marshal.PtrToStringAnsi((IntPtr)pExts[i].extensionName) ?? ""); |
|||
} |
|||
|
|||
bool hasExternalMemory = extNames.Contains("VK_KHR_external_memory_fd"); |
|||
bool hasDmaBuf = extNames.Contains("VK_EXT_external_memory_dma_buf"); |
|||
bool hasDrmModifier = extNames.Contains("VK_EXT_image_drm_format_modifier"); |
|||
bool hasQueueFamilyForeign = extNames.Contains("VK_EXT_queue_family_foreign"); |
|||
|
|||
results.Add(new TestResult("Vulkan_DmaBuf_Extension_Availability", |
|||
hasDmaBuf && hasDrmModifier ? TestStatus.Passed : TestStatus.Skipped, |
|||
$"external_memory_fd={hasExternalMemory}, dma_buf={hasDmaBuf}, drm_modifier={hasDrmModifier}, queue_family_foreign={hasQueueFamilyForeign}")); |
|||
|
|||
if (!hasDmaBuf || !hasDrmModifier || !hasExternalMemory) |
|||
{ |
|||
results.Add(new TestResult("Vulkan_DmaBuf_All", TestStatus.Skipped, |
|||
"required extensions not available")); |
|||
return results; |
|||
} |
|||
|
|||
results.Add(TestModifierQuery(physicalDevice)); |
|||
|
|||
var requiredExtensions = new List<string> |
|||
{ |
|||
"VK_KHR_external_memory_fd", |
|||
"VK_EXT_external_memory_dma_buf", |
|||
"VK_EXT_image_drm_format_modifier" |
|||
}; |
|||
if (hasQueueFamilyForeign) |
|||
requiredExtensions.Add("VK_EXT_queue_family_foreign"); |
|||
|
|||
uint queueFamilyCount = 0; |
|||
VkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, null); |
|||
var queueFamilies = stackalloc VkQueueFamilyProperties[(int)queueFamilyCount]; |
|||
VkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies); |
|||
|
|||
uint graphicsQueueFamily = uint.MaxValue; |
|||
for (uint i = 0; i < queueFamilyCount; i++) |
|||
{ |
|||
if ((queueFamilies[i].queueFlags & 0x01) != 0) |
|||
{ |
|||
graphicsQueueFamily = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (graphicsQueueFamily == uint.MaxValue) |
|||
{ |
|||
results.Add(new TestResult("Vulkan_DmaBuf_All", TestStatus.Skipped, "no graphics queue")); |
|||
return results; |
|||
} |
|||
|
|||
var extNamePtrs = new IntPtr[requiredExtensions.Count]; |
|||
for (int i = 0; i < requiredExtensions.Count; i++) |
|||
extNamePtrs[i] = Marshal.StringToHGlobalAnsi(requiredExtensions[i]); |
|||
|
|||
try |
|||
{ |
|||
float priority = 1.0f; |
|||
var queueCreateInfo = new VkDeviceQueueCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
|||
queueFamilyIndex = graphicsQueueFamily, |
|||
queueCount = 1, |
|||
pQueuePriorities = &priority |
|||
}; |
|||
|
|||
fixed (IntPtr* pExtNames = extNamePtrs) |
|||
{ |
|||
var deviceCreateInfo = new VkDeviceCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
|||
queueCreateInfoCount = 1, |
|||
pQueueCreateInfos = &queueCreateInfo, |
|||
enabledExtensionCount = (uint)requiredExtensions.Count, |
|||
ppEnabledExtensionNames = (byte**)pExtNames |
|||
}; |
|||
|
|||
result = VkCreateDevice(physicalDevice, &deviceCreateInfo, null, &device); |
|||
} |
|||
|
|||
if (result != VK_SUCCESS) |
|||
{ |
|||
results.Add(new TestResult("Vulkan_DmaBuf_All", TestStatus.Failed, |
|||
$"vkCreateDevice failed: {result}")); |
|||
return results; |
|||
} |
|||
|
|||
results.Add(TestDmaBufImageImport(physicalDevice, device, graphicsQueueFamily, |
|||
hasQueueFamilyForeign)); |
|||
} |
|||
finally |
|||
{ |
|||
for (int i = 0; i < extNamePtrs.Length; i++) |
|||
Marshal.FreeHGlobal(extNamePtrs[i]); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if (device != IntPtr.Zero) |
|||
VkDestroyDevice(device, null); |
|||
if (instance != IntPtr.Zero) |
|||
VkDestroyInstance(instance, null); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
private static TestResult TestModifierQuery(IntPtr physicalDevice) |
|||
{ |
|||
var modList = new VkDrmFormatModifierPropertiesListEXT |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT |
|||
}; |
|||
var fmtProps = new VkFormatProperties2Native |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, |
|||
pNext = &modList |
|||
}; |
|||
|
|||
VkGetPhysicalDeviceFormatProperties2(physicalDevice, VK_FORMAT_B8G8R8A8_UNORM, &fmtProps); |
|||
|
|||
if (modList.drmFormatModifierCount == 0) |
|||
return new TestResult("Vulkan_DmaBuf_Modifier_Query", TestStatus.Failed, |
|||
"no DRM format modifiers for B8G8R8A8_UNORM"); |
|||
|
|||
var modifiers = new VkDrmFormatModifierPropertiesEXT[modList.drmFormatModifierCount]; |
|||
fixed (VkDrmFormatModifierPropertiesEXT* pMods = modifiers) |
|||
{ |
|||
modList.pDrmFormatModifierProperties = pMods; |
|||
fmtProps.pNext = &modList; |
|||
VkGetPhysicalDeviceFormatProperties2(physicalDevice, VK_FORMAT_B8G8R8A8_UNORM, &fmtProps); |
|||
} |
|||
|
|||
bool hasLinear = false; |
|||
foreach (var mod in modifiers) |
|||
if (mod.drmFormatModifier == 0) |
|||
hasLinear = true; |
|||
|
|||
return new TestResult("Vulkan_DmaBuf_Modifier_Query", TestStatus.Passed, |
|||
$"{modList.drmFormatModifierCount} modifiers, linear={hasLinear}"); |
|||
} |
|||
|
|||
private static TestResult TestDmaBufImageImport(IntPtr physicalDevice, IntPtr device, |
|||
uint graphicsQueueFamily, bool hasQueueFamilyForeign) |
|||
{ |
|||
const uint width = 64, height = 64; |
|||
|
|||
using var allocator = new DmaBufAllocator(); |
|||
if (!allocator.IsAvailable) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Skipped, |
|||
"no DMA-BUF allocator available"); |
|||
|
|||
using var alloc = allocator.AllocateLinear(width, height, GBM_FORMAT_ARGB8888, 0xFF00FF00); |
|||
if (alloc == null) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Skipped, |
|||
"could not allocate DMA-BUF"); |
|||
|
|||
var importFd = Dup(alloc.Fd); |
|||
if (importFd < 0) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, "dup() failed"); |
|||
|
|||
ulong vkImage = 0; |
|||
ulong vkMemory = 0; |
|||
ulong vkImageView = 0; |
|||
|
|||
try |
|||
{ |
|||
var getMemFdPropsPtr = VkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR"); |
|||
if (getMemFdPropsPtr == IntPtr.Zero) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
"vkGetMemoryFdPropertiesKHR not found"); |
|||
|
|||
var getMemFdProps = Marshal.GetDelegateForFunctionPointer<VkGetMemoryFdPropertiesKHRDelegate>(getMemFdPropsPtr); |
|||
|
|||
var fdProps = new VkMemoryFdPropertiesKHRNative |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR |
|||
}; |
|||
var result = getMemFdProps(device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, importFd, &fdProps); |
|||
if (result != VK_SUCCESS) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"vkGetMemoryFdPropertiesKHR failed: {result}"); |
|||
|
|||
var externalMemoryInfo = new VkExternalMemoryImageCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, |
|||
handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT |
|||
}; |
|||
|
|||
var planeLayout = new VkSubresourceLayout |
|||
{ |
|||
offset = 0, |
|||
rowPitch = alloc.Stride, |
|||
size = 0, |
|||
arrayPitch = 0, |
|||
depthPitch = 0 |
|||
}; |
|||
|
|||
var drmModifierInfo = new VkImageDrmFormatModifierExplicitCreateInfoEXT |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, |
|||
pNext = &externalMemoryInfo, |
|||
drmFormatModifier = alloc.Modifier, |
|||
drmFormatModifierPlaneCount = 1, |
|||
pPlaneLayouts = &planeLayout |
|||
}; |
|||
|
|||
var imageCreateInfo = new VkImageCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, |
|||
pNext = &drmModifierInfo, |
|||
imageType = VK_IMAGE_TYPE_2D, |
|||
format = VK_FORMAT_B8G8R8A8_UNORM, |
|||
extent_width = width, |
|||
extent_height = height, |
|||
extent_depth = 1, |
|||
mipLevels = 1, |
|||
arrayLayers = 1, |
|||
samples = VK_SAMPLE_COUNT_1_BIT, |
|||
tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, |
|||
usage = VK_IMAGE_USAGE_SAMPLED_BIT, |
|||
sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
initialLayout = VK_IMAGE_LAYOUT_UNDEFINED |
|||
}; |
|||
|
|||
result = VkCreateImage(device, &imageCreateInfo, null, &vkImage); |
|||
if (result != VK_SUCCESS) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"vkCreateImage failed: {result}"); |
|||
|
|||
VkMemoryRequirements memReqs; |
|||
VkGetImageMemoryRequirements(device, vkImage, &memReqs); |
|||
|
|||
VkPhysicalDeviceMemoryProperties memProps; |
|||
VkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); |
|||
|
|||
uint memTypeIndex = uint.MaxValue; |
|||
uint compatBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits; |
|||
for (uint i = 0; i < memProps.memoryTypeCount; i++) |
|||
{ |
|||
if ((compatBits & (1u << (int)i)) != 0) |
|||
{ |
|||
memTypeIndex = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (memTypeIndex == uint.MaxValue) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"no compatible memory type (image={memReqs.memoryTypeBits:X}, fd={fdProps.memoryTypeBits:X})"); |
|||
|
|||
var dedicatedInfo = new VkMemoryDedicatedAllocateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, |
|||
image = vkImage, |
|||
buffer = 0 |
|||
}; |
|||
|
|||
var importFdInfo = new VkImportMemoryFdInfoKHR |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, |
|||
pNext = &dedicatedInfo, |
|||
handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, |
|||
fd = importFd |
|||
}; |
|||
|
|||
var allocInfo = new VkMemoryAllocateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, |
|||
pNext = &importFdInfo, |
|||
allocationSize = memReqs.size, |
|||
memoryTypeIndex = memTypeIndex |
|||
}; |
|||
|
|||
result = VkAllocateMemory(device, &allocInfo, null, &vkMemory); |
|||
if (result != VK_SUCCESS) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"vkAllocateMemory failed: {result}"); |
|||
|
|||
importFd = -1; // Vulkan took ownership
|
|||
|
|||
result = VkBindImageMemory(device, vkImage, vkMemory, 0); |
|||
if (result != VK_SUCCESS) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"vkBindImageMemory failed: {result}"); |
|||
|
|||
var viewCreateInfo = new VkImageViewCreateInfo |
|||
{ |
|||
sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, |
|||
image = vkImage, |
|||
viewType = VK_IMAGE_VIEW_TYPE_2D, |
|||
format = VK_FORMAT_B8G8R8A8_UNORM, |
|||
components_r = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
components_g = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
components_b = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
components_a = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
subresourceRange_aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
|||
subresourceRange_baseMipLevel = 0, |
|||
subresourceRange_levelCount = 1, |
|||
subresourceRange_baseArrayLayer = 0, |
|||
subresourceRange_layerCount = 1 |
|||
}; |
|||
|
|||
result = VkCreateImageView(device, &viewCreateInfo, null, &vkImageView); |
|||
if (result != VK_SUCCESS) |
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Failed, |
|||
$"vkCreateImageView failed: {result}"); |
|||
|
|||
return new TestResult("Vulkan_DmaBuf_Image_Import", TestStatus.Passed, |
|||
$"image + view created, memType={memTypeIndex}, modifier=0x{alloc.Modifier:X}"); |
|||
} |
|||
finally |
|||
{ |
|||
if (vkImageView != 0) |
|||
VkDestroyImageView(device, vkImageView, null); |
|||
if (vkMemory != 0) |
|||
VkFreeMemory(device, vkMemory, null); |
|||
if (vkImage != 0) |
|||
VkDestroyImage(device, vkImage, null); |
|||
if (importFd >= 0) |
|||
Close(importFd); |
|||
} |
|||
} |
|||
|
|||
// We need dup() for duplicating the DMA-BUF fd
|
|||
[LibraryImport("libc", EntryPoint = "dup")] |
|||
private static partial int Dup(int fd); |
|||
} |
|||
Loading…
Reference in new issue