vulkan-renderer: support ycbcr shm

Initial implementation for ycbcr shm buffers.
This requires copying multiplanar shm buffers to Vulkan images,
which was so far unimplemented in vulkan-renderer.

Signed-off-by: Erico Nunes <nunes.erico@gmail.com>
This commit is contained in:
Erico Nunes 2026-03-05 15:28:37 +01:00
parent 238221e833
commit 3665661a09
3 changed files with 316 additions and 12 deletions

View file

@ -697,7 +697,7 @@ static const struct pixel_format_info pixel_format_table[] = {
COLOR_MODEL(YUV),
.num_planes = 1,
.hsub = 2,
VULKAN_FORMAT(VK_FORMAT_B8G8R8G8_422_UNORM),
// VULKAN_FORMAT(VK_FORMAT_B8G8R8G8_422_UNORM),
},
{
DRM_FORMAT(YVYU),

View file

@ -188,6 +188,7 @@ struct vulkan_renderer {
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR get_xcb_presentation_support;
#endif
PFN_vkBindImageMemory2KHR bind_image_memory2;
PFN_vkGetImageMemoryRequirements2KHR get_image_memory_requirements2;
PFN_vkGetMemoryFdPropertiesKHR get_memory_fd_properties;
PFN_vkGetSemaphoreFdKHR get_semaphore_fd;

View file

@ -63,6 +63,9 @@
#include <xf86drm.h> /* Physical device drm */
#define ALIGN(value, alignment) (((value) + alignment - 1) & ~(alignment - 1))
#define MAX_PLANES 3
enum vulkan_debug_mode {
DEBUG_MODE_NONE = 0,
DEBUG_MODE_FRAGMENT,
@ -2886,6 +2889,101 @@ create_texture_image(struct vulkan_renderer *vr,
create_image_view(vr->dev, texture->image, pixel_format->vulkan_format, NULL, &texture->image_view);
}
static void
update_texture_image_nv12(struct vulkan_renderer *vr,
struct vulkan_renderer_texture_image *texture,
VkImageLayout expected_layout,
const struct pixel_format_info *pixel_format,
uint32_t buffer_width, uint32_t buffer_height,
const void * const pixels)
{
uint32_t plane_count = pixel_format_get_plane_count(pixel_format);
// TODO
const uint32_t plane_bpp_nv12[] = {
sizeof(uint8_t),
sizeof(uint16_t),
};
assert(plane_count <= MAX_PLANES);
VkDeviceSize buffer_size = 0;
for (uint32_t i = 0; i < plane_count; i++) {
uint32_t hsub = pixel_format_hsub(pixel_format, i);
uint32_t vsub = pixel_format_vsub(pixel_format, i);
buffer_size += (buffer_width/hsub) * (buffer_height/vsub) * plane_bpp_nv12[i];
}
VkResult result;
assert(pixels);
vkWaitForFences(vr->dev, 1, &texture->upload_fence, VK_TRUE, UINT64_MAX);
vkResetFences(vr->dev, 1, &texture->upload_fence);
memcpy(texture->staging_map, pixels, (size_t)buffer_size);
const VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VkCommandBuffer cmd_buffer = texture->upload_cmd;
result = vkBeginCommandBuffer(cmd_buffer, &begin_info);
check_vk_success(result, "vkBeginCommandBuffer");
transition_image_layout(cmd_buffer, texture->image,
expected_layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
VkBufferImageCopy regions[MAX_PLANES] = { 0 };
uint32_t buffer_offset = 0;
for (uint32_t i = 0; i < plane_count; i++) {
uint32_t hsub = pixel_format_hsub(pixel_format, i);
uint32_t vsub = pixel_format_vsub(pixel_format, i);
const VkOffset3D image_offset = { 0, 0, 0 };
const VkExtent3D image_extent = { buffer_width/hsub, buffer_height/vsub, 1 };
const VkBufferImageCopy region = {
.bufferOffset = buffer_offset,
.bufferRowLength = 0, // these are 0 for tightly packed data (match imageExtent texels)
.bufferImageHeight = 0, // these are 0 for tightly packed data (match imageExtent texels)
.imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << i,
.imageSubresource.mipLevel = 0,
.imageSubresource.baseArrayLayer = 0,
.imageSubresource.layerCount = 1,
.imageOffset = image_offset,
.imageExtent = image_extent,
};
regions[i] = region;
buffer_offset = buffer_offset + (buffer_width/hsub) * (buffer_height/vsub) * plane_bpp_nv12[i];
}
vkCmdCopyBufferToImage(cmd_buffer, texture->staging_buffer, texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, plane_count, regions);
transition_image_layout(cmd_buffer, texture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
result = vkEndCommandBuffer(cmd_buffer);
check_vk_success(result, "vkEndCommandBuffer");
const VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &cmd_buffer,
};
result = vkQueueSubmit(vr->queue, 1, &submit_info, texture->upload_fence);
check_vk_success(result, "vkQueueSubmit");
}
static void
vulkan_renderer_flush_damage(struct weston_paint_node *pnode)
{
@ -2926,19 +3024,28 @@ vulkan_renderer_flush_damage(struct weston_paint_node *pnode)
data = wl_shm_buffer_get_data(buffer->shm_buffer);
// TODO
if (buffer->pixel_format->color_model == COLOR_MODEL_YUV)
vb->needs_full_upload = true;
if (vb->needs_full_upload || quirks->force_full_upload) {
wl_shm_buffer_begin_access(buffer->shm_buffer);
if (buffer->pixel_format->color_model != COLOR_MODEL_YUV) {
for (int j = 0; j < vb->num_textures; j++) {
int hsub = pixel_format_hsub(buffer->pixel_format, j);
int vsub = pixel_format_vsub(buffer->pixel_format, j);
void *pixels = data + vb->offset[j];
uint32_t buffer_width = buffer->width / hsub;
uint32_t buffer_height = buffer->height / vsub;
for (int j = 0; j < vb->num_textures; j++) {
int hsub = pixel_format_hsub(buffer->pixel_format, j);
int vsub = pixel_format_vsub(buffer->pixel_format, j);
void *pixels = data + vb->offset[j];
uint32_t buffer_width = buffer->width / hsub;
uint32_t buffer_height = buffer->height / vsub;
update_texture_image_all(vr, &vb->texture, VK_IMAGE_LAYOUT_UNDEFINED,
buffer->pixel_format, buffer_width, buffer_height,
vb->pitch, pixels);
update_texture_image_all(vr, &vb->texture, VK_IMAGE_LAYOUT_UNDEFINED,
buffer->pixel_format, buffer_width, buffer_height,
vb->pitch, pixels);
}
} else { // YUV
update_texture_image_nv12(vr, &vb->texture, VK_IMAGE_LAYOUT_UNDEFINED,
buffer->pixel_format, buffer->width, buffer->height,
data);
}
wl_shm_buffer_end_access(buffer->shm_buffer);
goto done;
@ -2990,6 +3097,152 @@ handle_buffer_destroy(struct wl_listener *listener, void *data)
destroy_buffer_state(vb);
}
static void
create_image_nv12(struct vulkan_renderer *vr,
uint32_t width, uint32_t height,
struct vulkan_renderer_texture_image *texture,
VkFormat format, VkImageTiling tiling,
VkImageUsageFlags usage, VkMemoryPropertyFlags properties)
{
assert(vulkan_device_has(vr, EXTENSION_KHR_BIND_MEMORY_2));
assert(vulkan_device_has(vr, EXTENSION_KHR_GET_MEMORY_REQUIREMENTS_2));
VkResult result;
const VkFenceCreateInfo fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
result = vkCreateFence(vr->dev, &fence_info, NULL, &texture->upload_fence);
check_vk_success(result, "vkCreateFence");
const VkCommandBufferAllocateInfo cmd_alloc_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandPool = vr->cmd_pool,
.commandBufferCount = 1,
};
result = vkAllocateCommandBuffers(vr->dev, &cmd_alloc_info, &texture->upload_cmd);
check_vk_success(result, "vkAllocateCommandBuffers");
const VkImageCreateInfo image_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = tiling,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.flags = VK_IMAGE_CREATE_DISJOINT_BIT,
};
result = vkCreateImage(vr->dev, &image_info, NULL, &texture->image);
check_vk_success(result, "vkCreateImage");
/* TODO assumes 2 planes */
VkImagePlaneMemoryRequirementsInfo plane_req_info0 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO,
.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT
};
VkImageMemoryRequirementsInfo2 mem_reqs_info0 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
.image = texture->image,
};
pnext(&mem_reqs_info0, &plane_req_info0);
VkMemoryRequirements2 mem_reqs0 = {
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
};
vr->get_image_memory_requirements2(vr->dev, &mem_reqs_info0, &mem_reqs0);
VkImagePlaneMemoryRequirementsInfo plane_req_info1 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO,
.planeAspect = VK_IMAGE_ASPECT_PLANE_1_BIT
};
VkImageMemoryRequirementsInfo2 mem_reqs_info1 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
.image = texture->image,
};
pnext(&mem_reqs_info1, &plane_req_info1);
VkMemoryRequirements2 mem_reqs1 = {
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
};
vr->get_image_memory_requirements2(vr->dev, &mem_reqs_info1, &mem_reqs1);
VkDeviceSize size0 = mem_reqs0.memoryRequirements.size;
VkDeviceSize size1 = mem_reqs1.memoryRequirements.size;
VkDeviceSize align1 = mem_reqs1.memoryRequirements.alignment;
VkDeviceSize offset0 = 0;
VkDeviceSize offset1 = ALIGN(offset0 + size0, align1);
VkDeviceSize total_size = ALIGN(offset1 + size1, 4096);
uint32_t allowed_bits = mem_reqs0.memoryRequirements.memoryTypeBits &
mem_reqs1.memoryRequirements.memoryTypeBits;
assert(allowed_bits != 0);
int memory_type = find_memory_type(vr, allowed_bits, properties);
assert(memory_type >= 0);
const VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = total_size,
.memoryTypeIndex = memory_type,
};
result = vkAllocateMemory(vr->dev, &alloc_info, NULL, &texture->memory);
check_vk_success(result, "vkAllocateMemory");
/* TODO assumes 2 planes */
VkBindImagePlaneMemoryInfo bind_plane0 = {
.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT,
};
VkBindImageMemoryInfo bind_info0 = {
.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
.image = texture->image,
.memory = texture->memory,
.memoryOffset = offset0,
};
pnext(&bind_info0, &bind_plane0);
VkBindImagePlaneMemoryInfo bind_plane1 = {
.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
.planeAspect = VK_IMAGE_ASPECT_PLANE_1_BIT,
};
VkBindImageMemoryInfo bind_info1 = {
.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
.image = texture->image,
.memory = texture->memory,
.memoryOffset = offset1,
};
pnext(&bind_info1, &bind_plane1);
const VkBindImageMemoryInfo bind_info[2] = {
bind_info0,
bind_info1,
};
result = vr->bind_image_memory2(vr->dev, 2, bind_info);
check_vk_success(result, "vkBindImageMemory2");
VkDeviceSize image_size = total_size;
create_buffer(vr, image_size,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&texture->staging_buffer, &texture->staging_memory);
result = vkMapMemory(vr->dev, texture->staging_memory, 0, image_size, 0, &texture->staging_map);
check_vk_success(result, "vkMapMemory");
}
static void
vulkan_renderer_attach_shm(struct weston_surface *surface, struct weston_buffer *buffer)
{
@ -3006,8 +3259,53 @@ vulkan_renderer_attach_shm(struct weston_surface *surface, struct weston_buffer
int bpp = buffer->pixel_format->bpp;
num_planes = pixel_format_get_plane_count(buffer->pixel_format);
if (num_planes > 1) {
if (pixel_format_is_opaque(buffer->pixel_format))
pipeline_variant = PIPELINE_VARIANT_RGBX;
else
pipeline_variant = PIPELINE_VARIANT_RGBA;
vb = xzalloc(sizeof(*vb));
vb->vr = vr;
wl_list_init(&vb->destroy_listener.link);
pixman_region32_init(&vb->texture_damage);
vb->pipeline_variant = pipeline_variant;
ARRAY_COPY(vb->offset, offset);
ARRAY_COPY(vb->vulkan_format, vulkan_format);
vb->needs_full_upload = true;
vb->num_textures = num_planes;
vs->buffer = vb;
vs->surface = surface;
if (buffer->pixel_format->color_model == COLOR_MODEL_YUV &&
vulkan_device_has(vr, EXTENSION_KHR_SAMPLER_YCBCR_CONVERSION))
create_texture_ycbcr_conv(vr, buffer->pixel_format->vulkan_format, &vb->ycbcr_conv);
create_image_nv12(vr, buffer->width, buffer->height, &vb->texture,
buffer->pixel_format->vulkan_format, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// TODO
if (buffer->pixel_format->color_model == COLOR_MODEL_YUV &&
vulkan_device_has(vr, EXTENSION_KHR_SAMPLER_YCBCR_CONVERSION))
create_image_view(vr->dev, vb->texture.image, buffer->pixel_format->vulkan_format, &vb->ycbcr_conv, &vb->texture.image_view);
create_texture_sampler(vr, &vb->sampler_nearest, &vb->ycbcr_conv, VK_FILTER_NEAREST);
create_texture_sampler(vr, &vb->sampler_linear, &vb->ycbcr_conv, VK_FILTER_LINEAR);
create_vs_ubo_buffer(vr, &vb->vs_ubo_buffer, &vb->vs_ubo_memory, &vb->vs_ubo_map);
create_fs_ubo_buffer(vr, &vb->fs_ubo_buffer, &vb->fs_ubo_memory, &vb->fs_ubo_map);
return;
}
assert(pixel_format_get_plane_count(buffer->pixel_format) == 1);
num_planes = 1;
if (pixel_format_is_opaque(buffer->pixel_format))
pipeline_variant = PIPELINE_VARIANT_RGBX;
@ -4358,6 +4656,11 @@ vulkan_renderer_setup_device_extensions(struct vulkan_renderer *vr)
if (vulkan_device_has(vr, EXTENSION_KHR_SAMPLER_YCBCR_CONVERSION)) {
load_device_proc(vr, "vkCreateSamplerYcbcrConversionKHR", &vr->create_sampler_ycbcr_conversion);
}
// VK_KHR_bind_memory2
if (vulkan_device_has(vr, EXTENSION_KHR_BIND_MEMORY_2)) {
load_device_proc(vr, "vkBindImageMemory2KHR", &vr->bind_image_memory2);
}
}
static void