From 56df938d8fccefc7510d7b3149e781571d718d5b Mon Sep 17 00:00:00 2001 From: onetr1ck <64502803+onetr1ck@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:04:43 +0200 Subject: [PATCH 1/2] evdi: harden software colour path and color_props advertisement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply only compositor GAMMA_LUT/CTM blobs (no gamma↔CTM synthesis). Which properties the CRTC advertises is selected at module load with color_props=both|gamma|ctm so Mutter can be steered like apple-drm (CTM-only) or gamma-only; reload the module to change it. Skip identity transforms, fuse diagonal CTMs into a 256-entry LUT for a cheap hot path, reuse a per-device GRABPIX scratch row, and full-dirty only when the effective apply payload changes so static Night Light updates still re-grab without spamming USB on no-op churn. Add a read-only color_status parameter for per-card apply path and scale dumps. --- module/evdi_color.c | 249 ++++++++++++++++++++++++++++++++++++------ module/evdi_color.h | 43 ++++---- module/evdi_drm_drv.c | 4 +- module/evdi_modeset.c | 7 +- module/evdi_painter.c | 14 +-- module/evdi_params.c | 23 +++- module/evdi_params.h | 15 ++- 7 files changed, 276 insertions(+), 79 deletions(-) diff --git a/module/evdi_color.c b/module/evdi_color.c index c40820a..ac4516e 100644 --- a/module/evdi_color.c +++ b/module/evdi_color.c @@ -8,21 +8,24 @@ */ #include +#include #include +#include #include +#include +#include #include #include #include #include "evdi_color.h" +#include "evdi_debug.h" +#include "evdi_drm_drv.h" +#include "evdi_params.h" + +static LIST_HEAD(evdi_color_list); +static DEFINE_MUTEX(evdi_color_list_lock); -/* - * struct drm_color_ctm stores each entry as S31.32 sign-magnitude (bit 63 - * is the sign, bits 0-62 are the unsigned value) rather than the two's - * complement drm_fixed.h (drm_fixp_t) format the drm_fixp_* helpers use. - * Some newer kernels provide drm_sm2fixp() for this, but it isn't present - * across the full 4.15+ range evdi supports, so convert it ourselves. - */ static s64 evdi_sm2fixp(u64 sm) { s64 magnitude = (s64)(sm & ~BIT_ULL(63)); @@ -30,8 +33,64 @@ static s64 evdi_sm2fixp(u64 sm) return (sm & BIT_ULL(63)) ? -magnitude : magnitude; } -static void evdi_color_update_gamma(struct evdi_color_data *data, - struct drm_crtc_state *crtc_state) +static bool evdi_gamma_is_identity(const u8 g[3][EVDI_GAMMA_LUT_SIZE]) +{ + int c, i; + + for (c = 0; c < 3; ++c) + for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) + if (g[c][i] != (u8)i) + return false; + return true; +} + +static bool evdi_ctm_is_identity(const s64 m[3][3]) +{ + int r, c; + + for (r = 0; r < 3; ++r) { + for (c = 0; c < 3; ++c) { + s64 expect = (r == c) ? (s64)DRM_FIXED_ONE : 0; + + if (m[r][c] != expect) + return false; + } + } + return true; +} + +static bool evdi_ctm_is_diagonal(const s64 m[3][3]) +{ + int r, c; + + for (r = 0; r < 3; ++r) + for (c = 0; c < 3; ++c) + if (r != c && m[r][c] != 0) + return false; + return true; +} + +/* Same diagonal matrix, table form — not a different colour model. */ +static void evdi_fuse_diagonal_ctm_to_gamma(struct evdi_color_data *data) +{ + int c, i; + + for (c = 0; c < 3; ++c) { + s64 scale = data->ctm[c][c]; + + for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) { + s64 v = drm_fixp_mul(scale, drm_int2fixp(i)); + + data->gamma[c][i] = clamp(drm_fixp2int_round(v), 0, 255); + } + } + data->has_gamma = true; + data->has_ctm = false; + data->fused_diagonal = true; +} + +static void evdi_color_load_gamma(struct evdi_color_data *data, + struct drm_crtc_state *crtc_state) { const struct drm_color_lut *lut; unsigned int lut_len; @@ -44,19 +103,11 @@ static void evdi_color_update_gamma(struct evdi_color_data *data, lut = (const struct drm_color_lut *)crtc_state->gamma_lut->data; lut_len = crtc_state->gamma_lut->length / sizeof(*lut); - if (lut_len == 0) { data->has_gamma = false; return; } - /* - * Build a direct 256-entry table indexed by raw 8-bit channel value, - * regardless of the length of the LUT userspace actually uploaded - * (expected to be EVDI_GAMMA_LUT_SIZE, since that's what we advertise - * via drm_mode_crtc_set_gamma_size(), but not all clients necessarily - * respect that hint). - */ for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) { s64 idx_fp = drm_fixp_div(drm_int2fixp(i * (lut_len - 1)), drm_int2fixp(EVDI_GAMMA_LUT_SIZE - 1)); @@ -72,16 +123,14 @@ static void evdi_color_update_gamma(struct evdi_color_data *data, s64 interp = floor_fp + drm_fixp_mul(ceil_fp - floor_fp, frac); int val16 = clamp(drm_fixp2int(interp), 0, 65535); - /* 16-bit LUT entry -> 8-bit raw pixel channel */ data->gamma[c][i] = val16 >> 8; } } - data->has_gamma = true; } -static void evdi_color_update_ctm(struct evdi_color_data *data, - struct drm_crtc_state *crtc_state) +static void evdi_color_load_ctm(struct evdi_color_data *data, + struct drm_crtc_state *crtc_state) { const struct drm_color_ctm *ctm; int r, c; @@ -99,52 +148,180 @@ static void evdi_color_update_ctm(struct evdi_color_data *data, data->has_ctm = true; } -void evdi_color_transform_init(struct evdi_color_transform *color) +static void evdi_color_finalize_locked(struct evdi_color_data *data) +{ + data->fused_diagonal = false; + + /* Apply DRM order without degamma: CTM then gamma. */ + if (data->has_ctm && evdi_ctm_is_identity(data->ctm)) + data->has_ctm = false; + else if (data->has_ctm && evdi_ctm_is_diagonal(data->ctm)) + evdi_fuse_diagonal_ctm_to_gamma(data); + + if (data->has_gamma && evdi_gamma_is_identity(data->gamma)) + data->has_gamma = false; + + data->active = data->has_gamma || data->has_ctm; +} + +void evdi_color_transform_init(struct evdi_color_transform *color, + struct drm_device *ddev) { + memset(color, 0, sizeof(*color)); mutex_init(&color->lock); - memset(&color->data, 0, sizeof(color->data)); + color->ddev = ddev; + INIT_LIST_HEAD(&color->link); + + mutex_lock(&evdi_color_list_lock); + list_add_tail(&color->link, &evdi_color_list); + mutex_unlock(&evdi_color_list_lock); +} + +void evdi_color_transform_cleanup(struct evdi_color_transform *color) +{ + mutex_lock(&evdi_color_list_lock); + list_del_init(&color->link); + mutex_unlock(&evdi_color_list_lock); + vfree(color->scratch); + color->scratch = NULL; + color->scratch_bytes = 0; + mutex_destroy(&color->lock); } bool evdi_color_transform_update(struct evdi_color_transform *color, struct drm_crtc_state *crtc_state) { + struct evdi_color_data before; + bool changed; + if (!crtc_state->color_mgmt_changed) return false; mutex_lock(&color->lock); - evdi_color_update_gamma(&color->data, crtc_state); - evdi_color_update_ctm(&color->data, crtc_state); - color->data.active = color->data.has_gamma || color->data.has_ctm; + before = color->data; + memset(&color->data, 0, sizeof(color->data)); + evdi_color_load_gamma(&color->data, crtc_state); + evdi_color_load_ctm(&color->data, crtc_state); + evdi_color_finalize_locked(&color->data); + changed = memcmp(&before, &color->data, sizeof(before)) != 0; mutex_unlock(&color->lock); - return true; + return changed; } bool evdi_color_transform_snapshot(struct evdi_color_transform *color, - struct evdi_color_data *snapshot) + struct evdi_color_data *snapshot) { mutex_lock(&color->lock); *snapshot = color->data; mutex_unlock(&color->lock); - return snapshot->active; } +void *evdi_color_get_scratch(struct evdi_color_transform *color, size_t bytes) +{ + void *p; + + if (bytes == 0) + return NULL; + + mutex_lock(&color->lock); + if (color->scratch_bytes >= bytes) { + p = color->scratch; + mutex_unlock(&color->lock); + return p; + } + vfree(color->scratch); + color->scratch = NULL; + color->scratch_bytes = 0; + mutex_unlock(&color->lock); + + p = vmalloc(bytes); + if (!p) + return NULL; + + mutex_lock(&color->lock); + if (color->scratch_bytes >= bytes) { + vfree(p); + p = color->scratch; + } else { + vfree(color->scratch); + color->scratch = p; + color->scratch_bytes = bytes; + } + mutex_unlock(&color->lock); + return p; +} + static s64 evdi_color_apply_ctm_channel(const struct evdi_color_data *snapshot, - int row, s64 r, s64 g, s64 b) + int row, s64 r, s64 g, s64 b) { return drm_fixp_mul(snapshot->ctm[row][0], r) + drm_fixp_mul(snapshot->ctm[row][1], g) + drm_fixp_mul(snapshot->ctm[row][2], b); } +int evdi_color_format_status(char *buf, size_t size) +{ + struct evdi_color_transform *color; + int n = 0; + + n += scnprintf(buf + n, size - n, + "color_props=%s (reload module to change)\n" + "# apply only what the compositor programmed; no gamma↔ctm synthesis\n" + "# path=lut|ctm|fused_ctm_lut|off drm=N is /dev/dri/cardN\n", + evdi_color_props ? evdi_color_props : "both"); + + mutex_lock(&evdi_color_list_lock); + list_for_each_entry(color, &evdi_color_list, link) { + struct evdi_color_data d; + int r_s, g_s, b_s; + int drm_idx = -1; + const char *path; + + mutex_lock(&color->lock); + d = color->data; + if (color->ddev && color->ddev->primary) + drm_idx = color->ddev->primary->index; + mutex_unlock(&color->lock); + + if (d.has_gamma) { + r_s = d.gamma[0][255]; + g_s = d.gamma[1][255]; + b_s = d.gamma[2][255]; + } else if (d.has_ctm) { + r_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[0][0], drm_int2fixp(255))), 0, 255); + g_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[1][1], drm_int2fixp(255))), 0, 255); + b_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[2][2], drm_int2fixp(255))), 0, 255); + } else { + r_s = g_s = b_s = 255; + } + + if (!d.active) + path = "off"; + else if (d.fused_diagonal) + path = "fused_ctm_lut"; + else if (d.has_ctm) + path = "ctm"; + else if (d.has_gamma) + path = "lut"; + else + path = "off"; + + n += scnprintf(buf + n, size > n ? size - n : 0, + "drm=%d path=%s active=%d has_gamma=%d has_ctm=%d scales_rgb≈%d/%d/%d\n", + drm_idx, path, d.active, d.has_gamma, d.has_ctm, + r_s, g_s, b_s); + } + mutex_unlock(&evdi_color_list_lock); + return n; +} + void evdi_color_transform_apply_row(const struct evdi_color_data *snapshot, - void *row, int width_px, bool swap_rb) + void *row, int width_px, bool swap_rb) { - /* - * XRGB8888/ARGB8888 ("x:R:G:B" MSB-to-LSB, little endian per - * drm_fourcc.h) store B at byte 0 and R at byte 2; XBGR8888/ - * ABGR8888 swap that. G (byte 1) and X/alpha (byte 3) never move. - */ u8 *px = row; const int r_idx = swap_rb ? 0 : 2; const int b_idx = swap_rb ? 2 : 0; diff --git a/module/evdi_color.h b/module/evdi_color.h index 3d1408b..efda996 100644 --- a/module/evdi_color.h +++ b/module/evdi_color.h @@ -9,19 +9,21 @@ #ifndef EVDI_COLOR_H #define EVDI_COLOR_H +#include #include #include struct drm_crtc_state; /* - * evdi has no hardware CRTC gamma/CTM block, so Night Light and other - * DRM color management clients need the transform applied in software - * to the raw framebuffer bytes before they leave the driver. + * Software colour management: apply whatever the compositor programs on the + * CRTC (GAMMA_LUT and/or CTM). No synthesis between the two. * - * Advertised via drm_mode_crtc_set_gamma_size()/drm_crtc_enable_color_mgmt() - * and consumed here as a direct 8-bit-indexed lookup table, independent of - * whatever length LUT userspace actually uploads (see evdi_color_transform_update()). + * Which properties exist is chosen at module load by color_props= + * both|gamma|ctm (see evdi_params). Reload the module to change that. + * + * Diagonal CTMs are fused into a 256-entry LUT only as a speed optimisation + * of the same matrix (Night Light style scales); identity is skipped. */ #define EVDI_GAMMA_LUT_SIZE 256 @@ -29,6 +31,7 @@ struct evdi_color_data { bool active; bool has_gamma; bool has_ctm; + bool fused_diagonal; u8 gamma[3][EVDI_GAMMA_LUT_SIZE]; /* row-major 3x3, drm_fixed.h S32.32 two's-complement fixed point */ s64 ctm[3][3]; @@ -36,29 +39,29 @@ struct evdi_color_data { struct evdi_color_transform { struct mutex lock; + struct list_head link; struct evdi_color_data data; + struct drm_device *ddev; + void *scratch; + size_t scratch_bytes; }; -void evdi_color_transform_init(struct evdi_color_transform *color); +void evdi_color_transform_init(struct evdi_color_transform *color, + struct drm_device *ddev); +void evdi_color_transform_cleanup(struct evdi_color_transform *color); -/* Recomputes the LUT/CTM from crtc_state. Returns true when colour - * properties actually changed (caller should mark the scanout dirty so - * clients re-grab). No-op when !color_mgmt_changed. - */ +/* Returns true if the effective apply payload changed (caller may full-dirty). */ bool evdi_color_transform_update(struct evdi_color_transform *color, struct drm_crtc_state *crtc_state); -/* Copies the current transform out under lock. Returns whether it's a - * no-op identity transform, letting the caller skip apply_row() entirely. - */ bool evdi_color_transform_snapshot(struct evdi_color_transform *color, - struct evdi_color_data *snapshot); + struct evdi_color_data *snapshot); + +void *evdi_color_get_scratch(struct evdi_color_transform *color, size_t bytes); -/* Applies gamma/CTM in place to one packed 32bpp scanline of width_px - * pixels. swap_rb selects XBGR/ABGR (R and B swapped vs XRGB/ARGB) byte - * order; the alpha/padding byte is always left untouched. - */ void evdi_color_transform_apply_row(const struct evdi_color_data *snapshot, - void *row, int width_px, bool swap_rb); + void *row, int width_px, bool swap_rb); + +int evdi_color_format_status(char *buf, size_t size); #endif diff --git a/module/evdi_drm_drv.c b/module/evdi_drm_drv.c index 5701164..86858c0 100644 --- a/module/evdi_drm_drv.c +++ b/module/evdi_drm_drv.c @@ -159,6 +159,7 @@ static void evdi_drm_device_release_cb(__always_unused struct drm_device *dev, { struct evdi_device *evdi = dev->dev_private; + evdi_color_transform_cleanup(&evdi->color); evdi_cursor_free(evdi->cursor); evdi_painter_cleanup(evdi->painter); kfree(evdi); @@ -188,7 +189,7 @@ static int evdi_drm_device_init(struct drm_device *dev) ret = evdi_cursor_init(&evdi->cursor); if (ret) goto err_free; - evdi_color_transform_init(&evdi->color); + evdi_color_transform_init(&evdi->color, dev); evdi_modeset_init(dev); @@ -206,6 +207,7 @@ static int evdi_drm_device_init(struct drm_device *dev) return 0; err_init: + evdi_color_transform_cleanup(&evdi->color); err_free: EVDI_ERROR("Failed to setup drm device %d\n", ret); evdi_cursor_free(evdi->cursor); diff --git a/module/evdi_modeset.c b/module/evdi_modeset.c index e561f1a..6f96669 100644 --- a/module/evdi_modeset.c +++ b/module/evdi_modeset.c @@ -90,9 +90,10 @@ static void evdi_crtc_atomic_flush( bool notify_dpms = crtc_state->active_changed || evdi_painter_needs_full_modeset(evdi->painter); /* - * Colour is applied only on GRABPIX of dirty rects. When the compositor - * changes GAMMA_LUT/CTM without repainting, force a full-frame dirty so - * DisplayLinkManager re-grabs and the new transform is visible. + * Refresh the software transform from compositor blobs. If the + * effective apply payload changed, full-dirty so a static desktop + * re-grabs (Night Light toggle / temperature). Identity is skipped + * in evdi_color so redundant updates do not spam USB. */ if (evdi_color_transform_update(&evdi->color, crtc_state)) { struct drm_clip_rect full = diff --git a/module/evdi_painter.c b/module/evdi_painter.c index fab4327..8ae560e 100644 --- a/module/evdi_painter.c +++ b/module/evdi_painter.c @@ -231,7 +231,8 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, int num_rects, struct drm_clip_rect *rects, int const max_x, int const max_y, - const struct evdi_color_data *color) + const struct evdi_color_data *color, + struct evdi_color_transform *color_tf) { struct drm_framebuffer *fb = &efb->base; struct drm_clip_rect *r; @@ -246,7 +247,7 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, #endif if (color->active) { - scratch_row = vmalloc(max_x * 4); + scratch_row = evdi_color_get_scratch(color_tf, (size_t)max_x * 4); if (!scratch_row) return -ENOMEM; } @@ -264,7 +265,6 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, /* rect size may correspond to previous resolution */ if (max_x < r->x2 || max_y < r->y2) { EVDI_WARN("Rect size beyond expected dimensions\n"); - vfree(scratch_row); return -EFAULT; } @@ -279,10 +279,8 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, memcpy(scratch_row, src, byte_span); evdi_color_transform_apply_row(color, scratch_row, byte_span / 4, swap_rb); - if (copy_to_user(dst, scratch_row, byte_span)) { - vfree(scratch_row); + if (copy_to_user(dst, scratch_row, byte_span)) return -EFAULT; - } } else if (copy_to_user(dst, src, byte_span)) { return -EFAULT; } @@ -292,7 +290,6 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, } } - vfree(scratch_row); return 0; } @@ -1212,7 +1209,8 @@ int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data, dirty_rects, cmd->buf_width, cmd->buf_height, - &color); + &color, + &evdi->color); if (err == 0 && !evdi->cursor_events_enabled) copy_cursor_pixels(efb, cmd->buffer, diff --git a/module/evdi_params.c b/module/evdi_params.c index 8fa0d14..93eb341 100644 --- a/module/evdi_params.c +++ b/module/evdi_params.c @@ -7,12 +7,14 @@ * more details. */ +#include #include #include #include #include "evdi_params.h" #include "evdi_debug.h" +#include "evdi_color.h" unsigned int evdi_loglevel __read_mostly = EVDI_LOGLEVEL_INFO; unsigned short int evdi_initial_device_count __read_mostly; @@ -25,9 +27,23 @@ module_param_named(initial_device_count, evdi_initial_device_count, ushort, 0644); MODULE_PARM_DESC(initial_device_count, "Initial DRM device count (default: 0)"); -module_param_named(color_props, evdi_color_props, charp, 0644); +module_param_named(color_props, evdi_color_props, charp, 0444); MODULE_PARM_DESC(color_props, - "Colour properties to advertise: both, gamma, or ctm (default: both)"); + "Load-time DRM colour props: both, gamma, or ctm (default both). " + "Requires module reload. Mutter uses GAMMA_LUT if present, else CTM."); + +static int color_status_get(char *buffer, const struct kernel_param *kp) +{ + return evdi_color_format_status(buffer, PAGE_SIZE); +} + +static const struct kernel_param_ops color_status_ops = { + .get = color_status_get, +}; + +module_param_cb(color_status, &color_status_ops, NULL, 0444); +MODULE_PARM_DESC(color_status, + "Read-only: advertised mode and per-card effective transform"); bool evdi_color_props_has_gamma(void) { @@ -35,7 +51,6 @@ bool evdi_color_props_has_gamma(void) return true; if (!strcmp(evdi_color_props, "ctm")) return false; - /* both, gamma, or unknown → offer gamma */ return true; } @@ -45,7 +60,5 @@ bool evdi_color_props_has_ctm(void) return true; if (!strcmp(evdi_color_props, "gamma")) return false; - /* both, ctm, or unknown → offer CTM */ return true; } - diff --git a/module/evdi_params.h b/module/evdi_params.h index b6c0729..d5a746f 100644 --- a/module/evdi_params.h +++ b/module/evdi_params.h @@ -13,14 +13,17 @@ extern unsigned int evdi_loglevel; extern unsigned short int evdi_initial_device_count; /* - * Which DRM colour properties to advertise (compositors pick Night Light path - * from what is present). Set at module load, e.g.: + * color_props — load-time only (read at CRTC init). Reload the module to change. + * + * both (default) — advertise GAMMA_LUT + CTM + * gamma — GAMMA_LUT only + * ctm — CTM only + * * modprobe evdi color_props=ctm - * EVDI_COLOR_PROPS=gamma sudo ./scripts/install.sh + * EVDI_COLOR_PROPS=ctm sudo ./scripts/install.sh * - * both — GAMMA_LUT + CTM (default; GNOME prefers GAMMA_LUT) - * gamma — GAMMA_LUT only - * ctm — CTM only (useful to force Mutter CTM path) + * Mutter Night Light: uses GAMMA_LUT if present, else CTM. So color_props=ctm + * makes GNOME send a real CTM (with a Mutter that supports CTM Night Light). */ extern char *evdi_color_props; From b31b64f33659bd1b5f6fcfa725ffb04924bc2002 Mon Sep 17 00:00:00 2001 From: onetr1ck <64502803+onetr1ck@users.noreply.github.com> Date: Sat, 15 Aug 2026 00:03:24 +0200 Subject: [PATCH 2/2] evdi: fix colour fuse, GRABPIX scratch, and cursor tint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagonal CTM fuse overwrote a compositor gamma LUT when both were set; compose CTM-then-gamma into the table instead. Shared GRABPIX scratch could be freed under concurrent grab — use a per-call buffer. DisplayLinkManager enables cursor events, so the SW tint path never ran and the pointer stayed day-white under Night Light. While colour is active, hide the HW cursor event and SW-blend with the same transform, re-advertising on colour change so DLM does not double-draw. --- module/evdi_color.c | 54 +++++++++-------------------------- module/evdi_color.h | 8 ++---- module/evdi_modeset.c | 66 ++++++++++++++++++++++++++++++++----------- module/evdi_painter.c | 56 ++++++++++++++++++++++++++++-------- 4 files changed, 110 insertions(+), 74 deletions(-) diff --git a/module/evdi_color.c b/module/evdi_color.c index ac4516e..71eb991 100644 --- a/module/evdi_color.c +++ b/module/evdi_color.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -70,18 +69,29 @@ static bool evdi_ctm_is_diagonal(const s64 m[3][3]) return true; } -/* Same diagonal matrix, table form — not a different colour model. */ +/* + * Same diagonal matrix as a 256-entry LUT — speed only, not a new model. + * DRM order is CTM then gamma: when a real LUT is already loaded, compose + * gamma_out[i] = gamma_in[clamp(scale * i)] instead of overwriting it. + */ static void evdi_fuse_diagonal_ctm_to_gamma(struct evdi_color_data *data) { + u8 src_gamma[3][EVDI_GAMMA_LUT_SIZE]; + const bool compose = data->has_gamma; int c, i; + if (compose) + memcpy(src_gamma, data->gamma, sizeof(src_gamma)); + for (c = 0; c < 3; ++c) { s64 scale = data->ctm[c][c]; for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) { s64 v = drm_fixp_mul(scale, drm_int2fixp(i)); + int mid = clamp(drm_fixp2int_round(v), 0, 255); - data->gamma[c][i] = clamp(drm_fixp2int_round(v), 0, 255); + data->gamma[c][i] = compose ? src_gamma[c][mid] + : (u8)mid; } } data->has_gamma = true; @@ -182,9 +192,6 @@ void evdi_color_transform_cleanup(struct evdi_color_transform *color) mutex_lock(&evdi_color_list_lock); list_del_init(&color->link); mutex_unlock(&evdi_color_list_lock); - vfree(color->scratch); - color->scratch = NULL; - color->scratch_bytes = 0; mutex_destroy(&color->lock); } @@ -217,41 +224,6 @@ bool evdi_color_transform_snapshot(struct evdi_color_transform *color, return snapshot->active; } -void *evdi_color_get_scratch(struct evdi_color_transform *color, size_t bytes) -{ - void *p; - - if (bytes == 0) - return NULL; - - mutex_lock(&color->lock); - if (color->scratch_bytes >= bytes) { - p = color->scratch; - mutex_unlock(&color->lock); - return p; - } - vfree(color->scratch); - color->scratch = NULL; - color->scratch_bytes = 0; - mutex_unlock(&color->lock); - - p = vmalloc(bytes); - if (!p) - return NULL; - - mutex_lock(&color->lock); - if (color->scratch_bytes >= bytes) { - vfree(p); - p = color->scratch; - } else { - vfree(color->scratch); - color->scratch = p; - color->scratch_bytes = bytes; - } - mutex_unlock(&color->lock); - return p; -} - static s64 evdi_color_apply_ctm_channel(const struct evdi_color_data *snapshot, int row, s64 r, s64 g, s64 b) { diff --git a/module/evdi_color.h b/module/evdi_color.h index efda996..ff6176c 100644 --- a/module/evdi_color.h +++ b/module/evdi_color.h @@ -23,7 +23,9 @@ struct drm_crtc_state; * both|gamma|ctm (see evdi_params). Reload the module to change that. * * Diagonal CTMs are fused into a 256-entry LUT only as a speed optimisation - * of the same matrix (Night Light style scales); identity is skipped. + * of the same matrix (Night Light style scales). If a gamma LUT is also + * present it is composed (CTM then gamma), not overwritten. Identity is + * skipped. */ #define EVDI_GAMMA_LUT_SIZE 256 @@ -42,8 +44,6 @@ struct evdi_color_transform { struct list_head link; struct evdi_color_data data; struct drm_device *ddev; - void *scratch; - size_t scratch_bytes; }; void evdi_color_transform_init(struct evdi_color_transform *color, @@ -57,8 +57,6 @@ bool evdi_color_transform_update(struct evdi_color_transform *color, bool evdi_color_transform_snapshot(struct evdi_color_transform *color, struct evdi_color_data *snapshot); -void *evdi_color_get_scratch(struct evdi_color_transform *color, size_t bytes); - void evdi_color_transform_apply_row(const struct evdi_color_data *snapshot, void *row, int width_px, bool swap_rb); diff --git a/module/evdi_modeset.c b/module/evdi_modeset.c index 6f96669..4903c74 100644 --- a/module/evdi_modeset.c +++ b/module/evdi_modeset.c @@ -101,6 +101,12 @@ static void evdi_crtc_atomic_flush( if (full.x2 > full.x1 && full.y2 > full.y1) evdi_painter_mark_dirty(evdi, &full); + /* + * Cursor events path must re-hide/restore the HW cursor when + * software colour turns on/off so tint stays on the SW blend. + */ + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); } if (notify_mode_changed) @@ -173,12 +179,19 @@ static int evdi_crtc_cursor_set(struct drm_crtc *crtc, /* * For now we don't care whether the application wanted the mouse set, - * or not. + * or not. Colour-active forces SW blend (tinted); keep DLM's HW cursor + * hidden via the set event when events are enabled. */ - if (evdi->cursor_events_enabled) - evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); - else - evdi_mark_full_screen_dirty(evdi); + { + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, &color_snap); + + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); + if (!evdi->cursor_events_enabled || color_active) + evdi_mark_full_screen_dirty(evdi); + } return 0; } @@ -186,11 +199,14 @@ static int evdi_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) { struct drm_device *dev = crtc->dev; struct evdi_device *evdi = dev->dev_private; + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, &color_snap); EVDI_CHECKPT(); evdi_cursor_move(evdi->cursor, x, y); - if (evdi->cursor_events_enabled) + if (evdi->cursor_events_enabled && !color_active) evdi_painter_send_cursor_move(evdi->painter, evdi->cursor); else evdi_mark_full_screen_dirty(evdi); @@ -390,19 +406,35 @@ static void evdi_cursor_atomic_update(struct drm_plane *plane, cursor_changed = true; } - if (!evdi->cursor_events_enabled) { - if (fb != NULL) { - if (efb->obj->allow_sw_cursor_rect_updates) { - evdi_cursor_atomic_get_rect(&old_rect, old_state); - evdi_cursor_atomic_get_rect(&rect, state); - - evdi_painter_mark_dirty(evdi, &old_rect); - } else { - rect = evdi_painter_framebuffer_size(evdi->painter); + { + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, + &color_snap); + /* SW blend when events off, or when colour forces tint. */ + const bool sw_cursor = + !evdi->cursor_events_enabled || color_active; + + if (sw_cursor) { + if (fb != NULL) { + if (efb->obj->allow_sw_cursor_rect_updates) { + evdi_cursor_atomic_get_rect(&old_rect, + old_state); + evdi_cursor_atomic_get_rect(&rect, state); + + evdi_painter_mark_dirty(evdi, &old_rect); + } else { + rect = evdi_painter_framebuffer_size( + evdi->painter); + } + evdi_painter_mark_dirty(evdi, &rect); } - evdi_painter_mark_dirty(evdi, &rect); + /* Keep DLM HW cursor hidden while colour is active. */ + if (evdi->cursor_events_enabled && cursor_changed) + evdi_painter_send_cursor_set(evdi->painter, + evdi->cursor); + return; } - return; } if (cursor_changed) diff --git a/module/evdi_painter.c b/module/evdi_painter.c index 8ae560e..de9fdef 100644 --- a/module/evdi_painter.c +++ b/module/evdi_painter.c @@ -231,13 +231,14 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, int num_rects, struct drm_clip_rect *rects, int const max_x, int const max_y, - const struct evdi_color_data *color, - struct evdi_color_transform *color_tf) + const struct evdi_color_data *color) { struct drm_framebuffer *fb = &efb->base; struct drm_clip_rect *r; const bool swap_rb = evdi_format_swaps_rb(fb->format->format); + /* Per-grab row — never share freable storage across concurrent GRABPIX. */ char *scratch_row = NULL; + int ret = 0; EVDI_CHECKPT(); @@ -247,7 +248,9 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, #endif if (color->active) { - scratch_row = evdi_color_get_scratch(color_tf, (size_t)max_x * 4); + if (max_x <= 0) + return 0; + scratch_row = kvmalloc((size_t)max_x * 4, GFP_KERNEL); if (!scratch_row) return -ENOMEM; } @@ -265,7 +268,8 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, /* rect size may correspond to previous resolution */ if (max_x < r->x2 || max_y < r->y2) { EVDI_WARN("Rect size beyond expected dimensions\n"); - return -EFAULT; + ret = -EFAULT; + goto out; } EVDI_VERBOSE("copy rect %d,%d-%d,%d\n", r->x1, r->y1, r->x2, @@ -279,10 +283,13 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, memcpy(scratch_row, src, byte_span); evdi_color_transform_apply_row(color, scratch_row, byte_span / 4, swap_rb); - if (copy_to_user(dst, scratch_row, byte_span)) - return -EFAULT; + if (copy_to_user(dst, scratch_row, byte_span)) { + ret = -EFAULT; + goto out; + } } else if (copy_to_user(dst, src, byte_span)) { - return -EFAULT; + ret = -EFAULT; + goto out; } src += fb->pitches[0]; @@ -290,7 +297,9 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, } } - return 0; +out: + kvfree(scratch_row); + return ret; } static void copy_cursor_pixels(struct evdi_framebuffer *efb, @@ -498,6 +507,10 @@ static struct drm_pending_event *create_cursor_set_event( { struct evdi_event_cursor_set_pending *event; struct evdi_gem_object *eobj = NULL; + struct evdi_device *evdi = painter->drm_device ? + painter->drm_device->dev_private : NULL; + struct evdi_color_data color_snap; + bool color_active = false; event = kzalloc_obj(*event, GFP_KERNEL); if (!event) { @@ -508,6 +521,10 @@ static struct drm_pending_event *create_cursor_set_event( event->cursor_set.base.type = DRM_EVDI_EVENT_CURSOR_SET; event->cursor_set.base.length = sizeof(event->cursor_set); + if (evdi) + color_active = evdi_color_transform_snapshot(&evdi->color, + &color_snap); + evdi_cursor_lock(cursor); event->cursor_set.enabled = evdi_cursor_enabled(cursor); evdi_cursor_hotpoint(cursor, &event->cursor_set.hot_x, @@ -526,6 +543,16 @@ static struct drm_pending_event *create_cursor_set_event( event->cursor_set.enabled = false; event->cursor_set.buffer_length = 0; } + /* + * Hardware cursor events expose the untinted GEM. While software + * colour is active, hide the HW cursor so GRABPIX SW-blends a tinted + * one instead (see grabpix_ioctl). + */ + if (color_active) { + event->cursor_set.enabled = false; + event->cursor_set.buffer_handle = 0; + event->cursor_set.buffer_length = 0; + } evdi_cursor_unlock(cursor); event->base.event = &event->cursor_set.base; @@ -1209,9 +1236,13 @@ int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data, dirty_rects, cmd->buf_width, cmd->buf_height, - &color, - &evdi->color); - if (err == 0 && !evdi->cursor_events_enabled) + &color); + /* + * Cursor events hand DLM an untinted GEM. When colour is active, force + * the software blend path (which applies the same transform) instead. + * create_cursor_set_event hides the HW cursor so DLM does not double-draw. + */ + if (err == 0 && (!evdi->cursor_events_enabled || color.active)) copy_cursor_pixels(efb, cmd->buffer, cmd->buf_byte_stride, @@ -1565,6 +1596,9 @@ int evdi_painter_enable_cursor_events_ioctl(struct drm_device *drm_dev, void *da struct drm_evdi_enable_cursor_events *cmd = data; evdi->cursor_events_enabled = cmd->enable; + /* Re-advertise cursor so colour-active force-hide / restore applies. */ + if (evdi->painter && evdi->cursor) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); return 0; }