The existing yuv_u16_to_argb_u16 test performs a full RGB->YCbCr->RGB round trip with a tolerance of 0x1ff. That tolerance is required because fully saturated primaries clamp their chroma at encode time and cannot round-trip exactly, but it also makes the test unable to detect small scaling errors in the conversion matrices (such as using a 2^n instead of a 2^n - 1 full-range maximum for limited range, an error of ~129 at gray - below the tolerance).
Add a parameterised test that checks the exact S31.32 fixed-point coefficients and luma offset returned by get_conversion_matrix_to_argb_u16() for each encoding and range against reference values. This directly pins the matrices and catches sub-tolerance regressions that the round-trip test cannot. Assisted-by: Copilot:claude-opus-4.8 Signed-off-by: Harry Wentland <[email protected]> --- drivers/gpu/drm/vkms/tests/vkms_format_test.c | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/drivers/gpu/drm/vkms/tests/vkms_format_test.c b/drivers/gpu/drm/vkms/tests/vkms_format_test.c index 789c59d07ffb..e746b18bf37b 100644 --- a/drivers/gpu/drm/vkms/tests/vkms_format_test.c +++ b/drivers/gpu/drm/vkms/tests/vkms_format_test.c @@ -263,8 +263,130 @@ KUNIT_ARRAY_PARAM(yuv_u16_to_argb_u16, yuv_u16_to_argb_u16_cases, vkms_format_test_yuv_u16_to_argb_u16_case_desc ); +/* + * struct conversion_matrix_case - Reference matrix to test the YUV to RGB + * conversion matrices returned by get_conversion_matrix_to_argb_u16() + * + * @encoding: Encoding of the conversion matrix under test + * @range: Range of the conversion matrix under test + * @expected: Expected S31.32 fixed-point matrix and luma offset + * + * The limited-range coefficients use the studio-range scaling mandated by the + * DRM UAPI and IGT's igt_ycbcr_to_rgb_matrix(): the narrow range is expanded + * relative to a full-range maximum of 2^n - 1 (255 for 8-bit), i.e. luma by + * 255/(235 - 16) and chroma by 255/(240 - 128). Generating them with the + * common 2^n normalisation (as colour.matrix_YCbCr(is_legal=True) does) is off + * by a factor of 256/255. See the DRM_COLOROP_FM_YCBCR*_LIMITED_RGB + * documentation in <drm/drm_colorop.h>. + */ +struct conversion_matrix_case { + enum drm_color_encoding encoding; + enum drm_color_range range; + struct conversion_matrix expected; +}; + +static struct conversion_matrix_case conversion_matrix_cases[] = { + { + .encoding = DRM_COLOR_YCBCR_BT601, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .expected = { .matrix = { + { 4294967296, 0, 6021544149 }, + { 4294967296, -1478054095, -3067191994 }, + { 4294967296, 7610682049, 0 }, + }, .y_offset = 0 }, + }, + { + .encoding = DRM_COLOR_YCBCR_BT601, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .expected = { .matrix = { + { 5000989317, 0, 6854882848 }, + { 5000989317, -1682606224, -3491669458 }, + { 5000989317, 8663946082, 0 }, + }, .y_offset = 16 }, + }, + { + .encoding = DRM_COLOR_YCBCR_BT709, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .expected = { .matrix = { + { 4294967296, 0, 6763714498 }, + { 4294967296, -804551626, -2010578443 }, + { 4294967296, 7969741314, 0 }, + }, .y_offset = 0 }, + }, + { + .encoding = DRM_COLOR_YCBCR_BT709, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .expected = { .matrix = { + { 5000989317, 0, 7699764272 }, + { 5000989317, -915895824, -2288828138 }, + { 5000989317, 9072696586, 0 }, + }, .y_offset = 16 }, + }, + { + .encoding = DRM_COLOR_YCBCR_BT2020, + .range = DRM_COLOR_YCBCR_FULL_RANGE, + .expected = { .matrix = { + { 4294967296, 0, 6333358775 }, + { 4294967296, -706750298, -2453942994 }, + { 4294967296, 8080551471, 0 }, + }, .y_offset = 0 }, + }, + { + .encoding = DRM_COLOR_YCBCR_BT2020, + .range = DRM_COLOR_YCBCR_LIMITED_RANGE, + .expected = { .matrix = { + { 5000989317, 0, 7209850391 }, + { 5000989317, -804559491, -2793551177 }, + { 5000989317, 9198842076, 0 }, + }, .y_offset = 16 }, + }, +}; + +/* + * vkms_format_test_conversion_matrix - Verify the YUV to RGB conversion matrices + * + * This test checks that get_conversion_matrix_to_argb_u16() returns the exact + * fixed-point coefficients expected for each encoding and range. Unlike the + * round-trip test above, it is sensitive to small (sub-tolerance) scaling + * errors such as using a 2^n instead of a 2^n - 1 full-range maximum for the + * limited-range matrices. + */ +static void vkms_format_test_conversion_matrix(struct kunit *test) +{ + const struct conversion_matrix_case *param = test->param_value; + struct conversion_matrix matrix; + + get_conversion_matrix_to_argb_u16(DRM_FORMAT_NV12, param->encoding, + param->range, false, &matrix); + + for (size_t i = 0; i < 3; i++) { + for (size_t j = 0; j < 3; j++) { + KUNIT_EXPECT_EQ_MSG(test, matrix.matrix[i][j], + param->expected.matrix[i][j], + "matrix[%zu][%zu] mismatch for %s - %s", + i, j, + drm_get_color_encoding_name(param->encoding), + drm_get_color_range_name(param->range)); + } + } + + KUNIT_EXPECT_EQ(test, matrix.y_offset, param->expected.y_offset); +} + +static void vkms_format_test_conversion_matrix_case_desc(struct conversion_matrix_case *t, + char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s - %s", + drm_get_color_encoding_name(t->encoding), drm_get_color_range_name(t->range)); +} + +KUNIT_ARRAY_PARAM(conversion_matrix, conversion_matrix_cases, + vkms_format_test_conversion_matrix_case_desc +); + static struct kunit_case vkms_format_test_cases[] = { KUNIT_CASE_PARAM(vkms_format_test_yuv_u16_to_argb_u16, yuv_u16_to_argb_u16_gen_params), + KUNIT_CASE_PARAM(vkms_format_test_conversion_matrix, conversion_matrix_gen_params), {} }; -- 2.55.0
