Repository navigation
HDR outputs are always captured as sRGB, even for colour-management-aware screencopy clients #16412
Replies: 2 comments
|
This discussion has been automatically closed. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Hi! I've been trying to stream an HDR desktop from Hyprland with Sunshine (to Moonlight on a Mac), using a headless output as the stream target. I got it working in the end, but only after patching Hyprland in a few places, so I wanted to share what I found and ask how you'd prefer this to be handled before I open a PR.
Setup
mainand the relevant code is unchanged; line numbers below are frommain.wp_color_manager_v1and reads the captured output's image description overwlr-screencopy. If the output is BT.2020 + PQ, it sends HDR10 to the client.hyprctl output create headless), switched to HDR with amonitorv2block (supports_hdr = 1,bitdepth = 10,cm = hdr, plus explicit luminances since there's no EDID).Steps to reproduce
cm = hdr,bitdepth = 10.wp_color_manager_v1and treats the frame according to the output's image description (patched Sunshine above; any CM-aware client should show the same thing).Expected: the client gets the frame in the output's own colour space (PQ / BT.2020), since it asked about it through the colour-management protocol and was told the output is HDR.
Observed: the client gets sRGB-encoded pixels. Because the output advertises PQ, the client decodes them as PQ: everything is far too bright and oversaturated, and warm whites shift towards red. A non-CM-aware client like
grimgets plain sRGB (whites at 255), which is fine for it.What I found
There are three places where the capture ends up in sRGB no matter what the client supports:
src/managers/screenshare/ScreenshareFrame.cpp:copyDmabuf()(L405) andcopyShm()(L446) always set the target framebuffer's image description toDEFAULT_SRGB_IMAGE_DESCRIPTION.renderMonitor()already computesIS_CM_AWAREand passescmBackToSRGB = !IS_CM_AWARE, but because the target FB description is set explicitly,renderTextureuses it (OpenGL.cpp,TARGET_IMAGE_DESCRIPTION), and the frame gets converted to sRGB anyway.src/output/MonitorResources.cpp:getMirrorTexImageDescription()(L202–207) returnsDEFAULT_SRGB_IMAGE_DESCRIPTIONfor anything that isn't sRGB/gamma 2.2, so the capture mirror of a PQ output is already SDR before the screenshare code runs.src/output/Monitor.cpp:needsUnmodifiedCopy()(L2659) returns true for a PQ output with non-default SDR settings whenever a copy FB is needed, even withrender:keep_unmodified_copy = 0. The unmodified copy is written bydoColorManagement()incm_helpers.glslwith the sRGB transfer function andSDR_MAX_LUMINANCE(80 nits). So a capture client never seessdr_max_luminance/sdrbrightness, and SDR content inside HDR looks very dull.On top of that, two defaults got in the way:
misc:screencopy_force_8bdefaults to true, so the capture of a 10-bit output is 8-bit.render:use_fp16 = 2(auto), a PQ output is composed in a linear FP16 work buffer. When the capture mirror is filled from it (saveBufferForMirror), the result is washed out with bluish blacks; the linear→PQ conversion there doesn't seem to scale the same way the final output pass does (doColorManagementskipstoNit()for a linear source). Withuse_fp16 = 0the work buffer is PQ and the copy is exact.What I did
I added an opt-in
misc:screencopy_hdr_passthrough(default false), which:getMirrorTexImageDescription()return the output's own description;copyDmabuf()/copyShm()when the client is CM-aware (isClientCMAware); others still get sRGB, as now;needsUnmodifiedCopy()return false, so the capture sees the frame as displayed, SDR adjustments included.Patch (against 0.56.2, +36/−5): https://github.com/Jhonfel/sunshine-hyprland-virtual-display/blob/feat/client-resolution/patches/hyprland-0.56.2-screencopy-hdr-passthrough.patch
With this,
screencopy_force_8b = falseanduse_fp16 = 0, I get correct HDR10 end to end. Colours match, blacks are black, and the desktop brightness followssdr_max_luminance.Questions
wp_color_manager_v1and queries the output presumably expects the output's colour space, butwlr-screencopyitself has no way to say which description the buffer is in, so I kept it opt-in to be safe.mainand open a PR.Related: #13831 (the opposite direction, HDR→SDR for non-CM-aware clients), #14931 and #10953 (screenshots and screenshare with HDR).
Thanks for all the work on colour management, by the way. Most of the pieces were already there.
All reactions