mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-03 08:08:43 +00:00
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:
parent
06c916d91c
commit
60a7359f0f
Notes:
github-actions[bot]
2025-08-18 22:32:02 +00:00
Author: https://github.com/erik-kz
Commit: 60a7359f0f
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5864
Reviewed-by: https://github.com/kalenikaliaksandr ✅
2 changed files with 272 additions and 1 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue