Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kms-hdr

HDR, wide-gamut colour, and NVIDIA feature parity for Wayland compositors that have no colour management — COSMIC, wlroots, and anything else that hasn't implemented wp-color-management-v1 yet.

Most Linux desktops can't drive an HDR or wide-gamut display because the compositor never programs the display's colour pipeline. kms-hdr does it directly through the kernel's atomic KMS API — the same DRM colour properties a compositor would use — so you get vivid, calibrated HDR output today, without waiting for the compositor to catch up.

┌─ kms-hdr ──────────────────────────────────────────────────────────────────┐
│  DEGAMMA (sRGB→linear) → CTM (BT.709 → BT.2020/DCI-P3 × saturation × white   │
│  balance) → GAMMA (linear → midtone γ → highlight roll-off → black lift → PQ)│
│  → HDR_OUTPUT_METADATA (ST 2084) + Colorspace=BT2020_RGB + max_bpc           │
└─────────────────────────────────────────────────────────────────────────────┘

Why this is hard

  • Compositors gate colour management. On Wayland the compositor owns the display pipeline. If it doesn't implement wp-color-management-v1 (COSMIC doesn't yet), nothing can put the panel into HDR or a wide gamut. kms-hdr takes DRM master and programs the CRTC's DEGAMMA_LUT / CTM / GAMMA_LUT / HDR_OUTPUT_METADATA / Colorspace properties itself.
  • NVIDIA exposes no atomic colour properties. The proprietary driver has no CTM / DEGAMMA_LUT / GAMMA_LUT atomic props — so on NVIDIA the only path is legacy per-channel gamma ramps (drmModeCrtcSetGamma). kms-hdr detects the GPU and folds the entire transform (white balance, gamma, tone-map) into those ramps so NVIDIA users still get HDR.
  • It's real colour science. Correct sRGB→linear de-gamma, a BT.709→BT.2020 matrix, linear-light saturation, a CCT-derived white point, ST 2084 (PQ) encoding, and a BT.2390-style highlight roll-off — all quantised into 16-bit LUTs and committed atomically (with a TEST_ONLY dry-run so a rejected state never flashes the screen).

Repository layout

This is a Cargo workspace monorepo with one shared source of truth.

Path What
core/ The C injector (kms-hdr.c, libdrm) — the kernel-facing colour pipeline, daemon, polkit policy, systemd unit.
crates/kms-hdr-core Shared Rust library: config I/O, EDID parsing, GPU detection, calibration profiles, injector invocation. Pure std, no heavy deps. Unit-tested.
crates/kms-hdr-cal Native GTK4 calibration wizard (below).
docs/ROADMAP.md The plan to grow this into a full NVIDIA control panel for COSMIC.

The libcosmic settings panel and the cosmic-settings integration consume kms-hdr-core (migration in progress — see the roadmap).

The calibration wizard

kms-hdr-cal is a ten-step, eyeball-driven wizard that previews live through the injector — going beyond KDE's single-slider HDR calibration:

  1. Display info — EDID peak, advertised gamut/EOTF, OLED + GPU pipeline.
  2. Panel preset — a one-click factory-tuned starting point for the detected panel (see below), then fine-tune the steps that follow.
  3. Black level — PLUGE-style near-black patches to set the shadow floor (OLED black-crush recovery).
  4. Peak luminance — the clipping-cross test: lower peak nits until the bright detail just disappears, finding the panel's true peak.
  5. SDR white reference — how bright SDR/desktop white appears in HDR.
  6. Gamut & vividness — output gamut + saturation, judged against a skin-tone patch.
  7. White balance — temperature (CCT) + tint against a neutral grey field. KDE has no white-balance control.
  8. Midtone gamma — a luminance ramp for contrast vs shadow detail.
  9. Highlight roll-off — soft BT.2390 knee instead of hard clipping. KDE has none.
  10. Summary & save — apply and optionally store a named profile.

Profiles are saved under ~/.config/kms-hdr/profiles/ in the same format as the system config, so any profile can be applied verbatim by the injector.

Panel presets

The wizard reads the EDID manufacturer + product name and recommends a tuned profile, so a known panel is correct in one click. Each preset is just a starting [Conf] you can still fine-tune:

Preset Panel Peak Gamut Notes
lg-g6 LG G6 OLED (2026) ~2700 DCI-P3 4-stack tandem WOLED, provisional
lg-g5 LG G5 OLED (2025) ~2300 DCI-P3 Primary RGB Tandem WOLED
lg-c6 LG C6 OLED (2026) ~1300 DCI-P3 evo WOLED
woled Generic WOLED ~800 DCI-P3 LG-display panel, model unknown
qd-oled Generic QD-OLED ~1000 BT.2020 Samsung/Sony QD-OLED
oled Generic OLED ~700 DCI-P3 unknown OLED
hisense-oled Hisense OLED ~600 DCI-P3
oled-desktop OLED-safe desktop ~600 DCI-P3 dim SDR white + fast auto-dim, longevity-first

LG WOLED covers ~96–99% DCI-P3 but only ~72–83% BT.2020, so the LG presets target DCI-P3 — BT.2020 expansion would overshoot what the panel can render. The presets assume the TV's own dynamic tone-mapping is off so the injector owns the PQ roll-off: on LG, set the input icon to PC or enable HGiG (Game Optimiser), otherwise the TV double-maps highlights. 2026 figures (G6/C6) are provisional and err low until measured; the wizard keeps the EDID-reported peak if it's higher.

Panel lottery by size: the bright 4-stack "Primary RGB Tandem" panel is not in every size. G5/G6 are tandem at 55″+ (the 48″ is standard WOLED). The C6 is tandem only at 77″/83″ — 42–65″ C6 is standard WOLED (~1100–1200 nits, C-class), not G-class. So a 65″ C6 behaves like lg-c6/woled, while a 77″ C6 is closer to the G presets. Peak is a starting point; the clipping-cross step dials it in.

Build & install

Requires: libdrm, a Rust toolchain, GTK 4, pkexec (polkit).

make            # builds the C injector + the Rust workspace
sudo make install

This installs kms-hdr, kms-hdr-cal, hdr-game, the systemd unit, the polkit policy (active-session, no password), and a desktop entry.

Usage

# enable HDR with sane defaults (auto-detects card/connector/EDID peak)
sudo kms-hdr

# tune individual knobs and persist them
sudo kms-hdr --peak-nits 1000 --gamut-mode bt2020 --white-temp 6504 \
             --tonemap rolloff --tonemap-knee 60 --save

# run the calibration wizard (applies live, saves on finish)
kms-hdr-cal

# restore SDR
sudo kms-hdr reset

# run as a daemon that re-applies on config change / SIGUSR1
sudo kms-hdr --daemon

Run kms-hdr --help for the full flag list.

Status

Used daily on an RTX 5090 (Blackwell) + 4K HDR display under COSMIC. AMD/Intel get the full atomic DEGAMMA+CTM+GAMMA pipeline; NVIDIA gets the legacy-gamma fallback. HDR signalling (PQ/BT.2020/DCI-P3) works on both. The roadmap tracks the path to full NVIDIA feature parity (RTX-HDR-alike inverse tone-mapping, VRR, sharpening, per-app profiles).

License

GPL-3.0-or-later.

About

KMS atomic CTM injector — vivid color pipeline for Wayland compositors without color management

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages