Skip to content

Conversation

@vaxerski
Copy link
Member

@vaxerski vaxerski commented Dec 23, 2025

Adds loading of icc profiles for the properties we support

ref #9064

Usage

v2: icc = /full/path
v1: , icc, /full/path

Info

This also changes how we handle CM in general, where our intermediate buffer (offload) is in sRGB, and we CM at the end.

This should also make it possible to have actual, proper pixel-perfect screencopy in a follow-up MR (as we can copy the offloaded fb for screencopy purposes) - But that's for a follow-up MR

Requires:

  • HDR offs ICC
  • Testing!!!
  • Fix fucking render glitches XDDDD
  • Clean up the code its a mess of different ideas
  • (optional?) Load VCGT ramps into KMS
  • wiki for icc and new variables
  • Screencopy must not sample CM'd buffer

@vaxerski
Copy link
Member Author

@fufexan can we get nix

@vaxerski
Copy link
Member Author

can you guys let me know if this works well? I have done very limited testing.

@Flat
Copy link

Flat commented Dec 23, 2025

can you guys let me know if this works well? I have done very limited testing.

Hyprland 0.52.0 built from branch icc-support at commit a3520d917afdbbd084c76a8daa8ce1d6c02811f5 clean (cm: add ICC profile loading).
Date: Tue Dec 23 11:06:26 2025
Tag: v0.52.0-183-ga3520d91, commits: 6742

Libraries:
Hyprgraphics: built against 0.4.0, system has 0.4.0
Hyprutils: built against 0.11.0, system has 0.11.0
Hyprcursor: built against 0.1.13, system has 0.1.13

Hyprlang: built against 0.6.7, system has 0.6.7
Aquamarine: built against 0.10.0, system has 0.10.0

Do not see anything in the logs showing it is loading the ICC profile, or rather nothing icc at all. What am I doing wrong there?

monitorv2 {
    output = DP-2
    mode = 3440x1440@143.97
    scale = 1
    position = 0x0
    bitdepth = 10
    cm = dcip3
    sdr_min_luminance = 0.0005
    sdr_max_luminance = 250
    icc = /home/flat/.config/icc/dell-icc-profile.icm
}
monitorv2 {
    output = DP-1
    mode = 3440x1440@143.97
    scale = 1
    position = auto-up
    bitdepth = 10
    cm = dcip3
    sdr_min_luminance = 0.005
    sdr_max_luminance = 250
    icc = /home/flat/.config/icc/dell-icc-profile.icm
}

hyprland.log

@vaxerski
Copy link
Member Author

oh oops, I think I forgot v2

@vaxerski
Copy link
Member Author

my bad should work now

@vaxerski
Copy link
Member Author

it should dump what we were able to decode from the icc into the log like so if it loaded successfully

image

@Flat
Copy link

Flat commented Dec 23, 2025

Yup, it does attempt to load now. Unfortunately looks like the only ICC profile for my monitor is split-trc, so I won't be able to assist much with testing here.

failed: Hyprland cannot represent split-trc profiles yet

@vaxerski
Copy link
Member Author

I've added logging of the trc to the debug output, can you post the icc data dump here?

@Flat
Copy link

Flat commented Dec 24, 2025

Sure, here you go!
iccdump.log

@vaxerski
Copy link
Member Author

thanks, despite xmas :P

@vaxerski
Copy link
Member Author

vaxerski commented Dec 24, 2025

This should work. I've loaded my ICC profile and it does seem to change the colors on my panel a bit, but without a colorimeter I have no way of verifying if it's correct. Please load your ICC and let me know.

@Flat
Copy link

Flat commented Dec 24, 2025

It does seem to change the colors. I've ordered a colorimeter to verify though, should have it within a week.

@vaxerski
Copy link
Member Author

talk about dedication. I do think its wrong, sway's is different.

@vaxerski
Copy link
Member Author

@UjinT34 would you mind checking the LUT shader changes? I'm quite sure I am doing something wrong there. Shadows are a tad too bright I think.

