From c83bbd525a0f8225b6df3e2f7be638ed11fbaf45 Mon Sep 17 00:00:00 2001 From: jasaaved Date: Tue, 17 Mar 2026 21:34:08 -0700 Subject: [PATCH 1/7] Add Querying HDR display luminance Query the display's HDR min/max luminance and feed those values into the post-processing pipeline and shaders. - Add SwapChain::QueryDisplayHDRCapabilities(), include dxgi1_6, and call it during swapchain creation and resize to populate g_backend_info.hdr_max_luminance_nits and hdr_min_luminance_nits. - Extend BackendInfo with hdr_max_luminance_nits and hdr_min_luminance_nits. - Add hdr_max_luminance_nits/min to the built-in uniform block and fill them in PostProcessing::FillUniformBuffer so shaders can access actual panel values. - Update AutoHDR.glsl to use the reported hdr_max_luminance_nits (with a fallback to HDR_DISPLAY_MAX_NITS) when computing the auto-HDR white scaling. This enables more accurate AutoHDR tonemapping by using the display's real luminance capabilities when available. --- Data/Sys/Shaders/AutoHDR.glsl | 3 ++- .../VideoBackends/D3DCommon/SwapChain.cpp | 27 ++++++++++++++++++- .../Core/VideoBackends/D3DCommon/SwapChain.h | 3 ++- Source/Core/VideoCommon/PostProcessing.cpp | 6 +++++ Source/Core/VideoCommon/VideoConfig.h | 2 ++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Data/Sys/Shaders/AutoHDR.glsl b/Data/Sys/Shaders/AutoHDR.glsl index b806d65838..65a7579cd1 100644 --- a/Data/Sys/Shaders/AutoHDR.glsl +++ b/Data/Sys/Shaders/AutoHDR.glsl @@ -54,7 +54,8 @@ void main() // Find the color luminance (it works better than average) float sdr_ratio = luminance(color.rgb); - const float auto_hdr_max_white = max(HDR_DISPLAY_MAX_NITS / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; + const float display_max_nits = hdr_max_luminance_nits > 0.0 ? hdr_max_luminance_nits : HDR_DISPLAY_MAX_NITS; + const float auto_hdr_max_white = max(display_max_nits / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; if (sdr_ratio > AUTO_HDR_SHOULDER_START_ALPHA && AUTO_HDR_SHOULDER_START_ALPHA < 1.0) { const float auto_hdr_shoulder_ratio = 1.0 - (max(1.0 - sdr_ratio, 0.0) / (1.0 - AUTO_HDR_SHOULDER_START_ALPHA)); diff --git a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp index 1c91275a52..6de330c86f 100644 --- a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp +++ b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp @@ -188,9 +188,34 @@ bool SwapChain::CreateSwapChain(bool stereo, bool hdr) return false; } + QueryDisplayHDRCapabilities(); return true; } +void SwapChain::QueryDisplayHDRCapabilities() +{ + g_backend_info.hdr_max_luminance_nits = 0.0f; + g_backend_info.hdr_min_luminance_nits = 0.0f; + + if (!m_hdr) + { + return; + } + + Microsoft::WRL::ComPtr output; + Microsoft::WRL::ComPtr output6; + DXGI_OUTPUT_DESC1 desc; + + if (FAILED(m_swap_chain->GetContainingOutput(&output)) || FAILED(output.As(&output6)) || + FAILED(output6->GetDesc1(&desc))) + { + return; + } + + g_backend_info.hdr_max_luminance_nits = desc.MaxLuminance; + g_backend_info.hdr_min_luminance_nits = desc.MinLuminance; +} + void SwapChain::DestroySwapChain() { DestroySwapChainBuffers(); @@ -225,7 +250,7 @@ bool SwapChain::ResizeSwapChain() m_width = desc.BufferDesc.Width; m_height = desc.BufferDesc.Height; } - + QueryDisplayHDRCapabilities(); return CreateSwapChainBuffers(); } diff --git a/Source/Core/VideoBackends/D3DCommon/SwapChain.h b/Source/Core/VideoBackends/D3DCommon/SwapChain.h index 7d489cc002..80a78fbc08 100644 --- a/Source/Core/VideoBackends/D3DCommon/SwapChain.h +++ b/Source/Core/VideoBackends/D3DCommon/SwapChain.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include "Common/CommonTypes.h" @@ -54,6 +54,7 @@ protected: u32 GetSwapChainFlags() const; bool CreateSwapChain(bool stereo = false, bool hdr = false); void DestroySwapChain(); + void QueryDisplayHDRCapabilities(); virtual bool CreateSwapChainBuffers() = 0; virtual void DestroySwapChainBuffers() = 0; diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 804c24eef6..44ae3a4c5f 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -637,6 +637,8 @@ std::string PostProcessing::GetUniformBufferHeader(bool user_post_process) const ss << " int hdr_output;\n"; ss << " float hdr_paper_white_nits;\n"; ss << " float hdr_sdr_white_nits;\n"; + ss << " float hdr_max_luminance_nits;\n"; + ss << " float hdr_min_luminance_nits;\n"; if (user_post_process) { @@ -856,6 +858,8 @@ struct BuiltinUniforms s32 hdr_output; float hdr_paper_white_nits; float hdr_sdr_white_nits; + float hdr_max_luminance_nits; + float hdr_min_luminance_nits; }; size_t PostProcessing::CalculateUniformsSize(bool user_post_process) const @@ -912,6 +916,8 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, builtin_uniforms.hdr_paper_white_nits = g_ActiveConfig.color_correction.fHDRPaperWhiteNits; // A value of 1 1 1 usually matches 80 nits in HDR builtin_uniforms.hdr_sdr_white_nits = 80.f; + builtin_uniforms.hdr_max_luminance_nits = g_backend_info.hdr_max_luminance_nits; + builtin_uniforms.hdr_min_luminance_nits = g_backend_info.hdr_min_luminance_nits; std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms)); buffer += sizeof(builtin_uniforms); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 3f82e3afe7..2af24cd1db 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -180,6 +180,8 @@ struct BackendInfo bool bSupportsVSLinePointExpand = false; bool bSupportsGLLayerInFS = true; bool bSupportsHDROutput = false; + float hdr_max_luminance_nits = 0.f; + float hdr_min_luminance_nits = 0.f; bool bSupportsUnrestrictedDepthRange = false; }; From f9fd18282f530fc61fe0e626e6a67bba010dcb78 Mon Sep 17 00:00:00 2001 From: jasaaved Date: Wed, 18 Mar 2026 16:40:58 -0700 Subject: [PATCH 2/7] Modify Perceptual HDR Slider Obtain SDR white data from the display when it is available. --- Data/Sys/Shaders/PerceptualHDR.glsl | 17 +++++++---- .../VideoBackends/D3DCommon/SwapChain.cpp | 30 ++++++++++++++++--- Source/Core/VideoCommon/PostProcessing.cpp | 16 ++++++++-- Source/Core/VideoCommon/VideoConfig.h | 1 + 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Data/Sys/Shaders/PerceptualHDR.glsl b/Data/Sys/Shaders/PerceptualHDR.glsl index 8d020dc659..5cd2484edb 100644 --- a/Data/Sys/Shaders/PerceptualHDR.glsl +++ b/Data/Sys/Shaders/PerceptualHDR.glsl @@ -2,12 +2,12 @@ [configuration] [OptionRangeFloat] -GUIName = Amplificiation +GUIName = Brightness Multiplier OptionName = AMPLIFICATION -MinValue = 1.0 -MaxValue = 6.0 -StepAmount = 0.25 -DefaultValue = 2.5 +MinValue = 0.5 +MaxValue = 4.0 +StepAmount = 0.05 +DefaultValue = 1.0 [/configuration] */ @@ -119,8 +119,13 @@ void main() // // For more information, see this desmos demonstrating this scaling process: // https://www.desmos.com/calculator/syjyrjsj5c + // Use the display's actual peak luminance when available, otherwise fall back to the manual slider. + // When display data is available, the ratio peak/sdr_white gives the display's total HDR headroom + // above its SDR baseline — independent of the user's paper white target. The brightness multiplier + // scales on top (1.0 = calibrated to the display). Otherwise the multiplier is used as a raw value. + const float display_amplification = hdr_max_luminance_nits > 0.0 ? (hdr_max_luminance_nits / hdr_sdr_white_nits) * AMPLIFICATION : AMPLIFICATION; float exposure = length(ictcp_color.xyz); - ictcp_color *= pow(HLG_f(AMPLIFICATION), exposure); + ictcp_color *= pow(HLG_f(display_amplification), exposure); // Convert back to Linear RGB and output the color to the display. // We use hdr_paper_white to renormalize the color to the comfortable diff --git a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp index 6de330c86f..cdb7251eb3 100644 --- a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp +++ b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp @@ -5,12 +5,15 @@ #include #include +#include +#include #include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/HRWrap.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +#include "Common/ScopeGuard.h" #include "VideoCommon/VideoConfig.h" @@ -196,26 +199,45 @@ void SwapChain::QueryDisplayHDRCapabilities() { g_backend_info.hdr_max_luminance_nits = 0.0f; g_backend_info.hdr_min_luminance_nits = 0.0f; + g_backend_info.hdr_sdr_white_nits = 80.f; if (!m_hdr) - { return; - } Microsoft::WRL::ComPtr output; Microsoft::WRL::ComPtr output6; - DXGI_OUTPUT_DESC1 desc; + DXGI_OUTPUT_DESC1 desc{}; if (FAILED(m_swap_chain->GetContainingOutput(&output)) || FAILED(output.As(&output6)) || FAILED(output6->GetDesc1(&desc))) { return; - } + } g_backend_info.hdr_max_luminance_nits = desc.MaxLuminance; g_backend_info.hdr_min_luminance_nits = desc.MinLuminance; + + try + { + winrt::init_apartment(); + Common::ScopeGuard uninit_guard([] { winrt::uninit_apartment(); }); + + auto statics = + winrt::get_activation_factory(); + winrt::Windows::Graphics::Display::DisplayInformation display_info{nullptr}; + winrt::check_hresult(statics->GetForMonitor( + desc.Monitor, winrt::guid_of(), + winrt::put_abi(display_info))); + g_backend_info.hdr_sdr_white_nits = display_info.GetAdvancedColorInfo().SdrWhiteLevelInNits(); + } + catch (...) + { + g_backend_info.hdr_sdr_white_nits = 80.f; + } } + void SwapChain::DestroySwapChain() { DestroySwapChainBuffers(); diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 44ae3a4c5f..f3ec68b1f6 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -913,9 +913,19 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, builtin_uniforms.linear_space_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F; // Implies output values can be beyond the 0-1 range builtin_uniforms.hdr_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F; - builtin_uniforms.hdr_paper_white_nits = g_ActiveConfig.color_correction.fHDRPaperWhiteNits; - // A value of 1 1 1 usually matches 80 nits in HDR - builtin_uniforms.hdr_sdr_white_nits = 80.f; + // Clamp paper white to at least the display's SDR white level when display data is available. + // This prevents hdr_paper_white (= paper_white_nits / sdr_white_nits) from dropping below 1.0, + // which would scale colors up before tone mapping and risk clipping highlights. + // Note: amplification headroom is computed from peak/sdr_white in the shader, not from paper_white, + // so this floor does not affect how much HDR range the shader uses. + const float effective_paper_white = + g_backend_info.hdr_max_luminance_nits > 0.f ? + std::max(g_ActiveConfig.color_correction.fHDRPaperWhiteNits, + g_backend_info.hdr_sdr_white_nits) : + g_ActiveConfig.color_correction.fHDRPaperWhiteNits; + builtin_uniforms.hdr_paper_white_nits = effective_paper_white; + // Queried from the display on DX backends; falls back to 80 (scRGB spec value) + builtin_uniforms.hdr_sdr_white_nits = g_backend_info.hdr_sdr_white_nits; builtin_uniforms.hdr_max_luminance_nits = g_backend_info.hdr_max_luminance_nits; builtin_uniforms.hdr_min_luminance_nits = g_backend_info.hdr_min_luminance_nits; diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 2af24cd1db..86d1f6dab7 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -182,6 +182,7 @@ struct BackendInfo bool bSupportsHDROutput = false; float hdr_max_luminance_nits = 0.f; float hdr_min_luminance_nits = 0.f; + float hdr_sdr_white_nits = 80.f; bool bSupportsUnrestrictedDepthRange = false; }; From 7ac148ee6500a5416f092dcf5c14c4ac485ddc73 Mon Sep 17 00:00:00 2001 From: jasaaved Date: Wed, 18 Mar 2026 23:36:53 -0700 Subject: [PATCH 3/7] AutoHDR now uses queried or user settings for HDR If the user hasn't set a HDR DISPLAY MAX NITS, the first time a game is launched using DirectX11 or 12, the queried display values are saved in the ini Of course user settings always take preference. But for someone that doesn't have the HDR settings set, the correct default value should be set so clipping and those bright neon/fluorescent colors can be avoided. --- Data/Sys/Shaders/AutoHDR.glsl | 3 +-- Source/Core/VideoCommon/PostProcessing.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Data/Sys/Shaders/AutoHDR.glsl b/Data/Sys/Shaders/AutoHDR.glsl index 65a7579cd1..b806d65838 100644 --- a/Data/Sys/Shaders/AutoHDR.glsl +++ b/Data/Sys/Shaders/AutoHDR.glsl @@ -54,8 +54,7 @@ void main() // Find the color luminance (it works better than average) float sdr_ratio = luminance(color.rgb); - const float display_max_nits = hdr_max_luminance_nits > 0.0 ? hdr_max_luminance_nits : HDR_DISPLAY_MAX_NITS; - const float auto_hdr_max_white = max(display_max_nits / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; + const float auto_hdr_max_white = max(HDR_DISPLAY_MAX_NITS / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; if (sdr_ratio > AUTO_HDR_SHOULDER_START_ALPHA && AUTO_HDR_SHOULDER_START_ALPHA < 1.0) { const float auto_hdr_shoulder_ratio = 1.0 - (max(1.0 - sdr_ratio, 0.0) / (1.0 - AUTO_HDR_SHOULDER_START_ALPHA)); diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index f3ec68b1f6..140b9d0c3e 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -259,6 +259,7 @@ void PostProcessingConfiguration::LoadOptionsConfiguration() Common::IniFile ini; ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); std::string section = m_current_shader + "-options"; + bool needs_save = false; // We already expect all the options to be marked as "dirty" when we reach here for (auto& it : m_options) @@ -295,10 +296,24 @@ void PostProcessingConfiguration::LoadOptionsConfiguration() it.second.m_float_values = float_values; } } + else if (it.second.m_option_name == "HDR_DISPLAY_MAX_NITS" && + g_backend_info.hdr_max_luminance_nits > 0.f) + { + it.second.m_float_values[0] = g_backend_info.hdr_max_luminance_nits; + it.second.m_dirty = true; + std::ostringstream queried_value; + queried_value.imbue(std::locale("C")); + queried_value << g_backend_info.hdr_max_luminance_nits; + ini.GetOrCreateSection(section)->Set(it.second.m_option_name, queried_value.str()); + needs_save = true; + } } break; } } + + if (needs_save) + ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); } void PostProcessingConfiguration::SaveOptionsConfiguration() From 077969b97e7a94555db0cac149812f31a4fc6a3b Mon Sep 17 00:00:00 2001 From: jasaaved Date: Thu, 19 Mar 2026 00:43:34 -0700 Subject: [PATCH 4/7] Restore Perceptual HDR to Master Perceptual HDR does not benefit from the queries we make for luminance so we can restore the default values and the original code. --- Data/Sys/Shaders/PerceptualHDR.glsl | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Data/Sys/Shaders/PerceptualHDR.glsl b/Data/Sys/Shaders/PerceptualHDR.glsl index 5cd2484edb..8d020dc659 100644 --- a/Data/Sys/Shaders/PerceptualHDR.glsl +++ b/Data/Sys/Shaders/PerceptualHDR.glsl @@ -2,12 +2,12 @@ [configuration] [OptionRangeFloat] -GUIName = Brightness Multiplier +GUIName = Amplificiation OptionName = AMPLIFICATION -MinValue = 0.5 -MaxValue = 4.0 -StepAmount = 0.05 -DefaultValue = 1.0 +MinValue = 1.0 +MaxValue = 6.0 +StepAmount = 0.25 +DefaultValue = 2.5 [/configuration] */ @@ -119,13 +119,8 @@ void main() // // For more information, see this desmos demonstrating this scaling process: // https://www.desmos.com/calculator/syjyrjsj5c - // Use the display's actual peak luminance when available, otherwise fall back to the manual slider. - // When display data is available, the ratio peak/sdr_white gives the display's total HDR headroom - // above its SDR baseline — independent of the user's paper white target. The brightness multiplier - // scales on top (1.0 = calibrated to the display). Otherwise the multiplier is used as a raw value. - const float display_amplification = hdr_max_luminance_nits > 0.0 ? (hdr_max_luminance_nits / hdr_sdr_white_nits) * AMPLIFICATION : AMPLIFICATION; float exposure = length(ictcp_color.xyz); - ictcp_color *= pow(HLG_f(display_amplification), exposure); + ictcp_color *= pow(HLG_f(AMPLIFICATION), exposure); // Convert back to Linear RGB and output the color to the display. // We use hdr_paper_white to renormalize the color to the comfortable From fff8338c8ba70441cf1e88d0fec2dafeffb63184 Mon Sep 17 00:00:00 2001 From: jasaaved Date: Thu, 19 Mar 2026 02:08:24 -0700 Subject: [PATCH 5/7] Add Use Display Peak Luminance If checked, display peak will always be used. Works even across different devices if ini is saved on the cloud. Or game is changed between different displays. --- Data/Sys/Shaders/AutoHDR.glsl | 13 ++++++++++- .../Graphics/PostProcessingConfigWindow.cpp | 22 +++++++++++++++++++ .../Graphics/PostProcessingConfigWindow.h | 1 + Source/Core/VideoCommon/PostProcessing.cpp | 15 +++++++++---- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Data/Sys/Shaders/AutoHDR.glsl b/Data/Sys/Shaders/AutoHDR.glsl index b806d65838..36ce5fbe00 100644 --- a/Data/Sys/Shaders/AutoHDR.glsl +++ b/Data/Sys/Shaders/AutoHDR.glsl @@ -3,6 +3,11 @@ /* [configuration] +[OptionBool] +GUIName = Use Display Peak Luminance +OptionName = USE_DISPLAY_PEAK_LUMINANCE +DefaultValue = 0 + [OptionRangeFloat] GUIName = HDR Display Max Nits OptionName = HDR_DISPLAY_MAX_NITS @@ -54,7 +59,13 @@ void main() // Find the color luminance (it works better than average) float sdr_ratio = luminance(color.rgb); - const float auto_hdr_max_white = max(HDR_DISPLAY_MAX_NITS / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; + // When "Use Display Peak Luminance" is enabled, use the peak luminance reported by the display + // (only available on DirectX 11/12). Falls back to the manual slider on other APIs or if + // no display data is available. + const float display_max_nits = (OptionEnabled(USE_DISPLAY_PEAK_LUMINANCE) && hdr_max_luminance_nits > 0.0) + ? hdr_max_luminance_nits + : HDR_DISPLAY_MAX_NITS; + const float auto_hdr_max_white = max(display_max_nits / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits; if (sdr_ratio > AUTO_HDR_SHOULDER_START_ALPHA && AUTO_HDR_SHOULDER_START_ALPHA < 1.0) { const float auto_hdr_shoulder_ratio = 1.0 - (max(1.0 - sdr_ratio, 0.0) / (1.0 - AUTO_HDR_SHOULDER_START_ALPHA)); diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp index 7070b60887..de28749f51 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp @@ -120,6 +120,12 @@ void PostProcessingConfigWindow::Create() m_tabs->insertTab(0, general, tr("General")); } + // Apply initial enabled state for options controlled by USE_DISPLAY_PEAK_LUMINANCE + const auto use_peak_it = m_config_map.find("USE_DISPLAY_PEAK_LUMINANCE"); + const auto max_nits_it = m_config_map.find("HDR_DISPLAY_MAX_NITS"); + if (use_peak_it != m_config_map.end() && max_nits_it != m_config_map.end()) + max_nits_it->second->SetEnabled(!use_peak_it->second->GetCheckboxValue()); + m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok); auto* layout = new QVBoxLayout(this); @@ -150,8 +156,16 @@ PostProcessingConfigWindow::CreateDependentTab(const std::unique_ptrSetOptionb(config_group->GetOptionName(), state); + m_post_processor->SaveOptionsConfiguration(); config_group->EnableSuboptions(state); + + if (config_group->GetOptionName() == "USE_DISPLAY_PEAK_LUMINANCE") + { + const auto it = m_config_map.find("HDR_DISPLAY_MAX_NITS"); + if (it != m_config_map.end()) + it->second->SetEnabled(!state); + } } void PostProcessingConfigWindow::UpdateInteger(ConfigGroup* const config_group, const int value) @@ -346,6 +360,14 @@ u32 PostProcessingConfigWindow::ConfigGroup::AddFloat(PostProcessingConfigWindow return row + 1; } +void PostProcessingConfigWindow::ConfigGroup::SetEnabled(const bool state) +{ + for (auto& slider : m_sliders) + slider->setEnabled(state); + for (auto& value_box : m_value_boxes) + value_box->setEnabled(state); +} + void PostProcessingConfigWindow::ConfigGroup::EnableSuboptions(const bool state) { for (auto& it : m_subgroups) diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h index 5c02368c8c..1f20bd466a 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h @@ -46,6 +46,7 @@ private: const std::vector>& GetSubGroups() const noexcept; u32 AddWidgets(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row); void EnableSuboptions(bool state); + void SetEnabled(bool state); int GetCheckboxValue() const; int GetSliderValue(size_t index) const; void SetSliderText(size_t index, const QString& text); diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 140b9d0c3e..fbb5f5a6ea 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -259,7 +259,6 @@ void PostProcessingConfiguration::LoadOptionsConfiguration() Common::IniFile ini; ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); std::string section = m_current_shader + "-options"; - bool needs_save = false; // We already expect all the options to be marked as "dirty" when we reach here for (auto& it : m_options) @@ -305,15 +304,23 @@ void PostProcessingConfiguration::LoadOptionsConfiguration() queried_value.imbue(std::locale("C")); queried_value << g_backend_info.hdr_max_luminance_nits; ini.GetOrCreateSection(section)->Set(it.second.m_option_name, queried_value.str()); - needs_save = true; + ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); } } break; } } - if (needs_save) - ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); + // If USE_DISPLAY_PEAK_LUMINANCE is checked and we have queried data, sync the slider to the + // queried value so it reflects what the shader is actually using. + const auto use_display_peak = m_options.find("USE_DISPLAY_PEAK_LUMINANCE"); + const auto max_nits_option = m_options.find("HDR_DISPLAY_MAX_NITS"); + if (use_display_peak != m_options.end() && max_nits_option != m_options.end() && + use_display_peak->second.m_bool_value && g_backend_info.hdr_max_luminance_nits > 0.f) + { + max_nits_option->second.m_float_values[0] = g_backend_info.hdr_max_luminance_nits; + max_nits_option->second.m_dirty = true; + } } void PostProcessingConfiguration::SaveOptionsConfiguration() From 8a27a5b8e871da68d71e9a42e467d263d6620847 Mon Sep 17 00:00:00 2001 From: jasaaved Date: Fri, 20 Mar 2026 01:52:35 -0700 Subject: [PATCH 6/7] Update HDR Peak and clarify "Use Display Peak Luminance" setting -Whenever the config setting for AutoHDR is loaded, the current display nits are shown on the slider. -Added some text to the checkbox clarifying DX11+ API is needed for this feature to work properly. --- Data/Sys/Shaders/AutoHDR.glsl | 2 +- .../Graphics/PostProcessingConfigWindow.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Data/Sys/Shaders/AutoHDR.glsl b/Data/Sys/Shaders/AutoHDR.glsl index 36ce5fbe00..d5c6abfa00 100644 --- a/Data/Sys/Shaders/AutoHDR.glsl +++ b/Data/Sys/Shaders/AutoHDR.glsl @@ -4,7 +4,7 @@ [configuration] [OptionBool] -GUIName = Use Display Peak Luminance +GUIName = Use Display Peak Luminance (DX11+) OptionName = USE_DISPLAY_PEAK_LUMINANCE DefaultValue = 0 diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp index de28749f51..fecffd035c 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp @@ -22,6 +22,7 @@ #include "VideoCommon/PostProcessing.h" #include "VideoCommon/Present.h" +#include "VideoCommon/VideoConfig.h" using ConfigurationOption = VideoCommon::PostProcessingConfiguration::ConfigurationOption; using OptionType = ConfigurationOption::OptionType; @@ -43,6 +44,21 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren setWindowTitle(tr("Post-Processing Shader Configuration")); PopulateGroups(); + + // If the display was queried since the shader was last loaded (e.g. window was resized or + // moved to another monitor), sync the slider to the latest queried peak luminance before + // building the widgets so the displayed value is always current. + if (g_backend_info.hdr_max_luminance_nits > 0.f) + { + const auto use_peak_it = m_config_map.find("USE_DISPLAY_PEAK_LUMINANCE"); + const auto max_nits_it = m_config_map.find("HDR_DISPLAY_MAX_NITS"); + if (use_peak_it != m_config_map.end() && max_nits_it != m_config_map.end() && + use_peak_it->second->GetConfigurationOption()->m_bool_value) + { + m_post_processor->SetOptionf("HDR_DISPLAY_MAX_NITS", 0, g_backend_info.hdr_max_luminance_nits); + } + } + Create(); ConnectWidgets(); From 3d1f44c73120751b04523a6267d1a2304066854c Mon Sep 17 00:00:00 2001 From: jasaaved Date: Fri, 20 Mar 2026 02:20:25 -0700 Subject: [PATCH 7/7] Fix coding style errors Also removed an old comment that no longer applies to current code. --- .../DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp | 2 +- Source/Core/VideoBackends/D3DCommon/SwapChain.cpp | 1 - Source/Core/VideoCommon/PostProcessing.cpp | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp index fecffd035c..3b29c2b684 100644 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp @@ -172,12 +172,12 @@ PostProcessingConfigWindow::CreateDependentTab(const std::unique_ptrSetOptionb(config_group->GetOptionName(), state); - m_post_processor->SaveOptionsConfiguration(); config_group->EnableSuboptions(state); if (config_group->GetOptionName() == "USE_DISPLAY_PEAK_LUMINANCE") { + m_post_processor->SaveOptionsConfiguration(); const auto it = m_config_map.find("HDR_DISPLAY_MAX_NITS"); if (it != m_config_map.end()) it->second->SetEnabled(!state); diff --git a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp index cdb7251eb3..df7575c371 100644 --- a/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp +++ b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp @@ -237,7 +237,6 @@ void SwapChain::QueryDisplayHDRCapabilities() } } - void SwapChain::DestroySwapChain() { DestroySwapChainBuffers(); diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index fbb5f5a6ea..bfa044db79 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -938,8 +938,6 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, // Clamp paper white to at least the display's SDR white level when display data is available. // This prevents hdr_paper_white (= paper_white_nits / sdr_white_nits) from dropping below 1.0, // which would scale colors up before tone mapping and risk clipping highlights. - // Note: amplification headroom is computed from peak/sdr_white in the shader, not from paper_white, - // so this floor does not affect how much HDR range the shader uses. const float effective_paper_white = g_backend_info.hdr_max_luminance_nits > 0.f ? std::max(g_ActiveConfig.color_correction.fHDRPaperWhiteNits, @@ -949,7 +947,7 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle& src, // Queried from the display on DX backends; falls back to 80 (scRGB spec value) builtin_uniforms.hdr_sdr_white_nits = g_backend_info.hdr_sdr_white_nits; builtin_uniforms.hdr_max_luminance_nits = g_backend_info.hdr_max_luminance_nits; - builtin_uniforms.hdr_min_luminance_nits = g_backend_info.hdr_min_luminance_nits; + builtin_uniforms.hdr_min_luminance_nits = g_backend_info.hdr_min_luminance_nits; std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms)); buffer += sizeof(builtin_uniforms);