Re: [PATCH v3 08/12] lib/igt_color: Refactor transform_pixels for input/output FBs and CSC

Alex Hung <[email protected]> Thu, 30 Jul 2026 11:09:56 -0600
Newsgroups org.freedesktop.lists.igt-dev
Message-ID <[email protected]>

On 6/23/26 11:57, Harry Wentland wrote:
> Refactor igt_color_transform_pixels() to accept separate input and
> output framebuffers, plus optional YUV encoding/range parameters for
> CSC conversion. This enables:
> - Reading from YUV input (NV12/P010) and writing to RGB output
> - Inline YUV-to-RGB CSC using the igt_color_encoding matrix library
> - Skipping the first transform when CSC is applied (since it's done
>    inline via the matrix)
> 
> Update apply_transforms() in kms_colorop.c to extract encoding/range
> from the first colorop when it's a FIXED_MATRIX type, and pass both input
> and output FBs to the refactored function.
> 
> Also update colorop_plane_test() to create sw_transform_fb with the
> output format rather than copying the input FB.
> 
> Assisted-by: Claude:claude-sonnet-4-5
> Signed-off-by: Harry Wentland <[email protected]>
> ---
>   lib/igt_color.c     | 170 +++++++++++++++++++++++++++++++++++---------
>   lib/igt_color.h     |   6 +-
>   tests/kms_colorop.c |  38 ++++++----
>   3 files changed, 166 insertions(+), 48 deletions(-)
> 
> diff --git a/lib/igt_color.c b/lib/igt_color.c
> index a451f5642bac..cc425cfe5ac9 100644
> --- a/lib/igt_color.c
> +++ b/lib/igt_color.c
> @@ -13,6 +13,7 @@
>   
>   #include "drmtest.h"
>   #include "igt_color.h"
> +#include "igt_color_encoding.h"
>   #include "igt_core.h"
>   #include "igt_x86.h"
>   
> @@ -578,58 +579,159 @@ igt_color_pixel_to_fourcc(uint32_t drm_format, igt_pixel_t *pixel)
>   	return raw_pixel;
>   }
>   
> -int igt_color_transform_pixels(igt_fb_t *fb, igt_pixel_transform transforms[], int num_transforms)
> +int igt_color_transform_pixels(igt_fb_t *input_fb, igt_fb_t *output_fb,
> +				  igt_pixel_transform transforms[],
> +				  int num_transforms,
> +				  int yuv_encoding,
> +				  enum igt_color_range yuv_range)
>   {
> -	uint32_t *line = NULL;
> -	void *map;
> -	char *ptr;
> -	int x, y, cpp = igt_drm_format_to_bpp(fb->drm_format) / 8;
> -	uint32_t stride = igt_fb_calc_plane_stride(fb, 0);
> +	uint32_t *input_line = NULL;
> +	uint32_t *output_line = NULL;
> +	void *input_map, *output_map;
> +	char *input_ptr, *output_ptr;
> +	int x, y;
> +	int input_cpp = 0;
> +	int output_cpp = igt_drm_format_to_bpp(output_fb->drm_format) / 8;
> +	uint32_t input_stride = 0, output_stride = igt_fb_calc_plane_stride(output_fb, 0);
> +	uint8_t *y_plane = NULL, *uv_plane = NULL;
> +	int y_stride = 0, uv_stride = 0;
> +	bool input_is_yuv = (input_fb->drm_format == DRM_FORMAT_NV12 ||
> +			     input_fb->drm_format == DRM_FORMAT_P010);
> +	bool output_is_yuv = (output_fb->drm_format == DRM_FORMAT_NV12 ||
> +			      output_fb->drm_format == DRM_FORMAT_P010);
> +	struct igt_mat4 csc_matrix;
> +	bool apply_csc = false;
>   
> -	if (fb->num_planes != 1)
> +	/* Validate framebuffer dimensions match */
> +	igt_assert(input_fb->width == output_fb->width);
> +	igt_assert(input_fb->height == output_fb->height);
> +
> +	if (input_is_yuv && yuv_encoding >= 0) {
> +		apply_csc = true;
> +
> +		csc_matrix = igt_ycbcr_to_rgb_matrix(input_fb->drm_format,
> +						     output_fb->drm_format,
> +						     yuv_encoding, yuv_range);
> +	}
> +
> +	/* Validate plane counts */
> +	if (!input_is_yuv && input_fb->num_planes != 1)
>   		return -EINVAL;
>   
> -	ptr = igt_fb_map_buffer(fb->fd, fb);
> -	igt_assert(ptr);
> -	map = ptr;
> +	if (input_is_yuv && input_fb->num_planes != 2)
> +		return -EINVAL;
>   
> -	/*
> -	 * Framebuffers are often uncached, which can make byte-wise accesses
> -	 * very slow. We copy each line of the FB into a local buffer to speed
> -	 * up the hashing.
> -	 */
> -	line = malloc(stride);
> -	if (!line) {
> -		munmap(map, fb->size);
> +	if (!output_is_yuv && output_fb->num_planes != 1)
> +		return -EINVAL;
> +
> +	if (output_is_yuv && output_fb->num_planes != 2)
> +		return -EINVAL;
> +
> +	/* Map input buffer */
> +	input_ptr = igt_fb_map_buffer(input_fb->fd, input_fb);
> +	igt_assert(input_ptr);
> +	input_map = input_ptr;
> +
> +	/* For YUV input, set up plane pointers */
> +	if (input_is_yuv) {
> +		y_plane = input_map;
> +		y_stride = input_fb->strides[0];
> +		uv_plane = y_plane + input_fb->offsets[1];
> +		uv_stride = input_fb->strides[1];
> +	} else {
> +		input_cpp = igt_drm_format_to_bpp(input_fb->drm_format) / 8;
> +		input_stride = igt_fb_calc_plane_stride(input_fb, 0);
> +	}
> +
> +	/* Map output buffer */
> +	output_ptr = igt_fb_map_buffer(output_fb->fd, output_fb);
> +	igt_assert(output_ptr);
> +	output_map = output_ptr;
> +
> +	/* Allocate line buffers for speed */
> +	if (!input_is_yuv) {
> +		input_line = malloc(input_stride);
> +		if (!input_line) {
> +			igt_fb_unmap_buffer(output_fb, output_map);
> +			igt_fb_unmap_buffer(input_fb, input_map);
> +			return -ENOMEM;
> +		}
> +	}
> +
> +	output_line = malloc(output_stride);
> +	if (!output_line) {
> +		free(input_line);
> +		igt_fb_unmap_buffer(output_fb, output_map);
> +		igt_fb_unmap_buffer(input_fb, input_map);
>   		return -ENOMEM;
>   	}
>   
> -	for (y = 0; y < fb->height; y++, ptr += stride) {
> +	for (y = 0; y < input_fb->height; y++) {
> +		/* For RGB input, read line from input buffer */
> +		if (!input_is_yuv)
> +			igt_memcpy_from_wc(input_line, input_ptr + y * input_stride,
> +					   input_fb->width * input_cpp);
>   
> -		/* get line from buffer */
> -		igt_memcpy_from_wc(line, ptr, fb->width * cpp);
> -
> -		for (x = 0; x < fb->width; x++) {
> -			uint32_t raw_pixel = le32_to_cpu(line[x]);
> +		for (x = 0; x < input_fb->width; x++) {
>   			igt_pixel_t pixel;
>   			int i;
> +			int start_transform;
>   
> -			igt_color_fourcc_to_pixel(raw_pixel, fb->drm_format, &pixel);
> +			/* READ from input buffer */
> +			if (input_is_yuv) {
> +				uint32_t raw_y, raw_u, raw_v;
> +				struct igt_vec4 yuv, rgb;
>   
> -			/* run transform on pixel */
> -			for (i = 0; i < num_transforms; i++)
> -				transforms[i](&pixel);
> +				/* Extract raw Y, U, V from YUV input buffer */
> +				igt_color_extract_yuv_pixel(input_fb, x, y, y_plane, uv_plane,
> +							    y_stride, uv_stride,
> +							    &raw_y, &raw_u, &raw_v);
>   
> -			/* write back to line */
> -			line[x] = cpu_to_le32(igt_color_pixel_to_fourcc(fb->drm_format, &pixel));
> +				/* Convert YUV to RGB using pre-computed CSC matrix */
> +				if (apply_csc) {
> +					float rgb_max;
> +
> +					/* Matrix expects raw format values */
> +					yuv.d[0] = raw_y;
> +					yuv.d[1] = raw_u;
> +					yuv.d[2] = raw_v;
> +					yuv.d[3] = 1.0f;
> +
> +					rgb = igt_matrix_transform(&csc_matrix, &yuv);
> +
> +					rgb_max = (output_fb->drm_format == DRM_FORMAT_XRGB2101010) ? 1023.0f : 255.0f;
> +
> +					pixel.r = rgb.d[0] / rgb_max;
> +					pixel.g = rgb.d[1] / rgb_max;
> +					pixel.b = rgb.d[2] / rgb_max;
> +				} else {
> +					igt_color_yuv_to_pixel(input_fb->drm_format, raw_y, raw_u, raw_v, &pixel);
> +				}
> +			} else {
> +				uint32_t raw_pixel = le32_to_cpu(input_line[x]);
> +				igt_color_fourcc_to_pixel(raw_pixel, input_fb->drm_format, &pixel);
> +			}
> +
> +			/* Transform pixel through remaining transforms */
> +			/* Skip first transform if it was a CSC that we already applied */
> +			start_transform = apply_csc ? 1 : 0;
> +			for (i = start_transform; i < num_transforms; i++)
> +				if (transforms[i])
> +					transforms[i](&pixel);
> +
> +			/* write to output buffer */
> +			output_line[x] = cpu_to_le32(igt_color_pixel_to_fourcc(output_fb->drm_format, &pixel));
>   		}
>   
> -		/* copy line back to fb buffer */
> -		igt_memcpy_from_wc(ptr, line, fb->width * cpp);
> +		/* Copy output line to output buffer */
> +		memcpy(output_ptr + y * output_stride, output_line,
> +		       output_fb->width * output_cpp);

Can it be igt_memcpy_from_wc instead of memcpy for consistency?

>   	}
>   
> -	free(line);
> -	igt_fb_unmap_buffer(fb, map);
> +	free(output_line);
> +	free(input_line);
> +	igt_fb_unmap_buffer(output_fb, output_map);
> +	igt_fb_unmap_buffer(input_fb, input_map);
>   
>   	return 0;
>   }
> diff --git a/lib/igt_color.h b/lib/igt_color.h
> index 722446400f01..d66cfebc4191 100644
> --- a/lib/igt_color.h
> +++ b/lib/igt_color.h
> @@ -72,7 +72,11 @@ void igt_dump_fb(igt_display_t *display, igt_fb_t *fb, const char *path_name, co
>   
>   typedef void (*igt_pixel_transform)(igt_pixel_t *pixel);
>   
> -int igt_color_transform_pixels(igt_fb_t *fb, igt_pixel_transform transforms[], int num_transforms);
> +int igt_color_transform_pixels(igt_fb_t *input_fb, igt_fb_t *output_fb,
> +			       igt_pixel_transform transforms[],
> +			       int num_transforms,
> +			       int yuv_encoding,
> +			       enum igt_color_range yuv_range);
>   
>   /* colorop helpers */
>   
> diff --git a/tests/kms_colorop.c b/tests/kms_colorop.c
> index 4db83470df22..5cf1f688832f 100644
> --- a/tests/kms_colorop.c
> +++ b/tests/kms_colorop.c
> @@ -195,15 +195,30 @@ static bool compare_with_bracket(igt_fb_t *in, igt_fb_t *out)
>   
>   #define MAX_COLOROPS 5
>   
> -static void apply_transforms(kms_colorop_t *colorops[], igt_fb_t *sw_transform_fb)
> +static void apply_transforms(kms_colorop_t *colorops[], igt_fb_t *input_fb,
> +			     igt_fb_t *sw_transform_fb)
>   {
>   	int i;
> +	int yuv_encoding = -1;
> +	enum igt_color_range yuv_range = IGT_COLOR_YCBCR_LIMITED_RANGE;
>   	igt_pixel_transform transforms[MAX_COLOROPS];
>   
>   	for (i = 0; colorops[i]; i++)
>   		transforms[i] = colorops[i]->transform;
>   
> -	igt_color_transform_pixels(sw_transform_fb, transforms, i);
> +	/* If first colorop is CSC FF, extract encoding/range for sw reference */
> +	if (colorops[0] && colorops[0]->type == KMS_COLOROP_FIXED_MATRIX) {
> +		enum igt_color_encoding encoding;
> +		enum igt_color_range range;
> +
> +		fixed_matrix_type_to_encoding_range(colorops[0]->fixed_matrix_info.fixed_matrix_type_name,
> +					     &encoding, &range);
> +		yuv_encoding = encoding;
> +		yuv_range = range;
> +	}
> +
> +	igt_color_transform_pixels(input_fb, sw_transform_fb, transforms, i,
> +				   yuv_encoding, yuv_range);
>   }
>   
>   static void colorop_plane_test(igt_display_t *display,
> @@ -217,7 +232,7 @@ static void colorop_plane_test(igt_display_t *display,
>   {
>   	igt_colorop_t *color_pipeline = NULL;
>   	igt_fb_t sw_transform_fb;
> -	igt_crc_t input_crc, output_crc;
> +	igt_crc_t input_crc;
>   	int res;
>   
>   	igt_fb_get_fnv1a_crc(input_fb, &input_crc);

input_crc is not used below so it can be removed too

> @@ -235,18 +250,15 @@ static void colorop_plane_test(igt_display_t *display,
>   				NULL);
>   	igt_get_and_wait_out_fence(output);
>   
> -	/* Compare input and output buffers. They should be equal here. */
> -	igt_fb_get_fnv1a_crc(output_fb, &output_crc);
> -
> -	igt_assert_crc_equal(&input_crc, &output_crc);
> -
> -	/* create sw transformed buffer */
> -	res = igt_copy_fb(display->drm_fd, input_fb, &sw_transform_fb);
> +	/* create sw transform buffer with output format */
> +	res = igt_create_fb(display->drm_fd,
> +			    input_fb->width, input_fb->height,
> +			    output_fb->drm_format,
> +			    DRM_FORMAT_MOD_LINEAR,
> +			    &sw_transform_fb);
>   	igt_assert_lte(0, res);
>   
> -	igt_assert(igt_cmp_fb_pixels(input_fb, &sw_transform_fb, 0, 0));
> -
> -	apply_transforms(colorops, &sw_transform_fb);
> +	apply_transforms(colorops, input_fb, &sw_transform_fb);
>   
>   	if (data.dump_check)
>   		igt_dump_fb(display, &sw_transform_fb, ".", "sw_transform");