[client] wayland: forward HDR mastering metadata

Propagate valid ST 2086 mastering primaries and luminance data, MaxCLL,
and MaxFALL to the Wayland compositor using the protocol's expected
units.
This commit is contained in:
Geoffrey McRae
2026-07-19 02:15:48 +10:00
parent 1f6dbb0dae
commit ad5d7753e8
5 changed files with 36 additions and 25 deletions

View File

@@ -82,7 +82,8 @@ static void applyHDRPending(void)
wlWm.pendingHDRMinDisplayLuminance,
wlWm.pendingHDRMaxCLL,
wlWm.pendingHDRMaxFALL,
wlWm.pendingHDRPQ);
wlWm.pendingHDRPQ,
wlWm.pendingHDRMetadata);
}
else if (wlWm.pendingHDRClear)
{
@@ -200,7 +201,7 @@ EGLNativeWindowType waylandGetEGLNativeWindow(void)
void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
bool hdrPQ)
bool hdrPQ, bool hdrMetadata)
{
if (!wlWm.colorManager)
return;
@@ -294,26 +295,32 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
wlWm.hdrWhiteLevels.pq :
wlWm.hdrWhiteLevels.scRGB);
// 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)
// KVMFR uses the ST 2086/DXGI scale of 50,000 units per coordinate while
// color-management-v1 uses 1,000,000 units per coordinate.
if (hdrPQ && hdrMetadata && wlWm.cmHasMasteringPrimaries)
{
wp_image_description_creator_params_v1_set_mastering_display_primaries(
wlWm.hdrImageCreator,
displayPrimary[0][0], displayPrimary[0][1],
displayPrimary[1][0], displayPrimary[1][1],
displayPrimary[2][0], displayPrimary[2][1],
whitePoint[0], whitePoint[1]);
displayPrimary[0][0] * 20, displayPrimary[0][1] * 20,
displayPrimary[1][0] * 20, displayPrimary[1][1] * 20,
displayPrimary[2][0] * 20, displayPrimary[2][1] * 20,
whitePoint[0] * 20, whitePoint[1] * 20);
// 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.
if ((uint64_t)maxDisplayLuminance * 10000 > minDisplayLuminance)
wp_image_description_creator_params_v1_set_mastering_luminance(
wlWm.hdrImageCreator,
minDisplayLuminance, maxDisplayLuminance);
}
if (hdrPQ && hdrMetadata)
{
if (maxCLL > 0)
wp_image_description_creator_params_v1_set_max_cll(
wlWm.hdrImageCreator, maxCLL);
if (maxFALL > 0 && (maxCLL == 0 || maxFALL <= maxCLL))
wp_image_description_creator_params_v1_set_max_fall(
wlWm.hdrImageCreator, maxFALL);
}
wlWm.hdrImageDesc =
wp_image_description_creator_params_v1_create(wlWm.hdrImageCreator);
@@ -349,7 +356,7 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
bool waylandRequestHDR(const uint16_t displayPrimary[3][2],
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
bool hdrPQ)
bool hdrPQ, bool hdrMetadata)
{
if (!wlWm.cmFeaturesDone || !wlWm.cmHasParametric)
return false;
@@ -363,6 +370,7 @@ bool waylandRequestHDR(const uint16_t displayPrimary[3][2],
return false;
wlWm.pendingHDRPQ = hdrPQ;
wlWm.pendingHDRMetadata = hdrMetadata;
// Write all metadata before setting the apply flag to ensure the
// render thread sees the complete HDR metadata when it observes

View File

@@ -268,7 +268,7 @@ static bool waylandHDRCallback(const void * rendererFormat)
return waylandRequestHDR(format->hdrDisplayPrimary, format->hdrWhitePoint,
format->hdrMaxDisplayLuminance, format->hdrMinDisplayLuminance,
format->hdrMaxContentLightLevel, format->hdrMaxFrameAverageLightLevel,
format->hdrPQ);
format->hdrPQ, format->hdrMetadata);
else
waylandRequestClearHDR();
return true;

View File

@@ -221,6 +221,7 @@ struct WaylandDSState
_Atomic(bool) pendingHDRApply;
_Atomic(bool) pendingHDRClear;
bool pendingHDRPQ;
bool pendingHDRMetadata;
uint16_t pendingHDRDisplayPrimary[3][2];
uint16_t pendingHDRWhitePoint[2];
uint32_t pendingHDRMaxDisplayLuminance;
@@ -316,7 +317,7 @@ void waylandColorMgmtFree(void);
void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
bool hdrPQ);
bool hdrPQ, bool hdrMetadata);
void waylandClearHDRImageDescription(void);
// Queue HDR change from any thread (applied in waylandEGLSwapBuffers).
@@ -325,7 +326,7 @@ void waylandClearHDRImageDescription(void);
bool waylandRequestHDR(const uint16_t displayPrimary[3][2],
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
bool hdrPQ);
bool hdrPQ, bool hdrMetadata);
void waylandRequestClearHDR(void);
// idle module

View File

@@ -74,6 +74,7 @@ typedef struct LG_RendererFormat
FrameType type; // frame type
bool hdr; // if the frame is HDR or not
bool hdrPQ; // if the HDR content is PQ mapped
bool hdrMetadata; // if the HDR static metadata is valid
unsigned int screenWidth; // actual width of the host
unsigned int screenHeight; // actual height of the host
unsigned int dataWidth; // the width of the packed data
@@ -85,7 +86,7 @@ typedef struct LG_RendererFormat
unsigned int bpp; // bits per pixel (zero if compressed)
LG_RendererRotate rotate; // guest rotation
// HDR static metadata (from KVMFRFrame, valid when hdr is true)
// HDR static metadata (from KVMFRFrame, valid when hdrMetadata is true)
uint16_t hdrDisplayPrimary[3][2];
uint16_t hdrWhitePoint[2];
uint32_t hdrMaxDisplayLuminance;

View File

@@ -641,10 +641,11 @@ int main_frameThread(void * unused)
lgrFormat.pitch = frame->pitch;
lgrFormat.hdr = frame->flags & FRAME_FLAG_HDR;
lgrFormat.hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ;
lgrFormat.hdrMetadata = frame->flags & FRAME_FLAG_HDR_METADATA;
lgrFormat.sdrWhiteLevel = frame->sdrWhiteLevel ?
frame->sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;
if (lgrFormat.hdr)
if (lgrFormat.hdrMetadata)
{
memcpy(lgrFormat.hdrDisplayPrimary, frame->hdrDisplayPrimary, sizeof(lgrFormat.hdrDisplayPrimary));
memcpy(lgrFormat.hdrWhitePoint , frame->hdrWhitePoint , sizeof(lgrFormat.hdrWhitePoint ));