* [PATCH v6 01/10] drm/colorop: Add DRM_COLOROP_FIXED_MATRIX
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 02/10] drm/vkms: Fix limited-range YCbCr to RGB conversion scaling Harry Wentland
` (9 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx
Cc: Chaitanya Kumar Borah, Harry Wentland, Melissa Wen, Robert Mader,
Alex Hung, Pekka Paalanen
From: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
Introduce DRM_COLOROP_FIXED_MATRIX, a new colorop type representing a
hardware that performs a fixed matrix operation.
Unlike CTM-based colorops, this block does not expose programmable
coefficients. Instead, userspace selects one of the predefined
hardware modes via a new FIXED_MATRIX_TYPE enum property. Supported modes
include common YCbCr->RGB and RGB709->RGB2020 conversions.
v5:
- Drop YCbCr limited to full and RGB709 to RGB2020 matrices
until we have non-IGT userspace that shows their use (Robert)
v4:
- Add limited-range YCbCr-RGB conversion matrix enums (Robert)
- Document matrix values (Pekka)
- Clarify RGB709 to RGB2020 is a full-range matrix (Pekka)
- Fix confusing "CSC preset" doc (Pekka)
v2:
- Naming changes (Pekka)
v3:
- Fix NC matrix enum name and string (Melissa)
- Rebase
Co-developed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Robert Mader <robert.mader@collabora.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.com>
---
drivers/gpu/drm/drm_atomic.c | 4 +
drivers/gpu/drm/drm_atomic_uapi.c | 7 ++
drivers/gpu/drm/drm_colorop.c | 107 +++++++++++++++++++++++++
include/drm/drm_colorop.h | 127 ++++++++++++++++++++++++++++++
include/uapi/drm/drm_mode.h | 12 +++
5 files changed, 257 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index e5c8ef06caed..45df6376f408 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -925,6 +925,10 @@ static void drm_atomic_colorop_print_state(struct drm_printer *p,
drm_get_colorop_lut3d_interpolation_name(state->lut3d_interpolation));
drm_printf_indent(p, 1, "data blob id=%d\n", state->data ? state->data->base.id : 0);
break;
+ case DRM_COLOROP_FIXED_MATRIX:
+ drm_printf_indent(p, 1, "fixed_matrix_type=%s\n",
+ drm_get_colorop_fixed_matrix_type_name(state->fixed_matrix_type));
+ break;
default:
break;
}
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 1050dddadb17..d52504d823bc 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -786,6 +786,11 @@ static int drm_atomic_colorop_set_property(struct drm_colorop *colorop,
return drm_atomic_color_set_data_property(colorop, state,
property, val,
replaced);
+ } else if (property == colorop->fixed_matrix_type_property) {
+ if (state->fixed_matrix_type != val) {
+ state->fixed_matrix_type = val;
+ *replaced = true;
+ }
} else {
drm_dbg_atomic(colorop->dev,
"[COLOROP:%d:%d] unknown property [PROP:%d:%s]\n",
@@ -818,6 +823,8 @@ drm_atomic_colorop_get_property(struct drm_colorop *colorop,
*val = state->lut3d_interpolation;
else if (property == colorop->data_property)
*val = (state->data) ? state->data->base.id : 0;
+ else if (property == colorop->fixed_matrix_type_property)
+ *val = state->fixed_matrix_type;
else
return -EINVAL;
diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index 682fcc651525..95da03d6c65c 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -68,6 +68,7 @@ static const struct drm_prop_enum_list drm_colorop_type_enum_list[] = {
{ DRM_COLOROP_CTM_3X4, "3x4 Matrix"},
{ DRM_COLOROP_MULTIPLIER, "Multiplier"},
{ DRM_COLOROP_3D_LUT, "3D LUT"},
+ { DRM_COLOROP_FIXED_MATRIX, "Fixed Matrix"},
};
static const char * const colorop_curve_1d_type_names[] = {
@@ -90,6 +91,15 @@ static const struct drm_prop_enum_list drm_colorop_lut3d_interpolation_list[] =
{ DRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL, "Tetrahedral" },
};
+static const char * const colorop_fixed_matrix_type_names[] = {
+ [DRM_COLOROP_FM_YCBCR601_FULL_RGB] = "YCbCr 601 Full to RGB",
+ [DRM_COLOROP_FM_YCBCR601_LIMITED_RGB] = "YCbCr 601 Limited to RGB",
+ [DRM_COLOROP_FM_YCBCR709_FULL_RGB] = "YCbCr 709 Full to RGB",
+ [DRM_COLOROP_FM_YCBCR709_LIMITED_RGB] = "YCbCr 709 Limited to RGB",
+ [DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB] = "YCbCr 2020 NC Full to RGB",
+ [DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB] = "YCbCr 2020 NC Limited to RGB",
+};
+
/* Init Helpers */
static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *colorop,
@@ -453,6 +463,80 @@ int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *col
}
EXPORT_SYMBOL(drm_plane_colorop_3dlut_init);
+/**
+ * drm_plane_colorop_fixed_matrix_init - Initialize a DRM_COLOROP_FIXED_MATRIX
+ *
+ * @dev: DRM device
+ * @colorop: The drm_colorop object to initialize
+ * @plane: The associated drm_plane
+ * @funcs: control functions for the new colorop
+ * @supported_fm: A bitfield of supported drm_colorop_fixed_matrix_type enum values,
+ * created using BIT(fixed_matrix_type) and combined with the OR '|'
+ * operator.
+ * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
+ * @return zero on success, -E value on failure
+ */
+int drm_plane_colorop_fixed_matrix_init(struct drm_device *dev, struct drm_colorop *colorop,
+ struct drm_plane *plane,
+ const struct drm_colorop_funcs *funcs,
+ u64 supported_fm, uint32_t flags)
+{
+ struct drm_prop_enum_list enum_list[DRM_COLOROP_FM_COUNT];
+ int i, len;
+ struct drm_property *prop;
+ int ret;
+
+ if (!supported_fm) {
+ drm_err(dev,
+ "No supported FM type op for new Fixed Matrix colorop on [PLANE:%d:%s]\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ if ((supported_fm & -BIT(DRM_COLOROP_FM_COUNT)) != 0) {
+ drm_err(dev, "Unknown Fixed Matrix provided on [PLANE:%d:%s]\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ ret = drm_plane_colorop_init(dev, colorop, plane, funcs, DRM_COLOROP_FIXED_MATRIX, flags);
+ if (ret)
+ return ret;
+
+ len = 0;
+ for (i = 0; i < DRM_COLOROP_FM_COUNT; i++) {
+ if ((supported_fm & BIT(i)) == 0)
+ continue;
+
+ enum_list[len].type = i;
+ enum_list[len].name = colorop_fixed_matrix_type_names[i];
+ len++;
+ }
+
+ if (WARN_ON(len <= 0))
+ return -EINVAL;
+
+ prop = drm_property_create_enum(dev, DRM_MODE_PROP_ATOMIC, "FIXED_MATRIX_TYPE",
+ enum_list, len);
+
+ if (!prop)
+ return -ENOMEM;
+
+ colorop->fixed_matrix_type_property = prop;
+ /*
+ * Default to the first supported CSC mode as provided by the driver.
+ * Intuitively this should be something that keeps the colorop in pixel bypass
+ * mode but that is already handled via the standard colorop bypass
+ * property.
+ */
+ drm_object_attach_property(&colorop->base, colorop->fixed_matrix_type_property,
+ enum_list[0].type);
+ drm_colorop_reset(colorop);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_plane_colorop_fixed_matrix_init);
+
static void __drm_atomic_helper_colorop_duplicate_state(struct drm_colorop *colorop,
struct drm_colorop_state *state)
{
@@ -533,6 +617,13 @@ static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state,
&val))
colorop_state->lut3d_interpolation = val;
}
+
+ if (colorop->fixed_matrix_type_property) {
+ if (!drm_object_property_get_default_value(&colorop->base,
+ colorop->fixed_matrix_type_property,
+ &val))
+ colorop_state->fixed_matrix_type = val;
+ }
}
/**
@@ -596,6 +687,7 @@ static const char * const colorop_type_name[] = {
[DRM_COLOROP_CTM_3X4] = "3x4 Matrix",
[DRM_COLOROP_MULTIPLIER] = "Multiplier",
[DRM_COLOROP_3D_LUT] = "3D LUT",
+ [DRM_COLOROP_FIXED_MATRIX] = "Fixed Matrix",
};
static const char * const colorop_lu3d_interpolation_name[] = {
@@ -652,6 +744,21 @@ const char *drm_get_colorop_lut3d_interpolation_name(enum drm_colorop_lut3d_inte
return colorop_lu3d_interpolation_name[type];
}
+/**
+ * drm_get_colorop_fixed_matrix_type_name: return a string for fixed matrix type
+ * @type: fixed matrix type to compute name of
+ *
+ * In contrast to the other drm_get_*_name functions this one here returns a
+ * const pointer and hence is threadsafe.
+ */
+const char *drm_get_colorop_fixed_matrix_type_name(enum drm_colorop_fixed_matrix_type type)
+{
+ if (WARN_ON(type >= ARRAY_SIZE(colorop_fixed_matrix_type_names)))
+ return "unknown";
+
+ return colorop_fixed_matrix_type_names[type];
+}
+
/**
* drm_colorop_set_next_property - sets the next pointer
* @colorop: drm colorop
diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h
index 224fae40ed2b..26ef67c835e2 100644
--- a/include/drm/drm_colorop.h
+++ b/include/drm/drm_colorop.h
@@ -134,6 +134,114 @@ enum drm_colorop_curve_1d_type {
DRM_COLOROP_1D_CURVE_COUNT
};
+/**
+ * enum drm_colorop_fixed_matrix_type - type of Fixed Matrix
+ *
+ * Describes a Fixed Matrix operation to be applied by the DRM_COLOROP_FIXED_MATRIX
+ */
+enum drm_colorop_fixed_matrix_type {
+ /**
+ * @DRM_COLOROP_FM_YCBCR601_FULL_RGB:
+ *
+ * enum string "YCbCr 601 Full to RGB"
+ *
+ * Converts full-range YCbCr into full-range RGB using the BT.601
+ * coefficients. Y is normalized to [0, 1] and Cb, Cr are centered
+ * at 0 (the nominal 0.5 offset removed) before the matrix::
+ *
+ * | R | | 1.0 0.000000 1.402000 | | Y |
+ * | G | = | 1.0 -0.344136 -0.714136 | x | Cb |
+ * | B | | 1.0 1.772000 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR601_FULL_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_YCBCR601_LIMITED_RGB:
+ *
+ * enum string "YCbCr 601 Limited to RGB"
+ *
+ * Converts limited- (narrow-) range YCbCr into full-range RGB using
+ * the BT.601 coefficients. Before the matrix Y is offset by 16/255
+ * and Cb, Cr are centered at 0, following the studio-range
+ * convention of ITU-R BT.601::
+ *
+ * | R | | 1.164384 0.000000 1.596027 | | Y - 16/255 |
+ * | G | = | 1.164384 -0.391762 -0.812968 | x | Cb |
+ * | B | | 1.164384 2.017232 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR601_LIMITED_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_YCBCR709_FULL_RGB:
+ *
+ * enum string "YCbCr 709 Full to RGB"
+ *
+ * Converts full-range YCbCr into full-range RGB using the BT.709
+ * coefficients. Y is normalized to [0, 1] and Cb, Cr are centered
+ * at 0 (the nominal 0.5 offset removed) before the matrix::
+ *
+ * | R | | 1.0 0.000000 1.574800 | | Y |
+ * | G | = | 1.0 -0.187324 -0.468124 | x | Cb |
+ * | B | | 1.0 1.855600 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR709_FULL_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_YCBCR709_LIMITED_RGB:
+ *
+ * enum string "YCbCr 709 Limited to RGB"
+ *
+ * Converts limited- (narrow-) range YCbCr into full-range RGB using
+ * the BT.709 coefficients. Before the matrix Y is offset by 16/255
+ * and Cb, Cr are centered at 0, following the studio-range
+ * convention of ITU-R BT.709::
+ *
+ * | R | | 1.164384 0.000000 1.792741 | | Y - 16/255 |
+ * | G | = | 1.164384 -0.213249 -0.532909 | x | Cb |
+ * | B | | 1.164384 2.112402 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR709_LIMITED_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB:
+ *
+ * enum string "YCbCr 2020 NC Full to RGB"
+ *
+ * Converts full-range YCbCr into full-range RGB using the BT.2020
+ * non-constant luminance coefficients. Y is normalized to [0, 1]
+ * and Cb, Cr are centered at 0 (the nominal 0.5 offset removed)
+ * before the matrix::
+ *
+ * | R | | 1.0 0.000000 1.474600 | | Y |
+ * | G | = | 1.0 -0.164553 -0.571353 | x | Cb |
+ * | B | | 1.0 1.881400 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB:
+ *
+ * enum string "YCbCr 2020 NC Limited to RGB"
+ *
+ * Converts limited- (narrow-) range YCbCr into full-range RGB using
+ * the BT.2020 non-constant luminance coefficients. Before the matrix
+ * Y is offset by 16/255 and Cb, Cr are centered at 0, following the
+ * studio-range convention of ITU-R BT.2020::
+ *
+ * | R | | 1.164384 0.000000 1.678674 | | Y - 16/255 |
+ * | G | = | 1.164384 -0.187326 -0.650424 | x | Cb |
+ * | B | | 1.164384 2.141772 0.000000 | | Cr |
+ */
+ DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB,
+
+ /**
+ * @DRM_COLOROP_FM_COUNT:
+ *
+ * enum value denoting the size of the enum
+ */
+ DRM_COLOROP_FM_COUNT
+};
+
/**
* struct drm_colorop_state - mutable colorop state
*/
@@ -197,6 +305,13 @@ struct drm_colorop_state {
*/
enum drm_colorop_lut3d_interpolation_type lut3d_interpolation;
+ /**
+ * @fixed_matrix_type:
+ *
+ * Type of Fixed Matrix operation.
+ */
+ enum drm_colorop_fixed_matrix_type fixed_matrix_type;
+
/** @state: backpointer to global drm_atomic_commit */
struct drm_atomic_commit *state;
};
@@ -366,6 +481,13 @@ struct drm_colorop {
*/
struct drm_property *data_property;
+ /**
+ * @fixed_matrix_type_property:
+ *
+ * Sub-type for DRM_COLOROP_FIXED_MATRIX type.
+ */
+ struct drm_property *fixed_matrix_type_property;
+
/**
* @next_property:
*
@@ -422,6 +544,10 @@ int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *col
uint32_t lut_size,
enum drm_colorop_lut3d_interpolation_type interpolation,
uint32_t flags);
+int drm_plane_colorop_fixed_matrix_init(struct drm_device *dev, struct drm_colorop *colorop,
+ struct drm_plane *plane,
+ const struct drm_colorop_funcs *funcs,
+ u64 supported_fm, uint32_t flags);
struct drm_colorop_state *
drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop);
@@ -480,6 +606,7 @@ drm_get_colorop_lut1d_interpolation_name(enum drm_colorop_lut1d_interpolation_ty
const char *
drm_get_colorop_lut3d_interpolation_name(enum drm_colorop_lut3d_interpolation_type type);
+const char *drm_get_colorop_fixed_matrix_type_name(enum drm_colorop_fixed_matrix_type type);
void drm_colorop_set_next_property(struct drm_colorop *colorop, struct drm_colorop *next);
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index bd435effdcee..ed1a660a3dfd 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -955,6 +955,18 @@ enum drm_colorop_type {
* color = lut3d[index]
*/
DRM_COLOROP_3D_LUT,
+
+ /**
+ * @DRM_COLOROP_FIXED_MATRIX:
+ *
+ * enum string "Fixed Matrix"
+ *
+ * A Colorop block that performs a pre-defined matrix operation selected
+ * via the FIXED_MATRIX_TYPE enum property. The driver advertises the supported
+ * operations through this property.
+ */
+ DRM_COLOROP_FIXED_MATRIX,
+
};
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 02/10] drm/vkms: Fix limited-range YCbCr to RGB conversion scaling
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
2026-08-14 21:18 ` [PATCH v6 01/10] drm/colorop: Add DRM_COLOROP_FIXED_MATRIX Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-21 13:18 ` Robert Mader
2026-08-14 21:18 ` [PATCH v6 03/10] drm/vkms: Add fixed matrix colorop to color pipeline Harry Wentland
` (8 subsequent siblings)
10 siblings, 1 reply; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
The limited-/studio-range YCbCr to RGB conversion matrices were generated
with colour.matrix_YCbCr(is_legal=True, bits=8), which normalises the
narrow range by 2^bits (256 for 8-bit) rather than by the full-range
maximum 2^bits - 1 (255). As a result the luma scale was 256/219 and the
chroma scale 256/224 instead of the correct 255/219 and 255/224.
This over-scales every limited-range conversion by a factor of 256/255,
producing an error of up to ~3/255 (8-bit) and causing IGT tests that
compare software-computed surfaces against VKMS-processed ones to fail.
Recompute the three limited-range matrices from first principles rather
than relying on colour's is_legal scaling:
1. Start from the standard ITU-R YCbCr -> RGB relations for the luma
weights Kr, Kb (Kg = 1 - Kr - Kb) of each encoding (BT.601, BT.709
and BT.2020 non-constant luminance). These are the same relations
that produce the existing full-range matrices.
2. Expand the studio input range to full range relative to a full-range
maximum of 2^n - 1: the luma coefficient by 255/(235 - 16) and the
chroma coefficients by 255/(240 - 16). This matches the DRM UAPI
definition and IGT's igt_ycbcr_to_rgb_matrix().
3. Convert each coefficient to S31.32 fixed point (round(coeff * 2^32)).
Update the limited-range reference values in the vkms-format KUnit test
accordingly. Provide a python script to show how the kunit test value
are calculated.
Fixes: fe22d21e9342 ("drm/vkms: Add YUV support")
Assisted-by: Copilot:claude-opus-4.8
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
.../gpu/drm/vkms/tests/gen_yuv_conversion.py | 87 +++++++++++++++++++
drivers/gpu/drm/vkms/tests/vkms_format_test.c | 38 ++++----
drivers/gpu/drm/vkms/vkms_formats.c | 55 ++++++++----
3 files changed, 144 insertions(+), 36 deletions(-)
create mode 100755 drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
diff --git a/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py b/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
new file mode 100755
index 000000000000..f4d226137cc2
--- /dev/null
+++ b/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+"""
+Derive the VKMS YCbCr -> RGB conversion constants and the vkms-format KUnit
+reference values.
+
+It produces:
+ 1. The S31.32 fixed-point conversion matrices used in
+ drivers/gpu/drm/vkms/vkms_formats.c and asserted by
+ vkms_format_test_conversion_matrix.
+ 2. The 16-bit reference YCbCr inputs for the limited-range
+ yuv_u16_to_argb_u16 round-trip cases (the values changed by the fix).
+
+Requires numpy.
+"""
+
+import numpy as np
+
+FP = 1 << 32 # S31.32 scale: fixed = round(coeff * 2^32)
+
+# ITU-R luma weights (Kr, Kb); Kg = 1 - Kr - Kb.
+ENCODINGS = {
+ "BT601": (0.299, 0.114),
+ "BT709": (0.2126, 0.0722),
+ "BT2020": (0.2627, 0.0593), # non-constant luminance
+}
+
+# 8-bit studio-range levels: luma 16..235 (range 219), chroma 16..240
+# (range 224, neutral 128); full-range maximum is 2^n - 1 = 255.
+FULL_MAX = 255
+Y_RANGE = 235 - 16 # 219
+C_RANGE = 240 - 16 # 224
+
+# 16-bit RGB test colors.
+COLORS = {
+ "white": (0xffff, 0xffff, 0xffff),
+ "gray": (0x8080, 0x8080, 0x8080),
+ "black": (0x0000, 0x0000, 0x0000),
+ "red": (0xffff, 0x0000, 0x0000),
+ "green": (0x0000, 0xffff, 0x0000),
+ "blue": (0x0000, 0x0000, 0xffff),
+}
+
+
+def rgb_matrix(kr, kb, limited):
+ """Normalized YCbCr -> RGB (Y in [0,1], Cb/Cr in [-0.5, 0.5])."""
+ kg = 1.0 - kr - kb
+ # Standard full-range relations.
+ m = [[1.0, 0.0, 2 * (1 - kr)],
+ [1.0, -2 * (1 - kb) * kb / kg, -2 * (1 - kr) * kr / kg],
+ [1.0, 2 * (1 - kb), 0.0]]
+ if limited:
+ # Expand studio input range to full range: luma by 255/219,
+ # chroma by 255/224 (relative to full-range maximum 2^n - 1).
+ ys, cs = FULL_MAX / Y_RANGE, FULL_MAX / C_RANGE
+ m = [[r[0] * ys, r[1] * cs, r[2] * cs] for r in m]
+ return m
+
+
+def ref_yuv(m, y_offset):
+ """16-bit YCbCr that VKMS decodes back to each RGB color.
+
+ VKMS decode: [R,G,B] = M . [Y - y_offset*257, Cb - 128*257, Cr - 128*257]
+ (8-bit offsets lifted to 16-bit by *257 = 65535/255), so invert and
+ re-add the offsets.
+ """
+ inv = np.linalg.inv(np.array(m, float))
+ off = np.array([y_offset * 257, 128 * 257, 128 * 257])
+ return {name: tuple(int(min(max(round(v), 0), 0xffff))
+ for v in inv @ np.array(rgb, float) + off)
+ for name, rgb in COLORS.items()}
+
+print(f"== RGB REFERENCE VALUES ==")
+for name, rgb in COLORS.items():
+ print(f" {name:6} {{ {rgb[0]:#06x}, {rgb[1]:#06x}, {rgb[2]:#06x} }}")
+print()
+
+for enc, (kr, kb) in ENCODINGS.items():
+ for limited in (False, True):
+ m = rgb_matrix(kr, kb, limited)
+ y_offset = 16 if limited else 0
+ print(f"== {enc} {'LIMITED' if limited else 'FULL'} (y_offset={y_offset}) ==")
+ for row in m:
+ print(" { " + ", ".join(str(round(v * FP)) for v in row) + " },")
+ for name, yuv in ref_yuv(m, y_offset).items():
+ print(f" {name:6} {{ {yuv[0]:#06x}, {yuv[1]:#06x}, {yuv[2]:#06x} }}")
+ print()
diff --git a/drivers/gpu/drm/vkms/tests/vkms_format_test.c b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
index a7788fbc45dc..d2ae6321383b 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_format_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
@@ -54,6 +54,8 @@ struct yuv_u16_to_argb_u16_case {
* The YUV color representation were acquired via the colour python framework.
* Below are the function calls used for generating each case.
*
+ * The limited-range cases are generated by gen_yuv_conversion.py.
+ *
* For more information got to the docs:
* https://colour.readthedocs.io/en/master/generated/colour.RGB_to_YCbCr.html
*/
@@ -101,12 +103,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
.range = DRM_COLOR_YCBCR_LIMITED_RANGE,
.n_colors = 6,
.colors = {
- { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
- { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
- { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
- { "red", { 0x517b, 0x5a34, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
- { "green", { 0x908e, 0x35cc, 0x2237 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
- { "blue", { 0x28f7, 0xf000, 0x6dc9 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
+ { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
+ { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
+ { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
+ { "red", { 0x51cd, 0x5a8e, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
+ { "green", { 0x911e, 0x3602, 0x2259 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
+ { "blue", { 0x2920, 0xf0f0, 0x6e37 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
}
},
/*
@@ -151,12 +153,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
.range = DRM_COLOR_YCBCR_LIMITED_RANGE,
.n_colors = 6,
.colors = {
- { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
- { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
- { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
- { "red", { 0x3e8f, 0x6656, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
- { "green", { 0xaca1, 0x29aa, 0x1a45 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
- { "blue", { 0x1fd0, 0xf000, 0x75bb }, { 0xffff, 0x0000, 0x0000, 0xffff }},
+ { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
+ { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
+ { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
+ { "red", { 0x3ece, 0x66bc, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
+ { "green", { 0xad4e, 0x29d4, 0x1a5f }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
+ { "blue", { 0x1ff0, 0xf0f0, 0x7631 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
}
},
/*
@@ -201,12 +203,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
.range = DRM_COLOR_YCBCR_LIMITED_RANGE,
.n_colors = 6,
.colors = {
- { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
- { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
- { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
- { "red", { 0x4988, 0x60b9, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
- { "green", { 0xa47b, 0x2f47, 0x1902 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
- { "blue", { 0x1cfd, 0xf000, 0x76fe }, { 0xffff, 0x0000, 0x0000, 0xffff }},
+ { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
+ { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
+ { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
+ { "red", { 0x49d2, 0x611a, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
+ { "green", { 0xa520, 0x2f76, 0x191b }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
+ { "blue", { 0x1d1a, 0xf0f0, 0x7775 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
}
},
};
diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c
index dfb8e13cba87..4d5fcaeb82c5 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.c
+++ b/drivers/gpu/drm/vkms/vkms_formats.c
@@ -788,15 +788,36 @@ static const struct conversion_matrix yuv_bt601_full = {
};
/*
- * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.601"],
- * is_legal = True,
- * bits = 8) * 2**32).astype(int)
+ * BT.601 limited-/studio-range YCbCr to full-range RGB.
+ *
+ * The coefficients are derived as follows:
+ *
+ * 1. Take the standard ITU-R YCbCr -> RGB relations for luma weights
+ * Kr, Kb (Kg = 1 - Kr - Kb), with Y in [0, 1] and Cb, Cr in
+ * [-0.5, 0.5]. For BT.601 Kr = 0.299 and Kb = 0.114:
+ *
+ * R = Y + 2 * (1 - Kr) * Cr
+ * G = Y - 2 * (1 - Kb) * Kb / Kg * Cb - 2 * (1 - Kr) * Kr / Kg * Cr
+ * B = Y + 2 * (1 - Kb) * Cb
+ *
+ * These are exactly the yuv_bt601_full coefficients above.
+ *
+ * 2. Expand the studio input range to full range, relative to a
+ * full-range maximum of 2^n - 1 (255 for 8-bit): the luma
+ * coefficient is scaled by 255/(235 - 16) and the chroma
+ * coefficients by 255/(240 - 16). This matches the DRM UAPI
+ * definition and IGT's igt_ycbcr_to_rgb_matrix(). Note this differs
+ * from colour.matrix_YCbCr(is_legal=True), which normalises by 2^n
+ * and is thus off by a factor of 256/255.
+ *
+ * 3. Convert each coefficient to S31.32 fixed point, i.e.
+ * round(coeff * 2^32).
*/
static const struct conversion_matrix yuv_bt601_limited = {
.matrix = {
- { 5020601039, 0, 6881764740 },
- { 5020601039, -1689204679, -3505362278 },
- { 5020601039, 8697922339, 0 },
+ { 5000989317, 0, 6854882848 },
+ { 5000989317, -1682606224, -3491669458 },
+ { 5000989317, 8663946082, 0 },
},
.y_offset = 16,
};
@@ -816,15 +837,14 @@ static const struct conversion_matrix yuv_bt709_full = {
};
/*
- * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.709"],
- * is_legal = True,
- * bits = 8) * 2**32).astype(int)
+ * BT.709 limited-range YCbCr to full-range RGB (Kr = 0.2126, Kb = 0.0722).
+ * Derived as described for yuv_bt601_limited.
*/
static const struct conversion_matrix yuv_bt709_limited = {
.matrix = {
- { 5020601039, 0, 7729959424 },
- { 5020601039, -919487572, -2297803934 },
- { 5020601039, 9108275786, 0 },
+ { 5000989317, 0, 7699764272 },
+ { 5000989317, -915895824, -2288828138 },
+ { 5000989317, 9072696586, 0 },
},
.y_offset = 16,
};
@@ -844,15 +864,14 @@ static const struct conversion_matrix yuv_bt2020_full = {
};
/*
- * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"],
- * is_legal = True,
- * bits = 8) * 2**32).astype(int)
+ * BT.2020 non-constant-luminance limited-range YCbCr to full-range RGB
+ * (Kr = 0.2627, Kb = 0.0593). Derived as described for yuv_bt601_limited.
*/
static const struct conversion_matrix yuv_bt2020_limited = {
.matrix = {
- { 5020601039, 0, 7238124312 },
- { 5020601039, -807714626, -2804506279 },
- { 5020601039, 9234915964, 0 },
+ { 5000989317, 0, 7209850391 },
+ { 5000989317, -804559491, -2793551177 },
+ { 5000989317, 9198842076, 0 },
},
.y_offset = 16,
};
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v6 02/10] drm/vkms: Fix limited-range YCbCr to RGB conversion scaling
2026-08-14 21:18 ` [PATCH v6 02/10] drm/vkms: Fix limited-range YCbCr to RGB conversion scaling Harry Wentland
@ 2026-08-21 13:18 ` Robert Mader
0 siblings, 0 replies; 14+ messages in thread
From: Robert Mader @ 2026-08-21 13:18 UTC (permalink / raw)
To: Harry Wentland, dri-devel, amd-gfx; +Cc: Alex Hung
FYI: Following my previous observation that the Weston
test ("color-representation-drm") that should have detected this error
did not do so, I opened a MR to reduce the fuzziness (accepted error
range) of said test [1]. The test checks all six cases with llvmpipe,
lavapipe, Weston-internal GL fallback shaders and VKMS. All of them
succeed within an error range of 1 per 8bit channel *with* the change
from this patch. Without it the VKMS results have higher errors[2] -
confirming again that this patch clearly is an improvement.
So hope we can move forward with the series soon :)
Best regards,
Robert
1: https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/2184
2: https://gitlab.freedesktop.org/rmader/weston/-/pipelines/1731461
On 14.08.26 23:18, Harry Wentland wrote:
> The limited-/studio-range YCbCr to RGB conversion matrices were generated
> with colour.matrix_YCbCr(is_legal=True, bits=8), which normalises the
> narrow range by 2^bits (256 for 8-bit) rather than by the full-range
> maximum 2^bits - 1 (255). As a result the luma scale was 256/219 and the
> chroma scale 256/224 instead of the correct 255/219 and 255/224.
>
> This over-scales every limited-range conversion by a factor of 256/255,
> producing an error of up to ~3/255 (8-bit) and causing IGT tests that
> compare software-computed surfaces against VKMS-processed ones to fail.
>
> Recompute the three limited-range matrices from first principles rather
> than relying on colour's is_legal scaling:
>
> 1. Start from the standard ITU-R YCbCr -> RGB relations for the luma
> weights Kr, Kb (Kg = 1 - Kr - Kb) of each encoding (BT.601, BT.709
> and BT.2020 non-constant luminance). These are the same relations
> that produce the existing full-range matrices.
> 2. Expand the studio input range to full range relative to a full-range
> maximum of 2^n - 1: the luma coefficient by 255/(235 - 16) and the
> chroma coefficients by 255/(240 - 16). This matches the DRM UAPI
> definition and IGT's igt_ycbcr_to_rgb_matrix().
> 3. Convert each coefficient to S31.32 fixed point (round(coeff * 2^32)).
>
> Update the limited-range reference values in the vkms-format KUnit test
> accordingly. Provide a python script to show how the kunit test value
> are calculated.
>
> Fixes: fe22d21e9342 ("drm/vkms: Add YUV support")
> Assisted-by: Copilot:claude-opus-4.8
> Signed-off-by: Harry Wentland <harry.wentland@amd.com>
> Reviewed-by: Alex Hung <alex.hung@amd.com>
> Tested-by: Robert Mader <robert.mader@collabora.com>
> ---
> .../gpu/drm/vkms/tests/gen_yuv_conversion.py | 87 +++++++++++++++++++
> drivers/gpu/drm/vkms/tests/vkms_format_test.c | 38 ++++----
> drivers/gpu/drm/vkms/vkms_formats.c | 55 ++++++++----
> 3 files changed, 144 insertions(+), 36 deletions(-)
> create mode 100755 drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
>
> diff --git a/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py b/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
> new file mode 100755
> index 000000000000..f4d226137cc2
> --- /dev/null
> +++ b/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
> @@ -0,0 +1,87 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0+
> +"""
> +Derive the VKMS YCbCr -> RGB conversion constants and the vkms-format KUnit
> +reference values.
> +
> +It produces:
> + 1. The S31.32 fixed-point conversion matrices used in
> + drivers/gpu/drm/vkms/vkms_formats.c and asserted by
> + vkms_format_test_conversion_matrix.
> + 2. The 16-bit reference YCbCr inputs for the limited-range
> + yuv_u16_to_argb_u16 round-trip cases (the values changed by the fix).
> +
> +Requires numpy.
> +"""
> +
> +import numpy as np
> +
> +FP = 1 << 32 # S31.32 scale: fixed = round(coeff * 2^32)
> +
> +# ITU-R luma weights (Kr, Kb); Kg = 1 - Kr - Kb.
> +ENCODINGS = {
> + "BT601": (0.299, 0.114),
> + "BT709": (0.2126, 0.0722),
> + "BT2020": (0.2627, 0.0593), # non-constant luminance
> +}
> +
> +# 8-bit studio-range levels: luma 16..235 (range 219), chroma 16..240
> +# (range 224, neutral 128); full-range maximum is 2^n - 1 = 255.
> +FULL_MAX = 255
> +Y_RANGE = 235 - 16 # 219
> +C_RANGE = 240 - 16 # 224
> +
> +# 16-bit RGB test colors.
> +COLORS = {
> + "white": (0xffff, 0xffff, 0xffff),
> + "gray": (0x8080, 0x8080, 0x8080),
> + "black": (0x0000, 0x0000, 0x0000),
> + "red": (0xffff, 0x0000, 0x0000),
> + "green": (0x0000, 0xffff, 0x0000),
> + "blue": (0x0000, 0x0000, 0xffff),
> +}
> +
> +
> +def rgb_matrix(kr, kb, limited):
> + """Normalized YCbCr -> RGB (Y in [0,1], Cb/Cr in [-0.5, 0.5])."""
> + kg = 1.0 - kr - kb
> + # Standard full-range relations.
> + m = [[1.0, 0.0, 2 * (1 - kr)],
> + [1.0, -2 * (1 - kb) * kb / kg, -2 * (1 - kr) * kr / kg],
> + [1.0, 2 * (1 - kb), 0.0]]
> + if limited:
> + # Expand studio input range to full range: luma by 255/219,
> + # chroma by 255/224 (relative to full-range maximum 2^n - 1).
> + ys, cs = FULL_MAX / Y_RANGE, FULL_MAX / C_RANGE
> + m = [[r[0] * ys, r[1] * cs, r[2] * cs] for r in m]
> + return m
> +
> +
> +def ref_yuv(m, y_offset):
> + """16-bit YCbCr that VKMS decodes back to each RGB color.
> +
> + VKMS decode: [R,G,B] = M . [Y - y_offset*257, Cb - 128*257, Cr - 128*257]
> + (8-bit offsets lifted to 16-bit by *257 = 65535/255), so invert and
> + re-add the offsets.
> + """
> + inv = np.linalg.inv(np.array(m, float))
> + off = np.array([y_offset * 257, 128 * 257, 128 * 257])
> + return {name: tuple(int(min(max(round(v), 0), 0xffff))
> + for v in inv @ np.array(rgb, float) + off)
> + for name, rgb in COLORS.items()}
> +
> +print(f"== RGB REFERENCE VALUES ==")
> +for name, rgb in COLORS.items():
> + print(f" {name:6} {{ {rgb[0]:#06x}, {rgb[1]:#06x}, {rgb[2]:#06x} }}")
> +print()
> +
> +for enc, (kr, kb) in ENCODINGS.items():
> + for limited in (False, True):
> + m = rgb_matrix(kr, kb, limited)
> + y_offset = 16 if limited else 0
> + print(f"== {enc} {'LIMITED' if limited else 'FULL'} (y_offset={y_offset}) ==")
> + for row in m:
> + print(" { " + ", ".join(str(round(v * FP)) for v in row) + " },")
> + for name, yuv in ref_yuv(m, y_offset).items():
> + print(f" {name:6} {{ {yuv[0]:#06x}, {yuv[1]:#06x}, {yuv[2]:#06x} }}")
> + print()
> diff --git a/drivers/gpu/drm/vkms/tests/vkms_format_test.c b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
> index a7788fbc45dc..d2ae6321383b 100644
> --- a/drivers/gpu/drm/vkms/tests/vkms_format_test.c
> +++ b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
> @@ -54,6 +54,8 @@ struct yuv_u16_to_argb_u16_case {
> * The YUV color representation were acquired via the colour python framework.
> * Below are the function calls used for generating each case.
> *
> + * The limited-range cases are generated by gen_yuv_conversion.py.
> + *
> * For more information got to the docs:
> * https://colour.readthedocs.io/en/master/generated/colour.RGB_to_YCbCr.html
> */
> @@ -101,12 +103,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
> .range = DRM_COLOR_YCBCR_LIMITED_RANGE,
> .n_colors = 6,
> .colors = {
> - { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> - { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> - { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> - { "red", { 0x517b, 0x5a34, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> - { "green", { 0x908e, 0x35cc, 0x2237 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> - { "blue", { 0x28f7, 0xf000, 0x6dc9 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> + { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> + { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> + { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> + { "red", { 0x51cd, 0x5a8e, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> + { "green", { 0x911e, 0x3602, 0x2259 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> + { "blue", { 0x2920, 0xf0f0, 0x6e37 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> }
> },
> /*
> @@ -151,12 +153,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
> .range = DRM_COLOR_YCBCR_LIMITED_RANGE,
> .n_colors = 6,
> .colors = {
> - { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> - { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> - { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> - { "red", { 0x3e8f, 0x6656, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> - { "green", { 0xaca1, 0x29aa, 0x1a45 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> - { "blue", { 0x1fd0, 0xf000, 0x75bb }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> + { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> + { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> + { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> + { "red", { 0x3ece, 0x66bc, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> + { "green", { 0xad4e, 0x29d4, 0x1a5f }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> + { "blue", { 0x1ff0, 0xf0f0, 0x7631 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> }
> },
> /*
> @@ -201,12 +203,12 @@ static struct yuv_u16_to_argb_u16_case yuv_u16_to_argb_u16_cases[] = {
> .range = DRM_COLOR_YCBCR_LIMITED_RANGE,
> .n_colors = 6,
> .colors = {
> - { "white", { 0xeb00, 0x8000, 0x8000 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> - { "gray", { 0x7dee, 0x8000, 0x8000 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> - { "black", { 0x1000, 0x8000, 0x8000 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> - { "red", { 0x4988, 0x60b9, 0xf000 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> - { "green", { 0xa47b, 0x2f47, 0x1902 }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> - { "blue", { 0x1cfd, 0xf000, 0x76fe }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> + { "white", { 0xebeb, 0x8080, 0x8080 }, { 0xffff, 0xffff, 0xffff, 0xffff }},
> + { "gray", { 0x7e6c, 0x8080, 0x8080 }, { 0xffff, 0x8080, 0x8080, 0x8080 }},
> + { "black", { 0x1010, 0x8080, 0x8080 }, { 0xffff, 0x0000, 0x0000, 0x0000 }},
> + { "red", { 0x49d2, 0x611a, 0xf0f0 }, { 0xffff, 0xffff, 0x0000, 0x0000 }},
> + { "green", { 0xa520, 0x2f76, 0x191b }, { 0xffff, 0x0000, 0xffff, 0x0000 }},
> + { "blue", { 0x1d1a, 0xf0f0, 0x7775 }, { 0xffff, 0x0000, 0x0000, 0xffff }},
> }
> },
> };
> diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c
> index dfb8e13cba87..4d5fcaeb82c5 100644
> --- a/drivers/gpu/drm/vkms/vkms_formats.c
> +++ b/drivers/gpu/drm/vkms/vkms_formats.c
> @@ -788,15 +788,36 @@ static const struct conversion_matrix yuv_bt601_full = {
> };
>
> /*
> - * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.601"],
> - * is_legal = True,
> - * bits = 8) * 2**32).astype(int)
> + * BT.601 limited-/studio-range YCbCr to full-range RGB.
> + *
> + * The coefficients are derived as follows:
> + *
> + * 1. Take the standard ITU-R YCbCr -> RGB relations for luma weights
> + * Kr, Kb (Kg = 1 - Kr - Kb), with Y in [0, 1] and Cb, Cr in
> + * [-0.5, 0.5]. For BT.601 Kr = 0.299 and Kb = 0.114:
> + *
> + * R = Y + 2 * (1 - Kr) * Cr
> + * G = Y - 2 * (1 - Kb) * Kb / Kg * Cb - 2 * (1 - Kr) * Kr / Kg * Cr
> + * B = Y + 2 * (1 - Kb) * Cb
> + *
> + * These are exactly the yuv_bt601_full coefficients above.
> + *
> + * 2. Expand the studio input range to full range, relative to a
> + * full-range maximum of 2^n - 1 (255 for 8-bit): the luma
> + * coefficient is scaled by 255/(235 - 16) and the chroma
> + * coefficients by 255/(240 - 16). This matches the DRM UAPI
> + * definition and IGT's igt_ycbcr_to_rgb_matrix(). Note this differs
> + * from colour.matrix_YCbCr(is_legal=True), which normalises by 2^n
> + * and is thus off by a factor of 256/255.
> + *
> + * 3. Convert each coefficient to S31.32 fixed point, i.e.
> + * round(coeff * 2^32).
> */
> static const struct conversion_matrix yuv_bt601_limited = {
> .matrix = {
> - { 5020601039, 0, 6881764740 },
> - { 5020601039, -1689204679, -3505362278 },
> - { 5020601039, 8697922339, 0 },
> + { 5000989317, 0, 6854882848 },
> + { 5000989317, -1682606224, -3491669458 },
> + { 5000989317, 8663946082, 0 },
> },
> .y_offset = 16,
> };
> @@ -816,15 +837,14 @@ static const struct conversion_matrix yuv_bt709_full = {
> };
>
> /*
> - * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.709"],
> - * is_legal = True,
> - * bits = 8) * 2**32).astype(int)
> + * BT.709 limited-range YCbCr to full-range RGB (Kr = 0.2126, Kb = 0.0722).
> + * Derived as described for yuv_bt601_limited.
> */
> static const struct conversion_matrix yuv_bt709_limited = {
> .matrix = {
> - { 5020601039, 0, 7729959424 },
> - { 5020601039, -919487572, -2297803934 },
> - { 5020601039, 9108275786, 0 },
> + { 5000989317, 0, 7699764272 },
> + { 5000989317, -915895824, -2288828138 },
> + { 5000989317, 9072696586, 0 },
> },
> .y_offset = 16,
> };
> @@ -844,15 +864,14 @@ static const struct conversion_matrix yuv_bt2020_full = {
> };
>
> /*
> - * numpy.around(colour.matrix_YCbCr(K=colour.WEIGHTS_YCBCR["ITU-R BT.2020"],
> - * is_legal = True,
> - * bits = 8) * 2**32).astype(int)
> + * BT.2020 non-constant-luminance limited-range YCbCr to full-range RGB
> + * (Kr = 0.2627, Kb = 0.0593). Derived as described for yuv_bt601_limited.
> */
> static const struct conversion_matrix yuv_bt2020_limited = {
> .matrix = {
> - { 5020601039, 0, 7238124312 },
> - { 5020601039, -807714626, -2804506279 },
> - { 5020601039, 9234915964, 0 },
> + { 5000989317, 0, 7209850391 },
> + { 5000989317, -804559491, -2793551177 },
> + { 5000989317, 9198842076, 0 },
> },
> .y_offset = 16,
> };
--
Robert Mader
Consultant Software Developer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 03/10] drm/vkms: Add fixed matrix colorop to color pipeline
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
2026-08-14 21:18 ` [PATCH v6 01/10] drm/colorop: Add DRM_COLOROP_FIXED_MATRIX Harry Wentland
2026-08-14 21:18 ` [PATCH v6 02/10] drm/vkms: Fix limited-range YCbCr to RGB conversion scaling Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 04/10] drm/vkms: Add atomic check and matrix handling for fixed matrix colorop Harry Wentland
` (7 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
Insert a fixed matrix colorop as the first operation in the
VKMS color pipeline, before the existing 1D curve. This allows
userspace to select YUV-to-RGB conversion via the color pipeline
for YCbCr framebuffers.
The CSC Fixed Matrix colorop advertises support for all six
YUV-to-RGB conversion presets:
- YUV601/709/2020 full-range to RGB
- YUV601/709/2020 limited-range to RGB
Bump MAX_COLOR_PIPELINE_OPS from 4 to 5 to accommodate the new op.
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Reviewed-by: Robert Mader <robert.mader@collabora.com>
---
drivers/gpu/drm/vkms/vkms_colorop.c | 66 ++++++++++++++++++++---------
1 file changed, 46 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/vkms/vkms_colorop.c b/drivers/gpu/drm/vkms/vkms_colorop.c
index 071f3a8d2e7c..9424b1994239 100644
--- a/drivers/gpu/drm/vkms/vkms_colorop.c
+++ b/drivers/gpu/drm/vkms/vkms_colorop.c
@@ -12,11 +12,19 @@ static const u64 supported_tfs =
BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF);
+static const u64 supported_fm =
+ BIT(DRM_COLOROP_FM_YCBCR601_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR601_LIMITED_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR709_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR709_LIMITED_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB);
+
static const struct drm_colorop_funcs vkms_colorop_funcs = {
.destroy = drm_colorop_destroy,
};
-#define MAX_COLOR_PIPELINE_OPS 4
+#define MAX_COLOR_PIPELINE_OPS 5
static int vkms_initialize_color_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list)
{
@@ -27,7 +35,25 @@ static int vkms_initialize_color_pipeline(struct drm_plane *plane, struct drm_pr
memset(ops, 0, sizeof(ops));
- /* 1st op: 1d curve */
+ /* 1st op: Fixed Matrix (YUV to RGB) */
+ ops[i] = kzalloc_obj(*ops[i]);
+ if (!ops[i]) {
+ drm_err(dev, "KMS: Failed to allocate colorop\n");
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ ret = drm_plane_colorop_fixed_matrix_init(dev, ops[i], plane, &vkms_colorop_funcs,
+ supported_fm,
+ DRM_COLOROP_FLAG_ALLOW_BYPASS);
+ if (ret)
+ goto cleanup;
+
+ list->type = ops[i]->base.id;
+
+ i++;
+
+ /* 2nd op: 1d curve */
ops[i] = kzalloc_obj(*ops[i]);
if (!ops[i]) {
drm_err(dev, "KMS: Failed to allocate colorop\n");
@@ -41,23 +67,6 @@ static int vkms_initialize_color_pipeline(struct drm_plane *plane, struct drm_pr
if (ret)
goto cleanup;
- list->type = ops[i]->base.id;
-
- i++;
-
- /* 2nd op: 3x4 matrix */
- ops[i] = kzalloc_obj(*ops[i]);
- if (!ops[i]) {
- drm_err(dev, "KMS: Failed to allocate colorop\n");
- ret = -ENOMEM;
- goto cleanup;
- }
-
- ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane, &vkms_colorop_funcs,
- DRM_COLOROP_FLAG_ALLOW_BYPASS);
- if (ret)
- goto cleanup;
-
drm_colorop_set_next_property(ops[i - 1], ops[i]);
i++;
@@ -79,7 +88,24 @@ static int vkms_initialize_color_pipeline(struct drm_plane *plane, struct drm_pr
i++;
- /* 4th op: 1d curve */
+ /* 4th op: 3x4 matrix */
+ ops[i] = kzalloc_obj(*ops[i]);
+ if (!ops[i]) {
+ drm_err(dev, "KMS: Failed to allocate colorop\n");
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane, &vkms_colorop_funcs,
+ DRM_COLOROP_FLAG_ALLOW_BYPASS);
+ if (ret)
+ goto cleanup;
+
+ drm_colorop_set_next_property(ops[i - 1], ops[i]);
+
+ i++;
+
+ /* 5th op: 1d curve */
ops[i] = kzalloc_obj(*ops[i]);
if (!ops[i]) {
drm_err(dev, "KMS: Failed to allocate colorop\n");
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 04/10] drm/vkms: Add atomic check and matrix handling for fixed matrix colorop
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (2 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 03/10] drm/vkms: Add fixed matrix colorop to color pipeline Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 05/10] drm/amd/display: Add fixed matrix colorop to color pipeline Harry Wentland
` (6 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
In vkms_plane_atomic_check(), validate that when a color pipeline
is active and the framebuffer format is YUV:
- The first colorop must be DRM_COLOROP_FIXED_MATRIX
- The fixed matrix colorop must not be bypassed
In vkms_plane_atomic_update(), when a color pipeline is active,
read the fixed_matrix_type from the first colorop state and map
it to the appropriate (color_encoding, color_range) pair for the
existing get_conversion_matrix_to_argb_u16() function.
In apply_colorop(), add a no-op case for DRM_COLOROP_FIXED_MATRIX
since the actual YUV-to-RGB conversion is already performed at
format read time via the conversion matrix.
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Reviewed-by: Robert Mader <robert.mader@collabora.com>
---
drivers/gpu/drm/vkms/tests/vkms_format_test.c | 2 +-
drivers/gpu/drm/vkms/vkms_composer.c | 6 ++
drivers/gpu/drm/vkms/vkms_formats.c | 9 +++
drivers/gpu/drm/vkms/vkms_formats.h | 2 +-
drivers/gpu/drm/vkms/vkms_plane.c | 55 ++++++++++++++++++-
5 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vkms/tests/vkms_format_test.c b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
index d2ae6321383b..bbb1ad18c99f 100644
--- a/drivers/gpu/drm/vkms/tests/vkms_format_test.c
+++ b/drivers/gpu/drm/vkms/tests/vkms_format_test.c
@@ -234,7 +234,7 @@ static void vkms_format_test_yuv_u16_to_argb_u16(struct kunit *test)
struct conversion_matrix matrix;
get_conversion_matrix_to_argb_u16
- (DRM_FORMAT_NV12, param->encoding, param->range, &matrix);
+ (DRM_FORMAT_NV12, param->encoding, param->range, false, &matrix);
argb = argb_u16_from_yuv161616(&matrix, color->yuv.y, color->yuv.u,
color->yuv.v);
diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c
index 83d217085ad0..899120cd07ac 100644
--- a/drivers/gpu/drm/vkms/vkms_composer.c
+++ b/drivers/gpu/drm/vkms/vkms_composer.c
@@ -187,6 +187,12 @@ static void apply_colorop(struct pixel_argb_s32 *pixel, struct drm_colorop *colo
if (colorop_state->data)
apply_3x4_matrix(pixel,
(struct drm_color_ctm_3x4 *)colorop_state->data->data);
+ } else if (colorop->type == DRM_COLOROP_FIXED_MATRIX) {
+ /*
+ * Fixed Matrix is a no-op here. YUV-to-RGB conversion
+ * is handled at format read time via the
+ * conversion_matrix set in vkms_plane_atomic_update().
+ */
}
}
diff --git a/drivers/gpu/drm/vkms/vkms_formats.c b/drivers/gpu/drm/vkms/vkms_formats.c
index 4d5fcaeb82c5..964b574d9ed7 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.c
+++ b/drivers/gpu/drm/vkms/vkms_formats.c
@@ -895,16 +895,25 @@ static void swap_uv_columns(struct conversion_matrix *matrix)
* @format: DRM_FORMAT_* value for which to obtain a conversion function (see [drm_fourcc.h])
* @encoding: DRM_COLOR_* value for which to obtain a conversion matrix
* @range: DRM_COLOR_*_RANGE value for which to obtain a conversion matrix
+ * @bypass: If true, return an identity (no-op) matrix that passes the samples
+ * through unchanged, ignoring @encoding and @range. Used when a fixed
+ * matrix colorop is present but bypassed.
* @matrix: Pointer to store the value into
*/
void get_conversion_matrix_to_argb_u16(u32 format,
enum drm_color_encoding encoding,
enum drm_color_range range,
+ bool bypass,
struct conversion_matrix *matrix)
{
const struct conversion_matrix *matrix_to_copy;
bool limited_range;
+ if (bypass) {
+ memcpy(matrix, &no_operation, sizeof(no_operation));
+ return;
+ }
+
switch (range) {
case DRM_COLOR_YCBCR_LIMITED_RANGE:
limited_range = true;
diff --git a/drivers/gpu/drm/vkms/vkms_formats.h b/drivers/gpu/drm/vkms/vkms_formats.h
index eeb208cdd6b1..d969cc669a5e 100644
--- a/drivers/gpu/drm/vkms/vkms_formats.h
+++ b/drivers/gpu/drm/vkms/vkms_formats.h
@@ -10,7 +10,7 @@ pixel_read_line_t get_pixel_read_line_function(u32 format);
pixel_write_t get_pixel_write_function(u32 format);
void get_conversion_matrix_to_argb_u16(u32 format, enum drm_color_encoding encoding,
- enum drm_color_range range,
+ enum drm_color_range range, bool bypass,
struct conversion_matrix *matrix);
#if IS_ENABLED(CONFIG_KUNIT)
diff --git a/drivers/gpu/drm/vkms/vkms_plane.c b/drivers/gpu/drm/vkms/vkms_plane.c
index 68cb2a3335e6..6ee5c3f3207c 100644
--- a/drivers/gpu/drm/vkms/vkms_plane.c
+++ b/drivers/gpu/drm/vkms/vkms_plane.c
@@ -6,6 +6,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
+#include <drm/drm_colorop.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
@@ -131,6 +132,9 @@ static void vkms_plane_atomic_update(struct drm_plane *plane,
struct drm_framebuffer *fb = new_state->fb;
struct vkms_frame_info *frame_info;
u32 fmt;
+ enum drm_color_encoding encoding = new_state->color_encoding;
+ enum drm_color_range range = new_state->color_range;
+ bool bypass = false;
if (!new_state->crtc || !fb)
return;
@@ -148,7 +152,49 @@ static void vkms_plane_atomic_update(struct drm_plane *plane,
frame_info->rotation = new_state->rotation;
vkms_plane_state->pixel_read_line = get_pixel_read_line_function(fmt);
- get_conversion_matrix_to_argb_u16(fmt, new_state->color_encoding, new_state->color_range,
+
+ if (new_state->color_pipeline) {
+ struct drm_colorop *colorop = new_state->color_pipeline;
+ struct drm_colorop_state *colorop_state;
+
+ colorop_state = drm_atomic_get_new_colorop_state(state, colorop);
+ bypass = !colorop_state || colorop_state->bypass;
+
+ if (!bypass) {
+ switch (colorop_state->fixed_matrix_type) {
+ case DRM_COLOROP_FM_YCBCR601_FULL_RGB:
+ encoding = DRM_COLOR_YCBCR_BT601;
+ range = DRM_COLOR_YCBCR_FULL_RANGE;
+ break;
+ case DRM_COLOROP_FM_YCBCR601_LIMITED_RGB:
+ encoding = DRM_COLOR_YCBCR_BT601;
+ range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+ break;
+ case DRM_COLOROP_FM_YCBCR709_FULL_RGB:
+ encoding = DRM_COLOR_YCBCR_BT709;
+ range = DRM_COLOR_YCBCR_FULL_RANGE;
+ break;
+ case DRM_COLOROP_FM_YCBCR709_LIMITED_RGB:
+ encoding = DRM_COLOR_YCBCR_BT709;
+ range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+ break;
+ case DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB:
+ encoding = DRM_COLOR_YCBCR_BT2020;
+ range = DRM_COLOR_YCBCR_FULL_RANGE;
+ break;
+ case DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB:
+ encoding = DRM_COLOR_YCBCR_BT2020;
+ range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+ break;
+ default:
+ encoding = DRM_COLOR_YCBCR_BT709;
+ range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+ break;
+ }
+ }
+ }
+
+ get_conversion_matrix_to_argb_u16(fmt, encoding, range, bypass,
&vkms_plane_state->conversion_matrix);
}
@@ -175,6 +221,13 @@ static int vkms_plane_atomic_check(struct drm_plane *plane,
if (ret != 0)
return ret;
+ if (new_plane_state->color_pipeline && new_plane_state->fb->format->is_yuv) {
+ struct drm_colorop *colorop = new_plane_state->color_pipeline;
+
+ if (colorop->type != DRM_COLOROP_FIXED_MATRIX)
+ return -EINVAL;
+ }
+
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 05/10] drm/amd/display: Add fixed matrix colorop to color pipeline
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (3 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 04/10] drm/vkms: Add atomic check and matrix handling for fixed matrix colorop Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 06/10] drm/amd/display: Implement fixed matrix colorop color space mapping Harry Wentland
` (5 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
Insert a fixed matrix colorop as the first operation in the
amdgpu color pipeline, before the existing DEGAM 1D curve. This
allows userspace to select YUV-to-RGB conversion via the color
pipeline for YCbCr framebuffers.
The fixed matrix colorop advertises support for all six YUV-to-RGB
conversion presets:
- YUV601/709/2020 full-range to RGB
- YUV601/709/2020 limited-range to RGB
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 27 ++++++++++++++++++-
.../amd/display/amdgpu_dm/amdgpu_dm_colorop.h | 1 +
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c
index 056a76b88f43..1ed12cdce317 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c
@@ -55,6 +55,14 @@ const u64 amdgpu_dm_supported_blnd_tfs =
BIT(DRM_COLOROP_1D_CURVE_GAMMA22);
EXPORT_IF_KUNIT(amdgpu_dm_supported_blnd_tfs);
+const u64 amdgpu_dm_supported_fm =
+ BIT(DRM_COLOROP_FM_YCBCR601_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR601_LIMITED_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR709_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR709_LIMITED_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB) |
+ BIT(DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB);
+
#define MAX_COLOR_PIPELINE_OPS 10
#define LUT3D_SIZE 17
@@ -73,6 +81,23 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
memset(ops, 0, sizeof(ops));
+ /* Fixed Matrix (YUV to RGB) */
+ ops[i] = kzalloc_obj(*ops[0]);
+ if (!ops[i]) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ ret = drm_plane_colorop_fixed_matrix_init(dev, ops[i], plane, &dm_colorop_funcs,
+ amdgpu_dm_supported_fm,
+ DRM_COLOROP_FLAG_ALLOW_BYPASS);
+ if (ret)
+ goto cleanup;
+
+ list->type = ops[i]->base.id;
+
+ i++;
+
/* 1D curve - DEGAM TF */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
@@ -86,7 +111,7 @@ amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane
if (ret)
goto cleanup;
- list->type = ops[i]->base.id;
+ drm_colorop_set_next_property(ops[i - 1], ops[i]);
i++;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h
index 77364d954d3b..7802efa5312d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.h
@@ -30,6 +30,7 @@
extern const u64 amdgpu_dm_supported_degam_tfs;
extern const u64 amdgpu_dm_supported_shaper_tfs;
extern const u64 amdgpu_dm_supported_blnd_tfs;
+extern const u64 amdgpu_dm_supported_fm;
int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 06/10] drm/amd/display: Implement fixed matrix colorop color space mapping
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (4 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 05/10] drm/amd/display: Add fixed matrix colorop to color pipeline Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 07/10] drm/amd/display: Use GAMCOR for first TF if YUV conversion is needed Harry Wentland
` (4 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
Add __set_dm_plane_colorop_fixed_matrix() which maps the fixed matrix
type enum to the appropriate dc_color_space for DC programming:
YCBCR601_FULL_RGB -> COLOR_SPACE_YCBCR601
YCBCR601_LIMITED_RGB -> COLOR_SPACE_YCBCR601_LIMITED
YCBCR709_FULL_RGB -> COLOR_SPACE_YCBCR709
YCBCR709_LIMITED_RGB -> COLOR_SPACE_YCBCR709_LIMITED
YCBCR2020_FULL_RGB_NC -> COLOR_SPACE_2020_YCBCR_FULL
YCBCR2020_LIMITED_RGB_NC -> COLOR_SPACE_2020_YCBCR_LIMITED
When the fixed matrix is bypassed, color_space is set to
COLOR_SPACE_UNKNOWN.
Update amdgpu_dm_plane_set_colorop_properties() to process the
fixed matrix colorop first (before DEGAM), matching the new pipeline
order.
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
.../amd/display/amdgpu_dm/amdgpu_dm_color.c | 68 ++++++++++++++++++-
1 file changed, 67 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
index 357c7c5c85cf..62791077ceef 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
@@ -1545,6 +1545,61 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state,
}
EXPORT_IF_KUNIT(__set_colorop_in_tf_1d_curve);
+static int
+__set_dm_plane_colorop_fixed_matrix(struct drm_plane_state *plane_state,
+ struct dc_plane_state *dc_plane_state,
+ struct drm_colorop *colorop)
+{
+ struct drm_colorop *old_colorop;
+ struct drm_colorop_state *colorop_state = NULL, *new_colorop_state;
+ struct drm_atomic_commit *state = plane_state->state;
+ int i = 0;
+
+ old_colorop = colorop;
+
+ for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) {
+ if (new_colorop_state->colorop == old_colorop) {
+ colorop_state = new_colorop_state;
+ break;
+ }
+ }
+
+ if (!colorop_state)
+ return -EINVAL;
+
+ if (colorop_state->bypass) {
+ dc_plane_state->color_space = COLOR_SPACE_SRGB;
+ return 0;
+ }
+
+ switch (colorop_state->fixed_matrix_type) {
+ case DRM_COLOROP_FM_YCBCR601_FULL_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_YCBCR601;
+ break;
+ case DRM_COLOROP_FM_YCBCR601_LIMITED_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_YCBCR601_LIMITED;
+ break;
+ case DRM_COLOROP_FM_YCBCR709_FULL_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_YCBCR709;
+ break;
+ case DRM_COLOROP_FM_YCBCR709_LIMITED_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_YCBCR709_LIMITED;
+ break;
+ case DRM_COLOROP_FM_YCBCR2020_NC_FULL_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_2020_YCBCR_FULL;
+ break;
+ case DRM_COLOROP_FM_YCBCR2020_NC_LIMITED_RGB:
+ dc_plane_state->color_space = COLOR_SPACE_2020_YCBCR_LIMITED;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dc_plane_state->update_bits.full_update = 1;
+
+ return 0;
+}
+
static int
__set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state,
struct dc_plane_state *dc_plane_state,
@@ -1946,10 +2001,21 @@ amdgpu_dm_plane_set_colorop_properties(struct drm_plane_state *plane_state,
bool has_3dlut = adev->dm.dc->caps.color.dpp.hw_3d_lut || adev->dm.dc->caps.color.mpc.preblend;
int ret;
- /* 1D Curve - DEGAM TF */
+ /* Fixed Matrix (YUV to RGB) */
if (!colorop)
return -EINVAL;
+ ret = __set_dm_plane_colorop_fixed_matrix(plane_state, dc_plane_state, colorop);
+ if (ret)
+ return ret;
+
+ /* 1D Curve - DEGAM TF */
+ colorop = colorop->next;
+ if (!colorop) {
+ drm_dbg(dev, "no degamma colorop found\n");
+ return -EINVAL;
+ }
+
ret = __set_dm_plane_colorop_degamma(plane_state, dc_plane_state, colorop);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 07/10] drm/amd/display: Use GAMCOR for first TF if YUV conversion is needed
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (5 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 06/10] drm/amd/display: Implement fixed matrix colorop color space mapping Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 08/10] drm/amd/display: Check actual state during commit_tail Harry Wentland
` (3 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
For subsampled formats we need to use GAMCOR instead of
the DEGAM block. The color module can create a LUT for
that if we set map_user_ramp to true. So do that when
we have subsampled formats.
Assisted-by: Claude:claude-sonnet-4.5
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
.../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
index 62791077ceef..8913bc9f0f06 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
@@ -1524,6 +1524,8 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state,
struct dc_transfer_func *tf = &dc_plane_state->in_transfer_func;
struct drm_colorop *colorop = colorop_state->colorop;
struct drm_device *drm = colorop->dev;
+ struct dc_color_caps *color_caps = NULL;
+ bool is_subsampled_format;
if (colorop->type != DRM_COLOROP_1D_CURVE)
return -EINVAL;
@@ -1537,10 +1539,23 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state,
}
drm_dbg(drm, "Degamma colorop with ID: %d\n", colorop->base.id);
-
tf->type = TF_TYPE_PREDEFINED;
+
+ /* Check if format requires post-scale color processing (subsampled formats) */
+ is_subsampled_format = (dc_plane_state->format >= SURFACE_PIXEL_FORMAT_VIDEO_BEGIN &&
+ dc_plane_state->format < SURFACE_PIXEL_FORMAT_SUBSAMPLE_END);
+
tf->tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type);
+ if (dc_plane_state->ctx && dc_plane_state->ctx->dc)
+ color_caps = &dc_plane_state->ctx->dc->caps.color;
+
+ if (!mod_color_calculate_degamma_params(color_caps, tf, NULL,
+ is_subsampled_format)) {
+ drm_err(drm, "Failed to calculate degamma params\n");
+ return -EINVAL;
+ }
+
return 0;
}
EXPORT_IF_KUNIT(__set_colorop_in_tf_1d_curve);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 08/10] drm/amd/display: Check actual state during commit_tail
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (6 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 07/10] drm/amd/display: Use GAMCOR for first TF if YUV conversion is needed Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 09/10] drm/amd/display: Set color_space to plane_infos Harry Wentland
` (2 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
Previously we checked the plane_state->state's plane_color_pipeline
client config when determining whether fill_plane_color_attributes
should run, i.e., whether we had a plane color pipeline and should
use that code for handling fixed matrix properties, or whether we
should look at legacy plane encoding and range.
The problem is that we also call this during commit_tail, during
which plane_state doesn't have the state backpointer. This meant
that during commit_tail we'd look at the legacy plane COLOR_RANGE
and COLOR_ENCODING values and overwrite what we pulled from the
fixed matrix colorop in atomic_check previously.
Instead pass the drm_atomic_state in explicitly and check that.
Assisted-by:Claude:claude-sonnet-4.5
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 12 +++++++-----
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 7 ++++---
.../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 16 ++++++++--------
3 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 38cffe127989..48d2d5dc9eb5 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -2983,7 +2983,8 @@ STATIC_IFN_KUNIT bool modereset_required(struct drm_crtc_state *crtc_state)
EXPORT_IF_KUNIT(modereset_required);
STATIC_IFN_KUNIT int
-fill_plane_color_attributes(const struct drm_plane_state *plane_state,
+fill_plane_color_attributes(struct drm_atomic_commit *state,
+ const struct drm_plane_state *plane_state,
const enum surface_pixel_format format,
enum dc_color_space *color_space)
{
@@ -2992,7 +2993,7 @@ fill_plane_color_attributes(const struct drm_plane_state *plane_state,
*color_space = COLOR_SPACE_SRGB;
/* Ignore properties when DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE is set */
- if (plane_state->state && plane_state->state->plane_color_pipeline)
+ if (state && state->plane_color_pipeline)
return 0;
/* DRM color properties only affect non-RGB formats. */
@@ -3033,6 +3034,7 @@ EXPORT_IF_KUNIT(fill_plane_color_attributes);
static int
fill_dc_plane_info_and_addr(struct amdgpu_device *adev,
+ struct drm_atomic_commit *state,
const struct drm_plane_state *plane_state,
const u64 tiling_flags,
struct dc_plane_info *plane_info,
@@ -3126,7 +3128,7 @@ fill_dc_plane_info_and_addr(struct amdgpu_device *adev,
plane_info->layer_index = plane_state->normalized_zpos;
- ret = fill_plane_color_attributes(plane_state, plane_info->format,
+ ret = fill_plane_color_attributes(state, plane_state, plane_info->format,
&plane_info->color_space);
if (ret)
return ret;
@@ -3167,7 +3169,7 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev,
dc_plane_state->clip_rect = scaling_info.clip_rect;
dc_plane_state->scaling_quality = scaling_info.scaling_quality;
- ret = fill_dc_plane_info_and_addr(adev, plane_state,
+ ret = fill_dc_plane_info_and_addr(adev, plane_state->state, plane_state,
afb->tiling_flags,
&plane_info,
&dc_plane_state->address,
@@ -4225,7 +4227,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
}
fill_dc_plane_info_and_addr(
- dm->adev, new_plane_state,
+ dm->adev, state, new_plane_state,
afb->tiling_flags,
&bundle->plane_infos[planes_count],
&bundle->flip_addrs[planes_count].address,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index 91affbdb2d6c..67c5b4bdf715 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -1130,9 +1130,10 @@ void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev,
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
int dm_plane_layer_index_cmp(const void *a, const void *b);
-int fill_plane_color_attributes(const struct drm_plane_state *plane_state,
- const enum surface_pixel_format format,
- enum dc_color_space *color_space);
+int fill_plane_color_attributes(struct drm_atomic_commit *state,
+ const struct drm_plane_state *plane_state,
+ const enum surface_pixel_format format,
+ enum dc_color_space *color_space);
bool modereset_required(struct drm_crtc_state *crtc_state);
void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
int *src_w, int *src_h);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
index 0b29bf0a7d04..670a88da4424 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c
@@ -100,7 +100,7 @@ static void dm_test_fill_color_attr_rgb_format(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_GRPH_ARGB8888,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -120,7 +120,7 @@ static void dm_test_fill_color_attr_bt601_full(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT601;
plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -140,7 +140,7 @@ static void dm_test_fill_color_attr_bt601_limited(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT601;
plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -161,7 +161,7 @@ static void dm_test_fill_color_attr_bt709_full(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -181,7 +181,7 @@ static void dm_test_fill_color_attr_bt709_limited(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT709;
plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -202,7 +202,7 @@ static void dm_test_fill_color_attr_bt2020_full(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020;
plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -223,7 +223,7 @@ static void dm_test_fill_color_attr_bt2020_limited(struct kunit *test)
plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020;
plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, 0);
@@ -244,7 +244,7 @@ static void dm_test_fill_color_attr_invalid_encoding(struct kunit *test)
plane_state.color_encoding = 99;
plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE;
- ret = fill_plane_color_attributes(&plane_state,
+ ret = fill_plane_color_attributes(NULL, &plane_state,
SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr,
&color_space);
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 09/10] drm/amd/display: Set color_space to plane_infos
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (7 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 08/10] drm/amd/display: Check actual state during commit_tail Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-14 21:18 ` [PATCH v6 10/10] drm/amd/display: Force GAMCOR for subsampled surfaces with PQ/Gamma22/HLG Harry Wentland
2026-08-17 7:51 ` [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Robert Mader
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, Alex Hung, Robert Mader
DC looks at plane_info structs.
For legacy drm_plane COLOR_ENCODING and COLOR_RANGE this was
done in fill_dc_plane_info_and_addr's call to
fill_plane_color_attributes.
Assisted-by: Claude:claude-sonnet-4.5
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 48d2d5dc9eb5..49f1a22f2888 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -4233,6 +4233,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
&bundle->flip_addrs[planes_count].address,
afb->tmz_surface);
+ bundle->plane_infos[planes_count].color_space = dc_plane->color_space;
+
drm_dbg_state(state->dev, "plane: id=%d dcc_en=%d\n",
new_plane_state->plane->index,
bundle->plane_infos[planes_count].dcc.enable);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 10/10] drm/amd/display: Force GAMCOR for subsampled surfaces with PQ/Gamma22/HLG
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (8 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 09/10] drm/amd/display: Set color_space to plane_infos Harry Wentland
@ 2026-08-14 21:18 ` Harry Wentland
2026-08-17 7:51 ` [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Robert Mader
10 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-14 21:18 UTC (permalink / raw)
To: dri-devel, amd-gfx; +Cc: Harry Wentland, stable, Alex Hung, Robert Mader
The ROM early-return checks for PQ, Gamma 2.2, and HLG in
mod_color_calculate_degamma_params() do not consider the map_user_ramp
parameter. When map_user_ramp is true (indicating a subsampled surface
that requires post-scaler degamma via GAMCOR), the function still takes
the ROM path, programming PRE_DEGAM instead.
For subsampled formats the chroma channels must be upsampled by the
scaler before degamma is applied. PRE_DEGAM sits before the scaler in
the pipeline (CNVC domain), while GAMCOR sits after it (CM domain).
Using the ROM for subsampled surfaces means degamma is applied to
subsampled chroma before upsampling, producing incorrect results.
Fix by gating the dcn_arch ROM capability checks on !map_user_ramp,
forcing PQ/Gamma22/HLG through the GAMCOR LUT path when the surface
is subsampled.
Fixes: a8bf71649088 ("drm/amd/display: Internal refactoring to abstract color caps")
Cc: stable@vger.kernel.org
Assisted-by: Copilot:claude-opus-4.6
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
---
drivers/gpu/drm/amd/display/modules/color/color_gamma.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/modules/color/color_gamma.c b/drivers/gpu/drm/amd/display/modules/color/color_gamma.c
index 03d88e78165d..5786ef5787ac 100644
--- a/drivers/gpu/drm/amd/display/modules/color/color_gamma.c
+++ b/drivers/gpu/drm/amd/display/modules/color/color_gamma.c
@@ -1696,7 +1696,8 @@ bool mod_color_calculate_degamma_params(struct dc_color_caps *dc_caps,
return true;
if (dc_caps != NULL &&
- dc_caps->dpp.dcn_arch == 1) {
+ dc_caps->dpp.dcn_arch == 1 &&
+ !map_user_ramp) {
if (input_tf->tf == TRANSFER_FUNCTION_PQ &&
dc_caps->dpp.dgam_rom_caps.pq == 1)
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS
2026-08-14 21:18 [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Harry Wentland
` (9 preceding siblings ...)
2026-08-14 21:18 ` [PATCH v6 10/10] drm/amd/display: Force GAMCOR for subsampled surfaces with PQ/Gamma22/HLG Harry Wentland
@ 2026-08-17 7:51 ` Robert Mader
2026-08-17 15:36 ` Harry Wentland
10 siblings, 1 reply; 14+ messages in thread
From: Robert Mader @ 2026-08-17 7:51 UTC (permalink / raw)
To: Harry Wentland, dri-devel, amd-gfx
Cc: Alex Hung, Daniel Stone, Chaitanya Kumar Borah, Uma Shankar,
Louis Chauvet, Melissa Wen, Simon Ser
Hi Harry, thanks!
I retested the series with the Weston branch[1], both on an AMD device
as well as the CI tests [2] with VKMS, and it looks all good to me.
Naveen Kumar also confirmed that the corresponding Intel series works
with the same Weston branch [3].
So I think we're all set, assuming we defer the discussed legacy
properties helper[4] to after the uapi changes and first implementations
have landed.
Who's reviews/acks do we still need to get the series landed? I suppose
one from Louis for the VKMS changes - and one for the uapi changes from
a maintainer?
Best regards,
Robert
1: https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/2133
2: this also needs
https://lore.kernel.org/dri-devel/20260709115855.53725-1-robert.mader@collabora.com/,
but that's unrelated to this series
3:
https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/2133#note_3615800
4:
https://lore.kernel.org/dri-devel/d42d5750-f3c5-4e2b-baa3-514b87e59e86@linux.intel.com/
On 14.08.26 23:18, Harry Wentland wrote:
> When we merged the drm_plane color pipeline API the major gap
> that existed was the lack of a YUV to RGB conversion colorop.
> We deprecated any legacy drm_plane color properties, which
> means that the COLOR_RANGE and COLOR_ENCODING properties can't
> be used with the COLOR_PIPELINE property on a drm_plane. In
> practice this means that we can't use a COLOR_PIPELINE on
> YCbCr encoded framebuffers.
>
> This patchset expands on the Fixed Matrix colorop proposed by Chaitanya
> and adds limited range variants of the YCbCr to RGB conversions.
>
> His full patchset can be found at
> https://patchwork.freedesktop.org/patch/709860
>
> This code has been tested with IGT and an experimental KWin branch.
>
> All patches are now reviewed and tested. We have a Weston and
> KWin implementation. IGT patches are missing one review. I
> deem these patches ready to merge once the last IGT patch review
> comes in.
>
> IGT branch:
> https://gitlab.freedesktop.org/hwentland/igt-gpu-tools/-/tree/yuv-fm-colorop
>
> KWin branch used for testing:
> https://invent.kde.org/hwentlan/kwin/-/tree/yuv-fm-colorop
>
> The kernel branch containing these changes, based on drm-misc-next
> can be found at:
> https://gitlab.freedesktop.org/hwentland/linux/-/tree/yuv-fm-colorop
>
> Further background on this work can be found at:
> https://hwentland.github.io/2026/03/10/plane-color-pipeline-csc-3d-lut-kwin.html
>
> v6:
> - Fix fill_plane_color_attributes build failure in Patch 8 when
> building with KUNIT (Robert)
>
> v5:
> - Drop new VKMS kunit tests for conversion matrices
> - Added script to show how VKMS kunit test values are computed (Pekka)
> - Removed fixed-matrix enums for "YCbCr limtied to full" and
> "RGB709 to RGB2020" as they're currently unused by userspace (Robert)
>
> v4:
> - Specify matrix entries in docs (Pekka)
> - Squash limited-range enums into "Add FM" patch (Robert)
> - Don't reject RGB planes with fixed matrix in VKMS as
> we don't want or need to make a colorop dependent on
> the framebuffer's pixel format. (Robert)
> - Fix conversion matrices in VKMS and implement kunit
> tests (discovered while documenting the matrices)
>
> v3:
> - base on Chaitanya's updated patch and rename code accordingly
> to Fixed_Matrix instead of CSC Fixed-Function
>
> v2:
> - use Chaitanya's CSC_FF block for named matrices
>
> Cc: Alex Hung <alex.hung@amd.com>
> Cc: Daniel Stone <daniels@collabora.com>
> Cc: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
> Cc: Uma Shankar <uma.shankar@intel.com>
> Cc: Louis Chauvet <louis.chauvet@bootlin.com>
> Cc: Melissa Wen <mwen@igalia.com>
> Cc: Simon Ser <contact@emersion.fr>
> Cc: Robert Mader <robert.mader@collabora.com>
>
> Chaitanya Kumar Borah (1):
> drm/colorop: Add DRM_COLOROP_FIXED_MATRIX
>
> Harry Wentland (9):
> drm/vkms: Fix limited-range YCbCr to RGB conversion scaling
> drm/vkms: Add fixed matrix colorop to color pipeline
> drm/vkms: Add atomic check and matrix handling for fixed matrix
> colorop
> drm/amd/display: Add fixed matrix colorop to color pipeline
> drm/amd/display: Implement fixed matrix colorop color space mapping
> drm/amd/display: Use GAMCOR for first TF if YUV conversion is needed
> drm/amd/display: Check actual state during commit_tail
> drm/amd/display: Set color_space to plane_infos
> drm/amd/display: Force GAMCOR for subsampled surfaces with
> PQ/Gamma22/HLG
>
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 +-
> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 7 +-
> .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 85 +++++++++++-
> .../amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 27 +++-
> .../amd/display/amdgpu_dm/amdgpu_dm_colorop.h | 1 +
> .../display/amdgpu_dm/tests/amdgpu_dm_test.c | 16 +--
> .../amd/display/modules/color/color_gamma.c | 3 +-
> drivers/gpu/drm/drm_atomic.c | 4 +
> drivers/gpu/drm/drm_atomic_uapi.c | 7 +
> drivers/gpu/drm/drm_colorop.c | 107 +++++++++++++++
> .../gpu/drm/vkms/tests/gen_yuv_conversion.py | 87 ++++++++++++
> drivers/gpu/drm/vkms/tests/vkms_format_test.c | 40 +++---
> drivers/gpu/drm/vkms/vkms_colorop.c | 66 ++++++---
> drivers/gpu/drm/vkms/vkms_composer.c | 6 +
> drivers/gpu/drm/vkms/vkms_formats.c | 64 ++++++---
> drivers/gpu/drm/vkms/vkms_formats.h | 2 +-
> drivers/gpu/drm/vkms/vkms_plane.c | 55 +++++++-
> include/drm/drm_colorop.h | 127 ++++++++++++++++++
> include/uapi/drm/drm_mode.h | 12 ++
> 19 files changed, 651 insertions(+), 79 deletions(-)
> create mode 100755 drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
>
> --
> 2.55.0
>
--
Robert Mader
Consultant Software Developer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS
2026-08-17 7:51 ` [PATCH v6 00/10] YUV conversion colorop with amdgpu and VKMS Robert Mader
@ 2026-08-17 15:36 ` Harry Wentland
0 siblings, 0 replies; 14+ messages in thread
From: Harry Wentland @ 2026-08-17 15:36 UTC (permalink / raw)
To: Robert Mader, dri-devel, amd-gfx, Louis Chauvet,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Dave Airlie,
Simona Vetter
Cc: Alex Hung, Daniel Stone, Chaitanya Kumar Borah, Uma Shankar,
Melissa Wen, Simon Ser
On 2026-08-17 03:51, Robert Mader wrote:
> Hi Harry, thanks!
>
> I retested the series with the Weston branch[1], both on an AMD device as well as the CI tests [2] with VKMS, and it looks all good to me. Naveen Kumar also confirmed that the corresponding Intel series works with the same Weston branch [3].
>
> So I think we're all set, assuming we defer the discussed legacy properties helper[4] to after the uapi changes and first implementations have landed.
>
Agree.
I'm still a bit on the fence about the value of the legacy properties helpers but might take a look again if I can find time. Happy to review patches from anyone else interested in it.
> Who's reviews/acks do we still need to get the series landed? I suppose one from Louis for the VKMS changes - and one for the uapi changes from a maintainer?
>
Louis, want to review the VKMS patches here or are you happy with the existing reviews?
Dave, Simona, Maarten, Maxime, Thomas, would you like to review the uapi changes or are you happy with the existing reviews?
If there are no objections I think these are ready to merge this week or next.
Harry
> Best regards,
>
> Robert
>
>
> 1: https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/2133
>
> 2: this also needs https://lore.kernel.org/dri-devel/20260709115855.53725-1-robert.mader@collabora.com/, but that's unrelated to this series
>
> 3: https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/2133#note_3615800
>
> 4: https://lore.kernel.org/dri-devel/d42d5750-f3c5-4e2b-baa3-514b87e59e86@linux.intel.com/
>
> On 14.08.26 23:18, Harry Wentland wrote:
>> When we merged the drm_plane color pipeline API the major gap
>> that existed was the lack of a YUV to RGB conversion colorop.
>> We deprecated any legacy drm_plane color properties, which
>> means that the COLOR_RANGE and COLOR_ENCODING properties can't
>> be used with the COLOR_PIPELINE property on a drm_plane. In
>> practice this means that we can't use a COLOR_PIPELINE on
>> YCbCr encoded framebuffers.
>>
>> This patchset expands on the Fixed Matrix colorop proposed by Chaitanya
>> and adds limited range variants of the YCbCr to RGB conversions.
>>
>> His full patchset can be found at
>> https://patchwork.freedesktop.org/patch/709860
>>
>> This code has been tested with IGT and an experimental KWin branch.
>>
>> All patches are now reviewed and tested. We have a Weston and
>> KWin implementation. IGT patches are missing one review. I
>> deem these patches ready to merge once the last IGT patch review
>> comes in.
>>
>> IGT branch:
>> https://gitlab.freedesktop.org/hwentland/igt-gpu-tools/-/tree/yuv-fm-colorop
>>
>> KWin branch used for testing:
>> https://invent.kde.org/hwentlan/kwin/-/tree/yuv-fm-colorop
>>
>> The kernel branch containing these changes, based on drm-misc-next
>> can be found at:
>> https://gitlab.freedesktop.org/hwentland/linux/-/tree/yuv-fm-colorop
>>
>> Further background on this work can be found at:
>> https://hwentland.github.io/2026/03/10/plane-color-pipeline-csc-3d-lut-kwin.html
>>
>> v6:
>> - Fix fill_plane_color_attributes build failure in Patch 8 when
>> building with KUNIT (Robert)
>>
>> v5:
>> - Drop new VKMS kunit tests for conversion matrices
>> - Added script to show how VKMS kunit test values are computed (Pekka)
>> - Removed fixed-matrix enums for "YCbCr limtied to full" and
>> "RGB709 to RGB2020" as they're currently unused by userspace (Robert)
>>
>> v4:
>> - Specify matrix entries in docs (Pekka)
>> - Squash limited-range enums into "Add FM" patch (Robert)
>> - Don't reject RGB planes with fixed matrix in VKMS as
>> we don't want or need to make a colorop dependent on
>> the framebuffer's pixel format. (Robert)
>> - Fix conversion matrices in VKMS and implement kunit
>> tests (discovered while documenting the matrices)
>>
>> v3:
>> - base on Chaitanya's updated patch and rename code accordingly
>> to Fixed_Matrix instead of CSC Fixed-Function
>>
>> v2:
>> - use Chaitanya's CSC_FF block for named matrices
>>
>> Cc: Alex Hung <alex.hung@amd.com>
>> Cc: Daniel Stone <daniels@collabora.com>
>> Cc: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
>> Cc: Uma Shankar <uma.shankar@intel.com>
>> Cc: Louis Chauvet <louis.chauvet@bootlin.com>
>> Cc: Melissa Wen <mwen@igalia.com>
>> Cc: Simon Ser <contact@emersion.fr>
>> Cc: Robert Mader <robert.mader@collabora.com>
>>
>> Chaitanya Kumar Borah (1):
>> drm/colorop: Add DRM_COLOROP_FIXED_MATRIX
>>
>> Harry Wentland (9):
>> drm/vkms: Fix limited-range YCbCr to RGB conversion scaling
>> drm/vkms: Add fixed matrix colorop to color pipeline
>> drm/vkms: Add atomic check and matrix handling for fixed matrix
>> colorop
>> drm/amd/display: Add fixed matrix colorop to color pipeline
>> drm/amd/display: Implement fixed matrix colorop color space mapping
>> drm/amd/display: Use GAMCOR for first TF if YUV conversion is needed
>> drm/amd/display: Check actual state during commit_tail
>> drm/amd/display: Set color_space to plane_infos
>> drm/amd/display: Force GAMCOR for subsampled surfaces with
>> PQ/Gamma22/HLG
>>
>> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 +-
>> .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 7 +-
>> .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 85 +++++++++++-
>> .../amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 27 +++-
>> .../amd/display/amdgpu_dm/amdgpu_dm_colorop.h | 1 +
>> .../display/amdgpu_dm/tests/amdgpu_dm_test.c | 16 +--
>> .../amd/display/modules/color/color_gamma.c | 3 +-
>> drivers/gpu/drm/drm_atomic.c | 4 +
>> drivers/gpu/drm/drm_atomic_uapi.c | 7 +
>> drivers/gpu/drm/drm_colorop.c | 107 +++++++++++++++
>> .../gpu/drm/vkms/tests/gen_yuv_conversion.py | 87 ++++++++++++
>> drivers/gpu/drm/vkms/tests/vkms_format_test.c | 40 +++---
>> drivers/gpu/drm/vkms/vkms_colorop.c | 66 ++++++---
>> drivers/gpu/drm/vkms/vkms_composer.c | 6 +
>> drivers/gpu/drm/vkms/vkms_formats.c | 64 ++++++---
>> drivers/gpu/drm/vkms/vkms_formats.h | 2 +-
>> drivers/gpu/drm/vkms/vkms_plane.c | 55 +++++++-
>> include/drm/drm_colorop.h | 127 ++++++++++++++++++
>> include/uapi/drm/drm_mode.h | 12 ++
>> 19 files changed, 651 insertions(+), 79 deletions(-)
>> create mode 100755 drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
>>
>> --
>> 2.55.0
>>
^ permalink raw reply [flat|nested] 14+ messages in thread