From: Mohammed Bilal <mohammed.bilal@intel.com>
To: igt-dev@lists.freedesktop.org
Cc: kunal1.joshi@intel.com, Mohammed Bilal <mohammed.bilal@intel.com>
Subject: [PATCH i-g-t v1 24/25] tests/chamelium/v3: Add color verification tests for Chamelium v3
Date: Tue, 28 Apr 2026 10:16:33 +0530 [thread overview]
Message-ID: <20260428044644.257001-25-mohammed.bilal@intel.com> (raw)
In-Reply-To: <20260428044644.257001-1-mohammed.bilal@intel.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=yes, Size: 30484 bytes --]
This commit adds color testing capabilities for Chamelium v3 including
color depth verification, gamma testing, and color space validation.
Signed-off-by: Mohammed Bilal <mohammed.bilal@intel.com>
---
tests/chamelium/v3/kms_chamelium_v3_color.c | 1058 +++++++++++++++++++
tests/meson.build | 1 +
2 files changed, 1059 insertions(+)
create mode 100644 tests/chamelium/v3/kms_chamelium_v3_color.c
diff --git a/tests/chamelium/v3/kms_chamelium_v3_color.c b/tests/chamelium/v3/kms_chamelium_v3_color.c
new file mode 100644
index 000000000..1a41a0c10
--- /dev/null
+++ b/tests/chamelium/v3/kms_chamelium_v3_color.c
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ *
+ * Chamelium V3 Color / Color Management Tests
+ *
+ * Tests degamma, gamma, CTM (Color Transformation Matrix),
+ * and broadcast RGB range using Chamelium frame capture.
+ *
+ * Connector selection priority:
+ * 1. Prefer HDMI if connected (to avoid DP FSM issues)
+ * 2. Fall back to DP if no HDMI is available
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "igt.h"
+#include "igt_kms.h"
+#include "chamelium/v3/igt_chamelium.h"
+
+/**
+ * TEST: kms chamelium v3 color
+ * Category: Display
+ * Description: Color management tests for Chamelium V3
+ * Driver requirement: i915, xe
+ * Mega feature: General Display Features
+ */
+
+IGT_TEST_DESCRIPTION("Test Color Features at Pipe level using Chamelium V3 to verify");
+
+#define HPD_WAIT_TIME_MS 3000
+
+/* Chamelium V3 port IDs */
+#define FPGA_DP1_PORT 4
+#define FPGA_DP2_PORT 5
+#define ITE_HDMI1_PORT 2
+#define ITE_HDMI2_PORT 3
+
+/* Color type for painting */
+typedef struct {
+ double r, g, b;
+} color_t;
+
+/* Gamma LUT type */
+typedef struct {
+ int size;
+ color_t coeffs[];
+} gamma_lut_t;
+
+struct color_test_data {
+ /* Chamelium V3 */
+ struct igt_chamelium_v3 *chamelium;
+ chamelium_v3_port_id *ports;
+ int port_count;
+ chamelium_v3_port_id selected_port;
+ bool hdmi_selected;
+
+ /* DRM display */
+ int drm_fd;
+ uint32_t devid;
+ igt_display_t display;
+ igt_output_t *output;
+ igt_plane_t *primary;
+ drmModeModeInfo *mode;
+
+ /* Color properties */
+ uint32_t drm_format;
+ uint32_t color_depth;
+ uint64_t degamma_lut_size;
+ uint64_t gamma_lut_size;
+};
+
+/*
+ * Tolerance for analog frame comparison (analog tolerance).
+ * Analog signals have some noise, so we allow small differences.
+ */
+#define FRAME_TOLERANCE 10
+
+
+static gamma_lut_t *alloc_lut(int lut_size)
+{
+ gamma_lut_t *gamma;
+
+ igt_assert_lt(0, lut_size);
+ gamma = malloc(sizeof(*gamma) + lut_size * sizeof(gamma->coeffs[0]));
+ igt_assert(gamma);
+ gamma->size = lut_size;
+
+ return gamma;
+}
+
+static void free_lut(gamma_lut_t *gamma)
+{
+ free(gamma);
+}
+
+static void set_rgb(color_t *coeff, double value)
+{
+ coeff->r = coeff->g = coeff->b = value;
+}
+
+static gamma_lut_t *generate_table(int lut_size, double exp_val)
+{
+ gamma_lut_t *gamma = alloc_lut(lut_size);
+ int i;
+
+ set_rgb(&gamma->coeffs[0], 0.0);
+ for (i = 1; i < lut_size; i++)
+ set_rgb(&gamma->coeffs[i], pow(i * 1.0 / (lut_size - 1), exp_val));
+
+ return gamma;
+}
+
+static gamma_lut_t *generate_table_max(int lut_size)
+{
+ gamma_lut_t *gamma = alloc_lut(lut_size);
+ int i;
+
+ set_rgb(&gamma->coeffs[0], 0.0);
+ for (i = 1; i < lut_size; i++)
+ set_rgb(&gamma->coeffs[i], 1.0);
+
+ return gamma;
+}
+
+static struct drm_color_lut *coeffs_to_lut(struct color_test_data *data,
+ const gamma_lut_t *gamma,
+ uint32_t color_depth)
+{
+ struct drm_color_lut *lut;
+ int i, lut_size = gamma->size;
+ uint32_t max_value = (1 << 16) - 1;
+ uint32_t mask;
+
+ if (is_intel_device(data->drm_fd))
+ mask = ((1 << color_depth) - 1) << (16 - color_depth);
+ else
+ mask = max_value;
+
+ lut = malloc(sizeof(struct drm_color_lut) * lut_size);
+
+ for (i = 0; i < lut_size; i++) {
+ uint32_t r = gamma->coeffs[i].r * max_value;
+ uint32_t g = gamma->coeffs[i].g * max_value;
+ uint32_t b = gamma->coeffs[i].b * max_value;
+
+ r &= mask;
+ g &= mask;
+ b &= mask;
+
+ lut[i].red = r;
+ lut[i].green = g;
+ lut[i].blue = b;
+ }
+
+ return lut;
+}
+
+static void set_degamma(struct color_test_data *data, igt_crtc_t *pipe,
+ const gamma_lut_t *gamma)
+{
+ size_t size = sizeof(struct drm_color_lut) * gamma->size;
+ struct drm_color_lut *lut = coeffs_to_lut(data, gamma, data->color_depth);
+
+ igt_crtc_replace_prop_blob(pipe, IGT_CRTC_DEGAMMA_LUT, lut, size);
+ free(lut);
+}
+
+static void set_gamma(struct color_test_data *data, igt_crtc_t *pipe,
+ const gamma_lut_t *gamma)
+{
+ size_t size = sizeof(struct drm_color_lut) * gamma->size;
+ struct drm_color_lut *lut = coeffs_to_lut(data, gamma, data->color_depth);
+
+ igt_crtc_replace_prop_blob(pipe, IGT_CRTC_GAMMA_LUT, lut, size);
+ free(lut);
+}
+
+static void set_ctm(igt_crtc_t *pipe, const double *coefficients)
+{
+ struct drm_color_ctm ctm;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctm.matrix); i++) {
+ if (coefficients[i] < 0) {
+ ctm.matrix[i] = (int64_t)(-coefficients[i] * ((int64_t)1L << 32));
+ ctm.matrix[i] |= 1ULL << 63;
+ } else {
+ ctm.matrix[i] = (int64_t)(coefficients[i] * ((int64_t)1L << 32));
+ }
+ }
+
+ igt_crtc_replace_prop_blob(pipe, IGT_CRTC_CTM, &ctm, sizeof(ctm));
+}
+
+static void disable_prop(igt_crtc_t *pipe, enum igt_atomic_crtc_properties prop)
+{
+ if (igt_crtc_has_prop(pipe, prop))
+ igt_crtc_replace_prop_blob(pipe, prop, NULL, 0);
+}
+
+#define disable_degamma(pipe) disable_prop(pipe, IGT_CRTC_DEGAMMA_LUT)
+#define disable_gamma(pipe) disable_prop(pipe, IGT_CRTC_GAMMA_LUT)
+#define disable_ctm(pipe) disable_prop(pipe, IGT_CRTC_CTM)
+
+
+static void paint_gradient_rectangles(struct color_test_data *data,
+ drmModeModeInfo *mode,
+ const color_t *colors,
+ struct igt_fb *fb)
+{
+ cairo_t *cr = igt_get_cairo_ctx(data->drm_fd, fb);
+ int i, l = mode->hdisplay / 3;
+ int rows_remaining = mode->hdisplay % 3;
+
+ for (i = 0; i < 3; i++) {
+ igt_paint_color_gradient_range(cr, i * l, 0, l, mode->vdisplay,
+ colors[i].r != 0 ? 0.2 : 0,
+ colors[i].g != 0 ? 0.2 : 0,
+ colors[i].b != 0 ? 0.2 : 0,
+ colors[i].r, colors[i].g, colors[i].b);
+ }
+
+ if (rows_remaining > 0)
+ igt_paint_color_gradient_range(cr, i * l, 0, rows_remaining,
+ mode->vdisplay,
+ colors[i-1].r != 0 ? 0.2 : 0,
+ colors[i-1].g != 0 ? 0.2 : 0,
+ colors[i-1].b != 0 ? 0.2 : 0,
+ colors[i-1].r, colors[i-1].g,
+ colors[i-1].b);
+
+ igt_put_cairo_ctx(cr);
+}
+
+static void paint_rectangles(struct color_test_data *data,
+ drmModeModeInfo *mode,
+ const color_t *colors,
+ struct igt_fb *fb)
+{
+ cairo_t *cr = igt_get_cairo_ctx(data->drm_fd, fb);
+ int i, l = mode->hdisplay / 3;
+ int rows_remaining = mode->hdisplay % 3;
+
+ for (i = 0; i < 3; i++) {
+ igt_paint_color(cr, i * l, 0, l, mode->vdisplay,
+ colors[i].r, colors[i].g, colors[i].b);
+ }
+
+ if (rows_remaining > 0)
+ igt_paint_color(cr, i * l, 0, rows_remaining, mode->vdisplay,
+ colors[i-1].r, colors[i-1].g, colors[i-1].b);
+
+ igt_put_cairo_ctx(cr);
+}
+
+
+/*
+ * Compare captured RGB frame against expected framebuffer.
+ * Returns true if frames match within tolerance.
+ */
+static bool compare_frame_to_fb(struct color_test_data *data,
+ struct chamelium_v3_frame *frame,
+ struct igt_fb *fb,
+ int tolerance)
+{
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ unsigned char *fb_data;
+ int stride;
+ int x, y;
+ int mismatches = 0;
+ int total_pixels = frame->width * frame->height;
+ int max_mismatches = total_pixels / 10; /* Allow 10% mismatch for analog/edge effects */
+ int mid_x, mid_y, frame_idx, fb_idx;
+ uint8_t cap_r, cap_g, cap_b, fb_b, fb_g, fb_r;
+ int diff_r, diff_g, diff_b;
+
+ /* Get framebuffer data */
+ cr = igt_get_cairo_ctx(data->drm_fd, fb);
+ surface = cairo_get_target(cr);
+ cairo_surface_flush(surface);
+ fb_data = cairo_image_surface_get_data(surface);
+ stride = cairo_image_surface_get_stride(surface);
+
+ igt_info("Comparing frame %dx%d against framebuffer (stride=%d)...\n",
+ frame->width, frame->height, stride);
+
+ /* Sample some middle pixels for debug */
+ mid_x = frame->width / 2;
+ mid_y = frame->height / 2;
+ frame_idx = (mid_y * frame->width + mid_x) * 3;
+ fb_idx = mid_y * stride + mid_x * 4;
+ igt_info("Sample at (%d,%d): captured RGB(%d,%d,%d), expected RGB(%d,%d,%d)\n",
+ mid_x, mid_y,
+ frame->data[frame_idx + 0], frame->data[frame_idx + 1], frame->data[frame_idx + 2],
+ fb_data[fb_idx + 2], fb_data[fb_idx + 1], fb_data[fb_idx + 0]);
+
+ /* Compare pixel by pixel */
+ for (y = 0; y < frame->height && mismatches <= max_mismatches; y++) {
+ for (x = 0; x < frame->width && mismatches <= max_mismatches; x++) {
+ /* Captured frame is RGB */
+ frame_idx = (y * frame->width + x) * 3;
+ cap_r = frame->data[frame_idx + 0];
+ cap_g = frame->data[frame_idx + 1];
+ cap_b = frame->data[frame_idx + 2];
+
+ /* Framebuffer is XRGB8888 (BGRA in memory on little-endian) */
+ fb_idx = y * stride + x * 4;
+ fb_b = fb_data[fb_idx + 0];
+ fb_g = fb_data[fb_idx + 1];
+ fb_r = fb_data[fb_idx + 2];
+
+ diff_r = abs((int)cap_r - (int)fb_r);
+ diff_g = abs((int)cap_g - (int)fb_g);
+ diff_b = abs((int)cap_b - (int)fb_b);
+
+ if (diff_r > tolerance || diff_g > tolerance || diff_b > tolerance) {
+ mismatches++;
+ if (mismatches <= 10) {
+ igt_debug("Mismatch at (%d,%d): captured RGB(%d,%d,%d) vs expected RGB(%d,%d,%d)\n",
+ x, y, cap_r, cap_g, cap_b, fb_r, fb_g, fb_b);
+ }
+ }
+ }
+ }
+
+ igt_put_cairo_ctx(cr);
+
+ igt_info("Frame comparison: %d mismatches out of %d pixels (%.2f%%)\n",
+ mismatches, total_pixels, 100.0 * mismatches / total_pixels);
+
+ return mismatches <= max_mismatches;
+}
+
+
+/*
+ * Find the DRM output that corresponds to the Chamelium port.
+ * For HDMI port 2, look for HDMI-A-1 connector.
+ * For DP port 4, look for DP-1 connector.
+ */
+static bool find_output_for_port(struct color_test_data *data)
+{
+ igt_output_t *output;
+ const char *expected_type;
+ igt_crtc_t *crtc;
+
+ if (data->hdmi_selected)
+ expected_type = "HDMI";
+ else
+ expected_type = "DP";
+
+ igt_info("Looking for %s connector...\n", expected_type);
+
+ for_each_crtc(&data->display, crtc) {
+ for_each_valid_output_on_crtc(&data->display, crtc, output) {
+ uint32_t conn_type = output->config.connector->connector_type;
+ bool is_match = false;
+
+ if (data->hdmi_selected) {
+ is_match = (conn_type == DRM_MODE_CONNECTOR_HDMIA ||
+ conn_type == DRM_MODE_CONNECTOR_HDMIB);
+ } else {
+ is_match = (conn_type == DRM_MODE_CONNECTOR_DisplayPort);
+ }
+
+ if (is_match) {
+ data->output = output;
+ igt_info("Found matching output: %s on pipe %s\n",
+ output->name, kmstest_pipe_name(crtc->pipe));
+ return true;
+ }
+ }
+ }
+
+ igt_warn("No matching output found for %s port\n", expected_type);
+ return false;
+}
+
+static bool setup_display(struct color_test_data *data)
+{
+ igt_crtc_t *pipe_obj;
+ igt_crtc_t *crtc;
+
+ if (!find_output_for_port(data))
+ return false;
+
+ /* Find a valid pipe for this output and get primary plane */
+ for_each_crtc(&data->display, crtc) {
+ if (igt_crtc_connector_valid(crtc, data->output)) {
+ pipe_obj = crtc;
+ data->primary = igt_crtc_get_plane_type(pipe_obj, DRM_PLANE_TYPE_PRIMARY);
+ if (data->primary) {
+ igt_output_set_crtc(data->output, pipe_obj);
+ break;
+ }
+ }
+ }
+
+ if (!data->primary) {
+ igt_warn("Could not get primary plane for output\n");
+ return false;
+ }
+
+ pipe_obj = data->primary->crtc;
+ data->mode = igt_output_get_mode(data->output);
+
+ /* Get LUT sizes */
+ if (igt_crtc_has_prop(pipe_obj, IGT_CRTC_DEGAMMA_LUT_SIZE)) {
+ data->degamma_lut_size = igt_crtc_get_prop(pipe_obj, IGT_CRTC_DEGAMMA_LUT_SIZE);
+ } else {
+ data->degamma_lut_size = 256; /* Default */
+ }
+
+ if (igt_crtc_has_prop(pipe_obj, IGT_CRTC_GAMMA_LUT_SIZE)) {
+ data->gamma_lut_size = igt_crtc_get_prop(pipe_obj, IGT_CRTC_GAMMA_LUT_SIZE);
+ } else {
+ data->gamma_lut_size = 256; /* Default */
+ }
+
+ data->color_depth = 8;
+ data->drm_format = DRM_FORMAT_XRGB8888;
+
+ igt_info("Display setup: %s @ %dx%d, degamma_lut=%lu, gamma_lut=%lu\n",
+ data->output->name, data->mode->hdisplay, data->mode->vdisplay,
+ data->degamma_lut_size, data->gamma_lut_size);
+
+ return true;
+}
+
+/*
+ * Select the best port for color testing.
+ * Priority: HDMI > DP
+ */
+static int select_color_test_port(struct color_test_data *data)
+{
+ int i;
+ int dp_port_idx = -1;
+ int hdmi_port_idx = -1;
+
+ igt_info("Selecting port for color tests (prefer HDMI over DP)...\n");
+
+ for (i = 0; i < data->port_count; i++) {
+ chamelium_v3_port_id port_id = data->ports[i];
+ char *port_name = chamelium_v3_get_port_name(data->chamelium, port_id);
+ bool is_plugged = chamelium_v3_is_plugged(data->chamelium, port_id);
+
+ igt_info(" Port %d (%s): %s\n", port_id, port_name,
+ is_plugged ? "plugged" : "unplugged");
+
+ if (!is_plugged) {
+ free(port_name);
+ continue;
+ }
+
+ if (chamelium_v3_port_is_hdmi(data->chamelium, port_id)) {
+ if (hdmi_port_idx < 0) {
+ hdmi_port_idx = i;
+ igt_info(" -> Found HDMI port (preferred)\n");
+ }
+ } else if (chamelium_v3_port_is_dp(data->chamelium, port_id)) {
+ if (dp_port_idx < 0) {
+ dp_port_idx = i;
+ igt_info(" -> Found DP port (fallback)\n");
+ }
+ }
+
+ free(port_name);
+ }
+
+ if (hdmi_port_idx >= 0) {
+ data->selected_port = data->ports[hdmi_port_idx];
+ data->hdmi_selected = true;
+ igt_info("Selected HDMI port %d for color tests\n", data->selected_port);
+ return hdmi_port_idx;
+ }
+
+ if (dp_port_idx >= 0) {
+ data->selected_port = data->ports[dp_port_idx];
+ data->hdmi_selected = false;
+ igt_info("Selected DP port %d for color tests\n", data->selected_port);
+ return dp_port_idx;
+ }
+
+ igt_info("No suitable port found for color tests\n");
+ return -1;
+}
+
+
+/**
+ * SUBTEST: degamma
+ * Description: Verify degamma LUT with max transform using Chamelium frame capture.
+ */
+static void test_degamma(struct color_test_data *data)
+{
+ gamma_lut_t *degamma_full;
+ igt_crtc_t *pipe_obj;
+ struct igt_fb fb_modeset, fb, fbref;
+ struct chamelium_v3_frame *frame;
+ color_t red_green_blue[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ bool result;
+
+ igt_require(setup_display(data));
+ pipe_obj = data->primary->crtc;
+ igt_require(igt_crtc_has_prop(pipe_obj, IGT_CRTC_DEGAMMA_LUT));
+
+ degamma_full = generate_table_max(data->degamma_lut_size);
+
+ /* Create framebuffers */
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb_modeset);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fbref);
+
+ /* Initial modeset with solid color for video signal stabilization */
+ {
+ cairo_t *cr_init = igt_get_cairo_ctx(data->drm_fd, &fb_modeset);
+ igt_paint_color(cr_init, 0, 0, data->mode->hdisplay, data->mode->vdisplay,
+ 0.5, 0.5, 0.5);
+ igt_put_cairo_ctx(cr_init);
+ }
+ igt_plane_set_fb(data->primary, &fb_modeset);
+ disable_ctm(pipe_obj);
+ disable_gamma(pipe_obj);
+ disable_degamma(pipe_obj);
+ igt_display_commit(&data->display);
+
+ /* Wait for video signal to stabilize */
+ sleep(2);
+
+ /* Draw reference: solid colors (what gradient + max degamma should produce) */
+ paint_rectangles(data, data->mode, red_green_blue, &fbref);
+
+ /* Draw test: gradient with max degamma LUT */
+ paint_gradient_rectangles(data, data->mode, red_green_blue, &fb);
+ igt_plane_set_fb(data->primary, &fb);
+ set_degamma(data, pipe_obj, degamma_full);
+ igt_display_commit(&data->display);
+
+ /* Wait for display to stabilize after degamma change */
+ sleep(1);
+
+ /* Capture frame from Chamelium */
+ frame = chamelium_v3_capture_frame(data->chamelium, data->selected_port);
+ igt_assert(frame);
+
+ /* Compare captured frame against reference */
+ result = compare_frame_to_fb(data, frame, &fbref, FRAME_TOLERANCE);
+
+ /* Cleanup */
+ chamelium_v3_free_frame(frame);
+ disable_degamma(pipe_obj);
+ igt_plane_set_fb(data->primary, NULL);
+ igt_output_set_crtc(data->output, NULL);
+ igt_display_commit(&data->display);
+ igt_remove_fb(data->drm_fd, &fb_modeset);
+ igt_remove_fb(data->drm_fd, &fb);
+ igt_remove_fb(data->drm_fd, &fbref);
+ free_lut(degamma_full);
+
+ igt_assert(result);
+}
+
+/**
+ * SUBTEST: gamma
+ * Description: Verify gamma LUT with max transform using Chamelium frame capture.
+ */
+static void test_gamma(struct color_test_data *data)
+{
+ gamma_lut_t *gamma_full;
+ igt_crtc_t *pipe_obj;
+ struct igt_fb fb_modeset, fb, fbref;
+ struct chamelium_v3_frame *frame;
+ color_t red_green_blue[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ bool result;
+
+ igt_require(setup_display(data));
+ pipe_obj = data->primary->crtc;
+ igt_require(igt_crtc_has_prop(pipe_obj, IGT_CRTC_GAMMA_LUT));
+
+ gamma_full = generate_table_max(data->gamma_lut_size);
+
+ /* Create framebuffers */
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb_modeset);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fbref);
+
+ /* Initial modeset with solid color for video signal stabilization */
+ {
+ cairo_t *cr_init = igt_get_cairo_ctx(data->drm_fd, &fb_modeset);
+ igt_paint_color(cr_init, 0, 0, data->mode->hdisplay, data->mode->vdisplay,
+ 0.5, 0.5, 0.5);
+ igt_put_cairo_ctx(cr_init);
+ }
+ igt_plane_set_fb(data->primary, &fb_modeset);
+ disable_ctm(pipe_obj);
+ disable_degamma(pipe_obj);
+ igt_display_commit(&data->display);
+
+ /* Wait for video signal to stabilize */
+ sleep(2);
+
+ /* Draw reference: solid colors */
+ paint_rectangles(data, data->mode, red_green_blue, &fbref);
+
+ /* Draw test: gradient with max gamma LUT */
+ paint_gradient_rectangles(data, data->mode, red_green_blue, &fb);
+ igt_plane_set_fb(data->primary, &fb);
+ set_gamma(data, pipe_obj, gamma_full);
+ igt_display_commit(&data->display);
+
+ /* Wait for display to stabilize after gamma change */
+ sleep(1);
+
+ frame = chamelium_v3_capture_frame(data->chamelium, data->selected_port);
+ igt_assert(frame);
+
+ result = compare_frame_to_fb(data, frame, &fbref, FRAME_TOLERANCE);
+
+ chamelium_v3_free_frame(frame);
+ disable_gamma(pipe_obj);
+ igt_plane_set_fb(data->primary, NULL);
+ igt_output_set_crtc(data->output, NULL);
+ igt_display_commit(&data->display);
+ igt_remove_fb(data->drm_fd, &fb_modeset);
+ igt_remove_fb(data->drm_fd, &fb);
+ igt_remove_fb(data->drm_fd, &fbref);
+ free_lut(gamma_full);
+
+ igt_assert(result);
+}
+
+/*
+ * Generic CTM test helper
+ */
+static void test_pipe_ctm(struct color_test_data *data,
+ color_t *before, color_t *after,
+ const double *ctm_matrix)
+{
+ gamma_lut_t *degamma_linear, *gamma_linear;
+ igt_crtc_t *pipe_obj;
+ struct igt_fb fb_modeset, fb, fbref;
+ struct chamelium_v3_frame *frame;
+ bool result;
+
+ igt_require(setup_display(data));
+ pipe_obj = data->primary->crtc;
+ igt_require(igt_crtc_has_prop(pipe_obj, IGT_CRTC_CTM));
+
+ degamma_linear = generate_table(data->degamma_lut_size, 1.0);
+ gamma_linear = generate_table(data->gamma_lut_size, 1.0);
+
+ /* Create framebuffers */
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb_modeset);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fb);
+ igt_create_fb(data->drm_fd, data->mode->hdisplay, data->mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, &fbref);
+
+ /* Initial modeset with solid color for video signal stabilization */
+ {
+ cairo_t *cr_init = igt_get_cairo_ctx(data->drm_fd, &fb_modeset);
+ igt_paint_color(cr_init, 0, 0, data->mode->hdisplay, data->mode->vdisplay,
+ 0.5, 0.5, 0.5);
+ igt_put_cairo_ctx(cr_init);
+ }
+ igt_plane_set_fb(data->primary, &fb_modeset);
+
+ if (memcmp(before, after, sizeof(color_t) * 3)) {
+ set_degamma(data, pipe_obj, degamma_linear);
+ set_gamma(data, pipe_obj, gamma_linear);
+ } else {
+ disable_degamma(pipe_obj);
+ disable_gamma(pipe_obj);
+ }
+ disable_ctm(pipe_obj);
+ igt_display_commit(&data->display);
+
+ /* Wait for video signal to stabilize */
+ sleep(2);
+
+ /* Draw reference: expected colors after CTM */
+ paint_rectangles(data, data->mode, after, &fbref);
+
+ /* Draw test: before colors with CTM applied */
+ paint_rectangles(data, data->mode, before, &fb);
+ igt_plane_set_fb(data->primary, &fb);
+ set_ctm(pipe_obj, ctm_matrix);
+ igt_display_commit(&data->display);
+
+ /* Wait for display to stabilize after CTM change */
+ sleep(1);
+
+ frame = chamelium_v3_capture_frame(data->chamelium, data->selected_port);
+ igt_assert(frame);
+
+ result = compare_frame_to_fb(data, frame, &fbref, FRAME_TOLERANCE);
+
+ chamelium_v3_free_frame(frame);
+ disable_degamma(pipe_obj);
+ disable_gamma(pipe_obj);
+ disable_ctm(pipe_obj);
+ igt_plane_set_fb(data->primary, NULL);
+ igt_output_set_crtc(data->output, NULL);
+ igt_display_commit(&data->display);
+ igt_remove_fb(data->drm_fd, &fb_modeset);
+ igt_remove_fb(data->drm_fd, &fb);
+ igt_remove_fb(data->drm_fd, &fbref);
+ free_lut(degamma_linear);
+ free_lut(gamma_linear);
+
+ igt_assert(result);
+}
+
+/**
+ * SUBTEST: ctm-red-to-blue
+ * Description: Verify CTM red-to-blue channel swap.
+ */
+static void test_ctm_red_to_blue(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 0.0, 0.0, 1.0 }, /* Red -> Blue */
+ { 0.0, 1.0, 0.0 }, /* Green -> Green */
+ { 0.0, 0.0, 1.0 } /* Blue -> Blue */
+ };
+ double ctm[] = {
+ 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 1.0, 0.0, 1.0
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-green-to-red
+ * Description: Verify CTM green-to-red channel swap.
+ */
+static void test_ctm_green_to_red(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 1.0, 0.0, 0.0 }, /* Red -> Red */
+ { 1.0, 0.0, 0.0 }, /* Green -> Red */
+ { 0.0, 0.0, 1.0 } /* Blue -> Blue */
+ };
+ double ctm[] = {
+ 1.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-blue-to-red
+ * Description: Verify CTM blue-to-red channel swap.
+ */
+static void test_ctm_blue_to_red(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 1.0, 0.0, 0.0 }, /* Red -> Red */
+ { 0.0, 1.0, 0.0 }, /* Green -> Green */
+ { 1.0, 0.0, 0.0 } /* Blue -> Red */
+ };
+ double ctm[] = {
+ 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-max
+ * Description: Verify CTM with 100x scale (saturation to white).
+ */
+static void test_ctm_max(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 0.5, 0.0, 0.0 },
+ { 0.0, 0.5, 0.0 },
+ { 0.0, 0.0, 0.5 }
+ };
+ color_t after[] = {
+ { 1.0, 0.0, 0.0 }, /* Saturates to max */
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ double ctm[] = {
+ 100.0, 0.0, 0.0,
+ 0.0, 100.0, 0.0,
+ 0.0, 0.0, 100.0
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-negative
+ * Description: Verify CTM with negative scale (clamp to black).
+ */
+static void test_ctm_negative(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 0.0, 0.0, 0.0 }, /* Clamps to black */
+ { 0.0, 0.0, 0.0 },
+ { 0.0, 0.0, 0.0 }
+ };
+ double ctm[] = {
+ -1.0, 0.0, 0.0,
+ 0.0, -1.0, 0.0,
+ 0.0, 0.0, -1.0
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-0-25
+ * Description: Verify CTM with 0.25 scale factor.
+ */
+static void test_ctm_0_25(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 0.25, 0.0, 0.0 },
+ { 0.0, 0.25, 0.0 },
+ { 0.0, 0.0, 0.25 }
+ };
+ double ctm[] = {
+ 0.25, 0.0, 0.0,
+ 0.0, 0.25, 0.0,
+ 0.0, 0.0, 0.25
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-0-50
+ * Description: Verify CTM with 0.50 scale factor.
+ */
+static void test_ctm_0_50(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 0.5, 0.0, 0.0 },
+ { 0.0, 0.5, 0.0 },
+ { 0.0, 0.0, 0.5 }
+ };
+ double ctm[] = {
+ 0.5, 0.0, 0.0,
+ 0.0, 0.5, 0.0,
+ 0.0, 0.0, 0.5
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-0-75
+ * Description: Verify CTM with 0.75 scale factor.
+ */
+static void test_ctm_0_75(struct color_test_data *data)
+{
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { 0.75, 0.0, 0.0 },
+ { 0.0, 0.75, 0.0 },
+ { 0.0, 0.0, 0.75 }
+ };
+ double ctm[] = {
+ 0.75, 0.0, 0.0,
+ 0.0, 0.75, 0.0,
+ 0.0, 0.0, 0.75
+ };
+
+ test_pipe_ctm(data, before, after, ctm);
+}
+
+/**
+ * SUBTEST: ctm-limited-range
+ * Description: Verify CTM with limited-range broadcast RGB.
+ */
+static void test_ctm_limited_range(struct color_test_data *data)
+{
+ /*
+ * In limited range, the GPU maps full range [0-255] to [16-235]
+ * on the wire. The Chamelium captures raw wire values, so:
+ * FB 255 (1.0) -> wire 235
+ * FB 0 (0.0) -> wire 16
+ */
+ double limited_max = 235.0 / 255.0;
+ double limited_min = 16.0 / 255.0;
+ color_t before[] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ color_t after[] = {
+ { limited_max, limited_min, limited_min },
+ { limited_min, limited_max, limited_min },
+ { limited_min, limited_min, limited_max }
+ };
+ double ctm[] = {
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0
+ };
+
+ igt_require(setup_display(data));
+ igt_require(igt_output_has_prop(data->output, IGT_CONNECTOR_BROADCAST_RGB));
+
+ /* Set limited range */
+ igt_output_set_prop_value(data->output, IGT_CONNECTOR_BROADCAST_RGB, BROADCAST_RGB_16_235);
+
+ test_pipe_ctm(data, before, after, ctm);
+
+ /* Reset to full range */
+ igt_output_set_prop_value(data->output, IGT_CONNECTOR_BROADCAST_RGB, BROADCAST_RGB_FULL);
+}
+
+
+int igt_main()
+{
+ struct color_test_data data = { 0 };
+ int selected_port_idx;
+
+ igt_fixture() {
+ /* Setup Chamelium */
+ data.chamelium = chamelium_v3_init_from_config();
+ igt_require(data.chamelium);
+
+ data.port_count = chamelium_v3_get_supported_ports(data.chamelium, &data.ports);
+ igt_require(data.port_count > 0);
+
+ data.drm_fd = drm_open_driver_master(DRIVER_ANY);
+ igt_require(data.drm_fd >= 0);
+
+ data.devid = is_intel_device(data.drm_fd) ?
+ intel_get_drm_devid(data.drm_fd) : 0;
+
+ kmstest_set_vt_graphics_mode();
+ igt_display_require(&data.display, data.drm_fd);
+
+ igt_info("Found %d Chamelium ports\n", data.port_count);
+
+ selected_port_idx = select_color_test_port(&data);
+ igt_require_f(selected_port_idx >= 0,
+ "No connected port found for color tests\n");
+
+ igt_info("Using %s port %d for color tests\n",
+ data.hdmi_selected ? "HDMI" : "DP", data.selected_port);
+ }
+
+ igt_describe("Degamma LUT max transform verification");
+ igt_subtest("degamma")
+ test_degamma(&data);
+
+ igt_describe("Gamma LUT max transform verification");
+ igt_subtest("gamma")
+ test_gamma(&data);
+
+ igt_describe("CTM red-to-blue channel swap verification");
+ igt_subtest("ctm-red-to-blue")
+ test_ctm_red_to_blue(&data);
+
+ igt_describe("CTM green-to-red channel swap verification");
+ igt_subtest("ctm-green-to-red")
+ test_ctm_green_to_red(&data);
+
+ igt_describe("CTM blue-to-red channel swap verification");
+ igt_subtest("ctm-blue-to-red")
+ test_ctm_blue_to_red(&data);
+
+ igt_describe("CTM 100x scale factor (saturation to white)");
+ igt_subtest("ctm-max")
+ test_ctm_max(&data);
+
+ igt_describe("CTM negative scale (clamp to black)");
+ igt_subtest("ctm-negative")
+ test_ctm_negative(&data);
+
+ igt_describe("CTM 0.25 scale factor");
+ igt_subtest("ctm-0-25")
+ test_ctm_0_25(&data);
+
+ igt_describe("CTM 0.50 scale factor");
+ igt_subtest("ctm-0-50")
+ test_ctm_0_50(&data);
+
+ igt_describe("CTM 0.75 scale factor");
+ igt_subtest("ctm-0-75")
+ test_ctm_0_75(&data);
+
+ igt_describe("CTM with limited-range broadcast RGB");
+ igt_subtest("ctm-limited-range")
+ test_ctm_limited_range(&data);
+
+ igt_fixture() {
+ igt_display_fini(&data.display);
+ close(data.drm_fd);
+ free(data.ports);
+ chamelium_v3_uninit(data.chamelium);
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index c3dbff694..1c7facccd 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -377,6 +377,7 @@ chamelium_v3_progs = [
'kms_chamelium_v3_hpd',
'kms_chamelium_v3_edid',
'kms_chamelium_v3_frames',
+ 'kms_chamelium_v3_color',
]
test_deps = [ igt_deps ]
--
2.48.1
next prev parent reply other threads:[~2026-04-28 4:48 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-28 4:46 [PATCH i-g-t v1 00/25] Chamelium v3 Integration and Test Execution Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 01/25] lib/igt_kms: Add a detect timeout value Mohammed Bilal
2026-04-28 7:11 ` Jani Nikula
2026-04-28 7:16 ` Jani Nikula
2026-04-28 7:17 ` Jani Nikula
2026-04-28 4:46 ` [PATCH i-g-t v1 02/25] lib/igt_kms: Add helper to wait for a specific status on a connector Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 03/25] lib/igt_kms: Add function to list connected connectors Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 04/25] lib/igt_kms: Add helper to obtain a connector by its name or MST path Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 05/25] lib/igt_kms: Add function to get valid pipe for specific output Mohammed Bilal
2026-04-28 7:21 ` Jani Nikula
2026-04-28 4:46 ` [PATCH i-g-t v1 06/25] lib/monitor_edids: Add helper functions for using monitor_edid objects Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 07/25] lib/monitor_edids: Add helper to get an EDID by its name Mohammed Bilal
2026-04-28 7:23 ` Jani Nikula
2026-04-28 4:46 ` [PATCH i-g-t v1 08/25] lib/monitor_edids: Add helper to print all available EDID names Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 09/25] lib/monitor_edids: Fix missing names in some monitor EDID Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 10/25] lib/monitor_edids: Add new EDID for HDMI 4k Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 11/25] tests/chamelium: Extract Chamelium v2 tests into a separate directory Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 12/25] lib/chamelium/v2: Extract chamelium v2 wrapper into its own directory Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 13/25] lib/chamelium/v2: Rename chamelium to chamelium_v2 Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 14/25] lib/chamelium/v2: Rename HAVE_CHAMELIUM to HAVE_CHAMELIUM_V2 Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 15/25] lib/chamelium/v3: Introduce the foundation for the Chamelium v3 wrapper Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 16/25] lib/chamelium/v3: Introduce initialization and cleanup of Chamelium-related structures Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 17/25] lib/chamelium/v3: Add method to discover Chamelium ports Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 18/25] lib/chamelium/v3: Implement method to retrieve Chamelium port names Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 19/25] tests/chamelium/v3: Implement a basic Chamelium v3 accessibility test Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 20/25] lib/chamelium/v3: Add extended API for Chamelium v3 HPD, EDID, Frames, Color & Audio Mohammed Bilal
2026-04-29 10:44 ` Louis Chauvet
2026-04-28 4:46 ` [PATCH i-g-t v1 21/25] tests/chamelium/v3: Add HPD (Hot Plug Detect) tests for Chamelium v3 Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 22/25] tests/chamelium/v3: Add EDID " Mohammed Bilal
2026-04-28 4:46 ` [PATCH i-g-t v1 23/25] tests/chamelium/v3: Add frame capture and CRC " Mohammed Bilal
2026-04-28 4:46 ` Mohammed Bilal [this message]
2026-04-28 4:46 ` [PATCH i-g-t v1 25/25] tests/chamelium/v3: Add audio " Mohammed Bilal
2026-04-28 5:52 ` ✓ Xe.CI.BAT: success for Chamelium v3 Integration and Test Execution Patchwork
2026-04-28 6:09 ` ✗ i915.CI.BAT: failure " Patchwork
2026-04-28 12:36 ` ✗ Xe.CI.FULL: " Patchwork
2026-04-28 14:02 ` [PATCH i-g-t v1 00/25] " Louis Chauvet
2026-04-29 3:36 ` Bilal, Mohammed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260428044644.257001-25-mohammed.bilal@intel.com \
--to=mohammed.bilal@intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=kunal1.joshi@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox