fix(windows): strip HDR10+ metadata from the Adreno tone-map path

Disabling peak detection (efb0e885) did not stop the per-frame shader LUT regeneration on Snapdragon devices: libplacebo keys the tone-map LUT on the frame's raw HDR metadata, and mpv forwards HDR10+ per-scene values (scene_max, scene_avg, ootf) on every decoded frame, so the LUT - at tens of milliseconds per rebuild on Qualcomm's D3D11 driver - still regenerated continuously and playback kept stuttering and flickering (#2191).

Zero the dynamic metadata before the renderer with vf=format:hdr10plus=no on Qualcomm GPUs; tone mapping uses the static HDR10 mastering metadata and the LUT is generated once. Dolby Vision L1 metadata is deliberately not stripped: dovi=no would break profile-5 rendering. The applied HDR pipeline options are now reported into the app log on first file load so uploaded logs are self-diagnosing.
This commit is contained in:
edde746
2026-09-01 09:02:59 +02:00
parent efb0e885ae
commit 23f9afa5b1
2 changed files with 48 additions and 12 deletions
+41 -12
View File
@@ -29,15 +29,18 @@ namespace {
// Whether any GPU on this system is a Qualcomm Adreno.
//
// Dynamic HDR peak detection (hdr-compute-peak) moves the tone-mapping
// parameters every frame, and libplacebo regenerates its tone-map and
// gamut-map shader LUTs whenever they move. Qualcomm's D3D11 driver has no
// host-visible upload path (its libplacebo caps report buf_transfer and
// max_mapped_size as zero), so each regeneration costs tens of milliseconds:
// 4K HDR→SDR playback starves to single-digit fps and the swinging peak
// reads as brightness flicker (#2191). Static metadata-driven tone mapping
// generates its LUTs once, so peak detection is disabled when an Adreno may
// be doing the rendering.
// libplacebo regenerates its tone-mapping shader LUT whenever the tone-map
// parameters change, and the reuse key (pl_tone_map_params_equal) includes the
// frame's raw HDR metadata by exact float comparison. Two sources move it
// every frame: dynamic peak detection (hdr-compute-peak), and HDR10+
// per-scene metadata (scene_max/scene_avg/ootf), which mpv maps from decoder
// side data on every frame. Qualcomm's D3D11 driver has no host-visible
// upload path (its libplacebo caps report buf_transfer and max_mapped_size as
// zero), so each regeneration costs tens of milliseconds: 4K HDR→SDR playback
// starves to single-digit fps and the swinging tone curve reads as brightness
// flicker (#2191). Initialize therefore silences both dynamic sources on this
// GPU; tone mapping falls back to the static HDR10 mastering metadata and the
// LUT is generated once.
//
// The whole adapter list is scanned rather than predicting mpv's choice: mpv
// takes the DXGI default adapter, and no supported machine pairs an Adreno
@@ -621,10 +624,19 @@ bool MpvPlayer::Initialize(HWND view) {
// Let mpv use display/context detection instead of forcing HDR signaling.
mpv_set_option_string(mpv_, "target-colorspace-hint", plezy::mpv_common::TargetColorspaceHint(hdr_enabled_));
// Fallback tone mapping when display doesn't support HDR. Dynamic peak
// detection is pathological on Adreno — see SystemHasQualcommGpu.
// Fallback tone mapping when display doesn't support HDR. On Adreno,
// per-frame tone-map LUT regeneration is pathological — see
// SystemHasQualcommGpu — so both dynamic inputs to the LUT key are
// silenced: the peak detector, and HDR10+ per-scene metadata, which
// vf=format:hdr10plus=no zeroes before it reaches the renderer. Dolby
// Vision L1 metadata could still churn the LUT, but stripping it
// (dovi=no) would break profile-5 rendering outright, so it stays.
mpv_set_option_string(mpv_, "tone-mapping", "auto");
mpv_set_option_string(mpv_, "hdr-compute-peak", SystemHasQualcommGpu() ? "no" : "auto");
adreno_tone_map_workaround_ = SystemHasQualcommGpu();
mpv_set_option_string(mpv_, "hdr-compute-peak", adreno_tone_map_workaround_ ? "no" : "auto");
if (adreno_tone_map_workaround_) {
mpv_set_option_string(mpv_, "vf", "format:hdr10plus=no");
}
}
// When WASAPI becomes unavailable (sleep, device unplug), fall back to null
@@ -792,6 +804,18 @@ void MpvPlayer::LogRecovery(const std::string& text) {
SendEvent("log-message", data);
}
void MpvPlayer::LogHdrPipelineOnce() {
if (hdr_config_logged_ || audio_only_) return;
hdr_config_logged_ = true;
const char* text = adreno_tone_map_workaround_ ? "Qualcomm GPU: hdr-compute-peak=no, vf=format:hdr10plus=no"
: "hdr-compute-peak=auto";
flutter::EncodableMap data;
data[flutter::EncodableValue("prefix")] = flutter::EncodableValue("hdr-config");
data[flutter::EncodableValue("level")] = flutter::EncodableValue("info");
data[flutter::EncodableValue("text")] = flutter::EncodableValue(text);
SendEvent("log-message", data);
}
void MpvPlayer::TryAudioReload(const char* reason, int attempt, uint64_t request_generation) {
LogRecovery("issuing ao-reload (reason=" + std::string(reason) + ", attempt " + std::to_string(attempt) + ")");
const std::string reason_copy = reason;
@@ -895,6 +919,11 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
}
case MPV_EVENT_FILE_LOADED: {
audio_recovery_.SetFileLoaded(true);
// Deferred to here rather than Initialize: the Dart event callback is
// wired by the time a file loads, and the synthetic event bypasses the
// mpv log level, so the applied HDR pipeline options always land in an
// uploaded log (#2191 was undiagnosable without this).
LogHdrPipelineOnce();
SendEvent("file-loaded");
break;
}
+7
View File
@@ -104,6 +104,9 @@ class MpvPlayer {
void MaybeRunAudioRecovery();
void TryAudioReload(const char* reason, int attempt, uint64_t request_generation);
void LogRecovery(const std::string& text);
// Reports the applied HDR pipeline options once per player, as a synthetic
// log-message on the first file load (when the Dart callback is wired).
void LogHdrPipelineOnce();
void EnsureMpvInnerSubclassed();
void DetachMpvInnerSubclass();
@@ -125,6 +128,10 @@ class MpvPlayer {
// HDR state
bool hdr_enabled_ = true;
// Set during Initialize when a Qualcomm Adreno GPU is present; names the
// tone-map LUT workaround so the first file load can log it.
bool adreno_tone_map_workaround_ = false;
bool hdr_config_logged_ = false;
void SetHDREnabled(bool enabled, StatusCallback callback = nullptr);
};