[client] wayland: harden native HDR colour state

Serialize asynchronous image-description state and publish compositor
capabilities safely across the Wayland and render threads.

Use the predefined Windows-scRGB description, query each output image
description for its configured reference white, and reapply HDR when the
surface moves to an output with different colour settings.
This commit is contained in:
Geoffrey McRae
2026-07-19 04:42:46 +10:00
parent 70b795c5ba
commit 99ab6448f9
6 changed files with 391 additions and 39 deletions

View File

@@ -45,6 +45,9 @@ static void cmFeature(void * data, struct wp_color_manager_v1 * cm, uint32_t fea
case WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES:
wlWm.cmHasMasteringPrimaries = true;
break;
case WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB:
wlWm.cmHasWindowsSCRGB = true;
break;
default:
break;
}
@@ -69,18 +72,22 @@ static void cmPrimariesNamed(void * data, struct wp_color_manager_v1 * cm,
static void cmDone(void * data, struct wp_color_manager_v1 * cm)
{
wlWm.cmFeaturesDone = true;
wlWm.cmCanDoHDR = wlWm.cmHasParametric &&
((wlWm.cmHasTFSt2084PQ && wlWm.cmHasPrimariesBT2020) ||
(wlWm.cmHasTFExtLinear && wlWm.cmHasPrimariesSRGB));
const bool canPQ = wlWm.cmHasPerceptualIntent && wlWm.cmHasParametric &&
wlWm.cmHasTFSt2084PQ && wlWm.cmHasPrimariesBT2020;
atomic_store_explicit(&wlWm.cmCanDoHDR,
canPQ || (wlWm.cmHasPerceptualIntent && wlWm.cmHasWindowsSCRGB),
memory_order_relaxed);
atomic_store_explicit(&wlWm.cmFeaturesDone, true, memory_order_release);
DEBUG_INFO("Color management features: parametric:%d luminances:%d "
"mastering_primaries:%d st2084_pq:%d ext_linear:%d bt2020:%d srgb:%d "
"windows_scrgb:%d perceptual:%d "
"can_do_hdr:%d",
wlWm.cmHasParametric, wlWm.cmHasLuminances,
wlWm.cmHasMasteringPrimaries,
wlWm.cmHasTFSt2084PQ, wlWm.cmHasTFExtLinear,
wlWm.cmHasPrimariesBT2020, wlWm.cmHasPrimariesSRGB,
wlWm.cmCanDoHDR);
wlWm.cmHasWindowsSCRGB, wlWm.cmHasPerceptualIntent,
atomic_load(&wlWm.cmCanDoHDR));
}
static const struct wp_color_manager_v1_listener cmListener = {
@@ -97,6 +104,7 @@ bool waylandColorMgmtInit(void)
return true;
wp_color_manager_v1_add_listener(wlWm.colorManager, &cmListener, NULL);
waylandOutputColorMgmtInitAll();
return true;
}
@@ -105,6 +113,7 @@ void waylandColorMgmtFree(void)
if (!wlWm.colorManager)
return;
LG_LOCK(wlWm.hdrLock);
if (wlWm.hdrImageCreator)
{
wp_image_description_creator_params_v1_destroy(wlWm.hdrImageCreator);
@@ -121,6 +130,7 @@ void waylandColorMgmtFree(void)
wp_color_management_surface_v1_destroy(wlWm.colorSurface);
wlWm.colorSurface = NULL;
}
LG_UNLOCK(wlWm.hdrLock);
wp_color_manager_v1_destroy(wlWm.colorManager);
wlWm.colorManager = NULL;

View File

