From f6993ab0da19f96965936ef840462d1069cb3677 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 16 Jun 2024 12:17:41 +0200 Subject: [PATCH 1/3] render/vulkan: Prepare VK_EXT_external_memory_host --- include/render/vulkan.h | 2 ++ render/vulkan/vulkan.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 5b74b613e..17467a25f 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -54,6 +54,7 @@ struct wlr_vk_device { PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR; PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR; PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR; + PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT; } api; uint32_t format_prop_count; @@ -61,6 +62,7 @@ struct wlr_vk_device { struct wlr_drm_format_set dmabuf_render_formats; struct wlr_drm_format_set dmabuf_texture_formats; struct wlr_drm_format_set shm_texture_formats; + size_t minImportedHostPointerAlignment; }; // Tries to find the VkPhysicalDevice for the given drm fd. diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index 7cdc44a02..7582b2ab6 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -470,6 +470,12 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, extensions[extensions_len++] = VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.2 extensions[extensions_len++] = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME; // or vulkan 1.3 + bool has_ext_external_memory_host = + check_extension(avail_ext_props, avail_extc, VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + if (has_ext_external_memory_host) { + extensions[extensions_len++] = VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME; + } + for (size_t i = 0; i < extensions_len; i++) { if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { wlr_log(WLR_ERROR, "vulkan: required device extension %s not found", @@ -621,6 +627,20 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, load_device_proc(dev, "vkImportSemaphoreFdKHR", &dev->api.vkImportSemaphoreFdKHR); load_device_proc(dev, "vkQueueSubmit2KHR", &dev->api.vkQueueSubmit2KHR); + if (has_ext_external_memory_host) { + load_device_proc(dev, "vkGetMemoryHostPointerPropertiesEXT", + &dev->api.vkGetMemoryHostPointerPropertiesEXT); + VkPhysicalDeviceExternalMemoryHostPropertiesEXT memory_host_props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT, + }; + VkPhysicalDeviceProperties2 props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &memory_host_props, + }; + vkGetPhysicalDeviceProperties2(phdev, &props); + dev->minImportedHostPointerAlignment = memory_host_props.minImportedHostPointerAlignment; + } + size_t max_fmts; const struct wlr_vk_format *fmts = vulkan_get_format_list(&max_fmts); dev->format_props = calloc(max_fmts, sizeof(*dev->format_props)); From b385af41dc72f6eecdd3c2dd29d1765035a8d04f Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 16 Jun 2024 12:30:57 +0200 Subject: [PATCH 2/3] render/vulkan: Use host memory backed VkImages If VK_EXT_external_memory_host is supported and the incoming buffer matches the alignment requirements of the device, try to create a VkImage bound directly to the host memory. Otherwise, fall back to maintaining a copy in device memory. When host memory backed images are in use, we need to skip the shm immediate release optimization. Despite the client needing to maintain double buffers, the number of copies is still reduced as the data no longer goes through intermediate staging buffers. The efficiency in using host memory like this might depend on architecture. Integrated GPUs already use the host memory as "device" memory, with the main issue being that the memory is linear. --- render/vulkan/texture.c | 174 +++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 36 deletions(-) diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 086f59f84..b7130bd9e 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -167,6 +167,9 @@ static bool write_pixels(struct wlr_vk_texture *texture, static bool vulkan_texture_update_from_buffer(struct wlr_texture *wlr_texture, struct wlr_buffer *buffer, const pixman_region32_t *damage) { struct wlr_vk_texture *texture = vulkan_get_texture(wlr_texture); + if (texture->buffer) { + return false; + } void *data; uint32_t format; @@ -185,7 +188,6 @@ static bool vulkan_texture_update_from_buffer(struct wlr_texture *wlr_texture, ok = write_pixels(texture, stride, damage, data, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); - out: wlr_buffer_end_data_ptr_access(buffer); return ok; @@ -239,6 +241,9 @@ static void vulkan_texture_unref(struct wlr_texture *wlr_texture) { if (texture->buffer != NULL) { // Keep the texture around, in case the buffer is re-used later. We're // still listening to the buffer's destroy event. + if (!texture->dmabuf_imported) { + wlr_buffer_end_data_ptr_access(texture->buffer); + } wlr_buffer_unlock(texture->buffer); } else { vulkan_texture_destroy(texture); @@ -385,12 +390,28 @@ static void texture_set_format(struct wlr_vk_texture *texture, } } +static void texture_handle_buffer_destroy(struct wlr_addon *addon) { + struct wlr_vk_texture *texture = + wl_container_of(addon, texture, buffer_addon); + // We might keep the texture around, waiting for pending command buffers to + // complete before free'ing descriptor sets. + vulkan_texture_destroy(texture); +} + +static const struct wlr_addon_interface buffer_addon_impl = { + .name = "wlr_vk_texture", + .destroy = texture_handle_buffer_destroy, +}; + static struct wlr_texture *vulkan_texture_from_pixels( - struct wlr_vk_renderer *renderer, uint32_t drm_fmt, uint32_t stride, - uint32_t width, uint32_t height, const void *data) { + struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer, + uint32_t drm_fmt, uint32_t stride, const void *data) { VkResult res; VkDevice dev = renderer->dev->dev; + uint32_t width = buffer->width; + uint32_t height = buffer->height; + const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(renderer->dev, drm_fmt); if (fmt == NULL || fmt->format.is_ycbcr) { @@ -398,12 +419,12 @@ static struct wlr_texture *vulkan_texture_from_pixels( wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", format_name, drm_fmt); free(format_name); - return NULL; + goto error_access; } struct wlr_vk_texture *texture = vulkan_texture_create(renderer, width, height); if (texture == NULL) { - return NULL; + goto error_access; } texture_set_format(texture, &fmt->format, fmt->shm.has_mutable_srgb); @@ -435,10 +456,102 @@ static struct wlr_texture *vulkan_texture_from_pixels( img_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } + if (renderer->dev->api.vkGetMemoryHostPointerPropertiesEXT) { + img_info.tiling = VK_IMAGE_TILING_LINEAR; + img_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + + res = vkCreateImage(dev, &img_info, NULL, &texture->image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage failed", res); + goto error_texture; + } + + VkImageSubresource subresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .arrayLayer = 0, + }; + VkSubresourceLayout layout; + vkGetImageSubresourceLayout(dev, texture->image, &subresource, &layout); + + // It unfortunately won't work, try again with a more optimal image + if (layout.rowPitch != stride) { + goto try_copy; + } + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(dev, texture->image, &mem_reqs); + + int mem_type_index = vulkan_find_mem_type(renderer->dev, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mem_reqs.memoryTypeBits); + if (mem_type_index == -1) { + wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type"); + goto try_copy; + } + size_t alignment = renderer->dev->minImportedHostPointerAlignment; + + // Adjust base pointer to match alignment requirements. + size_t offs = (size_t)data % alignment; + char *aligned_ptr = (char *)data - offs; + size_t aligned_size = (stride * height + offs + alignment - 1) & ~(alignment-1); + + VkExternalMemoryHandleTypeFlagBits handle_type = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; + + VkMemoryHostPointerPropertiesEXT host_ptr_props = { + .sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT, + }; + res = renderer->dev->api.vkGetMemoryHostPointerPropertiesEXT(dev, handle_type, + aligned_ptr, &host_ptr_props); + if (res != VK_SUCCESS) { + wlr_vk_error("vkGetMemoryHostPointerPropertiesEXT failed", res); + goto try_copy; + } + + VkImportMemoryHostPointerInfoEXT import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, + .handleType = handle_type, + .pHostPointer = aligned_ptr, + }; + + VkMemoryAllocateInfo mem_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &import_info, + .allocationSize = aligned_size, + .memoryTypeIndex = mem_type_index, + }; + res = vkAllocateMemory(dev, &mem_info, NULL, &texture->memories[0]); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocatorMemory failed", res); + goto try_copy_mem; + } + + texture->mem_count = 1; + res = vkBindImageMemory(dev, texture->image, texture->memories[0], offs); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindMemory failed", res); + goto try_copy_mem; + } + + texture->buffer = wlr_buffer_lock(buffer); + wlr_addon_init(&texture->buffer_addon, &buffer->addons, renderer, + &buffer_addon_impl); + return &texture->wlr_texture; + +try_copy_mem: + vkFreeMemory(dev, texture->memories[0], NULL); + texture->mem_count = 0; +try_copy: + vkDestroyImage(dev, texture->image, NULL); + texture->image = NULL; + img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + } + res = vkCreateImage(dev, &img_info, NULL, &texture->image); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImage failed", res); - goto error; + goto error_texture; } VkMemoryRequirements mem_reqs; @@ -448,7 +561,7 @@ static struct wlr_texture *vulkan_texture_from_pixels( VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); if (mem_type_index == -1) { wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type"); - goto error; + goto error_texture; } VkMemoryAllocateInfo mem_info = { @@ -456,31 +569,33 @@ static struct wlr_texture *vulkan_texture_from_pixels( .allocationSize = mem_reqs.size, .memoryTypeIndex = mem_type_index, }; - res = vkAllocateMemory(dev, &mem_info, NULL, &texture->memories[0]); if (res != VK_SUCCESS) { wlr_vk_error("vkAllocatorMemory failed", res); - goto error; + goto error_texture; } texture->mem_count = 1; res = vkBindImageMemory(dev, texture->image, texture->memories[0], 0); if (res != VK_SUCCESS) { wlr_vk_error("vkBindMemory failed", res); - goto error; + goto error_texture; } pixman_region32_t region; pixman_region32_init_rect(®ion, 0, 0, width, height); if (!write_pixels(texture, stride, ®ion, data, VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0)) { - goto error; + goto error_texture; } + wlr_buffer_end_data_ptr_access(buffer); return &texture->wlr_texture; -error: +error_texture: vulkan_texture_destroy(texture); +error_access: + wlr_buffer_end_data_ptr_access(buffer); return NULL; } @@ -775,31 +890,9 @@ error: return NULL; } -static void texture_handle_buffer_destroy(struct wlr_addon *addon) { - struct wlr_vk_texture *texture = - wl_container_of(addon, texture, buffer_addon); - // We might keep the texture around, waiting for pending command buffers to - // complete before free'ing descriptor sets. - vulkan_texture_destroy(texture); -} - -static const struct wlr_addon_interface buffer_addon_impl = { - .name = "wlr_vk_texture", - .destroy = texture_handle_buffer_destroy, -}; - static struct wlr_texture *vulkan_texture_from_dmabuf_buffer( struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer, struct wlr_dmabuf_attributes *dmabuf) { - struct wlr_addon *addon = - wlr_addon_find(&buffer->addons, renderer, &buffer_addon_impl); - if (addon != NULL) { - struct wlr_vk_texture *texture = - wl_container_of(addon, texture, buffer_addon); - wlr_buffer_lock(texture->buffer); - return &texture->wlr_texture; - } - struct wlr_vk_texture *texture = vulkan_texture_from_dmabuf(renderer, dmabuf); if (texture == NULL) { return NULL; @@ -816,6 +909,16 @@ struct wlr_texture *vulkan_texture_from_buffer(struct wlr_renderer *wlr_renderer struct wlr_buffer *buffer) { struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + struct wlr_addon *addon = + wlr_addon_find(&buffer->addons, renderer, &buffer_addon_impl); + if (addon != NULL) { + // This buffer has already been imported before + struct wlr_vk_texture *texture = + wl_container_of(addon, texture, buffer_addon); + wlr_buffer_lock(texture->buffer); + return &texture->wlr_texture; + } + void *data; uint32_t format; size_t stride; @@ -825,8 +928,7 @@ struct wlr_texture *vulkan_texture_from_buffer(struct wlr_renderer *wlr_renderer } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { struct wlr_texture *tex = vulkan_texture_from_pixels(renderer, - format, stride, buffer->width, buffer->height, data); - wlr_buffer_end_data_ptr_access(buffer); + buffer, format, stride, data); return tex; } else { return NULL; From dfbb8190446fc8e40e06ad874a8b392d00d9df2c Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 25 Jun 2024 12:52:09 +0200 Subject: [PATCH 3/3] render/vulkan: Guard hostmem import with env flag Direct host memory import is experimental. Put it behind an environment variable to allow easy testing. --- docs/env_vars.md | 4 ++++ include/render/vulkan.h | 2 ++ render/vulkan/renderer.c | 3 +++ render/vulkan/texture.c | 3 ++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/env_vars.md b/docs/env_vars.md index a2a86c84d..cf21a7231 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -49,6 +49,10 @@ wlroots reads these environment variables * *WLR_RENDERER_ALLOW_SOFTWARE*: allows the gles2 renderer to use software rendering +## Vulkan renderer + +* *WLR_VK_DIRECT_SHM_IMPORT*: Allow importing shm buffers directly as host memory + ## scenes * *WLR_SCENE_DEBUG_DAMAGE*: specifies debug options for screen damage related diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 17467a25f..d99b6f5a5 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -324,6 +324,8 @@ struct wlr_vk_renderer { VkImage dst_image; VkDeviceMemory dst_img_memory; } read_pixels_cache; + + bool direct_shm_import; }; // vertex shader push constant range data diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 3aa66e5c6..b78f62fca 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -27,6 +27,7 @@ #include "render/vulkan/shaders/output.frag.h" #include "types/wlr_buffer.h" #include "types/wlr_matrix.h" +#include "util/env.h" // TODO: // - simplify stage allocation, don't track allocations but use ringbuffer-like @@ -2417,6 +2418,8 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev wl_list_init(&renderer->color_transforms); wl_list_init(&renderer->pipeline_layouts); + renderer->direct_shm_import = env_parse_bool("WLR_VK_DIRECT_SHM_IMPORT"); + if (!init_static_render_data(renderer)) { goto error; } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index b7130bd9e..b4558bd60 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -456,7 +456,8 @@ static struct wlr_texture *vulkan_texture_from_pixels( img_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } - if (renderer->dev->api.vkGetMemoryHostPointerPropertiesEXT) { + if (renderer->direct_shm_import && + renderer->dev->api.vkGetMemoryHostPointerPropertiesEXT) { img_info.tiling = VK_IMAGE_TILING_LINEAR; img_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;