render/vulkan: support hardware cursors on nvidia+vulkan

This commit is contained in:
khyperia 2025-12-10 18:41:37 +01:00
parent a962d58727
commit e1c8292c20
5 changed files with 221 additions and 10 deletions

View file

@ -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);

View file

@ -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, &region_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);

View file

@ -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,

View file

@ -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;

View file

@ -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;
}