diff --git a/Data/Sys/Shaders/AutoHDR.glsl b/Data/Sys/Shaders/AutoHDR.glsl index b806d65838..d5c6abfa00 100644 --- a/Data/Sys/Shaders/AutoHDR.glsl +++ b/Data/Sys/Shaders/AutoHDR.glsl @@ -3,6 +3,11 @@ /* [configuration] +[OptionBool] +GUIName = Use Display Peak Luminance (DX11+) +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..3b29c2b684 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(); @@ -120,6 +136,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); @@ -152,6 +174,14 @@ void PostProcessingConfigWindow::UpdateBool(ConfigGroup* const config_group, con m_post_processor->SetOptionb(config_group->GetOptionName(), state); 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); + } } void PostProcessingConfigWindow::UpdateInteger(ConfigGroup* const config_group, const int value) @@ -346,6 +376,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/VideoBackends/D3DCommon/SwapChain.cpp b/Source/Core/VideoBackends/D3DCommon/SwapChain.cpp index 1c91275a52..df7575c371 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" @@ -188,9 +191,52 @@ 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; + 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{}; + + 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(); @@ -225,7 +271,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..bfa044db79 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -295,10 +295,32 @@ 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()); + ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); + } } break; } } + + // 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() @@ -637,6 +659,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 +880,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 @@ -909,9 +935,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. + 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; 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..86d1f6dab7 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -180,6 +180,9 @@ 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; + float hdr_sdr_white_nits = 80.f; bool bSupportsUnrestrictedDepthRange = false; };