Skip to content

drm: clear stale color state on modeset#297

Merged
vaxerski merged 1 commit into
hyprwm:mainfrom
UncleJ4ck:fix/127-clear-stale-color-state
May 10, 2026
Merged

drm: clear stale color state on modeset#297
vaxerski merged 1 commit into
hyprwm:mainfrom
UncleJ4ck:fix/127-clear-stale-color-state

Conversation

@UncleJ4ck

@UncleJ4ck UncleJ4ck commented May 9, 2026

Copy link
Copy Markdown
Contributor

A previous compositor (kwin etc.) can leave HDR, gamma, degamma and CTM
properties set on a connector when it exits. Aquamarine only re-emitted
these on user commits, so anything we don't actively manage stayed at
the previous compositor's value, manifesting as washed-out colors or
shifted saturation when starting Hyprland after kwin.

HDR (#250) and CTM-with-non-identity (#256) were already covered. Extend
the modeset path to also:

  • re-send gamma_lut / degamma_lut on modeset; an empty STATE.gammaLut
    produces a zero blob, which clears the kernel state. only ride this
    path when the prop actually exists, otherwise we'd log a spurious
    no gamma_lut prop error on every modeset for connectors without
    programmable gamma.
  • re-send CTM unconditionally on modeset, identity included, so the
    kernel always replaces a stale matrix with our current one. the blob
    builder produces a real identity matrix when STATE.ctm == Mat3x3(),
    so we never send blob_id=0 (which drm: re-send ctm blob on modeset #256 documented as problematic).

Fixes #127.

A previous compositor (kwin etc.) can leave HDR, gamma, degamma and CTM
properties set on a connector when it exits. Aquamarine only re-emitted
these on user commits, so anything we don't actively manage stayed at
the previous compositor's value, manifesting as washed-out colors or
shifted saturation when starting Hyprland after kwin.

HDR (hyprwm#250) and CTM-with-non-identity (hyprwm#256) were already covered. Extend
the modeset path to also:

- re-send gamma_lut/degamma_lut on modeset; an empty STATE.gammaLut
  produces a zero blob, which clears the kernel state. only ride this
  path when the prop actually exists, otherwise we'd log a spurious
  "no gamma_lut prop" error on every modeset for connectors without
  programmable gamma.
- re-send CTM unconditionally on modeset, identity included, so the
  kernel always replaces a stale matrix with our current one. the blob
  builder produces a real identity matrix when STATE.ctm == Mat3x3(),
  so we never send blob_id=0 (which hyprwm#256 documented as problematic).

Fixes hyprwm#127.
@vaxerski vaxerski merged commit 6f406e8 into hyprwm:main May 10, 2026
1 check passed
@Arisa-Snowbell Arisa-Snowbell mentioned this pull request May 10, 2026
@cushycush

Copy link
Copy Markdown

Hey, a heads up that I think it might've introduced a black-screen on some setups, wanted to flag it in case I'm reading the path wrong.

I'm running a Hyprland 0.55-based fork with render:cm_enabled=false on Intel UHD 770 / i915. Under atomic mode, every panel goes black after the first modeset. Legacy DRM works fine. The framebuffer itself is correct: grim reads back the rendered desktop. But modetest -M i915 -p shows the CRTC's CTM property is all-zeros after the first modeset, so the kernel multiplies every output pixel by zero.

The PR's commit message says the blob builder produces identity when STATE.ctm == Mat3x3(), but I don't think it does. Hyprutils::Math::Mat3x3() zero-initializes its 9 floats, and the builder at impl/Atomic.cpp:419-423 runs the matrix through doubleToS3132Fixed and writes it verbatim. So a default-constructed Mat3x3 lands in the kernel as a zero blob, not identity. Hyprland's setCTM only fires from the cm_enabled=true renderer path, so forks running with it disabled never overwrite the zero default.

Easy fix would be treating a default-constructed Mat3x3 as identity inside the blob builder, contained to this file. Flipping Hyprutils::Math::Mat3x3()'s default ctor to identity upstream would also close it but has broader blast radius for anyone quietly relying on zero. Anyone hitting this before an upstream fix can seed setCTM(identity()) at output creation as a workaround (no-op under legacy, seeds an identity matrix under atomic instead of all-zeros). Happy to send a PR if it helps, but figured I'd flag and let you take it since you're already in this file. Am I missing why the default ctor would produce identity?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DRM properties set by other compositors are not reset and mess with AQ

3 participants