Browse Source

Add test program

dev/timill/dma-buf
Timothy Miller 2 weeks ago
parent
commit
9d49ed84bc
  1. 19
      tests/Avalonia.DmaBufInteropTests/Avalonia.DmaBufInteropTests.csproj
  2. 208
      tests/Avalonia.DmaBufInteropTests/DmaBufAllocator.cs
  3. 294
      tests/Avalonia.DmaBufInteropTests/NativeInterop.cs
  4. 88
      tests/Avalonia.DmaBufInteropTests/Program.cs
  5. 24
      tests/Avalonia.DmaBufInteropTests/TestResult.cs
  6. 48
      tests/Avalonia.DmaBufInteropTests/Tests/DrmFormatMappingTests.cs
  7. 366
      tests/Avalonia.DmaBufInteropTests/Tests/EglDmaBufImportTests.cs
  8. 790
      tests/Avalonia.DmaBufInteropTests/Tests/VulkanDmaBufImportTests.cs

19
tests/Avalonia.DmaBufInteropTests/Avalonia.DmaBufInteropTests.csproj

@ -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>

208
tests/Avalonia.DmaBufInteropTests/DmaBufAllocator.cs

@ -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);
}
}

294
tests/Avalonia.DmaBufInteropTests/NativeInterop.cs

@ -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);
}
}

88
tests/Avalonia.DmaBufInteropTests/Program.cs

@ -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");
}

24
tests/Avalonia.DmaBufInteropTests/TestResult.cs

@ -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}";
}
}

48
tests/Avalonia.DmaBufInteropTests/Tests/DrmFormatMappingTests.cs

@ -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");
}
}

366
tests/Avalonia.DmaBufInteropTests/Tests/EglDmaBufImportTests.cs

@ -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);
}

790
tests/Avalonia.DmaBufInteropTests/Tests/VulkanDmaBufImportTests.cs

@ -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…
Cancel
Save