diff --git a/client/displayservers/Wayland/gl.c b/client/displayservers/Wayland/gl.c index 22c8d541..883fbc1e 100644 --- a/client/displayservers/Wayland/gl.c +++ b/client/displayservers/Wayland/gl.c @@ -280,20 +280,20 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2], hdrPQ ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ : WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); - // Set luminance range from metadata. - // min_lum is in 0.0001 cd/m² units (multiplied by 10000 as per protocol). - // max_lum and reference_lum are in unscaled cd/m² units. + // Set the primary colour volume luminances. + // min_lum : 0.0001 cd/m² (source already scaled) -> pass through + // max_lum : cd/m² (used only for scRGB; ignored for PQ, where the + // compositor forces it to min_lum + 10000 cd/m²) + // reference_lum : cd/m² -> BT.2408 diffuse white (203 PQ / 80 scRGB) if (wlWm.cmHasLuminances) wp_image_description_creator_params_v1_set_luminances( wlWm.hdrImageCreator, - minDisplayLuminance > 0 ? minDisplayLuminance / 10000 : 50, - maxDisplayLuminance > 0 ? maxDisplayLuminance / 10000 : 1000, - hdrPQ ? 203 : 80); + minDisplayLuminance > 0 ? minDisplayLuminance : 50, + maxDisplayLuminance > 0 ? maxDisplayLuminance : 1000, + hdrPQ ? 203 : 80); - // Set mastering display primaries from frame HDR metadata. - // Always set when the compositor supports it, falling back to the - // primaries from the metadata (even if zero, in which case the - // compositor uses its own defaults). + // Advertise the content primaries (BT.2020 for PQ). These describe the real + // colour gamut of the signal and are safe to forward. if (wlWm.cmHasMasteringPrimaries) wp_image_description_creator_params_v1_set_mastering_display_primaries( wlWm.hdrImageCreator, @@ -302,10 +302,16 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2], displayPrimary[2][0], displayPrimary[2][1], whitePoint[0], whitePoint[1]); - if (maxCLL > 0) - wp_image_description_creator_params_v1_set_max_cll(wlWm.hdrImageCreator, maxCLL); - if (maxFALL > 0) - wp_image_description_creator_params_v1_set_max_fall(wlWm.hdrImageCreator, maxFALL); + // NOTE: we deliberately do NOT forward the host's mastering luminance range, + // MaxCLL or MaxFALL. Those values describe the *virtual display* the IDD + // advertises to Windows (~1000 cd/m² from our EDID), not the content. LG + // captures the full-range scRGB desktop composition and passes it through to + // PQ without tone mapping, so the signal spans the entire PQ range (scRGB + // 125.0 == 10000 cd/m²). Advertising the virtual display's peak as the + // content peak makes the compositor place its tone-mapping knee there and + // hard-clip everything brighter, crushing highlights. Leaving these unset + // lets the content be treated as standard full-range PQ so the compositor + // tone-maps it to the actual physical display. wlWm.hdrImageDesc = wp_image_description_creator_params_v1_create(wlWm.hdrImageCreator); @@ -332,8 +338,10 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2], WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); wlWm.hdrActive = true; - DEBUG_INFO("HDR image description set on surface (%s, %s, %u nits)", - hdrPQ ? "PQ" : "scRGB", hdrPQ ? "BT.2020" : "sRGB", maxDisplayLuminance); + DEBUG_INFO("HDR image description set on surface (%s, %s, " + "maxLum:%u cd/m² minLum:%u (0.0001 cd/m²) maxCLL:%u maxFALL:%u)", + hdrPQ ? "PQ" : "scRGB", hdrPQ ? "BT.2020" : "sRGB", + maxDisplayLuminance, minDisplayLuminance, maxCLL, maxFALL); } bool waylandRequestHDR(const uint16_t displayPrimary[3][2], diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 01c12f29..29190f7a 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -167,7 +167,11 @@ typedef struct KVMFRFrame // Display color primaries in 0.00002 units (SMPTE ST 2086 format) uint16_t hdrDisplayPrimary[3][2]; // Rx,Ry, Gx,Gy, Bx,By uint16_t hdrWhitePoint[2]; // Wx, Wy - uint32_t hdrMaxDisplayLuminance; // Max mastering display luminance (0.0001 cd/m²) + // Mastering display luminances follow SMPTE ST 2086 units: the maximum is + // in whole cd/m², the minimum in 0.0001 cd/m². (Note: the DXGI docs + // describe MaxMasteringLuminance as 0.0001 cd/m², but IddCx/ST 2086 provide + // it in whole cd/m².) + uint32_t hdrMaxDisplayLuminance; // Max mastering display luminance (cd/m²) uint32_t hdrMinDisplayLuminance; // Min mastering display luminance (0.0001 cd/m²) uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 763f4587..bc1e4ae0 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -396,8 +396,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer // D65 white point (in 0.00002 units): srcFormat.whitePoint[0] = 15635; srcFormat.whitePoint[1] = 16450; - // 80 cd/m² display, 0.005 cd/m² black (in 0.0001 cd/m² units): - srcFormat.maxDisplayLuminance = 800000; + // Mastering luminances follow SMPTE ST 2086 units: max in whole cd/m², + // min in 0.0001 cd/m². 80 cd/m² display, 0.005 cd/m² black: + srcFormat.maxDisplayLuminance = 80; srcFormat.minDisplayLuminance = 50; // Content light levels unknown: srcFormat.maxContentLightLevel = 0;