SUMMARY Applying an HDR ICC profile in KWin 6.7.4 severely crushes the lower PQ curve and compresses midtones. The same profile produces the expected PQ response when its BToA cLUT is applied in the native Wayland HDR test application instead of by KWin. This profile was created while the display was in HDR mode. The profiling patches were submitted by a native Wayland client using a Vulkan HDR10 ST 2084 swapchain. DisplayCAL and XWayland were not used. STEPS TO REPRODUCE 1. Enable HDR on the display. 2. Assign the attached ICC profile as the HDR ICC profile in Display Configuration. 3. Select the ICC profile as the HDR color profile source. 4. Display neutral HDR10 ST 2084 patches through a native Wayland client. 5. Measure 20%, 30%, 50%, and 100% PQ patches. 6. Change the HDR color profile source back to EDID. 7. Apply the same profile's BToA cLUT inside the native Wayland test client. 8. Repeat the same patches without changing the display, meter, signal format, or patch geometry. OBSERVED RESULT With KWin applying the profile: PQ input Measured luminance 20% No meter reading 30% 0.758 cd/m2 50% 51.425 cd/m2 100% 307.868 cd/m2 With the same profile applied locally by the native HDR client: PQ input ST 2084 value Measured luminance 20% 2.429 cd/m2 2.416 cd/m2 30% 10.038 cd/m2 10.033 cd/m2 50% 92.246 cd/m2 92.543 cd/m2 100% Display clips 307.726 cd/m2 With KWin applying the profile, 5%, 10%, and 15% all measured as black. The profile's local cLUT result follows ST 2084 closely from 20% through 50% and reaches the same display peak. The attached profile has a 33 point AToB0 cLUT, a 33 point BToA0 cLUT, 2048 entry input and output tables, 256 entry TRCs, matrix tags, and no MHC2 or VCGT tag. Its SHA-256 is: 2b704808224e5109baea26551cd16b2c41a7b972db95ae1af490c2f259644281 EXPECTED RESULT KWin should apply the profile without changing the intended PQ luminance response. The result should be comparable to applying the same BToA cLUT in the native HDR client. ADDITIONAL TESTS An MHC2 version of the profile produced nearly the same result through KWin: PQ input Measured luminance 20% No meter reading 30% 0.763 cd/m2 50% 50.930 cd/m2 100% 308.534 cd/m2 Changing the ICC lumi tag to 1000 cd/m2 did not correct the curve. Changing the Wayland surface reference luminance, mastering luminance metadata, and render intent also did not correct it. The local cLUT and KWin tests used the same display, 10% patch size, HDMI full range output, meter, and patch values. The display profile was built from 424 valid measured characterization patches and a separate 254 point neutral measurement. ArgyllCMS generated the ICC profile. Internal profile validation reported average Delta E 2000 of 0.459 and 95th percentile of 1.644. SYSTEM INFORMATION Operating System: Fedora Linux 44 KDE Plasma Desktop Edition KDE Plasma Version: 6.7.4 KWin Version: 6.7.4 KDE Frameworks Version: 6.28.0 Qt Version: 6.11.1 Kernel Version: 7.1.6-201.fc44.x86_64 Mesa Version: 26.1.5 Graphics Platform: Wayland GPU: AMD Phoenix1, amdgpu Display: YCT Innoview AMOLED connected over HDMI HDR: enabled Output range: full VRR: disabled Patch client: PGenerator+ Patch Companion 1.4.7, Vulkan renderer, native Wayland HDR10 ST 2084 surface Meter: Calibrite/X-Rite i1Display Pro Plus Patch geometry: borderless fullscreen with a centered 10% window ATTACHMENTS 1. Innoview_Fedora_Dense_256_Neutral_KDE-KDE-HDR-XYZ-cLUT-matrix.icc 2. dense256-local-clut-validation.json 3. dense256-kwin-system-validation.json 4. Innoview_Fedora_Dense_MHC2-HDR-MHC2-XYZ-cLUT-matrix.icc 5. kwin-mhc2-system-validation.json
Created attachment 194989 [details] Affected HDR ICC profile
Created attachment 194990 [details] Measurements with the profile BToA cLUT applied in the native HDR client
Created attachment 194991 [details] Measurements with the same profile applied by KWin
Created attachment 194992 [details] MHC2 comparison profile
Created attachment 194993 [details] Measurements with the MHC2 comparison profile applied by KWin
I traced this through KWin 6.7.4 source and found three concrete problems. The same code is still present on current KWin master. For context, we are developing PGenerator+, a display calibration and pattern generation system. These measurements use its Native Wayland patch application, which submits HDR patches through Wayland rather than X11 or an SDR compatibility path. The tested profile is an Argyll-generated ICC v2.2 display profile, not v4. Its B2A0 tag is a 16-bit mft2 transform with a 33x33x33 cLUT and 2048-entry input and output tables. 1. The DRM output checks the SDR profile source before attaching the HDR profile. In src/backends/drm/drm_output.cpp, DrmOutput::queueChanges() tests m_nextState->colorProfileSource. When HDR is active it should test hdrColorProfileSource. On this system the HDR source was ICC while the SDR source was sRGB, so KWin called setIccProfile(nullptr). Changing only the unused SDR source to ICC changed the measured HDR result from 30%=0.76, 50%=51.4, 100%=307.9 cd/m2 to 30%=6.37, 50%=56.7, 100%=249.5 cd/m2. This confirms that the SDR setting incorrectly controls whether the HDR profile pointer reaches the shadow render path. A minimal correction is: const auto profileSource = hdr ? m_nextState->hdrColorProfileSource : m_nextState->colorProfileSource; 2. KWin does not fall back from B2A1 to B2A0. The tested Argyll profile contains B2A0 but no B2A1. IccProfile::BToATag() returns null for relative and absolute colorimetric intents when B2A1 is absent. KWin then uses matrix/TRC instead of the profile cLUT. ICC transform precedence requires trying B2A0 before falling back to matrix/TRC when the intent-specific transform is unavailable. BToATag() should return B2A0 when B2A1 is absent. 3. The ICC shader rejects this Argyll mft2 BToA transform. parseBToATag() prepends a ColorMultiplier for ICC XYZ encoding. LittleCMS removes the identity matrix stage from this Argyll mft2 tag, leaving these operations: ColorMultiplier, ColorTransformation, ColorLUT3D, ColorTransformation IccShader::setProfile() accepts an optional ColorTransformation or ColorMatrix first, but not ColorMultiplier. It leaves the operations unconsumed, returns false, and KWin repeatedly logs: Could not represent ICC profile in the ICC shader! I confirmed this with a B2A1 probe profile whose cLUT forces solid red. The displayed patch remained neutral while the journal logged the shader error every frame. KWin's existing Framework 13 LUT test does not expose this because that test profile has a non-identity matrix stage, which absorbs the initial multiplier during ColorPipeline optimization. The shader can handle this case by consuming an initial ColorMultiplier and left-multiplying it into toXYZD50 before the B curves. setUniforms() also should not silently continue with stale or default shader state when setProfile() fails for an ordinary profile. ICC v2.2 does not have a named ST 2084 transfer-function tag. The profile represents the device transform with sampled curves and the cLUT. That limitation does not explain this failure: applying this same B2A0 cLUT directly in the PGenerator+ Native Wayland application measured the expected PQ response, including 20%=2.42, 30%=10.03, 50%=92.54 and 100%=307.73 cd/m2. With KWin profile handling, the same patches measured 30%=0.76, 50%=51.43 and 100%=307.87 cd/m2. I prepared a small candidate source patch for the wrong HDR source check, B2A0 fallback and initial ColorMultiplier handling. It passes git diff checks. I could not compile KWin on the development machine because it does not have the required ECM 6.26 development package.
One more relevant profile-format detail: the profile attached to this report is ICC v2.2, so it cannot contain the CICP tag added in ICC.1:2022 profile version 4.4. ICC v4.4 CICP can identify a display profile's data encoding as Rec.2020 PQ RGB and specify full or narrow range. For example, 9-16-0-1 identifies full-range Rec.2020 PQ RGB. CICP identifies primaries, transfer characteristics, matrix coefficients and range. It does not define display peak luminance or replace the profile transform. I searched KWin 6.7.4 and current master and found no CICP parsing. The HDR ICC path currently constructs the profile-side transfer function as gamma 2.2 in DrmOutput::createColorDescription(). This means that generating a valid v4.4 profile with a PQ CICP tag would not currently help because KWin would ignore the tag. This looks like an additional missing part of HDR display-profile support, separate from the three bugs described above. KWin likely needs to read CICP for v4.4 display profiles and use it to describe the profile's device encoding. We can generate a v4.4 Rec.2020 PQ test profile for validation once there is a code path that consumes the tag.
Could a Bugzilla administrator please remove the final paragraph of comment 6? It contains irrelevant information about the development environment and is not needed for this report.
> LittleCMS removes the identity matrix stage from this Argyll mft2 tag, leaving these operations: I think it's our own ColorPipeline code that optimizes things there. For 6.9, I hope to finally finish https://invent.kde.org/plasma/kwin/-/merge_requests/6743, which would make it impossible to overlook such problems. Either way, thanks for the thorough bug report.
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin/-/merge_requests/9824
I think the commits should cherry-pick cleanly to 6.7. It would be great if you could test them; on my monitor, the ICC profile you provided still results in somewhat weird colors, but that may just be from the profile not matching the screen.
Git commit eb6a57fba628f700e2046dafc3367677ee29ce6a by Xaver Hugl. Committed on 28/08/2026 at 12:47. Pushed by zamundaaa into branch 'master'. core/iccprofile: fall back to BToA0 if BToA1 isn't available The spec says to fall back to BToA0 if the tag for the used rendering intent can't be used, and to fall back to matrix+shaper if that can't be used either (which we do already). M +7 -1 src/core/iccprofile.cpp https://invent.kde.org/plasma/kwin/-/commit/eb6a57fba628f700e2046dafc3367677ee29ce6a
Git commit b23f4973e3747e00b2a8fa63a45d78142f1c7c2f by Xaver Hugl. Committed on 28/08/2026 at 12:47. Pushed by zamundaaa into branch 'master'. backends/drm: fix a color profile source check It always checked the SDR source, rather than the correct one for the current mode. M +4 -4 src/backends/drm/drm_output.cpp https://invent.kde.org/plasma/kwin/-/commit/b23f4973e3747e00b2a8fa63a45d78142f1c7c2f
Git commit 59ae700f62d68483f6154354fc3661437c0bb468 by Xaver Hugl. Committed on 28/08/2026 at 12:47. Pushed by zamundaaa into branch 'master'. opengl/icc_shader: handle multipliers, too ColorPipeline automatically optimizes scaling-only matrices to a multiplier, which is normally very helpful, but in this case caused the shader code to break. M +4 -0 src/opengl/icc_shader.cpp https://invent.kde.org/plasma/kwin/-/commit/59ae700f62d68483f6154354fc3661437c0bb468
Git commit 7bd406d40b35a1c2f8d79cf6030d39089acca13c by Xaver Hugl. Committed on 28/08/2026 at 13:04. Pushed by zamundaaa into branch 'Plasma/6.7'. backends/drm: fix a color profile source check It always checked the SDR source, rather than the correct one for the current mode. M +4 -4 src/backends/drm/drm_output.cpp https://invent.kde.org/plasma/kwin/-/commit/7bd406d40b35a1c2f8d79cf6030d39089acca13c
Git commit 946df15efeb1f2ebb5a679aecd5a2d36ebf8453d by Xaver Hugl. Committed on 28/08/2026 at 13:04. Pushed by zamundaaa into branch 'Plasma/6.7'. opengl/icc_shader: handle multipliers, too ColorPipeline automatically optimizes scaling-only matrices to a multiplier, which is normally very helpful, but in this case caused the shader code to break. M +4 -0 src/opengl/icc_shader.cpp https://invent.kde.org/plasma/kwin/-/commit/946df15efeb1f2ebb5a679aecd5a2d36ebf8453d
Git commit a8ee8e9f6a1873b633ba7ff5361b366573aa26a6 by Xaver Hugl. Committed on 28/08/2026 at 13:04. Pushed by zamundaaa into branch 'Plasma/6.7'. core/iccprofile: fall back to BToA0 if BToA1 isn't available The spec says to fall back to BToA0 if the tag for the used rendering intent can't be used, and to fall back to matrix+shaper if that can't be used either (which we do already). M +7 -1 src/core/iccprofile.cpp https://invent.kde.org/plasma/kwin/-/commit/a8ee8e9f6a1873b633ba7ff5361b366573aa26a6