Bug 509114

Summary: HDR Peak Brightness Clipping
Product: [Plasma] kwin Reporter: spyro3309
Component: colour-managementAssignee: KWin default assignee <kwin-bugs-null>
Status: RESOLVED FIXED    
Severity: normal CC: bugreports61, calvinatorzcraft, nate, serdarthtux, wolf, xaver.hugl
Priority: NOR    
Version First Reported In: 6.4.0   
Target Milestone: ---   
Platform: Bazzite   
OS: Linux   
Latest Commit: Version Fixed/Implemented In: 6.6.0
Sentry Crash Report:

Description spyro3309 2025-09-04 16:53:35 UTC
SUMMARY
HDR in newer KDE versions supporting the HDR calibration tool clips the maximum brightness of my monitor (MSI MPG321URX QD OLED)

STEPS TO REPRODUCE
1. configure HDR launch arguments in Steam
2. launch an HDR capable game
3. turn on HDR in game

OBSERVED RESULT
Any bright highlight that should be pushing my monitor's 1000 nits peak gets dulled, looking washed out like it's getting clipped incorrectly.

EXPECTED RESULT
HDR displaying full range of brightness up to peak 1000 nits

SOFTWARE/OS VERSIONS
Linux/KDE Plasma: Bazzite/Cachy both had this issue from my testing, though it seems to be KDE causing it.
KDE Plasma Version: 6.4 onward

ADDITIONAL INFORMATION
Happens regardless of settings configs in Display Configuration, while previous KDE versions work after setting reference white 203 nits as maximum SDR brightness (currently rolled back to KDE 6.3.5)
Comment 1 Zamundaaa 2025-09-05 12:02:53 UTC
Please attach the output of
> kscreen-doctor -o

Also, what are the "HDR launch arguments in Steam"?
Comment 2 spyro3309 2025-09-06 17:37:08 UTC
Output: 1 DP-1 62cbd1a6-1cb8-4677-b870-0d5a80c7a4b6
	enabled
	connected
	priority 1
	DisplayPort
	replication source:0
	Modes:  1:3840x2160@60!  2:3840x2160@240*  3:3840x2160@180  4:3840x2160@120  5:2560x1440@240  6:2560x1440@120  7:2560x1440@60  8:1920x1200@60  9:1920x1080@240  10:1920x1080@120  11:1920x1080@120  12:1920x1080@120  13:1920x1080@60  14:1920x1080@60  15:1920x1080@60  16:1600x1200@60  17:1680x1050@60  18:1280x1024@75  19:1280x1024@60  20:1440x900@60  21:1280x800@60  22:1280x720@120  23:1280x720@120  24:1280x720@60  25:1280x720@60  26:1024x768@75  27:1024x768@70  28:1024x768@60  29:800x600@75  30:800x600@72  31:800x600@60  32:800x600@56  33:720x576@60  34:720x576@50  35:720x480@60  36:720x480@60  37:640x480@75  38:640x480@73  39:640x480@67  40:640x480@60  41:640x480@60  42:1600x1200@240  43:1280x1024@240  44:1024x768@240  45:2560x1600@60  46:2560x1600@240  47:1920x1200@240  48:1280x800@240  49:3200x1800@60  50:3200x1800@240  51:2880x1620@60  52:2880x1620@240  53:1600x900@60  54:1600x900@240  55:1368x768@60  56:1368x768@240  57:1280x720@240 
	Geometry: 0,0 2649x1490
	Scale: 1.45
	Rotation: 1
	Overscan: 0
	Vrr: Never
	RgbRange: Automatic
	HDR: enabled
		SDR brightness: 1037 nits
		SDR gamut wideness: 0%
		Peak brightness: 1037 nits, overridden with: 1037 nits
		Max average brightness: 456 nits
		Min brightness: 0.0006 nits
	Wide Color Gamut: enabled
	ICC profile: none
	Color profile source: EDID
	Color power preference: prefer accuracy
	Brightness control: supported, set to 100% and dimming to 100%
	DDC/CI: allowed
	Color resolution: automatic (16), range: [8; 16] bits per color
	Allow EDR: unsupported
