LibGfx: Implement Vulkan image allocation

This introduces the ability to allocate Vulkan images which can be
shared across graphics APIs or across processes using the Linux dma-buf
interface.

The create_shareable_vulkan_image function takes a VulkanContext, image
width, height, and format, and an array of DRM format modifiers
representing image memory layouts accepted by the caller. The function
will intersect this list with the list of modifiers supported by the
Vulkan implementation, ensuring the resulting image uses one of those
layouts (exactly which one is up to the implementation). The function
will return a VulkanImage object, which is reference-counted and
encapsulates the VkImage itself as well as the backing memory
allocation. It also stores various image parameters such as usage,
tiling, etc.

The VulkanImage::get_dma_buf_fd function will create a file descriptor
representing the image's backing dma-buf which can then be imported by a
different API or sent over a unix domain socket.
This commit is contained in:
Erik Kurzinger 2025-08-14 19:00:44 -04:00 committed by Alexander Kalenik
commit 60a7359f0f
Notes: github-actions[bot] 2025-08-18 22:32:02 +00:00
2 changed files with 272 additions and 1 deletions

View file

@ -99,12 +99,18 @@ static ErrorOr<VkDevice> create_logical_device(VkPhysicalDevice physical_device,
queue_create_info.pQueuePriorities = &queue_priority;
VkPhysicalDeviceFeatures deviceFeatures {};
char const* device_extensions[] = {
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME
};
VkDeviceCreateInfo create_device_info {};
create_device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create_device_info.pQueueCreateInfos = &queue_create_info;
create_device_info.queueCreateInfoCount = 1;
create_device_info.pEnabledFeatures = &deviceFeatures;
create_device_info.enabledExtensionCount = sizeof(device_extensions) / sizeof(device_extensions[0]);
create_device_info.ppEnabledExtensionNames = device_extensions;
if (vkCreateDevice(physical_device, &create_device_info, nullptr, &device) != VK_SUCCESS) {
return Error::from_string_literal("Logical device creation failed");
@ -150,7 +156,7 @@ static ErrorOr<VkCommandBuffer> allocate_command_buffer(VkDevice logical_device,
ErrorOr<VulkanContext> create_vulkan_context()
{
uint32_t const api_version = VK_API_VERSION_1_0;
uint32_t const api_version = VK_API_VERSION_1_1; // v1.1 needed for vkGetPhysicalDeviceFormatProperties2
auto* instance = TRY(create_instance(api_version));
auto* physical_device = TRY(pick_physical_device(instance));
@ -162,6 +168,15 @@ ErrorOr<VulkanContext> create_vulkan_context()
VkCommandPool command_pool = TRY(create_command_pool(logical_device, graphics_queue_family));
VkCommandBuffer command_buffer = TRY(allocate_command_buffer(logical_device, command_pool));
PFN_vkGetMemoryFdKHR pfn_vk_get_memory_fd_khr = (PFN_vkGetMemoryFdKHR)vkGetDeviceProcAddr(logical_device, "vkGetMemoryFdKHR");
if (pfn_vk_get_memory_fd_khr == nullptr) {
return Error::from_string_literal("vkGetMemoryFdKHR unavailable");
}
PFN_vkGetImageDrmFormatModifierPropertiesEXT pfn_vk_get_image_drm_format_modifier_properties_khr = (PFN_vkGetImageDrmFormatModifierPropertiesEXT)vkGetDeviceProcAddr(logical_device, "vkGetImageDrmFormatModifierPropertiesEXT");
if (pfn_vk_get_image_drm_format_modifier_properties_khr == nullptr) {
return Error::from_string_literal("vkGetImageDrmFormatModifierPropertiesEXT unavailable");
}
return VulkanContext {
.api_version = api_version,
.instance = instance,
@ -171,7 +186,217 @@ ErrorOr<VulkanContext> create_vulkan_context()
.graphics_queue_family = graphics_queue_family,
.command_pool = command_pool,
.command_buffer = command_buffer,
.ext_procs = {
.get_memory_fd = pfn_vk_get_memory_fd_khr,
.get_image_drm_format_modifier_properties = pfn_vk_get_image_drm_format_modifier_properties_khr,
},
};
}
VulkanImage::~VulkanImage()
{
if (image != VK_NULL_HANDLE) {
vkDestroyImage(context.logical_device, image, nullptr);
}
if (memory != VK_NULL_HANDLE) {
vkFreeMemory(context.logical_device, memory, nullptr);
}
}
void VulkanImage::transition_layout(VkImageLayout old_layout, VkImageLayout new_layout)
{
vkResetCommandBuffer(context.command_buffer, 0);
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
.pInheritanceInfo = nullptr,
};
vkBeginCommandBuffer(context.command_buffer, &begin_info);
VkImageMemoryBarrier imageMemoryBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = 0,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
vkCmdPipelineBarrier(context.command_buffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
0, nullptr,
0, nullptr,
1, &imageMemoryBarrier);
vkEndCommandBuffer(context.command_buffer);
VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 0,
.pWaitSemaphores = nullptr,
.pWaitDstStageMask = nullptr,
.commandBufferCount = 1,
.pCommandBuffers = &context.command_buffer,
.signalSemaphoreCount = 0,
.pSignalSemaphores = nullptr,
};
vkQueueSubmit(context.graphics_queue, 1, &submit_info, nullptr);
vkQueueWaitIdle(context.graphics_queue);
}
int VulkanImage::get_dma_buf_fd()
{
VkMemoryGetFdInfoKHR get_fd_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.pNext = nullptr,
.memory = memory,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
int fd = -1;
VkResult result = context.ext_procs.get_memory_fd(context.logical_device, &get_fd_info, &fd);
if (result != VK_SUCCESS) {
dbgln("vkGetMemoryFdKHR returned {}", to_underlying(result));
return -1;
}
return fd;
}
ErrorOr<NonnullRefPtr<VulkanImage>> create_shared_vulkan_image(VulkanContext const& context, uint32_t width, uint32_t height, VkFormat format, uint32_t num_modifiers, uint64_t const* modifiers)
{
VkDrmFormatModifierPropertiesListEXT format_mod_props_list = {};
format_mod_props_list.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT;
format_mod_props_list.pNext = nullptr;
VkFormatProperties2 format_props = {};
format_props.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
format_props.pNext = &format_mod_props_list;
vkGetPhysicalDeviceFormatProperties2(context.physical_device, format, &format_props);
Vector<VkDrmFormatModifierPropertiesEXT> format_mod_props;
format_mod_props.resize(format_mod_props_list.drmFormatModifierCount);
format_mod_props_list.pDrmFormatModifierProperties = format_mod_props.data();
vkGetPhysicalDeviceFormatProperties2(context.physical_device, format, &format_props);
// populate a list of all format modifiers that are both renderable and accepted by the caller
Vector<uint64_t> format_mods;
for (VkDrmFormatModifierPropertiesEXT const& props : format_mod_props) {
if ((props.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) && (props.drmFormatModifierPlaneCount == 1)) {
for (uint32_t i = 0; i < num_modifiers; ++i) {
if (modifiers[i] == props.drmFormatModifier) {
format_mods.append(props.drmFormatModifier);
break;
}
}
}
}
NonnullRefPtr<VulkanImage> image = make_ref_counted<VulkanImage>(context);
VkImageDrmFormatModifierListCreateInfoEXT image_drm_format_modifier_list_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
.pNext = nullptr,
.drmFormatModifierCount = static_cast<uint32_t>(format_mods.size()),
.pDrmFormatModifiers = format_mods.data(),
};
VkExternalMemoryImageCreateInfo external_mem_image_info = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.pNext = &image_drm_format_modifier_list_info,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
uint32_t queue_families[] = { context.graphics_queue_family, VK_QUEUE_FAMILY_EXTERNAL };
VkImageCreateInfo image_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &external_mem_image_info,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {
.width = width,
.height = height,
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_CONCURRENT,
.queueFamilyIndexCount = sizeof(queue_families) / sizeof(queue_families[0]),
.pQueueFamilyIndices = queue_families,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
auto result = vkCreateImage(context.logical_device, &image_info, nullptr, &image->image);
if (result != VK_SUCCESS) {
dbgln("vkCreateImage returned {}", to_underlying(result));
return Error::from_string_literal("image creation failed");
}
VkMemoryRequirements mem_reqs;
vkGetImageMemoryRequirements(context.logical_device, image->image, &mem_reqs);
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(context.physical_device, &mem_props);
uint32_t mem_type_idx;
for (mem_type_idx = 0; mem_type_idx < mem_props.memoryTypeCount; ++mem_type_idx) {
if ((mem_reqs.memoryTypeBits & (1 << mem_type_idx)) && (mem_props.memoryTypes[mem_type_idx].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
break;
}
}
if (mem_type_idx == mem_props.memoryTypeCount) {
return Error::from_string_literal("unable to find suitable image memory type");
}
VkExportMemoryAllocateInfo export_mem_alloc_info = {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.pNext = nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
VkMemoryAllocateInfo mem_alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &export_mem_alloc_info,
.allocationSize = mem_reqs.size,
.memoryTypeIndex = mem_type_idx,
};
result = vkAllocateMemory(context.logical_device, &mem_alloc_info, nullptr, &image->memory);
if (result != VK_SUCCESS) {
dbgln("vkAllocateMemory returned {}", to_underlying(result));
return Error::from_string_literal("image memory allocation failed");
}
result = vkBindImageMemory(context.logical_device, image->image, image->memory, 0);
if (result != VK_SUCCESS) {
dbgln("vkBindImageMemory returned {}", to_underlying(result));
return Error::from_string_literal("bind image memory failed");
}
VkImageSubresource subresource = { VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, 0, 0 };
VkSubresourceLayout subresource_layout = {};
vkGetImageSubresourceLayout(context.logical_device, image->image, &subresource, &subresource_layout);
VkImageDrmFormatModifierPropertiesEXT image_format_mod_props = {};
image_format_mod_props.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT;
image_format_mod_props.pNext = nullptr;
result = context.ext_procs.get_image_drm_format_modifier_properties(context.logical_device, image->image, &image_format_mod_props);
if (result != VK_SUCCESS) {
dbgln("vkGetImageDrmFormatModifierPropertiesEXT returned {}", to_underlying(result));
return Error::from_string_literal("image format modifier retrieval failed");
}
// external APIs require general layout
VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL;
image->transition_layout(VK_IMAGE_LAYOUT_UNDEFINED, layout);
image->info = {
.format = image_info.format,
.extent = image_info.extent,
.tiling = image_info.tiling,
.usage = image_info.usage,
.sharing_mode = image_info.sharingMode,
.layout = layout,
.row_pitch = subresource_layout.rowPitch,
.modifier = image_format_mod_props.drmFormatModifier,
};
return image;
}
}

View file

@ -8,6 +8,10 @@
#ifdef USE_VULKAN
# include <AK/Assertions.h>
# include <AK/NonnullRefPtr.h>
# include <AK/RefCounted.h>
# include <libdrm/drm_fourcc.h>
# include <vulkan/vulkan.h>
namespace Gfx {
@ -21,9 +25,51 @@ struct VulkanContext {
uint32_t graphics_queue_family { 0 };
VkCommandPool command_pool { VK_NULL_HANDLE };
VkCommandBuffer command_buffer { VK_NULL_HANDLE };
struct
{
PFN_vkGetMemoryFdKHR get_memory_fd { nullptr };
PFN_vkGetImageDrmFormatModifierPropertiesEXT get_image_drm_format_modifier_properties { nullptr };
} ext_procs;
};
struct VulkanImage : public RefCounted<VulkanImage> {
VkImage image { VK_NULL_HANDLE };
VkDeviceMemory memory { VK_NULL_HANDLE };
struct {
VkFormat format;
VkExtent3D extent;
VkImageTiling tiling;
VkImageUsageFlags usage;
VkSharingMode sharing_mode;
VkImageLayout layout;
VkDeviceSize row_pitch; // for tiled images this is some implementation-specific value
uint64_t modifier { DRM_FORMAT_MOD_INVALID };
} info;
VulkanContext const& context;
int get_dma_buf_fd();
void transition_layout(VkImageLayout old_layout, VkImageLayout new_layout);
VulkanImage(VulkanContext const& context)
: context(context)
{
}
~VulkanImage();
};
static inline uint32_t vk_format_to_drm_format(VkFormat format)
{
switch (format) {
case VK_FORMAT_B8G8R8A8_UNORM:
return DRM_FORMAT_ARGB8888;
// add more as needed
default:
VERIFY_NOT_REACHED();
return DRM_FORMAT_INVALID;
}
}
ErrorOr<VulkanContext> create_vulkan_context();
ErrorOr<NonnullRefPtr<VulkanImage>> create_shared_vulkan_image(VulkanContext const& context, uint32_t width, uint32_t height, VkFormat format, uint32_t num_modifiers, uint64_t const* modifiers);
}