From efb0e885aeb26207f59cd2884b17e555350ebce0 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Tue, 1 Sep 2026 07:37:10 +0200 Subject: [PATCH] fix(windows): disable dynamic HDR peak detection on Qualcomm GPUs Playing HDR content tone-mapped to an SDR display on Windows-on-ARM stutters heavily and flickers in brightness: dynamic peak detection moves the tone-mapping parameters every frame, libplacebo regenerates its tone-map and gamut-map shader LUTs each time, and Qualcomm's Adreno D3D11 driver takes tens of milliseconds per regeneration - roughly 100 ms of work against a 42 ms frame budget at 24 fps. Scan the DXGI adapter list for a Qualcomm vendor id and set hdr-compute-peak=no when one is present, keeping auto elsewhere; static metadata-driven tone mapping generates its LUTs once. close #2191 --- windows/runner/CMakeLists.txt | 4 +-- windows/runner/mpv/mpv_player.cpp | 45 +++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index cfb56c1d7..8a83daa01 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -32,7 +32,7 @@ target_compile_features(simdutf PUBLIC cxx_std_17) target_compile_options(simdutf PRIVATE /W0) target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app flutter_wrapper_plugin) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib" "comctl32.lib") +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib" "comctl32.lib" "dxgi.lib") target_link_libraries(${BINARY_NAME} PRIVATE "${MPV_LIB_DIR}/libmpv.dll.a") target_link_libraries(${BINARY_NAME} PRIVATE simdutf) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") @@ -63,7 +63,7 @@ if(PLEZY_BUILD_MPV_PROPERTY_CONTRACT_TESTS) target_compile_definitions(mpv_player_property_contract_test PRIVATE "NOMINMAX") target_link_libraries( mpv_player_property_contract_test - PRIVATE flutter_wrapper_plugin "${MPV_LIB_DIR}/libmpv.dll.a" simdutf "comctl32.lib" "user32.lib" + PRIVATE flutter_wrapper_plugin "${MPV_LIB_DIR}/libmpv.dll.a" simdutf "comctl32.lib" "user32.lib" "dxgi.lib" ) target_include_directories( mpv_player_property_contract_test diff --git a/windows/runner/mpv/mpv_player.cpp b/windows/runner/mpv/mpv_player.cpp index beb82acd3..8c88f338d 100644 --- a/windows/runner/mpv/mpv_player.cpp +++ b/windows/runner/mpv/mpv_player.cpp @@ -1,6 +1,7 @@ #include "mpv_player.h" #include +#include #include #include @@ -26,6 +27,45 @@ struct InnerWindowSubclassState { 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. +// +// 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 +// with another GPU, so presence is equivalent to use. This also covers the +// x64 build running emulated on Windows-on-ARM, which an architecture check +// would miss. +bool SystemHasQualcommGpu() { + // Qualcomm's Windows driver reports the FourCC 'QCOM' as its DXGI vendor + // id (seen in the wild on the Adreno X1-85); 0x5143 is Qualcomm's PCI-SIG + // id, matched in case a driver reports that instead. + constexpr UINT kQualcommFourCc = 0x4D4F4351; + constexpr UINT kQualcommPci = 0x5143; + IDXGIFactory1* factory = nullptr; + if (FAILED(::CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) return false; + bool found = false; + IDXGIAdapter1* adapter = nullptr; + for (UINT i = 0; !found && SUCCEEDED(factory->EnumAdapters1(i, &adapter)); ++i) { + DXGI_ADAPTER_DESC1 desc; + if (SUCCEEDED(adapter->GetDesc1(&desc))) { + found = desc.VendorId == kQualcommFourCc || desc.VendorId == kQualcommPci; + } + adapter->Release(); + adapter = nullptr; + } + factory->Release(); + return found; +} + // Adapts the shared, bounded mpv_node walk onto Flutter's encodable values. struct EncodableNodeBuilder { using Value = flutter::EncodableValue; @@ -581,9 +621,10 @@ 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 + // Fallback tone mapping when display doesn't support HDR. Dynamic peak + // detection is pathological on Adreno — see SystemHasQualcommGpu. mpv_set_option_string(mpv_, "tone-mapping", "auto"); - mpv_set_option_string(mpv_, "hdr-compute-peak", "auto"); + mpv_set_option_string(mpv_, "hdr-compute-peak", SystemHasQualcommGpu() ? "no" : "auto"); } // When WASAPI becomes unavailable (sleep, device unplug), fall back to null