From c3894d9288e99500df4fc4704bcc614ea8d520ec Mon Sep 17 00:00:00 2001 From: UjinT34 <41110182+UjinT34@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:15:18 +0300 Subject: [PATCH] config/monitor: Add monitor v2 HDR rules (#10623) --- src/config/ConfigManager.cpp | 30 ++++++++++++++++ src/helpers/Monitor.cpp | 46 ++++++++++++++++++++++--- src/helpers/Monitor.hpp | 29 ++++++++++++++-- src/protocols/types/ColorManagement.hpp | 8 ++--- src/render/OpenGL.cpp | 8 ++--- src/render/OpenGL.hpp | 2 +- src/render/Renderer.cpp | 5 +-- 7 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 8fc6e3265..c3cb8a58a 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -808,6 +808,13 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); m_config->addSpecialConfigValue("monitorv2", "transform", {STRVAL_EMPTY}); // TODO use correct type + m_config->addSpecialConfigValue("monitorv2", "supports_wide_color", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "supports_hdr", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "sdr_min_luminance", Hyprlang::FLOAT{0.2}); + m_config->addSpecialConfigValue("monitorv2", "sdr_max_luminance", Hyprlang::INT{80}); + m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); + m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); + m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); // keywords m_config->registerHandler(&::handleExec, "exec", {false}); @@ -1069,6 +1076,29 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.parseTransform(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_wide_color", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().supportsWideColor = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_hdr", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().supportsHDR = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_min_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().sdrMinLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_max_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().sdrMaxLuminance = std::any_cast(VAL->getValue()); + + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "min_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().minLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().maxLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_avg_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().maxAvgLuminance = std::any_cast(VAL->getValue()); + auto newrule = parser.rule(); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index c2f1ab5b9..df9c4a8e3 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -464,7 +464,9 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { && ((DELTALESSTHAN(m_position.x, RULE->offset.x, 1) && DELTALESSTHAN(m_position.y, RULE->offset.y, 1)) || RULE->offset == Vector2D(-INT32_MAX, -INT32_MAX)) /* other properties hadnt changed */ && m_transform == RULE->transform && RULE->enable10bit == m_enabled10bit && RULE->cmType == m_cmType && RULE->sdrSaturation == m_sdrSaturation && - RULE->sdrBrightness == m_sdrBrightness && !std::memcmp(&m_customDrmMode, &RULE->drmMode, sizeof(m_customDrmMode))) { + RULE->sdrBrightness == m_sdrBrightness && RULE->sdrMinLuminance == m_minLuminance && RULE->sdrMaxLuminance == m_maxLuminance && + RULE->supportsWideColor == m_supportsWideColor && RULE->supportsHDR == m_supportsHDR && RULE->minLuminance == m_minLuminance && RULE->maxLuminance == m_maxLuminance && + RULE->maxAvgLuminance == m_maxAvgLuminance && !std::memcmp(&m_customDrmMode, &RULE->drmMode, sizeof(m_customDrmMode))) { Debug::log(LOG, "Not applying a new rule to {} because it's already applied!", m_name); @@ -734,15 +736,16 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_enabled10bit = set10bit; + m_supportsWideColor = RULE->supportsHDR; + m_supportsHDR = RULE->supportsHDR; + auto oldImageDescription = m_imageDescription; m_cmType = RULE->cmType; switch (m_cmType) { - case CM_AUTO: m_cmType = m_enabled10bit && m_output->parsedEDID.supportsBT2020 ? CM_WIDE : CM_SRGB; break; + case CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? CM_WIDE : CM_SRGB; break; case CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? CM_EDID : CM_SRGB; break; case CM_HDR: - case CM_HDR_EDID: - m_cmType = m_output->parsedEDID.supportsBT2020 && m_output->parsedEDID.hdrMetadata.has_value() && m_output->parsedEDID.hdrMetadata->supportsPQ ? m_cmType : CM_SRGB; - break; + case CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : CM_SRGB; break; default: break; } switch (m_cmType) { @@ -788,6 +791,19 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { break; default: UNREACHABLE(); } + + m_sdrMinLuminance = RULE->sdrMinLuminance; + m_sdrMaxLuminance = RULE->sdrMaxLuminance; + + m_minLuminance = RULE->minLuminance; + if (m_minLuminance >= 0) + m_imageDescription.luminances.min = m_minLuminance; + m_maxLuminance = RULE->maxLuminance; + if (m_maxLuminance >= 0) + m_imageDescription.luminances.max = m_maxLuminance; + m_maxAvgLuminance = RULE->maxAvgLuminance; + if (m_maxAvgLuminance >= 0) + m_imageDescription.luminances.reference = m_maxAvgLuminance; if (oldImageDescription != m_imageDescription) PROTO::colorManagement->onMonitorImageDescriptionChanged(m_self); @@ -1601,6 +1617,26 @@ void CMonitor::onCursorMovedOnMonitor() { m_tearingState.frameScheduledWhileBusy = true; } +bool CMonitor::supportsWideColor() { + return m_supportsWideColor || m_output->parsedEDID.supportsBT2020; +} + +bool CMonitor::supportsHDR() { + return supportsWideColor() && (m_supportsHDR || (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->supportsPQ : false)); +} + +float CMonitor::minLuminance() { + return m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0); +} + +int CMonitor::maxLuminance() { + return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 80); +} + +int CMonitor::maxAvgLuminance() { + return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 80); +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 4dc4ad881..0f35a4232 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -56,8 +56,19 @@ struct SMonitorRule { eCMType cmType = CM_SRGB; float sdrSaturation = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR - drmModeModeInfo drmMode = {}; - std::optional vrr; + + bool supportsWideColor = false; // false does nothing, true overrides EDID + bool supportsHDR = false; // false does nothing, true overrides EDID + float sdrMinLuminance = 0.2f; // SDR -> HDR + int sdrMaxLuminance = 80; // SDR -> HDR + + // Incorrect values will result in reduced luminance range or incorrect tonemapping. Shouldn't damage the HW. Use with care in case of a faulty monitor firmware. + float minLuminance = -1.0f; // >= 0 overrides EDID + int maxLuminance = -1; // >= 0 overrides EDID + int maxAvgLuminance = -1; // >= 0 overrides EDID + + drmModeModeInfo drmMode = {}; + std::optional vrr; }; class CMonitor; @@ -128,6 +139,8 @@ class CMonitor { eCMType m_cmType = CM_SRGB; float m_sdrSaturation = 1.0f; float m_sdrBrightness = 1.0f; + float m_sdrMinLuminance = 0.2f; + int m_sdrMaxLuminance = 80; bool m_createdByUser = false; bool m_isUnsafeFallback = false; @@ -214,6 +227,12 @@ class CMonitor { void debugLastPresentation(const std::string& message); void onMonitorFrame(); + bool supportsWideColor(); + bool supportsHDR(); + float minLuminance(); + int maxLuminance(); + int maxAvgLuminance(); + bool m_enabled = false; bool m_renderingInitPassed = false; WP m_previousFSWindow; @@ -244,4 +263,10 @@ class CMonitor { CHyprSignalListener presented; CHyprSignalListener commit; } m_listeners; + + bool m_supportsWideColor = false; + bool m_supportsHDR = false; + float m_minLuminance = -1.0f; + int m_maxLuminance = -1; + int m_maxAvgLuminance = -1; }; diff --git a/src/protocols/types/ColorManagement.hpp b/src/protocols/types/ColorManagement.hpp index 2dd1c075d..52f3f1b7b 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/protocols/types/ColorManagement.hpp @@ -181,7 +181,7 @@ namespace NColorManagement { return primaries; } - float getTFMinLuminance() const { + float getTFMinLuminance(float sdrMinLuminance = -1.0f) const { switch (transferFunction) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0; case CM_TRANSFER_FUNCTION_ST2084_PQ: @@ -196,11 +196,11 @@ namespace NColorManagement { case CM_TRANSFER_FUNCTION_EXT_SRGB: case CM_TRANSFER_FUNCTION_ST428: case CM_TRANSFER_FUNCTION_SRGB: - default: return SDR_MIN_LUMINANCE; + default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE; } }; - float getTFMaxLuminance() const { + float getTFMaxLuminance(int sdrMaxLuminance = -1) const { switch (transferFunction) { case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE; @@ -214,7 +214,7 @@ namespace NColorManagement { case CM_TRANSFER_FUNCTION_EXT_SRGB: case CM_TRANSFER_FUNCTION_ST428: case CM_TRANSFER_FUNCTION_SRGB: - default: return SDR_MAX_LUMINANCE; + default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE; } }; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 7ba4b6b9d..659bc1daa 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1436,7 +1436,7 @@ void CHyprOpenGLImpl::renderTextureWithDamage(SP tex, const CBox& box, static std::map, std::array> primariesConversionCache; void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const NColorManagement::SImageDescription& imageDescription, - const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR) { + const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { glUniform1i(shader.sourceTF, imageDescription.transferFunction); glUniform1i(shader.targetTF, targetImageDescription.transferFunction); @@ -1450,8 +1450,8 @@ void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const NColorManageme }; glUniformMatrix4x2fv(shader.targetPrimaries, 1, false, glTargetPrimaries); - glUniform2f(shader.srcTFRange, imageDescription.getTFMinLuminance(), imageDescription.getTFMaxLuminance()); - glUniform2f(shader.dstTFRange, targetImageDescription.getTFMinLuminance(), targetImageDescription.getTFMaxLuminance()); + glUniform2f(shader.srcTFRange, imageDescription.getTFMinLuminance(sdrMinLuminance), imageDescription.getTFMaxLuminance(sdrMaxLuminance)); + glUniform2f(shader.dstTFRange, targetImageDescription.getTFMinLuminance(sdrMinLuminance), targetImageDescription.getTFMaxLuminance(sdrMaxLuminance)); const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference; glUniform1f(shader.maxLuminance, maxLuminance * targetImageDescription.luminances.reference / imageDescription.luminances.reference); @@ -1480,7 +1480,7 @@ void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const NColorManageme } void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const SImageDescription& imageDescription) { - passCMUniforms(shader, imageDescription, m_renderData.pMonitor->m_imageDescription, true); + passCMUniforms(shader, imageDescription, m_renderData.pMonitor->m_imageDescription, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); } void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, const CBox& box, float alpha, const CRegion& damage, int round, float roundingPower, bool discardActive, diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 341ee575d..92d9b7d38 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -347,7 +347,7 @@ class CHyprOpenGLImpl { CFramebuffer* blurFramebufferWithDamage(float a, CRegion* damage, CFramebuffer& source); void passCMUniforms(const SShader&, const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription, - bool modifySDR = false); + bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); void passCMUniforms(const SShader&, const NColorManagement::SImageDescription& imageDescription); void renderTextureInternalWithDamage(SP, const CBox& box, float a, const CRegion& damage, int round = 0, float roundingPower = 2.0f, bool discardOpaque = false, bool noAA = false, bool allowCustomUV = false, bool allowDim = false, GLenum wrapX = GL_CLAMP_TO_EDGE, GLenum wrapY = GL_CLAMP_TO_EDGE); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index ecc46a36b..43d4ece5d 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1485,10 +1485,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { static auto PPASS = CConfigValue("render:cm_fs_passthrough"); const bool PHDR = pMonitor->m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ; - const bool SUPPORTSPQ = pMonitor->m_output->parsedEDID.hdrMetadata.has_value() ? pMonitor->m_output->parsedEDID.hdrMetadata->supportsPQ : false; - Debug::log(TRACE, "ColorManagement supportsBT2020 {}, supportsPQ {}", pMonitor->m_output->parsedEDID.supportsBT2020, SUPPORTSPQ); - - if (pMonitor->m_output->parsedEDID.supportsBT2020 && SUPPORTSPQ) { + if (pMonitor->supportsHDR()) { // HDR metadata determined by // PPASS = 0 monitor settings // PPASS = 1