Can't say for sure without a colorimeter but they do look a bit flat and weird.

@vaxerski
Copy link
Member Author

I checked KDE, colors shift a tad but not as much as we do. Something's wrong.

@UjinT34
Copy link
Contributor

UjinT34 commented Dec 25, 2025

I am not that familiar with math around icc luts. clamp looks sus to me, might be too early. EXT_LINEAR is expected to go outside of 0.0 - 1.0 and probably should be clamped after all the transformations.

@vaxerski
Copy link
Member Author

fixed, but it's again surfacing that nightmare where kitty sets the DEFAULTS and yet it changes how we render shit. I need to do something about this...

@vaxerski
Copy link
Member Author

nvm its not fixed it just had a bug where it wouldnt apply cm curves fuck my life

@vaxerski
Copy link
Member Author

in general @UjinT34 shouldn't we compose to a linear SRGB buffer internally and then do a final CM pass on the entire image? Why are we doing it per-surface?

This would be desirable as ICC shifts colors and will mess up e.g. color pickers. We should screencopy un-icc'd buffers.

@github-actions github-actions bot added the nix label Dec 25, 2025
@vaxerski
Copy link
Member Author

I've pushed a completely different approach with a 3D LUT instead, which also changes how we render CM. This will need some testing.

This needs an HDR-kill-switch internally (HDR should disable ICC, currently it's likely the other way round)

This needs people to run this shit and report any visual glitches or bugs, feel free to drive this

@Flat thanks for your testing, if you could compare how this looks vs KDE it would be great. Sway's ICC is busted on my end, but KWin's works properly.

@vaxerski
Copy link
Member Author

I've decided to use Gamma 2.2 for internal storage just like kwin does, this lifts up the shadows a bit and looks more accurate in my opinion.

@freevatar
Copy link

freevatar commented Dec 26, 2025

Yay! This is my use case! I have a colorimeter, good wide gamut display, its ICC profile, and the display itself has a pretty accurate sRGB clamp which I measured.

I hope to have some time to test this PR this weekend.

Meanwhile the only program I've found that applies ICC profile correctly (for sRGB clamp) is https://github.com/ledoge/novideo_srgb. But its for Windows. Every Linux DE implementation I tried was way off color-wise, at least the last time I checked.

@Flat
Copy link

Flat commented Dec 28, 2025

Looks like that does help, also just realized think I profiled these with cm set to wide, so probably need to redo those as well

@vaxerski
Copy link
Member Author

cm, x is overridden by icc, doesn't do anything. Almost all cm options get overridden

@Flat
Copy link

Flat commented Dec 28, 2025

Yes, I mean when I created the ICC profile, it was set to wide, which would have been changing the base color readings used to create the profile.

@vaxerski
Copy link
Member Author

ah, right. Idk much about profiling but that could be an issue

@Flat
Copy link

Flat commented Dec 28, 2025

Just recreated them, not much different honestly. I think the ICC support looks good to me, unless someone with more experience doing this in Linux comes along. Wayland is pretty much unsupported by about the only calibration tool available on Linux, for calibration anyway. Profiling seems to work ok. See eoyilmaz/displaycal-py3#133 for further info on that.

@njdom24
Copy link
Contributor

njdom24 commented Dec 28, 2025

Just tested this PR briefly, and found that HDR screenshare with cm = hdr no longer works with direct scanout enabled.
I tested both mpv playing an HDR video, and gamescope playing an HDR game, with the same results.

If I full-screen the app before opening OBS, the screenshare will fail. If I open OBS and share the screen, then full-screen the app, the app will output as if my display doesn't support HDR (extremely gray, washed-out). It also has a ~25% chance of just crashing instead when I do that.

Works fine if the app is windowed, and works again the moment I close OBS.

@vaxerski
Copy link
Member Author

screenshare is a bit wonk atm yes

@vaxerski
Copy link
Member Author

@Flat do you mean that KDE and Hyprland are now not much different?

@Flat
Copy link

Flat commented Dec 29, 2025

@Flat do you mean that KDE and Hyprland are now not much different?

Correct. I did not measure again, but they visually look about the same.

@Flat
Copy link

Flat commented Dec 29, 2025

Actual measurements (Normalized to White X + Y + Z)

Hyprland

Verify results:
  File 1 L* ref. X+Y+Z 0.951065 1.000000 1.088440
  File 2 L* ref. X+Y+Z 140.574247 147.742458 189.660116
  Total errors:     peak = 16.855547, avg = 8.286194
  Worst 10% errors: peak = 16.855547, avg = 12.674006
  Best  90% errors: peak = 11.591609, avg = 7.798659
  avg err X  1.002827, Y  1.049726, Z  1.025701
  avg err L* 3.163749, a* 3.252128, b* 6.158267

KWin

Verify results:
File 1 L* ref. X+Y+Z 0.951065 1.000000 1.088440
File 2 L* ref. X+Y+Z 138.960762 146.111848 188.965996
Total errors:     peak = 17.163952, avg = 8.387285
Worst 10% errors: peak = 17.163952, avg = 13.251886
Best  90% errors: peak = 11.753762, avg = 7.846774
avg err X  0.832850, Y  0.853621, Z  1.294807
avg err L* 2.607523, a* 3.445785, b* 6.302393

@Flat
Copy link

Flat commented Dec 30, 2025

Took the time to profile in X11, which is known to work, and get some better human readable verification reports.

X11 comes out almost spot on.
KWin is... not correct.
Hyprland seems to be even more off than KWin.

X11 Monitor Measurement Report.html
Hyprland Monitor Measurement Report.html
KWin Monitor Measurement Report.html

// decided it'd be great to have both 18 and 20
// FUCK YOU
size_t tableOff = 20;
if (raw.size() < tableOff + tableBytes)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be correct, this ICC profile with vcgt is not loaded because of this check. I flipped the comparison to load it and it seems to load the rest of the table at offset 18

DisplayA-X11.tar.gz

iccdump

tag 7:
  sig      'vcgt'
  type     'vcgt'
  offset   976
  size     1554
VideoCardGammaTable:
  channels  = 3
  entries   = 256
  entrysize = 2
DEBUG ]: ============= Begin ICC load =============
DEBUG ]: ICC size: 969536 bytes
DEBUG ]: Building a 33³ 3D LUT
DEBUG ]: 3D LUT constructed, size 107811
DEBUG ]: readVCGT16: table has 3 channels, 256 entries, and entry size of 2
DEBUG ]: readVCGT16: table is likely offset 18 not 20, re-reading
DEBUG ]: readVCGT16: red channel: [0, 257, ... 65278, 65535]
DEBUG ]: readVCGT16: green channel: [0, 257, ... 65278, 65535]
DEBUG ]: readVCGT16: blue channel: [0, 257, ... 65278, 65535]
DEBUG ]: ============= End ICC load =============

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you drop the icc profile so I can inspect it?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linked in the above comment, GitHub won't let me upload .icc so it's tar-ed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, I thought it was the log

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

latest commit should allow your vcgt ramps to load. Can you check color accuracy now?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they loaded correctly when I changed that check, the readings are about the same, but if you expect it to be different, I can measure again in a few minutes

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyprland2.html

DEBUG ]: ============= Begin ICC load =============
DEBUG ]: ICC size: 968640 bytes
DEBUG ]: Building a 33³ 3D LUT
DEBUG ]: 3D LUT constructed, size 107811
DEBUG ]: readVCGT16: table has 3 channels, 256 entries, and entry size of 2
DEBUG ]: readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18
DEBUG ]: readVCGT16: red channel: [0, 257, ... 65278, 65535]
DEBUG ]: readVCGT16: green channel: [0, 257, ... 65278, 65535]
DEBUG ]: readVCGT16: blue channel: [0, 257, ... 65278, 65535]
DEBUG ]: ============= End ICC load =============

@Flat
Copy link

Flat commented Dec 30, 2025

Just verified on Gnome Wayland, and it looks like their color management is correct, if that may help to reference as well.
Gnome.html

@vaxerski
Copy link
Member Author

I can feel an imbalance in the force... I feel this will be a painful battle

@UjinT34
Copy link
Contributor

UjinT34 commented Dec 31, 2025

Try creating the profile with cm_enabled=0 so it doesn't affect the result

@vaxerski
Copy link
Member Author

he created the profile on X11 iirc

@Flat
Copy link

Flat commented Dec 31, 2025

Correct, profile was created in X11, using DisplayCal/ArgyllCMS which disables any loaded profiles and resets video card gamma table before creating the profile.

@fxzzi
Copy link
Contributor

fxzzi commented Jan 1, 2026

i don't have a calibration tool or anything but I do have icc profiles for my monitors, one of them loads fine, the other doesn't
gigabyte_m27q.icm.zip

i set these whilst hyprland was open, but hyprland coredumps if i launch
log.txt

btw just from my eyes things look too dark but i could be wrong and stupid and wrong and stupid (did i mention that last one already?)

@njdom24
Copy link
Contributor

njdom24 commented Jan 1, 2026

btw just from my eyes things look too dark but i could be wrong and stupid and wrong and stupid (did i mention that last one already?)

This seems to be the case with Gamma 2.2 vs. other compositors (KDE, Sway) that use the same gamma22 EOTF for SDR content. I was able to measure with my phone's ambient light sensor and see that for all 3, when set to 203 nits, they all hit the same value on a white screen. Just seems like Hyprland has dimmer midtones for some reason.

@vaxerski
Copy link
Member Author

vaxerski commented Jan 1, 2026

@fxzzi can you get a debug stacktrace

@fxzzi
Copy link
Contributor

fxzzi commented Jan 1, 2026

errr is this what you're looking for

crash.txt

also this icc profile crashes too:
MO27Q28G.icm.zip
i pulled it from the hdr thing given from my monitor support page, crash for this one is different
hyprlandCrashReport18203.txt

@UjinT34
Copy link
Contributor

UjinT34 commented Jan 1, 2026

errr is this what you're looking for

crash.txt

also this icc profile crashes too: MO27Q28G.icm.zip i pulled it from the hdr thing given from my monitor support page, crash for this one is different hyprlandCrashReport18203.txt

Looks like it needs a rebase to include 5faa66d

@fxzzi
Copy link
Contributor

fxzzi commented Jan 1, 2026

probably but icc profile still fails to load either way

@vaxerski
Copy link
Member Author

vaxerski commented Jan 1, 2026

@fxzzi idk why lcms fails no clue icc profile bork

@fxzzi
Copy link
Contributor

fxzzi commented Jan 1, 2026

Just seems like Hyprland has dimmer midtones for some reason.

i'm not too sure i thikn hyprland might be parsing something in specific icc's wrong, because on my secondary monitor with its ICC profile everything looks pretty great, but on my main monitor which is an oled dark colours are definitely crushed

again idk how this stuff works so grain of salt

@vaxerski
Copy link
Member Author

vaxerski commented Jan 3, 2026

to be honest we're on par with KDE but if anyone knows what gnum does differently to get the accuracy actually good, lmk, otherwise idk much about how to push this forward

@Flat
Copy link

Flat commented Jan 4, 2026

So... after following the steps here https://planet.kde.org/xavers-blog-2024-07-15-how-to-profile-your-display-in-the-plasma-wayland-session/ I've measured the profile created by that (Display-DP-2-Wayland.tar.gz) on Gnome, KWin, and Hyprland. Doing the profile this way, Gnome is way off, and KDE is the most correct. Hyprland is close, but there is something wrong with the transform on the blue channel, specifically test patch 29.
Gnome.html
KWin.html
Hyprland.html

I think if that can be tracked down it would be correct, at least against KDE. I think it would take a bit more in terms of Wayland color management protocols to match X11.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants