src/Avalonia.Vulkan/VulkanExternalObjectsFeature.cs (272 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal unsafe class VulkanExternalObjectsFeature : IVulkanContextExternalObjectsFeature
{
public static string[] RequiredInstanceExtensions = {
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
};
private static string[] s_requiredCommonDeviceExtensions =
{
"VK_KHR_external_memory",
"VK_KHR_external_semaphore",
"VK_KHR_dedicated_allocation",
};
private static string[] s_requiredLinuxDeviceExtensions =
s_requiredCommonDeviceExtensions.Concat(new[]
{
"VK_KHR_external_semaphore_fd",
"VK_KHR_external_memory_fd"
}).ToArray();
private static string[] s_requiredWin32DeviceExtensions = s_requiredCommonDeviceExtensions.Concat(new[]
{
"VK_KHR_external_semaphore_win32",
"VK_KHR_external_memory_win32"
}).ToArray();
public static string[] RequiredDeviceExtensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? s_requiredWin32DeviceExtensions
: s_requiredLinuxDeviceExtensions;
private readonly VulkanContext _context;
private readonly VulkanCommandBufferPool _pool;
public VulkanExternalObjectsFeature(VulkanContext context)
{
_context = context;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
//TODO: keyed muted
SupportedImageHandleTypes = new[]
{
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle,
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueKmtHandle,
};
SupportedSemaphoreTypes = new[]
{
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueNtHandle,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueKmtHandle
};
}
else
{
SupportedImageHandleTypes = new[]
{
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor
};
SupportedSemaphoreTypes = new[]
{
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor
};
}
var physicalDeviceIDProperties = new VkPhysicalDeviceIDProperties()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES
};
var physicalDeviceProperties2 = new VkPhysicalDeviceProperties2()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
pNext = &physicalDeviceIDProperties
};
_context.InstanceApi.GetPhysicalDeviceProperties2(_context.PhysicalDeviceHandle, &physicalDeviceProperties2);
var luid = new Span<byte>(physicalDeviceIDProperties.deviceLUID, 8).ToArray();
if (luid.Any(b => b != 0))
DeviceLuid = luid;
var uuid = new Span<byte>(physicalDeviceIDProperties.deviceUUID, 16).ToArray();
if (uuid.Any(b => b != 0))
DeviceUuid = uuid;
_pool = new VulkanCommandBufferPool(_context, true);
}
public IReadOnlyList<string> SupportedImageHandleTypes { get; }
public IReadOnlyList<string> SupportedSemaphoreTypes { get; }
public byte[]? DeviceUuid { get; }
public byte[]? DeviceLuid { get; }
public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
{
if (!SupportedImageHandleTypes.Contains(imageHandleType))
throw new ArgumentException();
//TODO: keyed muted
return CompositionGpuImportedImageSynchronizationCapabilities.Semaphores;
}
public IVulkanExternalImage ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
{
if (!SupportedImageHandleTypes.Contains(handle.HandleDescriptor))
throw new NotSupportedException();
return new ImportedImage(_context, _pool, handle, properties);
}
public IVulkanExternalSemaphore ImportSemaphore(IPlatformHandle handle)
{
if (!SupportedSemaphoreTypes.Contains(handle.HandleDescriptor))
throw new NotSupportedException();
var typeBit = handle.HandleDescriptor switch
{
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueKmtHandle =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaqueNtHandle =>
VkExternalSemaphoreHandleTypeFlags.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT,
_ => throw new NotSupportedException()
};
var semaphore = new VulkanSemaphore(_context);
if (handle.HandleDescriptor ==
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
{
var info = new VkImportSemaphoreFdInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
fd = handle.Handle.ToInt32(),
handleType = typeBit,
semaphore = semaphore.Handle
};
var addr = _context.Instance.GetDeviceProcAddress(_context.Device.Handle, "vkImportSemaphoreFdKHR");
if (addr == IntPtr.Zero)
addr = _context.Instance.GetInstanceProcAddress(_context.Instance.Handle, "vkImportSemaphoreFdKHR");
_context.DeviceApi.ImportSemaphoreFdKHR(_context.DeviceHandle, &info)
.ThrowOnError("vkImportSemaphoreFdKHR");
return new ImportedSemaphore(_context, _pool, semaphore);
}
else
{
var info = new VkImportSemaphoreWin32HandleInfoKHR()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR,
handle = handle.Handle,
handleType = typeBit,
semaphore = semaphore.Handle
};
_context.DeviceApi.ImportSemaphoreWin32HandleKHR(_context.DeviceHandle, &info)
.ThrowOnError("vkImportSemaphoreWin32HandleKHR");
return new ImportedSemaphore(_context, _pool, semaphore);
}
}
class ImportedSemaphore : IVulkanExternalSemaphore
{
private readonly VulkanContext _context;
private readonly VulkanCommandBufferPool _pool;
private VulkanSemaphore? _sem;
private VulkanSemaphore Sem => _sem ?? throw new ObjectDisposedException(nameof(ImportedSemaphore));
public ImportedSemaphore(VulkanContext context, VulkanCommandBufferPool pool, VulkanSemaphore sem)
{
_context = context;
_pool = pool;
_sem = sem;
}
public void Dispose()
{
_sem?.Dispose();
_sem = null;
}
public ulong Handle => Sem.Handle.Handle;
void SubmitSemaphore(VulkanSemaphore? wait, VulkanSemaphore? signal)
{
var buf = _pool.CreateCommandBuffer();
buf.BeginRecording();
_context.DeviceApi.CmdPipelineBarrier(
buf.Handle,
VkPipelineStageFlags.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VkPipelineStageFlags.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
0,
IntPtr.Zero,
0,
IntPtr.Zero,
0,
null);
buf.EndRecording();
buf.Submit(wait != null ? new[] { wait }.AsSpan() : default,
new[] { VkPipelineStageFlags.VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT },
signal != null ? new[] { signal }.AsSpan() : default);
}
public void SubmitWaitSemaphore()
{
SubmitSemaphore(_sem, null);
}
public void SubmitSignalSemaphore()
{
SubmitSemaphore(null, _sem);
}
}
class ImportedImage : VulkanImageBase, IVulkanExternalImage
{
private readonly IVulkanPlatformGraphicsContext _context;
private readonly IPlatformHandle _importHandle;
private readonly PlatformGraphicsExternalImageProperties _properties;
private readonly VkExternalMemoryHandleTypeFlagBits _typeBit;
public VulkanImageInfo Info => ImageInfo;
public ImportedImage(IVulkanPlatformGraphicsContext context, VulkanCommandBufferPool commandBufferPool,
IPlatformHandle importHandle,
PlatformGraphicsExternalImageProperties properties) : base(context, commandBufferPool,
properties.Format switch
{
PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => VkFormat.VK_FORMAT_B8G8R8A8_UNORM,
PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => VkFormat.VK_FORMAT_R8G8B8A8_UNORM,
_ => throw new ArgumentException($"Format {properties.Format} is not supported")
}, new PixelSize(properties.Width, properties.Height), 1)
{
_context = context;
_importHandle = importHandle;
_properties = properties;
_typeBit = importHandle.HandleDescriptor switch
{
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor =>
VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueKmtHandle =>
VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle =>
VkExternalMemoryHandleTypeFlagBits.VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
_ => throw new NotSupportedException()
};
var externalAlloc = new VkExternalMemoryImageCreateInfo
{
handleTypes = _typeBit,
sType = VkStructureType.VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO
};
Initialize(&externalAlloc);
TransitionLayout(VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VkAccessFlags.VK_ACCESS_TRANSFER_READ_BIT);
this.CurrentLayout = VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
}
protected override VkDeviceMemory CreateMemory(VkImage image, ulong size, uint memoryTypeBits)
{
var handle = _importHandle;
if (_properties.MemoryOffset != 0 || _properties.MemorySize != size)
throw new Exception("Invalid memory size");
var dedicated = new VkMemoryDedicatedAllocateInfo()
{
image = image,
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
};
var isPosixHandle = handle.HandleDescriptor ==
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor;
var win32Info = new VkImportMemoryWin32HandleInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
handle = handle.Handle,
handleType = _typeBit,
pNext = &dedicated
};
var posixInfo = new VkImportMemoryFdInfoKHR()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
handleType = _typeBit,
fd = isPosixHandle ? handle.Handle.ToInt32() : 0,
pNext = &dedicated
};
var memoryAllocateInfo = new VkMemoryAllocateInfo
{
pNext = new IntPtr(isPosixHandle ? &posixInfo : &win32Info),
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
allocationSize = size,
memoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(_context,
memoryTypeBits,
VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
_context.DeviceApi.AllocateMemory(_context.DeviceHandle, ref memoryAllocateInfo, IntPtr.Zero,
out var imageMemory).ThrowOnError("vkAllocateMemory");
return imageMemory;
}
}
}