@@ -98,6 +98,7 @@ static void applyHDRPending(void)
void waylandClearHDRImageDescription(void)
{
LG_LOCK(wlWm.hdrLock);
if (wlWm.hdrImageCreator)
{
wp_image_description_creator_params_v1_destroy(wlWm.hdrImageCreator);
@@ -114,19 +115,25 @@ void waylandClearHDRImageDescription(void)
wp_color_management_surface_v1_unset_image_description(wlWm.colorSurface);
atomic_store(&wlWm.hdrActive, false);
LG_UNLOCK(wlWm.hdrLock);
DEBUG_INFO("HDR image description removed from surface");
}
static void hdrImageDescriptionReady(struct wp_image_description_v1 * desc)
{
LG_LOCK(wlWm.hdrLock);
if (desc != wlWm.hdrImageDesc)
{
LG_UNLOCK(wlWm.hdrLock);
return;
}
if (!wlWm.colorSurface)
{
DEBUG_WARN("HDR image description became ready without a color surface");
wp_image_description_v1_destroy(desc);
wlWm.hdrImageDesc = NULL;
LG_UNLOCK(wlWm.hdrLock);
return;
}
@@ -134,16 +141,22 @@ static void hdrImageDescriptionReady(struct wp_image_description_v1 * desc)
// then switch to native HDR for the next render, and that native frame and
// its image description become active in the same following commit.
wlWm.hdrImageDescReady = true;
const bool pq = wlWm.hdrImageDescPQ;
LG_UNLOCK(wlWm.hdrLock);
DEBUG_INFO("HDR image description is ready (%s)",
wlWm.hdrImageDescPQ ? "PQ" : "scRGB");
pq ? "PQ" : "scRGB");
app_invalidateWindow(true);
waylandStopWaitFrame();
}
static void activateReadyHDRImageDescription(void)
{
LG_LOCK(wlWm.hdrLock);
if (!wlWm.hdrImageDesc || !wlWm.hdrImageDescReady || !wlWm.colorSurface)
{
LG_UNLOCK(wlWm.hdrLock);
return;
}
struct wp_image_description_v1 * desc = wlWm.hdrImageDesc;
wp_color_management_surface_v1_set_image_description(
@@ -157,9 +170,11 @@ static void activateReadyHDRImageDescription(void)
wlWm.hdrImageDesc = NULL;
wlWm.hdrImageDescReady = false;
wp_image_description_v1_destroy(desc);
const bool pq = atomic_load(&wlWm.hdrActivePQ);
LG_UNLOCK(wlWm.hdrLock);
DEBUG_INFO("HDR image description pending next surface commit (%s)",
atomic_load(&wlWm.hdrActivePQ) ? "PQ" : "scRGB");
pq ? "PQ" : "scRGB");
app_invalidateWindow(true);
waylandStopWaitFrame();
}
@@ -168,14 +183,19 @@ static void hdrImageDescriptionFailed(void * data,
struct wp_image_description_v1 * desc, uint32_t cause, const char * message)
{
(void)data;
LG_LOCK(wlWm.hdrLock);
if (desc != wlWm.hdrImageDesc)
{
LG_UNLOCK(wlWm.hdrLock);
return;
}
DEBUG_WARN("Failed to create HDR image description (cause:%u): %s",
cause, message);
wlWm.hdrImageDesc = NULL;
wlWm.hdrImageDescReady = false;
wp_image_description_v1_destroy(desc);
LG_UNLOCK(wlWm.hdrLock);
app_invalidateWindow(true);
waylandStopWaitFrame();
}
@@ -295,13 +315,19 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
if (!wlWm.colorManager)
return;
if (!wlWm.cmFeaturesDone)
if (!atomic_load_explicit(&wlWm.cmFeaturesDone, memory_order_acquire))
{
DEBUG_WARN("Color management features not yet advertised, deferring HDR");
return;
}
if (!wlWm.cmHasParametric)
if (!wlWm.cmHasPerceptualIntent)
{
DEBUG_WARN("Compositor does not support the perceptual render intent");
return;
}
if (hdrPQ && !wlWm.cmHasParametric)
{
DEBUG_WARN("Compositor does not support parametric image descriptions");
return;
@@ -313,9 +339,9 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
DEBUG_WARN("Compositor does not support ST2084_PQ transfer function");
return;
}
if (!hdrPQ && !wlWm.cmHasTFExtLinear)
if (!hdrPQ && !wlWm.cmHasWindowsSCRGB)
{
DEBUG_WARN("Compositor does not support EXT_LINEAR transfer function");
DEBUG_WARN("Compositor does not support Windows-scRGB image descriptions");
return;
}
@@ -325,11 +351,7 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
DEBUG_WARN("Compositor does not support BT.2020 primaries");
return;
}
if (!hdrPQ && !wlWm.cmHasPrimariesSRGB)
{
DEBUG_WARN("Compositor does not support sRGB primaries");
return;
}
LG_LOCK(wlWm.hdrLock);
// Cancel only an in-flight replacement. The active surface description is
// retained until this replacement is ready.
@@ -360,29 +382,49 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
if (!wlWm.colorSurface)
{
DEBUG_WARN("Failed to get color management surface");
LG_UNLOCK(wlWm.hdrLock);
return;
}
}
if (!hdrPQ)
{
wlWm.hdrImageDesc =
wp_color_manager_v1_create_windows_scrgb(wlWm.colorManager);
if (!wlWm.hdrImageDesc)
{
DEBUG_WARN("Failed to create Windows-scRGB image description");
LG_UNLOCK(wlWm.hdrLock);
return;
}
wlWm.hdrImageDescPQ = false;
wlWm.hdrImageDescReady = false;
wp_image_description_v1_add_listener(
wlWm.hdrImageDesc, &hdrImageDescListener, NULL);
LG_UNLOCK(wlWm.hdrLock);
DEBUG_INFO("HDR image description requested (scRGB, Windows-scRGB)");
return;
}
wlWm.hdrImageCreator =
wp_color_manager_v1_create_parametric_creator(wlWm.colorManager);
if (!wlWm.hdrImageCreator)
{
DEBUG_WARN("Failed to create parametric image description creator");
LG_UNLOCK(wlWm.hdrLock);
return;
}
// Set primaries: BT.2020 for PQ HDR10, sRGB for scRGB/FP16
wp_image_description_creator_params_v1_set_primaries_named(
wlWm.hdrImageCreator,
hdrPQ ? WP_COLOR_MANAGER_V1_PRIMARIES_BT2020
: WP_COLOR_MANAGER_V1_PRIMARIES_SRGB);
WP_COLOR_MANAGER_V1_PRIMARIES_BT2020);
// Select transfer function: PQ for PQ-encoded content, linear for scRGB/FP16
wp_image_description_creator_params_v1_set_tf_named(
wlWm.hdrImageCreator,
hdrPQ ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ
: WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR);
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ);
// Set the primary colour volume luminances.
// min_lum : 0.0001 cd/m² (source already scaled) -> pass through
@@ -394,9 +436,7 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
wlWm.hdrImageCreator,
minDisplayLuminance > 0 ? minDisplayLuminance : 50,
maxDisplayLuminance > 0 ? maxDisplayLuminance : 1000,
hdrPQ ?
wlWm.hdrWhiteLevels.pq :
wlWm.hdrWhiteLevels.scRGB);
atomic_load(&wlWm.hdrPQWhiteLevel));
// KVMFR uses the ST 2086/DXGI scale of 50,000 units per coordinate while
// color-management-v1 uses 1,000,000 units per coordinate.
@@ -432,6 +472,7 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
if (!wlWm.hdrImageDesc)
{
DEBUG_WARN("Failed to create HDR image description");
LG_UNLOCK(wlWm.hdrLock);
return;
}
@@ -439,6 +480,7 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
wlWm.hdrImageDescReady = false;
wp_image_description_v1_add_listener(
wlWm.hdrImageDesc, &hdrImageDescListener, NULL);
LG_UNLOCK(wlWm.hdrLock);
DEBUG_INFO("HDR image description requested (%s, %s, "
"maxLum:%u cd/m² minLum:%u (0.0001 cd/m²) maxCLL:%u maxFALL:%u)",
@@ -451,15 +493,14 @@ bool waylandRequestHDR(const uint16_t displayPrimary[3][2],
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
bool hdrPQ, bool hdrMetadata)
{
if (!wlWm.cmFeaturesDone || !wlWm.cmHasParametric)
if (!atomic_load_explicit(&wlWm.cmFeaturesDone, memory_order_acquire))
return false;
if (hdrPQ && !wlWm.cmHasTFSt2084PQ)
if (!wlWm.cmHasPerceptualIntent)
return false;
if (!hdrPQ && !wlWm.cmHasTFExtLinear)
if (hdrPQ && (!wlWm.cmHasParametric || !wlWm.cmHasTFSt2084PQ ||
!wlWm.cmHasPrimariesBT2020))
return false;
if (hdrPQ && !wlWm.cmHasPrimariesBT2020)
return false;
if (!hdrPQ && !wlWm.cmHasPrimariesSRGB)
if (!hdrPQ && !wlWm.cmHasWindowsSCRGB)
return false;
atomic_store(&wlWm.hdrRequestedPQ, hdrPQ);

View File

@@ -21,12 +21,264 @@
#include "wayland.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <unistd.h>
#include "app.h"
#include "common/debug.h"
struct OutputColorDescription
{
struct WaylandOutput * output;
struct wp_image_description_v1 * description;
struct wp_image_description_info_v1 * info;
};
static void outputColorDescriptionFree(struct OutputColorDescription * desc)
{
if (!desc)
return;
if (desc->info)
wl_proxy_destroy((struct wl_proxy *)desc->info);
if (desc->description)
wp_image_description_v1_destroy(desc->description);
if (desc->output && desc->output->colorDescription == desc)
desc->output->colorDescription = NULL;
free(desc);
}
static struct WaylandOutput * outputFind(struct wl_output * output)
{
struct WaylandOutput * node;
wl_list_for_each(node, &wlWm.outputs, link)
if (node->output == output)
return node;
return NULL;
}
void waylandOutputUpdateHDRWhiteLevel(void)
{
uint32_t pqWhite = 203;
uint32_t scRGBWhite = 80;
struct SurfaceOutput * surfaceOutput;
wl_list_for_each(surfaceOutput, &wlWm.surfaceOutputs, link)
{
struct WaylandOutput * output = outputFind(surfaceOutput->output);
if (!output || !output->referenceWhiteValid)
continue;
pqWhite = scRGBWhite = output->referenceWhiteLevel;
break;
}
const uint32_t oldPQ = atomic_exchange(&wlWm.hdrPQWhiteLevel, pqWhite);
const uint32_t oldScRGB =
atomic_exchange(&wlWm.hdrScRGBWhiteLevel, scRGBWhite);
if ((oldPQ != pqWhite || oldScRGB != scRGBWhite) && wlWm.frameEvent)
{
DEBUG_INFO("Wayland output reference white: %u cd/m²", pqWhite);
if (atomic_load(&wlWm.hdrRequested))
{
LG_LOCK(wlWm.pendingHDRLock);
wlWm.pendingHDRAction = WAYLAND_HDR_PENDING_APPLY;
LG_UNLOCK(wlWm.pendingHDRLock);
}
app_invalidateWindow(true);
waylandStopWaitFrame();
}
}
static void outputInfoDone(void * data,
struct wp_image_description_info_v1 * info)
{
struct OutputColorDescription * desc = data;
desc->info = NULL; // the protocol destroys it after this event
outputColorDescriptionFree(desc);
}
static void outputInfoICCFile(void * data,
struct wp_image_description_info_v1 * info, int32_t fd, uint32_t size)
{
close(fd);
}
static void outputInfoPrimaries(void * data,
struct wp_image_description_info_v1 * info,
int32_t rx, int32_t ry, int32_t gx, int32_t gy,
int32_t bx, int32_t by, int32_t wx, int32_t wy)
{
}
static void outputInfoPrimariesNamed(void * data,
struct wp_image_description_info_v1 * info, uint32_t primaries)
{
}
static void outputInfoTFPower(void * data,
struct wp_image_description_info_v1 * info, uint32_t exponent)
{
}
static void outputInfoTFNamed(void * data,
struct wp_image_description_info_v1 * info, uint32_t tf)
{
}
static void outputInfoLuminances(void * data,
struct wp_image_description_info_v1 * info,
uint32_t minLum, uint32_t maxLum, uint32_t referenceLum)
{
struct OutputColorDescription * desc = data;
if (!referenceLum || desc->output->colorDescription != desc)
return;
desc->output->referenceWhiteLevel = referenceLum;
desc->output->referenceWhiteValid = true;
waylandOutputUpdateHDRWhiteLevel();
}
static void outputInfoTargetPrimaries(void * data,
struct wp_image_description_info_v1 * info,
int32_t rx, int32_t ry, int32_t gx, int32_t gy,
int32_t bx, int32_t by, int32_t wx, int32_t wy)
{
}
static void outputInfoTargetLuminance(void * data,
struct wp_image_description_info_v1 * info,
uint32_t minLum, uint32_t maxLum)
{
}
static void outputInfoTargetMaxCLL(void * data,
struct wp_image_description_info_v1 * info, uint32_t maxCLL)
{
}
static void outputInfoTargetMaxFALL(void * data,
struct wp_image_description_info_v1 * info, uint32_t maxFALL)
{
}
static const struct wp_image_description_info_v1_listener outputInfoListener =
{
.done = outputInfoDone,
.icc_file = outputInfoICCFile,
.primaries = outputInfoPrimaries,
.primaries_named = outputInfoPrimariesNamed,
.tf_power = outputInfoTFPower,
.tf_named = outputInfoTFNamed,
.luminances = outputInfoLuminances,
.target_primaries = outputInfoTargetPrimaries,
.target_luminance = outputInfoTargetLuminance,
.target_max_cll = outputInfoTargetMaxCLL,
.target_max_fall = outputInfoTargetMaxFALL,
};
static void outputDescriptionFailed(void * data,
struct wp_image_description_v1 * description,
uint32_t cause, const char * message)
{
struct OutputColorDescription * desc = data;
DEBUG_WARN("Failed to query Wayland output colour description "
"(cause:%u): %s", cause, message);
outputColorDescriptionFree(desc);
}
static void outputDescriptionReady(struct OutputColorDescription * desc)
{
desc->info = wp_image_description_v1_get_information(desc->description);
if (!desc->info)
{
outputColorDescriptionFree(desc);
return;
}
wp_image_description_info_v1_add_listener(
desc->info, &outputInfoListener, desc);
}
static void outputDescriptionReadyV1(void * data,
struct wp_image_description_v1 * description, uint32_t identity)
{
outputDescriptionReady(data);
}
static void outputDescriptionReadyV2(void * data,
struct wp_image_description_v1 * description,
uint32_t identityHi, uint32_t identityLo)
{
outputDescriptionReady(data);
}
static const struct wp_image_description_v1_listener outputDescriptionListener =
{
.failed = outputDescriptionFailed,
.ready = outputDescriptionReadyV1,
.ready2 = outputDescriptionReadyV2,
};
static void outputRequestColorDescription(struct WaylandOutput * output)
{
outputColorDescriptionFree(output->colorDescription);
output->referenceWhiteValid = false;
waylandOutputUpdateHDRWhiteLevel();
struct OutputColorDescription * desc = calloc(1, sizeof(*desc));
if (!desc)
return;
desc->output = output;
desc->description =
wp_color_management_output_v1_get_image_description(output->colorOutput);
if (!desc->description)
{
free(desc);
return;
}
output->colorDescription = desc;
wp_image_description_v1_add_listener(
desc->description, &outputDescriptionListener, desc);
}
static void outputImageDescriptionChanged(void * data,
struct wp_color_management_output_v1 * colorOutput)
{
outputRequestColorDescription(data);
}
static const struct wp_color_management_output_v1_listener colorOutputListener =
{
.image_description_changed = outputImageDescriptionChanged,
};
void waylandOutputColorMgmtInit(struct WaylandOutput * output)
{
if (!wlWm.colorManager || output->colorOutput)
return;
output->colorOutput =
wp_color_manager_v1_get_output(wlWm.colorManager, output->output);
if (!output->colorOutput)
return;
wp_color_management_output_v1_add_listener(
output->colorOutput, &colorOutputListener, output);
outputRequestColorDescription(output);
}
void waylandOutputColorMgmtInitAll(void)
{
struct WaylandOutput * output;
wl_list_for_each(output, &wlWm.outputs, link)
waylandOutputColorMgmtInit(output);
}
static void outputUpdateScale(struct WaylandOutput * node)
{
struct WaylandScale original = node->scale;
@@ -144,6 +396,9 @@ void waylandOutputFree(void)
struct WaylandOutput * temp;
wl_list_for_each_safe(node, temp, &wlWm.outputs, link)
{
outputColorDescriptionFree(node->colorDescription);
if (node->colorOutput)
wp_color_management_output_v1_destroy(node->colorOutput);
if (node->version >= 3)
wl_output_release(node->output);
if (node->xdgOutput)
@@ -188,6 +443,7 @@ void waylandOutputBind(uint32_t name, uint32_t version)
wl_output_add_listener(node->output, &outputListener, node);
wl_list_insert(&wlWm.outputs, &node->link);
waylandOutputColorMgmtInit(node);
}
void waylandOutputTryUnbind(uint32_t name)
@@ -198,12 +454,27 @@ void waylandOutputTryUnbind(uint32_t name)
{
if (node->name == name)
{
struct SurfaceOutput * surfaceOutput;
struct SurfaceOutput * temp;
wl_list_for_each_safe(
surfaceOutput, temp, &wlWm.surfaceOutputs, link)
if (surfaceOutput->output == node->output)
{
wl_list_remove(&surfaceOutput->link);
free(surfaceOutput);
}
outputColorDescriptionFree(node->colorDescription);
if (node->colorOutput)
wp_color_management_output_v1_destroy(node->colorOutput);
if (node->version >= 3)
wl_output_release(node->output);
if (node->xdgOutput)
zxdg_output_v1_destroy(node->xdgOutput);
wl_list_remove(&node->link);
free(node);
waylandWindowUpdateScale();
waylandOutputUpdateHDRWhiteLevel();
break;
}
}

View File

@@ -109,12 +109,12 @@ static bool waylandInit(const LG_DSInitParams params)
{
memset(&wlWm, 0, sizeof(wlWm));
LG_LOCK_INIT(wlWm.pendingHDRLock);
LG_LOCK_INIT(wlWm.hdrLock);
wlWm.desktop = WL_Desktops[0];
wlWm.hdrWhiteLevels = (LG_DSHDRWhiteLevels)
{
.pq = 203,
.scRGB = 80,
};
atomic_init(&wlWm.cmFeaturesDone, false);
atomic_init(&wlWm.cmCanDoHDR, false);
atomic_init(&wlWm.hdrPQWhiteLevel, 203);
atomic_init(&wlWm.hdrScRGBWhiteLevel, 80);
wlWm.display = wl_display_connect(NULL);
if (!wlWm.display)
@@ -212,6 +212,7 @@ static void waylandFree(void)
waylandRegistryFree();
waylandCursorFree();
wl_display_disconnect(wlWm.display);
LG_LOCK_FREE(wlWm.hdrLock);
LG_LOCK_FREE(wlWm.pendingHDRLock);
}
@@ -234,7 +235,11 @@ static bool waylandGetProp(LG_DSProperty prop, void * ret)
if (prop == LG_DS_HDR_WHITE_LEVELS)
{
*(LG_DSHDRWhiteLevels *)ret = wlWm.hdrWhiteLevels;
*(LG_DSHDRWhiteLevels *)ret = (LG_DSHDRWhiteLevels)
{
.pq = atomic_load(&wlWm.hdrPQWhiteLevel),
.scRGB = atomic_load(&wlWm.hdrScRGBWhiteLevel),
};
return true;
}

View File

@@ -19,6 +19,8 @@
*/
#include <stdbool.h>
#include <stdatomic.h>
#include <stdint.h>
#include <sys/types.h>
#include <wayland-client.h>
@@ -65,6 +67,8 @@ struct WaylandPoll
struct wl_list link;
};
struct OutputColorDescription;
struct WaylandOutput
{
uint32_t name;
@@ -77,6 +81,10 @@ struct WaylandOutput
bool modeRotate;
struct wl_output * output;
struct zxdg_output_v1 * xdgOutput;
struct wp_color_management_output_v1 * colorOutput;
struct OutputColorDescription * colorDescription;
uint32_t referenceWhiteLevel;
bool referenceWhiteValid;
uint32_t version;
struct wl_list link;
};
@@ -216,11 +224,12 @@ struct WaylandDSState
_Atomic(bool) hdrRequestedPQ;
bool hdrImageDescPQ;
bool hdrImageDescReady;
LG_Lock hdrLock;
// wp_color_manager_v1 feature advertisement tracking.
// Set to true after the done event for the color-manager has been received
// and all compositor capabilities have been recorded.
bool cmFeaturesDone;
_Atomic(bool) cmFeaturesDone;
bool cmHasParametric;
bool cmHasLuminances;
bool cmHasMasteringPrimaries;
@@ -228,11 +237,13 @@ struct WaylandDSState
bool cmHasTFExtLinear;
bool cmHasPrimariesBT2020;
bool cmHasPrimariesSRGB;
bool cmHasWindowsSCRGB;
bool cmHasPerceptualIntent;
bool cmCanDoHDR; // true if compositor supports features needed for HDR
_Atomic(bool) cmCanDoHDR; // compositor supports a native HDR encoding
// Reference white configured on native HDR surface image descriptions.
LG_DSHDRWhiteLevels hdrWhiteLevels;
// Local output reference whites used for native overlay composition.
_Atomic(uint32_t) hdrPQWhiteLevel;
_Atomic(uint32_t) hdrScRGBWhiteLevel;
// toplevel icon manager
struct xdg_toplevel_icon_manager_v1 * iconManager;
@@ -373,6 +384,9 @@ void waylandOutputFree(void);
void waylandOutputBind(uint32_t name, uint32_t version);
void waylandOutputTryUnbind(uint32_t name);
struct WaylandScale waylandOutputGetScale(struct wl_output * output);
void waylandOutputColorMgmtInit(struct WaylandOutput * output);
void waylandOutputColorMgmtInitAll(void);
void waylandOutputUpdateHDRWhiteLevel(void);
// poll module
bool waylandPollInit(void);

View File

@@ -76,6 +76,7 @@ static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, stru
node->output = output;
wl_list_insert(&wlWm.surfaceOutputs, &node->link);
waylandWindowUpdateScale();
waylandOutputUpdateHDRWhiteLevel();
}
static void wlSurfaceLeaveHandler(void * data, struct wl_surface * surface, struct wl_output * output)
@@ -85,9 +86,11 @@ static void wlSurfaceLeaveHandler(void * data, struct wl_surface * surface, stru
if (node->output == output)
{
wl_list_remove(&node->link);
free(node);
break;
}
waylandWindowUpdateScale();
waylandOutputUpdateHDRWhiteLevel();
}
static const struct wl_surface_listener wlSurfaceListener = {
@@ -169,6 +172,14 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen,
void waylandWindowFree(void)
{
struct SurfaceOutput * output;
struct SurfaceOutput * temp;
wl_list_for_each_safe(output, temp, &wlWm.surfaceOutputs, link)
{
wl_list_remove(&output->link);
free(output);
}
if (wlWm.fractionalScaleInterface)
wp_fractional_scale_v1_destroy(wlWm.fractionalScaleInterface);
if (wlWm.contentType)