Output: 2 DP-2 1ce71aee-548f-4fbf-836f-ecf2b6d530cb
	enabled
	connected
	priority 2
	DisplayPort
	replication source:0
	Modes:  58:1920x1080@60*!  59:1920x1080@60  60:1920x1080@60  61:1920x1080@50  62:1680x1050@60  63:1280x1024@75  64:1280x1024@60  65:1440x900@60  66:1280x960@60  67:1280x800@60  68:1152x864@75  69:1280x720@60  70:1280x720@60  71:1280x720@60  72:1280x720@50  73:1024x768@75  74:1024x768@70  75:1024x768@60  76:832x624@75  77:800x600@75  78:800x600@72  79:800x600@60  80:800x600@56  81:720x576@50  82:720x576@50  83:720x480@60  84:720x480@60  85:720x480@60  86:720x480@60  87:720x480@60  88:640x480@75  89:640x480@73  90:640x480@67  91:640x480@60  92:640x480@60  93:640x480@60  94:720x400@70  95:1600x900@60  96:1368x768@60 
	Geometry: 2649,0 1920x1080
	Scale: 1
	Rotation: 1
	Overscan: 0
	Vrr: incapable
	RgbRange: Automatic
	HDR: incapable
	Wide Color Gamut: incapable
	ICC profile: none
	Color profile source: sRGB
	Color power preference: prefer efficiency and performance
	Brightness control: supported, set to 100% and dimming to 100%
	DDC/CI: allowed
	Color resolution: automatic (10), range: [8; 16] bits per color
	Allow EDR: unsupported

HDR args include PotonGE's "PROTON_ENABLE_WAYLAND" and "PROTON_ENABLE_HDR=1" and Gamescope's "--hdr-enabled" commands. For additional information, the issue resolves when I turn off tonemapping via an environment file in /etc/ that I input "KWIN_DISABLE_TONEMAPPING=1," so it seems to be a problem there. Previous versions of the KDE Desktop environment (before the addition of HDR calibration) do not exhibit this issue, and it happens on the newer versions regardless of if I calibrate using the tool, or leave it alone from updating from an earlier version of KDE.
Comment 3 Zamundaaa 2025-09-06 22:12:13 UTC
> SDR brightness: 1037 nits
> Peak brightness: 1037 nits
> Brightness control: supported, set to 100% and dimming to 100%
mean that you've set your display to be a very bright SDR display. Heavy tone mapping is entirely expected with that.

Turn the brightness down to something more reasonable, and things will be presented much better.
Comment 4 spyro3309 2025-09-07 06:13:02 UTC
But this happens regardless of brightness settings and calibration values. If I set SDR brightness to something like 20%, the brights are still clipped below max brightness of my monitor. With tonemapping enabled, nothing hits the 1000 nits mark regardless of brightness settings.
Comment 5 Zamundaaa 2025-09-08 12:38:32 UTC
What kind of HDR metadata does the game set? You can check by setting WAYLAND_DEBUG=1 for the game and grep for wp_image_description_creator_params_v1
Comment 6 spyro3309 2025-09-09 04:39:38 UTC
I'll have to check for you sometime in the next day or so, got a bunch of stuff to do today. When I get a moment, I'll undo the Environment file param disabling tonemapping and grab you the info for both Helldivers 2 and Elden Ring: Nightreign.
Comment 7 spyro3309 2025-09-11 05:13:11 UTC
So I'm trying to grep, but how does that work? For Bazzite it uses Clapgrep for a visual of it, and I see it searching them, but where would I find the metadata? It gives a lot of instances of it appearing, and I have no idea what I should be looking for in these entries. 

There's an entry here that looks like this:
 <interface name="wp_image_description_creator_params_v1" version="1">
    <description summary="holder of image description parameters">
      This type of object is used for collecting all the parameters required
      to create a wp_image_description_v1 object. A complete set of required
      parameters consists of these properties:
      - transfer characteristic function (tf)
      - chromaticities of primaries and white point (primary color volume)

      The following properties are optional and have a well-defined default
      if not explicitly set:
      - primary color volume luminance range
      - reference white luminance level
      - mastering display primaries and white point (target color volume)
      - mastering luminance range

      The following properties are optional and will be ignored
      if not explicitly set:
      - maximum content light level
      - maximum frame-average light level

      Each required property must be set exactly once if the client is to create
      an image description. The set requests verify that a property was not
      already set. The create request verifies that all required properties are
      set. There may be several alternative requests for setting each property,
      and in that case the client must choose one of them.

      Once all properties have been set, the create request must be used to
      create the image description object, destroying the creator in the
      process.
    </description>

    <enum name="error">
      <description summary="protocol errors"/>

      <entry name="incomplete_set" value="0"
             summary="incomplete parameter set"/>
      <entry name="already_set" value="1"
             summary="property already set"/>
      <entry name="unsupported_feature" value="2"
             summary="request not supported"/>
      <entry name="invalid_tf" value="3"
             summary="invalid transfer characteristic"/>
      <entry name="invalid_primaries_named" value="4"
             summary="invalid primaries named"/>
      <entry name="invalid_luminance" value="5"
             summary="invalid luminance value or range"/>
    </enum>

 <request name="create" type="destructor">
      <description summary="Create the image description object using params">
        Create an image description object based on the parameters previously
        set on this object.

        The completeness of the parameter set is verified. If the set is not
        complete, the protocol error incomplete_set is raised. For the
        definition of a complete set, see the description of this interface.

        The protocol error invalid_luminance is raised if any of the following
        requirements is not met:
        - When max_cll is set, it must be greater than min L and less or equal
          to max L of the mastering luminance range.
        - When max_fall is set, it must be greater than min L and less or equal
          to max L of the mastering luminance range.
        - When both max_cll and max_fall are set, max_fall must be less or equal
          to max_cll.

        If the particular combination of the parameter set is not supported
        by the compositor, the resulting image description object shall
        immediately deliver the wp_image_description_v1.failed event with the
        'unsupported' cause. If a valid image description was created from the
        parameter set, the wp_image_description_v1.ready event will eventually
        be sent instead.

        This request destroys the wp_image_description_creator_params_v1
        object.

        The resulting image description object does not allow get_information
        request.
      </description>

There's a few lines in the grep selection in Clapgrep that says there's an instance where the thing is created, then interface, then destroyed. Idk if this is all what you're trying to find, so lmk.
Comment 8 Zamundaaa 2025-09-11 12:20:58 UTC
WAYLAND_DEBUG=1 insert_game_command_here 2>&1 | grep "wp_image_description_creator_params_v1"

should do the trick. Just upload the result of that here, and I'll take a look
Comment 9 spyro3309 2025-09-12 02:04:26 UTC
So put the debug command in Steam launch for the game? or do it from console? Sorry, I have little experience with linux, let alone debug and grep, so I'm having a lot of trouble figuring this out. Any way we could get into a discord call or something to walk through it if I can't figure it out?
Comment 10 Zamundaaa 2025-09-15 13:38:22 UTC
You can just put
> WAYLAND_DEBUG=1 %command% 2>&1 | grep "wp_image_description_creator_params_v1" > ~/test.log
into the launch options of the game in Steam. Run the game, close it again, and you should have that file with the needed data in your home directory.
Comment 11 spyro3309 2025-09-16 04:31:22 UTC
Copying the entire line of the command there and pasting it into launch settings, launching the game and closing, then checking the log, there's no info wrote into the log. Put "> WAYLAND_DEBUG=1 %command% 2>&1 | grep "wp_image_description_creator_params_v1" > ~/test.log" minus the quotes in its entirety into steam launch settings for the game, right? Plus, put my HDR commands in as well before the %command% correct? It just didn't write any info to the test log file in Home
Comment 12 Zamundaaa 2025-09-16 09:13:35 UTC
The ">" from the start is probably what's causing the issue, you shouldn't copy that. It's just needed for Bugzilla to mark it as a quote
Comment 13 spyro3309 2025-09-19 03:22:59 UTC
[4169225.185] {mesa vk display queue}  -> wp_color_manager_v1#70.create_parametric_creator(new id wp_image_description_creator_params_v1#93)
[4169225.188] {mesa vk display queue}  -> wp_image_description_creator_params_v1#93.set_primaries_named(6)
[4169225.190] {mesa vk display queue}  -> wp_image_description_creator_params_v1#93.set_tf_named(11)
[4169225.191] {mesa vk display queue}  -> wp_image_description_creator_params_v1#93.create(new id wp_image_description_v1#89)
[4169263.142] {mesa vk display queue}  -> wp_color_manager_v1#70.create_parametric_creator(new id wp_image_description_creator_params_v1#102)
[4169263.147] {mesa vk display queue}  -> wp_image_description_creator_params_v1#102.set_primaries_named(6)
[4169263.149] {mesa vk display queue}  -> wp_image_description_creator_params_v1#102.set_tf_named(11)
[4169263.152] {mesa vk display queue}  -> wp_image_description_creator_params_v1#102.create(new id wp_image_description_v1#88)
Comment 14 Zamundaaa 2025-09-26 16:42:57 UTC
Hmm, so it doesn't set any HDR metadata at all (or it's filtered out by Mesa for being too bad). This makes KWin assume a 10000 nits peak, which results in pretty aggressive tone mapping.

I'm not sure what to do about that, the protocol defaults the max luminance to the maximum of the transfer function. Maybe the protocol needs to be changed, or Mesa needs to assume some default for when the app doesn't set anything at all.
Comment 15 calvin 2025-09-26 18:10:24 UTC
(In reply to Zamundaaa from comment #14)
> Hmm, so it doesn't set any HDR metadata at all (or it's filtered out by Mesa
> for being too bad). This makes KWin assume a 10000 nits peak, which results
> in pretty aggressive tone mapping.
> 
> I'm not sure what to do about that, the protocol defaults the max luminance
> to the maximum of the transfer function. Maybe the protocol needs to be
> changed, or Mesa needs to assume some default for when the app doesn't set
> anything at all.

