ladybird/Libraries/LibGfx/VulkanContext.cpp
Erik Kurzinger ce03b8b3b1 LibGfx+LibWeb: Only compile dma-buf-related code on Linux
Shareable Vulkan image allocation on Linux relies on the dma-buf
interface, which is a Linux-specific thing. Therefore, we should only be
compiling it (and any code that uses it) on Linux. This change adds
preprocessor guards to do that. Enabling similar functionality on other
operating systems will need to leverage analogous interfaces on those
platforms, e.g. win32 handles on Windows.

All Vulkan image code will now be guarded by the USE_VULKAN_IMAGES
preprocessor definition, currently enabled on Linux if Vulkan is
available. Additionally, we shuffle around some code in
OpenGLContext.cpp to simplify the preprocessor conditionals.
2025-08-21 14:42:41 +02:00

415 lines
17 KiB
C++

/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/Vector.h>
#include <LibGfx/VulkanContext.h>
namespace Gfx {
static ErrorOr<VkInstance> create_instance(uint32_t api_version)
{
VkInstance instance;
VkApplicationInfo app_info {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = "Ladybird";
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.pEngineName = nullptr;
app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.apiVersion = api_version;
VkInstanceCreateInfo create_info {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = &app_info;
auto result = vkCreateInstance(&create_info, nullptr, &instance);
if (result != VK_SUCCESS) {
dbgln("vkCreateInstance returned {}", to_underlying(result));
return Error::from_string_literal("Application instance creation failed");
}
return instance;
}
static ErrorOr<VkPhysicalDevice> pick_physical_device(VkInstance instance)
{
uint32_t device_count = 0;
vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (device_count == 0)
return Error::from_string_literal("Can't find any physical devices available");
Vector<VkPhysicalDevice> devices;
devices.resize(device_count);
vkEnumeratePhysicalDevices(instance, &device_count, devices.data());
VkPhysicalDevice picked_device = VK_NULL_HANDLE;
// Pick discrete GPU or the first device in the list
for (auto const& device : devices) {
if (picked_device == VK_NULL_HANDLE)
picked_device = device;
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties(device, &device_properties);
if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
picked_device = device;
}
if (picked_device != VK_NULL_HANDLE)
return picked_device;
VERIFY_NOT_REACHED();
}
static ErrorOr<VkDevice> create_logical_device(VkPhysicalDevice physical_device, uint32_t* graphics_queue_family)
{
VkDevice device;
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);
Vector<VkQueueFamilyProperties> queue_families;
queue_families.resize(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data());
bool graphics_queue_family_found = false;
uint32_t graphics_queue_family_index = 0;
for (; graphics_queue_family_index < queue_families.size(); ++graphics_queue_family_index) {
if (queue_families[graphics_queue_family_index].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphics_queue_family_found = true;
break;
}
}
if (!graphics_queue_family_found) {
return Error::from_string_literal("Graphics queue family not found");
}
*graphics_queue_family = graphics_queue_family_index;
VkDeviceQueueCreateInfo queue_create_info {};
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_create_info.queueFamilyIndex = graphics_queue_family_index;
queue_create_info.queueCount = 1;
float const queue_priority = 1.0f;
queue_create_info.pQueuePriorities = &queue_priority;
VkPhysicalDeviceFeatures deviceFeatures {};
#ifdef USE_VULKAN_IMAGES
char const* device_extensions[] = {
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME
};
uint32_t device_extension_count = array_size(device_extensions);
#else
const char** device_extensions = nullptr;
uint32_t device_extension_count = 0;
#endif
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 = device_extension_count;
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");
}
return device;
}
#ifdef USE_VULKAN_IMAGES
static ErrorOr<VkCommandPool> create_command_pool(VkDevice logical_device, uint32_t queue_family_index)
{
VkCommandPoolCreateInfo command_pool_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = queue_family_index,
};
VkCommandPool command_pool = VK_NULL_HANDLE;
VkResult result = vkCreateCommandPool(logical_device, &command_pool_info, nullptr, &command_pool);
if (result != VK_SUCCESS) {
dbgln("vkCreateCommandPool returned {}", to_underlying(result));
return Error::from_string_literal("command pool creation failed");
}
return command_pool;
}
static ErrorOr<VkCommandBuffer> allocate_command_buffer(VkDevice logical_device, VkCommandPool commandPool)
{
VkCommandBufferAllocateInfo command_buffer_alloc_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.pNext = nullptr,
.commandPool = commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
VkResult result = vkAllocateCommandBuffers(logical_device, &command_buffer_alloc_info, &command_buffer);
if (result != VK_SUCCESS) {
dbgln("vkAllocateCommandBuffers returned {}", to_underlying(result));
return Error::from_string_literal("command buffer allocation failed");
}
return command_buffer;
}
#endif
ErrorOr<VulkanContext> create_vulkan_context()
{
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));
uint32_t graphics_queue_family = 0;
auto* logical_device = TRY(create_logical_device(physical_device, &graphics_queue_family));
VkQueue graphics_queue;
vkGetDeviceQueue(logical_device, graphics_queue_family, 0, &graphics_queue);
#ifdef USE_VULKAN_IMAGES
VkCommandPool command_pool = TRY(create_command_pool(logical_device, graphics_queue_family));
VkCommandBuffer command_buffer = TRY(allocate_command_buffer(logical_device, command_pool));
auto pfn_vk_get_memory_fd_khr = reinterpret_cast<PFN_vkGetMemoryFdKHR>(vkGetDeviceProcAddr(logical_device, "vkGetMemoryFdKHR"));
if (pfn_vk_get_memory_fd_khr == nullptr) {
return Error::from_string_literal("vkGetMemoryFdKHR unavailable");
}
auto pfn_vk_get_image_drm_format_modifier_properties_khr = reinterpret_cast<PFN_vkGetImageDrmFormatModifierPropertiesEXT>(vkGetDeviceProcAddr(logical_device, "vkGetImageDrmFormatModifierPropertiesEXT"));
if (pfn_vk_get_image_drm_format_modifier_properties_khr == nullptr) {
return Error::from_string_literal("vkGetImageDrmFormatModifierPropertiesEXT unavailable");
}
#endif
return VulkanContext {
.api_version = api_version,
.instance = instance,
.physical_device = physical_device,
.logical_device = logical_device,
.graphics_queue = graphics_queue,
.graphics_queue_family = graphics_queue_family,
#ifdef USE_VULKAN_IMAGES
.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,
},
#endif
};
}
#ifdef USE_VULKAN_IMAGES
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 = array_size(queue_families),
.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;
}
#endif
}