Add Sunshine pairing diagnostics#5096
Draft
NoviceAtPython wants to merge 21 commits into
Draft
Conversation
On the CloudDeploy live VM the stream falls to AV1 Main8 / Rec.601 even
though the display is scanning out HDR PQ via the patched KWin's NVIDIA
private DRM properties. Three independent bugs in the negotiation +
encode-selection chain were keeping HDR off; fix all three.
1. kmsgrab `is_hdr()` only saw the standard connector HDR_OUTPUT_METADATA
blob. The CloudDeploy KWin patch intentionally leaves that blob at 0
(NVIDIA's atomic check rejects the standard path on the forced/
headless virtual output) and drives NVIDIA private CRTC/plane props
instead:
CRTC NV_CRTC_REGAMMA_TF = PQ
primary plane NV_INPUT_COLORSPACE = BT.2100 PQ
primary plane NV_PLANE_DEGAMMA_TF = PQ
With is_hdr() returning false, video::colorspace_from_client_config
refused to pick bt2020 PQ even when dynamicRange=1 was requested.
Patch: add nvidia_private_hdr_active() to kmsgrab's display_t. It
walks the active CRTC props for NV_CRTC_REGAMMA_TF==PQ and the
active primary plane props for NV_INPUT_COLORSPACE==BT.2100 PQ (also
accepting "BT2100 PQ" without the dot, which some nvidia-drm
builds emit). Either match returns true. is_hdr() calls this first,
so the NVIDIA private path now reports HDR even with
hdr_metadata_blob_id==0. get_hdr_metadata() guards the new branch so
the per-frame metadata fetch doesn't deref a null optional - the
encode pipeline still gets bt2020 PQ colorspace from VUI; only the
per-frame infoframe metadata stays unavailable.
2. rtsp `cmd_announce()` parsed `x-nv-video[0].dynamicRangeMode` from
the client ANNOUNCE but never reconciled it with the HTTP /launch
`hdrMode=` flag stashed in `launch_session->enable_hdr`. The HTTP
flag was only used to drive display HDR state. When Moonlight
thought the server only advertised AV1 Main8 (see point 3), it
would set dynamicRangeMode=0 even when the user had HDR on, and the
encode took the 8-bit branch.
Patch: after parsing config.monitor.dynamicRange in cmd_announce,
if session.enable_hdr is true and dynamicRange came as 0, force it
to 1 with a loud INFO log explaining the override. Also: when
SUNSHINE_FORCE_AV1_HDR10=1 is set in the environment, do the same
override unconditionally (operator/testing knob; the env var was
asked for in the CloudDeploy task spec). A new structured log line
prints every signal that feeds the encode-selection chain right
after the args parse: codec, videoFormat, dynamicRangeMode,
encoderCscMode (with csc>>1 and full-range broken out),
chromaSamplingType, session.enable_hdr, active_hevc_mode,
active_av1_mode.
3. video `probe_encoders()` set active_av1_mode / active_hevc_mode to 3
only when the user-config value was 0 (auto) AND the probe passed
DR. Deploy scripts that hardcoded av1_mode=2 (= AV1 enabled, HDR not
forced) got stuck at active_av1_mode=2, which clamps
ServerCodecModeSupport in /serverinfo to SCM_AV1_MAIN8 only.
Moonlight never sees SCM_AV1_MAIN10, never requests HDR.
Patch: after the existing auto-resolution, if env
SUNSHINE_FORCE_AV1_HDR10=1 is set and the encoder probe just
confirmed DYNAMIC_RANGE for HEVC/AV1, bump active_hevc_mode and
active_av1_mode from 2 to 3. Leaves user-explicit 1 (disabled) and 3
(enabled-and-DR-required) alone. /serverinfo then advertises
SCM_AV1_MAIN10 / SCM_HEVC_MAIN10. New INFO log lines name the chosen
modes and per-codec DR support.
4. video `make_encode_device()` now logs the full selection state
alongside the existing Color coding / Color depth / Color range
lines: codec name, client dynamicRange, disp.is_hdr() result,
resolved colorspace, bit depth, selected pix_fmt (NV12 vs P010 vs
yuv420p10le), and chromaSamplingType. If client_dynamicRange>0 but
the resolved colorspace is not bt2020 PQ, an explicit WARNING points
at is_hdr() returning false and the NVIDIA private DRM properties.
env vars exposed for CloudDeploy integration
---------------------------------------------
* SUNSHINE_FORCE_AV1_HDR10=1
- probe_encoders(): bump active_hevc_mode / active_av1_mode 2 -> 3
when DR probe passed, so /serverinfo advertises SCM_*_MAIN10.
- cmd_announce(): force config.monitor.dynamicRange=1 if the client
RTSP arg came as 0.
Intended use: CloudDeploy sets this when ENABLE_HDR=1 (no
sunshine.conf change needed). The fork validates the encoder probe
before bumping, so this is safe to leave set permanently.
Files changed
-------------
src/platform/linux/kmsgrab.cpp - is_hdr() / get_hdr_metadata() NVIDIA
private detection
src/rtsp.cpp - cmd_announce structured log +
session.enable_hdr propagation +
SUNSHINE_FORCE_AV1_HDR10 override
src/video.cpp - probe_encoders SUNSHINE_FORCE_AV1_HDR10
handler + post-probe summary log
- make_encode_device selection log +
HDR-not-reached WARNING
Expected good-state log markers
-------------------------------
On a fresh deploy with ENABLE_HDR=1 + SUNSHINE_FORCE_AV1_HDR10=1, the
Sunshine journal should show:
is_hdr: NVIDIA private HDR via NV_CRTC_REGAMMA_TF=PQ on CRTC <N>
SUNSHINE_FORCE_AV1_HDR10: bumping active_av1_mode 2 -> 3 ...
Encoder probe results: active_hevc_mode=3 active_av1_mode=3 ...
Client video request: codec=AV1 (videoFormat=2) dynamicRangeMode=1 ...
session.enable_hdr=yes active_av1_mode=3
Creating encoder [av1_nvenc]
Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ)
Color depth: 10-bit
Encode selection: codec=AV1 ... selected_pix_fmt=p010 ...
Moonlight client log should then show `AV1 actual stream bitdepth 10`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom on the live VM after the last commit (aada2f1): everything internal to Sunshine is right - the kmsgrab capture detects NVIDIA private HDR, the RTSP cmd_announce path forces dynamicRange=1, make_encode_device picks bt2020 PQ + p010 + 10-bit, NVENC AV1 init succeeds - but the Moonlight client overlay still labels the stream "AV1 10-bit SDR". Root cause: Moonlight derives the overlay HDR/SDR badge from the Sunshine control packet control_hdr_mode_t.enabled, not from the bitstream colour primaries / transfer characteristics. The control packet is built in video::make_synced_session and video::capture_async with this pattern: hdr_info_t hdr_info = std::make_unique<hdr_info_raw_t>(false); if (colorspace_is_hdr(encode_device->colorspace)) { if (disp->get_hdr_metadata(hdr_info->metadata)) { hdr_info->enabled = true; } else { BOOST_LOG(error) << "Couldn't get display hdr metadata when ..."; } } ctx.hdr_events->raise(std::move(hdr_info)); On the CloudDeploy NVIDIA private HDR path, the standard DRM HDR_OUTPUT_METADATA blob is intentionally 0 (the patched KWin uses NV_CRTC_REGAMMA_TF=PQ / NV_INPUT_COLORSPACE=BT.2100 PQ on the active CRTC/plane instead - the standard atomic path is rejected by the NVIDIA driver on the forced/headless virtual output, this is by design and documented in the previous commit). kmsgrab's get_hdr_metadata() correctly returns false there, hdr_info->enabled stays false, stream::send_hdr_mode sends enabled=0, Moonlight shows SDR. Fix: synthesize HDR10 static metadata defaults at every point that currently early-returns on missing display metadata, gated on SUNSHINE_SYNTHESIZE_HDR10_METADATA=1 (the SUNSHINE_FORCE_AV1_HDR10=1 flag already used to bump active_av1_mode 2 -> 3 implies metadata synthesis for symmetry). New helpers in src/video.h + src/video.cpp: void video::populate_default_hdr10_metadata(SS_HDR_METADATA &m); bool video::synthesize_hdr10_metadata_enabled(); Defaults written: BT.2020 primaries (R 0.708/0.292, G 0.170/0.797, B 0.131/0.046), D65 white point (0.3127/0.3290), max luminance 1000 nits, min luminance 0.005 nits (encoded as 50 in 1/10000-nit units per the Sunshine SS_HDR_METADATA protocol extension), MaxCLL 1000, MaxFALL 400, maxFullFrameLuminance 1000. Same general scheme the existing pipewire path uses for SPA BT2020+SMPTE2084 buffers. Patch sites: src/platform/linux/kmsgrab.cpp - display_t::get_hdr_metadata() When is_hdr() returns true via the NVIDIA private path (nvidia_private_hdr_active(); hdr_metadata_blob_id is 0) AND synthesize_hdr10_metadata_enabled(), populate defaults and return true. INFO log line prints every field written. Without the env flag, the existing return-false path stays (with a clearer warning pointing at the flag). src/video.cpp - make_synced_session() + capture_async() inner thread After get_hdr_metadata() fails on a HDR colorspace, if synthesize_hdr10_metadata_enabled(), call populate_default_hdr10_metadata and set hdr_info->enabled = true. INFO log distinguishes "from display" vs "SYNTHESIZED" so the journal makes the source explicit. Else: clearer error pointing at the env flag. This is the belt-and-suspenders fallback in case the capture backend is something other than kmsgrab (pipewire, hypothetically a wlroots backend). src/video.cpp - make_avcodec_encode_session() AVFrame side data Same fallback for AVMasteringDisplayMetadata + AVContentLightMetadata side data. Affects libavcodec encoders (vaapi, software). The native NVENC path already programs primaries/transfer/matrix directly on the AV1 format_config in nvenc_base::create_encoder, so the side data is mostly there for consistency with non-NVENC encoders. INFO logs print has_primaries / has_luminance / maxLum etc. for both synthesized and display-sourced cases. src/stream.cpp - send_hdr_mode() Existing "Sent HDR mode: <flag>" debug line bumped to INFO and expanded to print the full metadata summary (enabled, luminance min/max, MaxCLL, MaxFALL, primaries[R], whitePoint). This is the single line that proves Moonlight saw the HDR control packet - invaluable when diagnosing future regressions. src/nvenc/nvenc_base.cpp - create_encoder() After enc_config is populated and just before nvEncInitializeEncoder, log codec / primaries / transfer / matrix / full_range / bit_depth / yuv444. Proves the bitstream colour config will be BT.2020 + SMPTE2084 + limited range + 10-bit + 4:2:0 on the HDR path. env vars (no CloudDeploy changes required for pickup - the existing SUNSHINE_FORCE_AV1_HDR10=1 already does the right thing): SUNSHINE_SYNTHESIZE_HDR10_METADATA=1 - explicit knob SUNSHINE_FORCE_AV1_HDR10=1 - implies synthesis Both accept "1", "true", "TRUE", "yes", "YES" (more forgiving than the strict "1" check on FORCE_AV1_HDR10; either keeps the existing behaviour and adds the new fallback). SDR regression analysis: * All synthesis is gated on colorspace_is_hdr(encode_device->colorspace) AND synthesize_hdr10_metadata_enabled(). Stream with dynamicRange=0 never enters the synthesis branches. * H.264 cannot trigger HDR synthesis: encoder.h264[DYNAMIC_RANGE] is hardcoded to false in probe_encoders() (video.cpp:3005). validate_config rejects HDR-on-H.264 at video.cpp:1859, so colorspace_from_client_config never returns bt2020 for a successfully started H.264 stream. * Encoder probe paths (test_hdr_and_yuv444) call is_codec_supported on the display, which doesn't touch get_hdr_metadata - probe behavior is unchanged. Expected good-state log markers (with SUNSHINE_FORCE_AV1_HDR10=1 or SUNSHINE_SYNTHESIZE_HDR10_METADATA=1): is_hdr: NVIDIA private HDR via NV_INPUT_COLORSPACE=BT.2100 PQ on plane <N> HDR metadata fallback: standard DRM HDR_OUTPUT_METADATA blob is 0 on NVIDIA private HDR path; synthesizing HDR10 static metadata defaults (BT.2020 primaries, D65 white point, max=1000 nits, min=50/10000 nits, MaxCLL=1000, MaxFALL=400) HDR control message (sync session): enabled=1 with SYNTHESIZED HDR10 defaults (...) OR enabled=1 with mastering metadata from display Creating encoder [av1_nvenc] Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ) Color depth: 10-bit selected_pix_fmt=p010 NvEnc color-config: codec=AV1 primaries=9 transfer=16 matrix=9 full_range=0 bit_depth=10 yuv444=no Sent HDR mode control packet to Moonlight: enabled=1 maxDisplayLuminance=1000 nits ... MaxCLL=1000 MaxFALL=400 ... Moonlight client overlay should switch from "AV1 10-bit SDR" to its HDR label after this commit + the env flag is set. If Moonlight still shows SDR after seeing the control packet with enabled=1, the issue is moonlight-stream/moonlight-qt side (overlay caching, decoder negotiation) and not Sunshine - that is detectable because the new "Sent HDR mode control packet" INFO line is the unambiguous server-side ground truth. Files changed ------------- src/video.h - helpers declared src/video.cpp - helpers defined; 3 consumer sites flip to synthesised-fallback path src/platform/linux/kmsgrab.cpp - get_hdr_metadata() synthesises on nvidia_private_hdr + env flag src/stream.cpp - send_hdr_mode log to INFO with full metadata src/nvenc/nvenc_base.cpp - colour-config snapshot log before nvEncInitializeEncoder Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Description
Screenshot
Issues Fixed or Closed
Roadmap Issues
Type of Change
Checklist
AI Usage