Add software GAMMA_LUT/CTM color management on the CRTC - #584
Conversation
evdi's virtual CRTC never called drm_crtc_enable_color_mgmt(), so it never exposed GAMMA_LUT/CTM properties. Compositors that apply color correction (e.g. GNOME's Night Light) exclusively through those DRM/KMS CRTC properties therefore have nothing to set on an evdi output, and silently do nothing there, while native hardware CRTCs are corrected normally. Since evdi has no real display hardware behind it, there is no gamma/ CTM block to program. Instead, register the properties (256-entry gamma LUT, 3x3 CTM) via drm_crtc_enable_color_mgmt() and apply the resulting transform in software, in evdi_painter.c's copy_primary_pixels()/copy_primary_pixels_on_xe() - the only place the driver has direct access to raw pixel bytes before they are copied out to userspace via the GRABPIX ioctl. The transform is skipped entirely (zero added cost) unless a client has actually set a LUT or CTM. evdi_color.c/.h hold the transform math (LUT + fixed-point CTM, reusing drm_fixed.h's S32.32 helpers) as a self-contained, unit-tested component; module/tests/test_evdi_color.c covers identity/gamma/CTM/ channel-order behavior without requiring hardware. Signed-off-by: jj <jj@fedora.local>
drm_fourcc.h documents DRM_FORMAT_XRGB8888/ARGB8888 as "[31:0] x:R:G:B ... little endian", which puts B at byte 0 and R at byte 2 in memory (XBGR8888/ABGR8888 swap that). The r_idx/b_idx assignment had this backwards, so a gamma/CTM transform meant for the red channel was applied to the blue byte and vice versa. Confirmed on real hardware: before this fix, Night Light produced a static purple/cool tint that didn't track the configured color temperature; after, it correctly renders as a warm orange shift that tracks temperature changes. Also corrects module/tests/test_evdi_color.c's expected values, which had been written to match the buggy output rather than derived independently from the true byte layout - the previous version of this test suite could not have caught this bug. Signed-off-by: jj <jj@fedora.local>
Neither driver has colour hardware to program: vino sends already encoded pixels to a dock, and evdi hands framebuffer pixels to a userspace client. A compositor that colour-corrects through the KMS properties -- GNOME's Night Light and KDE's Night Colour both do, rather than rewriting the framebuffer -- therefore had nowhere to place the correction on these outputs while native outputs were corrected normally. This is the gap DisplayLink/evdi#584 reports upstream. Fold the change into the commits that introduce the code rather than appending to the series: the bindings gain CTM alongside the gamma LUT they already exposed, and each driver gains the transform in the commit that adds it. vino advertised GAMMA_LUT but no CTM; evdi had no colour management at all, which matters because evdi is the module DisplayLinkManager actually drives. Both now advertise CTM and a 256-entry GAMMA_LUT and apply them in software, sharing one byte-identical colour module. tools/color-selftest.sh checks the two copies have not drifted and runs the arithmetic under plain rustc. That was worth having immediately: it caught the narrowing step dividing by 256 where the widening step multiplies by 257, so enabling colour management shifted every value above about 128 and an identity ramp was not a no-op. The in-tree KUnit tests assert the same properties but are gated behind a kernel built with CONFIG_KUNIT, and were not being compiled at all. tools/hardware/drm-crtc-props.py enumerates CRTC properties without libdrm, since neither modetest nor drm_info is available here. Give the patch export self-maintaining review-group bounds. The hardcoded total had gone three commits stale, so regeneration failed rather than reporting drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Hey I've implemented this feature in Vino and revdi |
Colour is applied only while copying dirty rects for GRABPIX. A compositor can update GAMMA_LUT or CTM without repainting, so force a full-frame dirty on color_mgmt_changed and let the existing update-ready path re-grab.
When cursor events are disabled the cursor is alpha-blended from the untinted GEM into the grab buffer after the primary copy. Apply the same CTM/gamma to the composed pixel so Night Light matches the desktop under the pointer.
Advertise both colour properties by default. color_props=gamma or color_props=ctm limits what the CRTC exposes so a compositor can be forced onto one Night Light path for testing.
|
I've been testing this on a DisplayLink dock with GNOME Night Light. Earlier follow-ups (full dirty on colour change, SW cursor tint, load-time color_props) are in iDoMeteor#1 (merged on that branch). Further hardening is in iDoMeteor#2: apply only real compositor blobs, color_props=both|gamma|ctm at module load (reload to change; modprobe.d can persist), identity skip, diagonal CTM fused to a LUT for cost, and full dirty only when the effective payload changes. Happy for you to take any of this into the upstream PR or leave it. |
evdi: colour management follow-ups (damage, cursor, color_props)
|
Great catch on the dirty frame, I think that will assist w/a small performance issue I had noticed! |
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.
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.
|
Pushed further follow-ups on your colour branch: iDoMeteor#2 (tip onetr1ck/evdi@wip/color-mgmt-followups). Happy for you to pull any of that into this PR or leave it. Worth linking #44 (gamma ramp, open since 2016) if you have not already — that looks like the long-standing issue this closes. |
evdi: harden software colour path and color_props advertisement
Problem
evdi's virtual CRTC never calls
drm_crtc_enable_color_mgmt(), so itnever exposes
GAMMA_LUT/CTMDRM/KMS properties. Compositors thatapply color correction exclusively through those CRTC properties
(e.g. GNOME's Night Light) have nothing to set on an evdi output and
silently do nothing there, while native hardware CRTCs on the same
session are corrected normally.
Fix
Since evdi has no real display hardware behind it, there's no
gamma/CTM block to program. This registers the properties (256-entry
gamma LUT, 3x3 CTM) via
drm_crtc_enable_color_mgmt()inevdi_crtc_init(), and applies the resulting transform in software inevdi_painter.c'scopy_primary_pixels()/copy_primary_pixels_on_xe()the only place the driver has direct access to raw pixel bytes
before they're copied out to userspace via the
GRABPIXioctl.evdi_color.c/.h: self-contained transform (LUT + fixed-point CTM,built on
drm_fixed.h's S32.32 helpers), independent of whatever LUTlength a client actually uploads.
The transform is skipped entirely (identity fast path, zero added
cost) unless a client has actually set a LUT or CTM - the common
case is unaffected.
module/tests/test_evdi_color.c: KUnit coverage of identity/gamma/CTM/channel-order behavior, no hardware required.
Testing
ci/run_style_check(checkpatch.pl): clean on all touched/new files.make KVER=...), clean withW=1.confirmed the CRTC now exposes
CTM/GAMMA_LUT/GAMMA_LUT_SIZE=256via a direct
DRM_IOCTL_MODE_OBJ_GETPROPERTIESquery.Note: on my own machine, GNOME's Night Light still doesn't visually
apply to this output in practice - but that traced back to an
unrelated GNOME/Mutter + colord monitor-identity collision (two
identical-model monitors whose EDID doesn't include a serial number),
not to anything in evdi. With a single monitor (or one with a real
EDID serial) behind the dock, this should work end-to-end.