Also have this issue, it seems like the vast majority of games on steam skip HDR metadata (only ones I found in my library when doing debugging is cyberpunk 2077 outputting 500 and silent hill 2 outputting 0 maxcll) and HDR fix mods like renodx might disable/break metadata in other cases. HDR WSI fixes it for now, but it's not ideal.
Comment 16 bugreports61 2025-10-26 09:20:16 UTC
@Zamundaaa: So what would be your recommendation for now to get things displayed as intended and not clipped ? Use KWIN_DISABLE_TONEMAPPING=1 for the time being ?

iirc there has been a similar situation in the past with ue5 games reporting nonsensical values (which you fixed somehow as disabling tonemapping did not help):
https://bugs.kde.org/show_bug.cgi?id=500144#c6

@Calvin: So you are using the HDR WSI layer (https://github.com/Zamundaaa/VK_hdr_layer) with newer mesa ? iirc that din't work at all for me last time i tried.
Comment 17 Bartosz Taudul 2025-10-30 19:13:46 UTC
(In reply to Zamundaaa from comment #14)
> Hmm, so it doesn't set any HDR metadata at all (or it's filtered out by Mesa
> for being too bad). This makes KWin assume a 10000 nits peak, which results
> in pretty aggressive tone mapping.

Can you elaborate on how an application would be supposed to communicate the peak brightness? In my image viewer (https://github.com/wolfpld/moderncore) I basically create a 10 bit PQ Vulkan swapchain, and that's it. PQ max brightness by definition is 10k nits, so that's probably what kwin is seeing, but it is not what my application is showing, as the images you load will have varying max luminances.

In kwin 6.3 the brightness you set the pixel at was the brightness seen on the screen, and all was fine (except for highlights, but that was largely acceptable). With kwin 6.4+ and tone mapper in action there's no way to get that back.

I could probably correct for this by remapping the loaded image max brightness to account for the 10k tonemapper, but that would mean a test image (https://alexfry.github.io/ACES_ODT_Candidates_Examples/imagesDiagnostic/PQ_100_to_10000nits.avif) suddenly maxes out at 100k+ nits, and how do you even encode that, it's obviously wrong to do so.

> I'm not sure what to do about that, the protocol defaults the max luminance
> to the maximum of the transfer function. Maybe the protocol needs to be
> changed, or Mesa needs to assume some default for when the app doesn't set
> anything at all.

My suggestion would be to give the user an option to disable the tone mapper, with the resulting 1:1 representation of the luminance levels on the monitor.
Comment 18 bugreports61 2025-12-17 09:32:58 UTC
Any news on that topic ?
Comment 19 Zamundaaa 2025-12-18 17:53:10 UTC
Git commit 957031ce526dd4973263262dfd981bf877eb8103 by Xaver Hugl.
Committed on 18/12/2025 at 17:29.
Pushed by zamundaaa into branch 'master'.

wayland/colormanagement: add workaround + configuration for Windows HDR apps

Many Windows HDR games have their own configuration for display brightness.
That configuration however relies on the Windows behavior of copying HDR content
to the screen unmodified, without any reference luminance or HDR metadata.
As a middle ground between that terrible behavior and not having Windows games
work as expected, this adds a workaround for these applications:
If a Windows application is detected, its reference, max average and maximum
luminance are overwritten with values from KWin's config, which are written to
by KScreen's HDR calibration app.

This means that while Windows games will work as expected on the screen the
configuration was written for, they will still be tonemapped properly when on
a different screen, or when taking a screenshot or screencast.

"detecting" Windows applications is implemented in two different ways, one is
simply the explicit "windows_scrgb" image description from the Wayland protocol,
and the other is a more hacky check for the executable name.
Once it's possible to set a "windows_pq" image description as well, and both
gamescope and Wine use that, we can remove the more hacky approach again.

M  +8    -1    src/scene/surfaceitem_wayland.cpp
M  +29   -10   src/wayland/colormanagement_v1.cpp
M  +5    -2    src/wayland/colormanagement_v1.h
M  +6    -0    src/wayland/surface.cpp
M  +6    -0    src/wayland/surface.h
M  +1    -0    src/wayland/surface_p.h

https://invent.kde.org/plasma/kwin/-/commit/957031ce526dd4973263262dfd981bf877eb8103