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