From 7ca85d7a21467e2bb18cc29722050a4790b8a0b2 Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:00:57 +0400 Subject: [PATCH 1/4] render/pixel-format: add function to determine YCbCr from drm fourcc --- include/render/pixel_format.h | 5 +++ render/pixel_format.c | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/include/render/pixel_format.h b/include/render/pixel_format.h index e0b500c75..b5f2b2411 100644 --- a/include/render/pixel_format.h +++ b/include/render/pixel_format.h @@ -63,4 +63,9 @@ enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt); */ bool pixel_format_has_alpha(uint32_t fmt); +/** + * Return true if the DRM FourCC fmt belongs to a YCbCr colorspace family, false otherwise. + */ +bool pixel_format_is_ycbcr(uint32_t fmt); + #endif diff --git a/render/pixel_format.c b/render/pixel_format.c index 5f2e8644a..80ba6b0ff 100644 --- a/render/pixel_format.c +++ b/render/pixel_format.c @@ -307,3 +307,68 @@ bool pixel_format_has_alpha(uint32_t fmt) { } return true; } + +bool pixel_format_is_ycbcr(uint32_t format) { + switch(format) { + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV15: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV20: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV30: + case DRM_FORMAT_NV42: + case DRM_FORMAT_NV61: + case DRM_FORMAT_P010: + case DRM_FORMAT_P012: + case DRM_FORMAT_P016: + case DRM_FORMAT_P030: + case DRM_FORMAT_P210: + case DRM_FORMAT_Q401: + case DRM_FORMAT_Q410: + case DRM_FORMAT_S010: + case DRM_FORMAT_S012: + case DRM_FORMAT_S016: + case DRM_FORMAT_S210: + case DRM_FORMAT_S212: + case DRM_FORMAT_S216: + case DRM_FORMAT_S410: + case DRM_FORMAT_S412: + case DRM_FORMAT_S416: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VUY101010: + case DRM_FORMAT_VUY888: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_X0L0: + case DRM_FORMAT_X0L2: + case DRM_FORMAT_XVYU12_16161616: + case DRM_FORMAT_XVYU16161616: + case DRM_FORMAT_XVYU2101010: + case DRM_FORMAT_XYUV8888: + case DRM_FORMAT_Y0L0: + case DRM_FORMAT_Y0L2: + case DRM_FORMAT_Y210: + case DRM_FORMAT_Y212: + case DRM_FORMAT_Y216: + case DRM_FORMAT_Y410: + case DRM_FORMAT_Y412: + case DRM_FORMAT_Y416: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV420_10BIT: + case DRM_FORMAT_YUV420_8BIT: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YVU444: + case DRM_FORMAT_YVYU: + return true; + } + return false; +} From 8f2ef81666c38cc33d75b2b5e04111b5ecb9378c Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:03:37 +0400 Subject: [PATCH 2/4] vulkan: make use of new pixel_format_is_ycbcr function --- include/render/vulkan.h | 2 +- render/vulkan/pass.c | 7 ++++--- render/vulkan/pixel_format.c | 24 ++++++++---------------- render/vulkan/renderer.c | 4 ++-- render/vulkan/texture.c | 10 +++++----- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index bb56b5534..2f8d9d241 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -86,7 +86,6 @@ struct wlr_vk_format { uint32_t drm; VkFormat vk; VkFormat vk_srgb; // sRGB version of the format, or 0 if nonexistent - bool is_ycbcr; }; extern const VkImageUsageFlags vulkan_render_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage; @@ -125,6 +124,7 @@ void vulkan_format_props_query(struct wlr_vk_device *dev, const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( const struct wlr_vk_format_props *props, uint64_t mod, bool render); void vulkan_format_props_finish(struct wlr_vk_format_props *props); +bool vulkan_format_is_ycbcr(const struct wlr_vk_format *format); struct wlr_vk_pipeline_layout_key { enum wlr_scale_filter_mode filter_mode; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 31f5116bd..271b426cc 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -822,12 +822,13 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } enum wlr_color_encoding color_encoding = options->color_encoding; - if (texture->format->is_ycbcr && color_encoding == WLR_COLOR_ENCODING_NONE) { + bool is_ycbcr = vulkan_format_is_ycbcr(texture->format); + if (is_ycbcr && color_encoding == WLR_COLOR_ENCODING_NONE) { color_encoding = WLR_COLOR_ENCODING_BT601; } enum wlr_color_range color_range = options->color_range; - if (texture->format->is_ycbcr && color_range == WLR_COLOR_RANGE_NONE) { + if (is_ycbcr && color_range == WLR_COLOR_RANGE_NONE) { color_range = WLR_COLOR_RANGE_LIMITED; } @@ -837,7 +838,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, .source = WLR_VK_SHADER_SOURCE_TEXTURE, .layout = { .ycbcr = { - .format = texture->format->is_ycbcr ? texture->format : NULL, + .format = is_ycbcr ? texture->format : NULL, .encoding = color_encoding, .range = color_range, }, diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c index 902feac9a..8c105f8f6 100644 --- a/render/vulkan/pixel_format.c +++ b/render/vulkan/pixel_format.c @@ -182,37 +182,30 @@ static const struct wlr_vk_format formats[] = { { .drm = DRM_FORMAT_UYVY, .vk = VK_FORMAT_B8G8R8G8_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUYV, .vk = VK_FORMAT_G8B8G8R8_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_NV12, .vk = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_NV16, .vk = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV420, .vk = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV422, .vk = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV444, .vk = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, - .is_ycbcr = true, }, // 3PACK16 formats split the memory in three 16-bit words, so they have an // inverted channel order compared to DRM formats. @@ -220,27 +213,22 @@ static const struct wlr_vk_format formats[] = { { .drm = DRM_FORMAT_P010, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P210, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P012, .vk = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P016, .vk = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_Q410, .vk = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, - .is_ycbcr = true, }, #endif // TODO: add DRM_FORMAT_NV24/VK_FORMAT_G8_B8R8_2PLANE_444_UNORM (requires @@ -446,7 +434,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, // check that specific modifier for render usage const char *errmsg = "unknown error"; if ((m.drmFormatModifierTilingFeatures & render_features) == render_features && - !props->format.is_ycbcr) { + !vulkan_format_is_ycbcr(&props->format)) { struct wlr_vk_format_modifier_props p = {0}; bool supported = false; if (query_modifier_usage_support(dev, props->format.vk, @@ -477,7 +465,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, // check that specific modifier for texture usage errmsg = "unknown error"; VkFormatFeatureFlags features = dma_tex_features; - if (props->format.is_ycbcr) { + if(vulkan_format_is_ycbcr(&props->format)) { features |= ycbcr_tex_features; } if ((m.drmFormatModifierTilingFeatures & features) == features) { @@ -522,7 +510,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, void vulkan_format_props_query(struct wlr_vk_device *dev, const struct wlr_vk_format *format) { - if (format->is_ycbcr && !dev->sampler_ycbcr_conversion) { + if (vulkan_format_is_ycbcr(format) && !dev->sampler_ycbcr_conversion) { return; } @@ -551,7 +539,7 @@ void vulkan_format_props_query(struct wlr_vk_device *dev, char shm_texture_status[256]; const char *errmsg = "unknown error"; if ((fmtp.formatProperties.optimalTilingFeatures & shm_tex_features) == shm_tex_features && - !format->is_ycbcr && format_info != NULL) { + !vulkan_format_is_ycbcr(format) && format_info != NULL) { VkImageFormatProperties ifmtp; bool supported = false, has_mutable_srgb = false; if (query_shm_support(dev, format->vk, format->vk_srgb, &ifmtp, &errmsg)) { @@ -621,3 +609,7 @@ const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( } return NULL; } + +bool vulkan_format_is_ycbcr(const struct wlr_vk_format *format) { + return pixel_format_is_ycbcr(format->drm); +} diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 0b411f5dd..cfa05e077 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1630,8 +1630,8 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { static bool pipeline_layout_key_equals( const struct wlr_vk_pipeline_layout_key *a, const struct wlr_vk_pipeline_layout_key *b) { - assert(!a->ycbcr.format || a->ycbcr.format->is_ycbcr); - assert(!b->ycbcr.format || b->ycbcr.format->is_ycbcr); + assert(!a->ycbcr.format || vulkan_format_is_ycbcr(a->ycbcr.format)); + assert(!b->ycbcr.format || vulkan_format_is_ycbcr(b->ycbcr.format)); if (a->filter_mode != b->filter_mode) { return false; diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 499178f5d..ffea87795 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -297,7 +297,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, - .components.a = texture->has_alpha || texture->format->is_ycbcr + .components.a = texture->has_alpha || vulkan_format_is_ycbcr(texture->format) ? VK_COMPONENT_SWIZZLE_IDENTITY : VK_COMPONENT_SWIZZLE_ONE, .subresourceRange = (VkImageSubresourceRange){ @@ -311,7 +311,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text }; VkSamplerYcbcrConversionInfo ycbcr_conversion_info; - if (texture->format->is_ycbcr) { + if (vulkan_format_is_ycbcr(texture->format)) { assert(pipeline_layout->ycbcr.conversion != VK_NULL_HANDLE); ycbcr_conversion_info = (VkSamplerYcbcrConversionInfo){ .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, @@ -355,7 +355,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text static void texture_set_format(struct wlr_vk_texture *texture, const struct wlr_vk_format *format, bool has_mutable_srgb) { - assert(!(format->is_ycbcr && has_mutable_srgb)); + assert(!(vulkan_format_is_ycbcr(format) && has_mutable_srgb)); texture->format = format; texture->using_mutable_srgb = has_mutable_srgb; @@ -366,7 +366,7 @@ static void texture_set_format(struct wlr_vk_texture *texture, texture->has_alpha = pixel_format_has_alpha(format->drm); } else { // We don't have format info for multi-planar formats - assert(texture->format->is_ycbcr); + assert(vulkan_format_is_ycbcr(texture->format)); } } @@ -378,7 +378,7 @@ static struct wlr_texture *vulkan_texture_from_pixels( const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(renderer->dev, drm_fmt); - if (fmt == NULL || fmt->format.is_ycbcr) { + if (fmt == NULL || vulkan_format_is_ycbcr(&fmt->format)) { char *format_name = drmGetFormatName(drm_fmt); wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", format_name, drm_fmt); From 7e34909a67757e0e660fa3ab2f4d3897470f6b1d Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:10:49 +0400 Subject: [PATCH 3/4] color_representation: ensure encoding/range/drm formats compatibility --- types/wlr_color_representation_v1.c | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index ff9b6e3b7..55ad98e6c 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include #include "color-representation-v1-protocol.h" +#include "render/pixel_format.h" #include "util/mem.h" #define WP_COLOR_REPRESENTATION_VERSION 1 @@ -234,8 +236,35 @@ static void color_repr_manager_handle_destroy(struct wl_client *client, wl_resource_destroy(resource); } +static void surface_synced_commit(struct wlr_surface_synced *synced) { + struct wlr_color_representation_v1 *color_repr = wl_container_of(synced, color_repr, synced); + + struct wlr_dmabuf_attributes dmabuf; + + if (!color_repr->surface->buffer || + !wlr_buffer_get_dmabuf(&color_repr->surface->buffer->base, &dmabuf)) { + return; + } + + bool is_unset = color_repr->current.coefficients == 0 && color_repr->current.range == 0; + bool is_ycbcr = pixel_format_is_ycbcr(dmabuf.format); + if (is_ycbcr) { + if (is_unset) { + color_repr->current.coefficients = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601; + color_repr->current.range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED; + } + } else /* rgb */ { + if (!is_unset) { + wl_resource_post_error(color_repr->resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, + "unexpected encoding/range for rgb"); + } + } +} + static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_color_representation_v1_surface_state), + .commit = surface_synced_commit }; static struct wlr_color_representation_v1 *color_repr_from_surface( @@ -280,6 +309,7 @@ static void color_repr_manager_handle_get_surface(struct wl_client *client, } color_repr->manager = manager_from_resource(manager_resource); + color_repr->surface = surface; if (!wlr_surface_synced_init(&color_repr->synced, surface, &surface_synced_impl, &color_repr->pending, &color_repr->current)) { From 23305aefa2d3dcf57b898000849cdb92510700fa Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:11:14 +0400 Subject: [PATCH 4/4] color-representation: add support for identity+full --- types/wlr_color_representation_v1.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index 55ad98e6c..45ae781a9 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -247,13 +247,25 @@ static void surface_synced_commit(struct wlr_surface_synced *synced) { } bool is_unset = color_repr->current.coefficients == 0 && color_repr->current.range == 0; + bool is_identity_full = color_repr->current.coefficients == WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY && color_repr->current.range == WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL; + bool is_ycbcr = pixel_format_is_ycbcr(dmabuf.format); if (is_ycbcr) { if (is_unset) { color_repr->current.coefficients = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601; color_repr->current.range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED; } + else if(is_identity_full) { + wl_resource_post_error(color_repr->resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, + "unexpected encoding/range for yuv"); + } } else /* rgb */ { + if(is_identity_full) { + color_repr->current.coefficients = 0; + color_repr->current.range = 0; + is_unset = true; + } if (!is_unset) { wl_resource_post_error(color_repr->resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, @@ -461,6 +473,10 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ struct wlr_color_representation_v1_coeffs_and_range coeffs_and_ranges[COEFFICIENTS_LEN * RANGES_LEN]; size_t coeffs_and_ranges_len = 0; + coeffs_and_ranges[coeffs_and_ranges_len++] = (struct wlr_color_representation_v1_coeffs_and_range){ + .coeffs = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY, + .range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL, + }; for (size_t i = 0; i < COEFFICIENTS_LEN; i++) { enum wp_color_representation_surface_v1_coefficients coeffs = coefficients[i]; enum wlr_color_encoding enc = wlr_color_representation_v1_color_encoding_to_wlr(coeffs);