From 60a7359f0f6827799621db8320b43e5d60d08ef2 Mon Sep 17 00:00:00 2001 From: Erik Kurzinger Date: Thu, 14 Aug 2025 19:00:44 -0400 Subject: [PATCH] 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. --- Libraries/LibGfx/VulkanContext.cpp | 227 ++++++++++++++++++++++++++++- Libraries/LibGfx/VulkanContext.h | 46 ++++++ 2 files changed, 272 insertions(+), 1 deletion(-) diff --git a/Libraries/LibGfx/VulkanContext.cpp b/Libraries/LibGfx/VulkanContext.cpp index 1afaeb92bb9..0fdadf63ddc 100644 --- a/Libraries/LibGfx/VulkanContext.cpp +++ b/Libraries/LibGfx/VulkanContext.cpp @@ -99,12 +99,18 @@ static ErrorOr 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 allocate_command_buffer(VkDevice logical_device, ErrorOr 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 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 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> 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 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 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 image = make_ref_counted(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(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; +} + } diff --git a/Libraries/LibGfx/VulkanContext.h b/Libraries/LibGfx/VulkanContext.h index d6109555248..6dea692e3cc 100644 --- a/Libraries/LibGfx/VulkanContext.h +++ b/Libraries/LibGfx/VulkanContext.h @@ -8,6 +8,10 @@ #ifdef USE_VULKAN +# include +# include +# include +# include # include 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 { + 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 create_vulkan_context(); +ErrorOr> create_shared_vulkan_image(VulkanContext const& context, uint32_t width, uint32_t height, VkFormat format, uint32_t num_modifiers, uint64_t const* modifiers); }