From e1c8292c203ae68d7206859aa72e54d03c1b87f9 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:41:37 +0100 Subject: [PATCH] render/vulkan: support hardware cursors on nvidia+vulkan --- include/render/vulkan.h | 17 ++++++- render/vulkan/pass.c | 92 ++++++++++++++++++++++++++++++++++-- render/vulkan/pixel_format.c | 41 ++++++++++++++++ render/vulkan/renderer.c | 72 +++++++++++++++++++++++++++- render/vulkan/texture.c | 9 ++-- 5 files changed, 221 insertions(+), 10 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index ac0af65fb..189b6b622 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -89,7 +89,7 @@ struct wlr_vk_format { bool is_ycbcr; }; -extern const VkImageUsageFlags vulkan_render_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage; +extern const VkImageUsageFlags vulkan_render_usage, vulkan_render_bridged_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage; // Returns all known format mappings. // Might not be supported for gpu/usecase. @@ -100,6 +100,7 @@ struct wlr_vk_format_modifier_props { VkDrmFormatModifierPropertiesEXT props; VkExtent2D max_extent; bool has_mutable_srgb; + bool render_needs_bridge; }; struct wlr_vk_format_props { @@ -233,6 +234,7 @@ struct wlr_vk_render_buffer { VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES]; uint32_t mem_count; + bool has_bridge; VkImage image; // Framebuffer and image view for rendering directly onto the buffer image, @@ -264,6 +266,17 @@ struct wlr_vk_render_buffer { struct wlr_vk_descriptor_pool *blend_attachment_pool; bool blend_transitioned; } two_pass; + + // Sometimes, we want to output to a buffer that cannot be used as a + // COLOR_ATTACHMENT, but can be used as a TRANSFER_DST. So, render to an + // intermediate bridge buffer, then transfer it to the real output. In + // this case, the image field above is the fake intermediate buffer, and + // bridge.image is the real output. + struct { + VkImage image; + VkDeviceMemory memory; + bool transitioned; + } bridge; }; bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, @@ -515,7 +528,7 @@ struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture); VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, const struct wlr_dmabuf_attributes *attribs, VkDeviceMemory mems[static WLR_DMABUF_MAX_PLANES], uint32_t *n_mems, - bool for_render, bool *using_mutable_srgb); + bool for_render, bool *render_needs_bridge, bool *using_mutable_srgb); struct wlr_texture *vulkan_texture_from_buffer( struct wlr_renderer *wlr_renderer, struct wlr_buffer *buffer); void vulkan_texture_destroy(struct wlr_vk_texture *texture); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index db7a659d6..0ebe3cf99 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -319,6 +319,84 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { vkCmdEndRenderPass(render_cb->vk); + if (render_buffer->has_bridge) { + VkImageLayout bridge_old_layout = VK_IMAGE_LAYOUT_GENERAL; + if (!render_buffer->bridge.transitioned) { + bridge_old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + render_buffer->bridge.transitioned = true; + } + + VkImageMemoryBarrier image_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT + | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = render_buffer->image, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + vkCmdPipelineBarrier(render_cb->vk, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &image_barrier); + + VkImageMemoryBarrier bridge_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = bridge_old_layout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .dstQueueFamilyIndex = renderer->dev->queue_family, + .image = render_buffer->bridge.image, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + vkCmdPipelineBarrier(render_cb->vk, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &bridge_barrier); + + int region_rects_len; + const pixman_region32_t *regions = rect_union_evaluate(&pass->updated_region); + const pixman_box32_t *clip_rects = pixman_region32_rectangles(regions, ®ion_rects_len); + + VkImageCopy *vkRegions = calloc(region_rects_len, sizeof(VkImageCopy)); + if (vkRegions == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + VkImageSubresourceLayers image_subresource_layers = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }; + for(int i = 0; i < region_rects_len; i++) { + vkRegions[i] = (VkImageCopy){ + .srcSubresource = image_subresource_layers, + .srcOffset.x = clip_rects[i].x1, + .srcOffset.y = clip_rects[i].y1, + .srcOffset.z = 0, + .dstSubresource = image_subresource_layers, + .dstOffset.x = clip_rects[i].x1, + .dstOffset.y = clip_rects[i].y1, + .dstOffset.z = 0, + .extent.width = clip_rects[i].x2 - clip_rects[i].x1, + .extent.height = clip_rects[i].y2 - clip_rects[i].y1, + .extent.depth = 1, + }; + } + + vkCmdCopyImage(render_cb->vk, + render_buffer->image, VK_IMAGE_LAYOUT_GENERAL, + render_buffer->bridge.image, VK_IMAGE_LAYOUT_GENERAL, + region_rects_len, vkRegions); + + free(vkRegions); + } + size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); size_t render_wait_cap = pass_textures_len * WLR_DMABUF_MAX_PLANES; render_wait = calloc(render_wait_cap, sizeof(*render_wait)); @@ -483,11 +561,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = renderer->dev->queue_family, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, - .image = render_buffer->image, + .image = render_buffer->has_bridge ? render_buffer->bridge.image : render_buffer->image, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .srcAccessMask = render_buffer->has_bridge + ? VK_ACCESS_TRANSFER_WRITE_BIT + : VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .dstAccessMask = 0, // ignored anyways .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.layerCount = 1, @@ -500,7 +579,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, barrier_count, acquire_barriers); - vkCmdPipelineBarrier(render_cb->vk, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VkPipelineStageFlags release_barrier_src_stage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + if (render_buffer->has_bridge) { + release_barrier_src_stage |= VK_PIPELINE_STAGE_TRANSFER_BIT; + } + + vkCmdPipelineBarrier(render_cb->vk, release_barrier_src_stage, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, barrier_count, release_barriers); diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c index 9319d0daa..c3dbc706b 100644 --- a/render/vulkan/pixel_format.c +++ b/render/vulkan/pixel_format.c @@ -231,6 +231,8 @@ const struct wlr_vk_format *vulkan_get_format_from_drm(uint32_t drm_format) { const VkImageUsageFlags vulkan_render_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +const VkImageUsageFlags vulkan_render_bridged_usage = + VK_IMAGE_USAGE_TRANSFER_DST_BIT; const VkImageUsageFlags vulkan_shm_tex_usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | @@ -242,6 +244,8 @@ const VkImageUsageFlags vulkan_dma_tex_usage = static const VkFormatFeatureFlags render_features = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; +static const VkFormatFeatureFlags render_bridged_features = + VK_FORMAT_FEATURE_TRANSFER_DST_BIT; static const VkFormatFeatureFlags shm_tex_features = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT | @@ -427,6 +431,43 @@ static bool query_modifier_support(struct wlr_vk_device *dev, 0, vulkan_render_usage, &m, &p, &errmsg); } + if (supported) { + props->dmabuf.render_mods[props->dmabuf.render_mod_count++] = p; + wlr_drm_format_set_add(&dev->dmabuf_render_formats, + props->format.drm, m.drmFormatModifier); + found = true; + } + } else if ((m.drmFormatModifierTilingFeatures & render_bridged_features) == render_bridged_features && + m.drmFormatModifier == DRM_FORMAT_MOD_LINEAR && !props->format.is_ycbcr) { + struct wlr_vk_format_modifier_props p = {0}; + bool supported = false; + if (query_modifier_usage_support(dev, props->format.vk, + props->format.vk_srgb, vulkan_render_bridged_usage, &m, &p, &errmsg)) { + supported = true; + p.has_mutable_srgb = props->format.vk_srgb != 0; + } + if (!supported && props->format.vk_srgb) { + supported = query_modifier_usage_support(dev, props->format.vk, + 0, vulkan_render_bridged_usage, &m, &p, &errmsg); + } + + if (supported) { + struct wlr_vk_format_modifier_props bridge_format = {0}; + supported = query_modifier_usage_support(dev, props->format.vk, + 0, vulkan_render_usage, &m, &bridge_format, &errmsg); + + if (supported) { + if (p.max_extent.width > bridge_format.max_extent.width) { + p.max_extent.width = bridge_format.max_extent.width; + } + if (p.max_extent.height > bridge_format.max_extent.height) { + p.max_extent.height = bridge_format.max_extent.height; + } + } + } + + p.render_needs_bridge = true; + if (supported) { props->dmabuf.render_mods[props->dmabuf.render_mod_count++] = p; wlr_drm_format_set_add(&dev->dmabuf_render_formats, diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 0b411f5dd..7fe4c5093 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -637,6 +637,9 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { buffer->two_pass.blend_descriptor_set); } + vkDestroyImage(dev, buffer->bridge.image, NULL); + vkFreeMemory(dev, buffer->bridge.memory, NULL); + vkDestroyImage(dev, buffer->image, NULL); for (size_t i = 0u; i < buffer->mem_count; ++i) { vkFreeMemory(dev, buffer->memories[i], NULL); @@ -898,6 +901,65 @@ error: return false; } +static bool setup_bridge(struct wlr_vk_render_buffer *buffer, const struct wlr_vk_format_props *format) { + VkResult res; + VkDevice dev = buffer->renderer->dev->dev; + + buffer->bridge.image = buffer->image; + buffer->image = 0; + + buffer->has_bridge = true; + + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = format->format.vk, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = { buffer->wlr_buffer->width, buffer->wlr_buffer->height, 1 }, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + }; + res = vkCreateImage(dev, &img_info, NULL, &buffer->image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage", res); + return false; + } + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(dev, buffer->image, &mem_reqs); + + int mem_type = vulkan_find_mem_type(buffer->renderer->dev, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); + if (mem_type < 0) { + wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type"); + return false; + } + + VkMemoryAllocateInfo mem_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + }; + mem_alloc_info.allocationSize = mem_reqs.size; + mem_alloc_info.memoryTypeIndex = mem_type; + + res = vkAllocateMemory(dev, &mem_alloc_info, NULL, &buffer->bridge.memory); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocateMemory", res); + return false; + } + res = vkBindImageMemory(dev, buffer->image, buffer->bridge.memory, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindImageMemory", res); + return false; + } + + return true; +} + static struct wlr_vk_render_buffer *create_render_buffer( struct wlr_vk_renderer *renderer, struct wlr_buffer *wlr_buffer) { struct wlr_vk_render_buffer *buffer = calloc(1, sizeof(*buffer)); @@ -920,8 +982,10 @@ static struct wlr_vk_render_buffer *create_render_buffer( (const char*) &dmabuf.format, dmabuf.width, dmabuf.height); bool using_mutable_srgb = false; + bool render_needs_bridge = false; buffer->image = vulkan_import_dmabuf(renderer, &dmabuf, - buffer->memories, &buffer->mem_count, true, &using_mutable_srgb); + buffer->memories, &buffer->mem_count, true, + &render_needs_bridge, &using_mutable_srgb); if (!buffer->image) { goto error; } @@ -934,6 +998,12 @@ static struct wlr_vk_render_buffer *create_render_buffer( goto error; } + if (render_needs_bridge) { + if (!setup_bridge(buffer, fmt)) { + goto error; + } + } + if (using_mutable_srgb) { if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf, true)) { goto error; diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 499178f5d..5876dca44 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -504,7 +504,7 @@ static bool is_dmabuf_disjoint(const struct wlr_dmabuf_attributes *attribs) { VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, const struct wlr_dmabuf_attributes *attribs, VkDeviceMemory mems[static WLR_DMABUF_MAX_PLANES], uint32_t *n_mems, - bool for_render, bool *using_mutable_srgb) { + bool for_render, bool *render_needs_bridge, bool *using_mutable_srgb) { VkResult res; VkDevice dev = renderer->dev->dev; *n_mems = 0u; @@ -568,7 +568,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .extent = (VkExtent3D) { attribs->width, attribs->height, 1 }, - .usage = for_render ? vulkan_render_usage : vulkan_dma_tex_usage, + .usage = for_render ? (mod->render_needs_bridge ? vulkan_render_bridged_usage : vulkan_render_usage) : vulkan_dma_tex_usage, }; if (disjoint) { img_info.flags = VK_IMAGE_CREATE_DISJOINT_BIT; @@ -719,6 +719,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, } *using_mutable_srgb = mod->has_mutable_srgb; + *render_needs_bridge = mod->render_needs_bridge; return image; error_image: @@ -751,8 +752,10 @@ static struct wlr_vk_texture *vulkan_texture_from_dmabuf( } bool using_mutable_srgb = false; + bool render_needs_bridge = false; texture->image = vulkan_import_dmabuf(renderer, attribs, - texture->memories, &texture->mem_count, false, &using_mutable_srgb); + texture->memories, &texture->mem_count, false, + &render_needs_bridge, &using_mutable_srgb); if (!texture->image) { goto error; }