diff --git a/include/render/allocator/vulkan.h b/include/render/allocator/vulkan.h new file mode 100644 index 000000000..0d05060b2 --- /dev/null +++ b/include/render/allocator/vulkan.h @@ -0,0 +1,37 @@ +#ifndef RENDER_ALLOCATOR_VULKAN_H +#define RENDER_ALLOCATOR_VULKAN_H + +#include +#include +#include + +struct wlr_vulkan_buffer { + struct wlr_buffer base; + struct wlr_vulkan_allocator *alloc; + + VkImage image; + VkDeviceMemory memory; + + struct wlr_dmabuf_attributes dmabuf; +}; + +struct wlr_vulkan_allocator { + struct wlr_allocator base; + + VkInstance instance; + VkPhysicalDevice phy_device; + + size_t mods_len; + struct wlr_vulkan_allocator_modifier *mods; + + VkDevice device; +}; + +struct wlr_vulkan_allocator_modifier { + VkDrmFormatModifierPropertiesEXT props; + VkExtent2D max_extent; +}; + +struct wlr_allocator *wlr_vulkan_allocator_create(int drm_fd); + +#endif diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in index 6a53d2174..cdd4d3d95 100644 --- a/include/wlr/config.h.in +++ b/include/wlr/config.h.in @@ -9,6 +9,7 @@ #mesondefine WLR_HAS_VULKAN_RENDERER #mesondefine WLR_HAS_GBM_ALLOCATOR +#mesondefine WLR_HAS_VULKAN_ALLOCATOR #mesondefine WLR_HAS_XWAYLAND diff --git a/meson.build b/meson.build index 0a584621e..0685062db 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ features = { 'gles2-renderer': false, 'vulkan-renderer': false, 'gbm-allocator': false, + 'vulkan-allocator': false, 'session': false, } internal_features = { diff --git a/render/allocator/allocator.c b/render/allocator/allocator.c index b380584c7..49d6e8dcf 100644 --- a/render/allocator/allocator.c +++ b/render/allocator/allocator.c @@ -19,6 +19,10 @@ #include "render/allocator/gbm.h" #endif +#if WLR_HAS_VULKAN_ALLOCATOR +#include "render/allocator/vulkan.h" +#endif + void wlr_allocator_init(struct wlr_allocator *alloc, const struct wlr_allocator_interface *impl, uint32_t buffer_caps) { assert(impl && impl->destroy && impl->create_buffer); @@ -99,6 +103,19 @@ struct wlr_allocator *allocator_autocreate_with_drm_fd( struct wlr_allocator *alloc = NULL; + // TODO: disable by default +#if WLR_HAS_VULKAN_ALLOCATOR + uint32_t vk_caps = WLR_BUFFER_CAP_DMABUF; + if ((backend_caps & vk_caps) && (renderer_caps & vk_caps) + && drm_fd >= 0) { + wlr_log(WLR_DEBUG, "Trying to create Vulkan allocator"); + if ((alloc = wlr_vulkan_allocator_create(drm_fd)) != NULL) { + return alloc; + } + wlr_log(WLR_DEBUG, "Failed to create Vulkan allocator"); + } +#endif + #if WLR_HAS_GBM_ALLOCATOR uint32_t gbm_caps = WLR_BUFFER_CAP_DMABUF; if ((backend_caps & gbm_caps) && (renderer_caps & gbm_caps) diff --git a/render/allocator/meson.build b/render/allocator/meson.build index 730a2a41b..e6f757e74 100644 --- a/render/allocator/meson.build +++ b/render/allocator/meson.build @@ -1,6 +1,6 @@ allocators = get_option('allocators') if 'auto' in allocators and get_option('auto_features').enabled() - allocators = ['gbm'] + allocators = ['gbm', 'vulkan'] elif 'auto' in allocators and get_option('auto_features').disabled() allocators = [] endif @@ -23,3 +23,13 @@ if gbm.found() has = cc.has_function('gbm_bo_get_fd_for_plane', dependencies: [gbm]) internal_config.set10('HAVE_GBM_BO_GET_FD_FOR_PLANE', has) endif + +vulkan = disabler() +if 'vulkan' in allocators or 'auto' in allocators + vulkan = dependency('vulkan', required: 'vulkan' in allocators) +endif +if vulkan.found() + wlr_files += files('vulkan.c') + wlr_deps += vulkan + features += { 'vulkan-allocator': true } +endif diff --git a/render/allocator/vulkan.c b/render/allocator/vulkan.c new file mode 100644 index 000000000..87519af5d --- /dev/null +++ b/render/allocator/vulkan.c @@ -0,0 +1,476 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/allocator/vulkan.h" + +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#else +#error "Missing major/minor for this platform" +#endif + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_vulkan_buffer *vulkan_buffer_from_buffer( + struct wlr_buffer *wlr_buf) { + assert(wlr_buf->impl == &buffer_impl); + return (struct wlr_vulkan_buffer *)wlr_buf; +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buf, + struct wlr_dmabuf_attributes *out) { + struct wlr_vulkan_buffer *buf = vulkan_buffer_from_buffer(wlr_buf); + memcpy(out, &buf->dmabuf, sizeof(buf->dmabuf)); + return true; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buf) { + struct wlr_vulkan_buffer *buf = vulkan_buffer_from_buffer(wlr_buf); + wlr_dmabuf_attributes_finish(&buf->dmabuf); + vkFreeMemory(buf->alloc->device, buf->memory, NULL); + vkDestroyImage(buf->alloc->device, buf->image, NULL); + free(buf); +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, +}; + +static const struct wlr_allocator_interface allocator_impl; + +static struct wlr_vulkan_allocator *vulkan_alloc_from_alloc( + struct wlr_allocator *wlr_alloc) { + assert(wlr_alloc->impl == &allocator_impl); + return (struct wlr_vulkan_allocator *)wlr_alloc; +} + +static const struct wlr_vulkan_allocator_modifier *find_mod(struct wlr_vulkan_allocator *alloc, + uint64_t mod) { + for (size_t i = 0; i < alloc->mods_len; i++) { + const struct wlr_vulkan_allocator_modifier *mod_info = &alloc->mods[i]; + if (mod_info->props.drmFormatModifier == mod) { + return mod_info; + } + } + return NULL; +} + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_alloc, int width, int height, + const struct wlr_drm_format *drm_format) { + struct wlr_vulkan_allocator *alloc = vulkan_alloc_from_alloc(wlr_alloc); + + // TODO: do not hardcode + assert(drm_format->format == DRM_FORMAT_ARGB8888 || + drm_format->format == DRM_FORMAT_XRGB8888); + VkFormat vk_format = VK_FORMAT_B8G8R8A8_SRGB; + + uint64_t *mods = calloc(drm_format->len, sizeof(mods[0])); + if (mods == NULL) { + return NULL; + } + + size_t mods_len = 0; + for (size_t i = 0; i < drm_format->len; i++) { + const struct wlr_vulkan_allocator_modifier *mod = + find_mod(alloc, drm_format->modifiers[i]); + if (mod == NULL) { + continue; + } + + if (mod->max_extent.width < (uint32_t)width || + mod->max_extent.height < (uint32_t)height) { + continue; + } + + // TODO: add support for DISJOINT images? + + mods[mods_len++] = mod->props.drmFormatModifier; + } + + if (mods_len == 0) { + wlr_log(WLR_ERROR, "Found zero compatible format modifiers"); + return NULL; + } + + struct wlr_vulkan_buffer *buf = calloc(1, sizeof(*buf)); + if (buf == NULL) { + return NULL; + } + wlr_buffer_init(&buf->base, &buffer_impl, width, height); + buf->alloc = alloc; + + VkImageDrmFormatModifierListCreateInfoEXT drm_format_mod = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, + .drmFormatModifierCount = mods_len, + .pDrmFormatModifiers = mods, + }; + VkExternalMemoryImageCreateInfo ext_mem = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .pNext = &drm_format_mod, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + VkImageCreateInfo img_create = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &ext_mem, + .imageType = VK_IMAGE_TYPE_2D, + .extent = { .width = width, .height = height, .depth = 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .format = vk_format, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .samples = VK_SAMPLE_COUNT_1_BIT, + }; + + VkResult res = vkCreateImage(alloc->device, &img_create, NULL, &buf->image); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkCreateImage failed"); + goto error_buf; + } + + free(mods); + + VkMemoryRequirements mem_reqs = {0}; + vkGetImageMemoryRequirements(alloc->device, buf->image, &mem_reqs); + + VkExportMemoryAllocateInfo export_mem = { + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + VkMemoryAllocateInfo mem_alloc = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &export_mem, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = 0, // TODO + }; + + res = vkAllocateMemory(alloc->device, &mem_alloc, NULL, &buf->memory); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkAllocateMemory failed"); + goto error_image; + } + + res = vkBindImageMemory(alloc->device, buf->image, buf->memory, 0); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkBindImageMemory failed"); + goto error_memory; + } + + PFN_vkGetImageDrmFormatModifierPropertiesEXT vkGetImageDrmFormatModifierPropertiesEXT = + (PFN_vkGetImageDrmFormatModifierPropertiesEXT) + vkGetInstanceProcAddr(alloc->instance, "vkGetImageDrmFormatModifierPropertiesEXT"); + assert(vkGetImageDrmFormatModifierPropertiesEXT != NULL); + PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR = (PFN_vkGetMemoryFdKHR) + vkGetInstanceProcAddr(alloc->instance, "vkGetMemoryFdKHR"); + assert(vkGetMemoryFdKHR != NULL); + + VkImageDrmFormatModifierPropertiesEXT img_mod_props = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT, + }; + res = vkGetImageDrmFormatModifierPropertiesEXT(alloc->device, buf->image, &img_mod_props); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkGetImageDrmFormatModifierPropertiesEXT failed"); + goto error_memory; + } + + VkMemoryGetFdInfoKHR mem_get_fd = { + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = buf->memory, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + int fd = -1; + res = vkGetMemoryFdKHR(alloc->device, &mem_get_fd, &fd); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkGetMemoryFdKHR failed"); + goto error_memory; + } + + const struct wlr_vulkan_allocator_modifier *mod = + find_mod(alloc, img_mod_props.drmFormatModifier); + assert(mod != NULL); + assert(mod->props.drmFormatModifierPlaneCount <= WLR_DMABUF_MAX_PLANES); + + buf->dmabuf = (struct wlr_dmabuf_attributes){ + .format = drm_format->format, + .modifier = img_mod_props.drmFormatModifier, + .width = width, + .height = height, + .n_planes = mod->props.drmFormatModifierPlaneCount, + }; + + // Duplicate the first FD to all other planes + buf->dmabuf.fd[0] = fd; + for (uint32_t i = 1; i < mod->props.drmFormatModifierPlaneCount; i++) { + int dup_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + goto error_memory; + } + buf->dmabuf.fd[i] = dup_fd; + } + + const VkImageAspectFlagBits plane_aspects[] = { + VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT, + }; + assert(mod->props.drmFormatModifierPlaneCount <= + sizeof(plane_aspects) / sizeof(plane_aspects[0])); + + for (uint32_t i = 0; i < mod->props.drmFormatModifierPlaneCount; i++) { + VkImageSubresource img_subres = { + .aspectMask = plane_aspects[i], + }; + VkSubresourceLayout subres_layout = {0}; + vkGetImageSubresourceLayout(alloc->device, buf->image, &img_subres, &subres_layout); + + buf->dmabuf.offset[i] = subres_layout.offset; + buf->dmabuf.stride[i] = subres_layout.rowPitch; + } + + return &buf->base; + +error_memory: + vkFreeMemory(buf->alloc->device, buf->memory, NULL); +error_image: + vkDestroyImage(buf->alloc->device, buf->image, NULL); +error_buf: + free(buf); + return NULL; +} + +static void allocator_destroy(struct wlr_allocator *wlr_alloc) { + struct wlr_vulkan_allocator *alloc = vulkan_alloc_from_alloc(wlr_alloc); + free(alloc->mods); + vkDestroyDevice(alloc->device, NULL); + vkDestroyInstance(alloc->instance, NULL); + free(alloc); +} + +static const struct wlr_allocator_interface allocator_impl = { + .create_buffer = allocator_create_buffer, + .destroy = allocator_destroy, +}; + +static VkPhysicalDevice find_phy_device_from_drm_fd(VkInstance instance, + int drm_fd) { + struct stat drm_stat; + if (fstat(drm_fd, &drm_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return VK_NULL_HANDLE; + } + + int64_t maj = (int64_t)major(drm_stat.st_rdev); + int64_t min = (int64_t)minor(drm_stat.st_rdev); + + uint32_t devices_len = 0; + vkEnumeratePhysicalDevices(instance, &devices_len, NULL); + if (devices_len == 0) { + wlr_log(WLR_ERROR, "No physical device found"); + return VK_NULL_HANDLE; + } + + VkPhysicalDevice *devices = calloc(devices_len, sizeof(VkPhysicalDevice)); + if (devices == NULL) { + return VK_NULL_HANDLE; + } + + VkResult res = vkEnumeratePhysicalDevices(instance, &devices_len, devices); + if (res != VK_SUCCESS) { + free(devices); + wlr_log(WLR_ERROR, "vkEnumeratePhysicalDevices failed"); + return VK_NULL_HANDLE; + } + + VkPhysicalDevice dev = VK_NULL_HANDLE; + for (size_t i = 0; i < devices_len; i++) { + VkPhysicalDeviceDrmPropertiesEXT drm_props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, + }; + VkPhysicalDeviceProperties2 props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &drm_props, + }; + vkGetPhysicalDeviceProperties2(devices[i], &props); + + if (props.properties.apiVersion < VK_API_VERSION_1_1) { + continue; + } + + if (drm_props.hasPrimary && drm_props.primaryMajor == maj && + drm_props.primaryMinor == min) { + dev = devices[i]; + } + if (drm_props.hasRender && drm_props.renderMajor == maj && + drm_props.renderMinor == min) { + dev = devices[i]; + } + + if (dev != VK_NULL_HANDLE) { + wlr_log(WLR_DEBUG, "Physical device: %s", + props.properties.deviceName); + break; + } + } + + free(devices); + + return dev; +} + +static bool create_device(struct wlr_vulkan_allocator *alloc) { + const char *exts[] = { + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, + }; + VkDeviceCreateInfo device_create = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .enabledExtensionCount = sizeof(exts) / sizeof(exts[0]), + .ppEnabledExtensionNames = exts, + }; + VkResult res = + vkCreateDevice(alloc->phy_device, &device_create, NULL, &alloc->device); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkCreateDevice failed"); + return false; + } + return true; +} + +struct wlr_allocator *wlr_vulkan_allocator_create(int drm_fd) { + struct wlr_vulkan_allocator *alloc = calloc(1, sizeof(*alloc)); + if (alloc == NULL) { + return NULL; + } + wlr_allocator_init(&alloc->base, &allocator_impl, WLR_BUFFER_CAP_DMABUF); + + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "wlroots", + .apiVersion = VK_API_VERSION_1_1, + }; + VkInstanceCreateInfo instance_create = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + }; + VkResult res = vkCreateInstance(&instance_create, NULL, &alloc->instance); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "vkCreateInstance failed"); + return NULL; + } + + alloc->phy_device = find_phy_device_from_drm_fd(alloc->instance, drm_fd); + if (alloc->phy_device == VK_NULL_HANDLE) { + wlr_log(WLR_ERROR, "Failed to find physical device from DRM FD"); + return NULL; + } + + if (!create_device(alloc)) { + return NULL; + } + + VkFormat vk_format = VK_FORMAT_B8G8R8A8_SRGB; + VkDrmFormatModifierPropertiesListEXT mod_props_list = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, + }; + VkFormatProperties2 fmt_props = { + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &mod_props_list, + }; + + vkGetPhysicalDeviceFormatProperties2(alloc->phy_device, vk_format, &fmt_props); + + mod_props_list.pDrmFormatModifierProperties = + calloc(mod_props_list.drmFormatModifierCount, + sizeof(mod_props_list.pDrmFormatModifierProperties[0])); + if (mod_props_list.pDrmFormatModifierProperties == NULL) { + return NULL; + } + + vkGetPhysicalDeviceFormatProperties2(alloc->phy_device, vk_format, &fmt_props); + + alloc->mods = calloc(mod_props_list.drmFormatModifierCount, sizeof(alloc->mods[0])); + if (alloc->mods == NULL) { + return NULL; + } + + for (uint32_t i = 0; i < mod_props_list.drmFormatModifierCount; i++) { + VkDrmFormatModifierPropertiesEXT props = mod_props_list.pDrmFormatModifierProperties[i]; + + if (!(props.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) { + continue; + } + + VkPhysicalDeviceImageDrmFormatModifierInfoEXT mod_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, + .drmFormatModifier = props.drmFormatModifier, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + }; + VkPhysicalDeviceExternalImageFormatInfo ext_img_fmt_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .pNext = &mod_info, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + VkPhysicalDeviceImageFormatInfo2 img_fmt_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .pNext = &ext_img_fmt_info, + .type = VK_IMAGE_TYPE_2D, + .format = vk_format, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + }; + + VkExternalImageFormatProperties ext_img_fmt_props = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + }; + VkImageFormatProperties2 img_fmt_props = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + .pNext = &ext_img_fmt_props, + }; + + res = vkGetPhysicalDeviceImageFormatProperties2(alloc->phy_device, + &img_fmt_info, &img_fmt_props); + if (res != VK_SUCCESS) { + if (res != VK_ERROR_FORMAT_NOT_SUPPORTED) { + wlr_log(WLR_ERROR, "vkGetPhysicalDeviceImageFormatProperties2 failed"); + } + continue; + } + + VkExternalMemoryFeatureFlags ext_mem_features = + ext_img_fmt_props.externalMemoryProperties.externalMemoryFeatures; + if (!(ext_mem_features & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT)) { + continue; + } + + VkExtent3D max_extent = img_fmt_props.imageFormatProperties.maxExtent; + alloc->mods[alloc->mods_len++] = (struct wlr_vulkan_allocator_modifier){ + .props = props, + .max_extent = { .width = max_extent.width, .height = max_extent.height }, + }; + } + + free(mod_props_list.pDrmFormatModifierProperties); + + if (alloc->mods_len == 0) { + wlr_log(WLR_ERROR, "Found zero supported format modifiers"); + return NULL; + } + + return &alloc->base; +}