Skip to content

feat(linux/wlgrab): report HDR from the output's image description - #5615

Open
superuser404notfound wants to merge 1 commit into
LizardByte:masterfrom
superuser404notfound:wlgrab-hdr
Open

feat(linux/wlgrab): report HDR from the output's image description#5615
superuser404notfound wants to merge 1 commit into
LizardByte:masterfrom
superuser404notfound:wlgrab-hdr

Conversation

@superuser404notfound

Copy link
Copy Markdown

What this changes

display_t::is_hdr() defaults to false in platform/common.h and is overridden in two places: kmsgrab.cpp, from the connector's HDR_OUTPUT_METADATA property, and pipewire.cpp, from the SPA colorimetry. wlgrab.cpp overrides neither, so colorspace_from_client_config() always takes the SDR branch:

if (config.dynamicRange > 0 && hdr_display) {
  colorspace.colorspace = colorspace_e::bt2020;
} else {
  ...
}

A client that asks for HDR over capture = wlr therefore gets a 10-bit encode of Rec. 709 and is correctly told HDR is off.

That is the right answer today, because the backend has no way to know better. It does not have to stay that way: colour-management-v1 publishes the output's whole image description to any client that asks, it carries more than the DRM property does, and it is available on a virtual output exactly as on a physical one.

So this binds wp_color_manager_v1, reads the captured output's image description once when the capture is set up — which is also every moment the answer could have changed, since the encode session is rebuilt on reconnect and on reinit — and answers is_hdr() and get_hdr_metadata() from it. It is the job pipewire.cpp already does with the SPA colorimetry, over the Wayland protocol instead.

Bound at version 1 deliberately: everything read here is in the first version, and a later one only adds events that would need handlers.

Fallback

A compositor without the protocol, or one that will not describe the output, leaves the colour unknown, which reads as SDR everywhere it is used. That is the behaviour every wlroots compositor had before this existed, so nothing regresses.

Testing

Compiles at -Wall -Werror against protocol headers generated from wayland-protocols 1.41 and 1.49, which is version 1 and version 3 of wp_color_manager_v1; the designated initialisers are there so it survives both, since 1.49 adds a ready2 event and grows the listener struct.

The metadata conversion is checked against BT.2020's red primary, 0.708 and 0.292, which has to come out as 35400 and 14600 in SS_HDR_METADATA's units.

On hardware: a headless sway 1.12 session on the Vulkan renderer, in an Incus container, on an RTX 4080 under the proprietary driver, streaming to a Moonlight client with HDR enabled.

Info: Output colour: primaries 6, transfer function 11 (HDR)
Info: Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ)
Info: Color depth: 10-bit

The picture is correct, checked by eye and not only in the log. The output was at 2250x1206 at 120 Hz, adopted from the client mid-stream, so HDR survives a mode change.

One dependency worth stating

This reports what the compositor says. For a headless output to be able to say anything, wlroots has to accept that a virtual output can present BT.2020 and PQ, which it does not today — the capability is read from a monitor's EDID and a headless output has no monitor. That is a ten-line change and is in flight separately at https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5453.

On a compositor whose output can already be in HDR, this change stands on its own.

@superuser404notfound

Copy link
Copy Markdown
Author

Pushed a revision for the SonarCloud gate. Of the 26 findings, 10 were fair and are fixed; the other 16 are the shape of the Wayland C protocol and are marked rather than worked around.

Fixed

  • cpp:S1186, eight empty methods: each now says inside the body why it is empty. Five are the wp_color_manager_v1 capability events, which have to exist because libwayland dispatches straight into the listener table and a null entry for an event the compositor does send is a crash rather than a no-op. Three are image-description events that carry the primary colour volume rather than the mastering display metadata SS_HDR_METADATA wants.
  • cpp:S5945, two C-style arrays: both are std::array now, which also let the eight coordinates be assigned in one line instead of a loop.

Marked with NOSONAR, following the convention already in the tree

  • cpp:S5008, 14× void *: the first parameter of every Wayland listener callback is void *data by definition of the C protocol. It cannot be given a meaningful type without breaking the ABI libwayland calls through.
  • cpp:S107, 2× ten parameters: primaries and target_primaries deliver eight CIE coordinates plus the proxy and the user pointer. The event's shape is fixed by the protocol xml.

Re-checked after the change: still compiles at -Wall -Werror against protocol headers generated from wayland-protocols 1.41 and 1.49, the BT.2020 red primary still converts to 35400,14600, and clang-format --dry-run -Werror is clean on all three files.

The two Read the Docs failures look unrelated to this change: both builds end in Timeout waiting for check runs with Check runs count: 0, which is what happens when a pull request comes from a fork whose workflows have not been approved yet.

display_t::is_hdr() defaults to false in platform/common.h and is
overridden in two places: kmsgrab, from the connector's
HDR_OUTPUT_METADATA property, and the PipeWire path, from the SPA
colorimetry. wlgrab overrides neither, so colorspace_from_client_config()
always takes the SDR branch and a client asking for HDR over
capture = wlr receives a 10-bit encode of Rec. 709.

That is the right answer today, because the backend has no way to know
better. It does not have to stay that way: colour-management-v1 publishes
the output's whole image description to any client that asks, it carries
more than the DRM property does, and it is available on a virtual output
exactly as it is on a physical one.

So bind wp_color_manager_v1, read the captured output's image description
once when the capture is set up, and answer is_hdr() and
get_hdr_metadata() from it. This is the job pipewire.cpp already does with
the SPA colorimetry, over the Wayland protocol instead.

Bound at version 1 deliberately: everything read here is in the first
version, and a later one only adds events that would need handlers.

The unit conversions are commented where they happen. The protocol carries
CIE 1931 coordinates multiplied by a million and SS_HDR_METADATA carries
them multiplied by fifty thousand; the luminances already agree, minimum
in ten-thousandths of a nit and the rest in whole nits.

A compositor without the protocol, or one that will not describe the
output, leaves the colour unknown, which reads as SDR everywhere it is
used - the behaviour every wlroots compositor had before this existed.
@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant