From ae884dd19ea07fe3ddf9ebc0fd79050a45b3151a Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Mon, 20 Nov 2023 14:43:07 +0100 Subject: [PATCH] backends/drm: add brightness metadata overrides and sdr gamut wideness setting The brightness overrides are for displays with missing or broken brightness data in their EDID, and allow the user to work around those displays. In the future we could also offer an HDR calibration process that allows determining the correct brightness values for the screen. The gamut wideness setting allows the user to tweak what gamut KWin assumes sRGB applications to have. This is useful for working around the gamut mapping displays do, which make sRGB content look washed out, and also to allow users to make colors of sRGB apps look more saturated if they wish to. --- src/backends/drm/drm_egl_layer_surface.cpp | 3 +- src/backends/drm/drm_output.cpp | 15 +++- src/backends/drm/drm_pipeline.cpp | 26 ++++++- src/backends/drm/drm_pipeline.h | 6 ++ src/core/colorspace.cpp | 77 ++++++++++++++------- src/core/colorspace.h | 11 ++- src/core/output.cpp | 42 ++++++++++++ src/core/output.h | 18 +++++ src/core/outputconfiguration.h | 4 ++ src/opengl/glshader.cpp | 5 +- src/outputconfigurationstore.cpp | 36 ++++++++++ src/outputconfigurationstore.h | 4 ++ src/wayland/frog_colormanagement_v1.cpp | 2 +- src/wayland/outputdevice_v2.cpp | 80 +++++++++++++++++++++- src/wayland/outputdevice_v2.h | 3 + src/wayland/outputmanagement_v2.cpp | 26 ++++++- 16 files changed, 319 insertions(+), 39 deletions(-) diff --git a/src/backends/drm/drm_egl_layer_surface.cpp b/src/backends/drm/drm_egl_layer_surface.cpp index d5ca335820..038b7dfec5 100644 --- a/src/backends/drm/drm_egl_layer_surface.cpp +++ b/src/backends/drm/drm_egl_layer_surface.cpp @@ -111,7 +111,8 @@ std::optional EglGbmLayerSurface::startRendering(cons if (enableColormanagement) { m_surface->intermediaryColorDescription = ColorDescription(colorDescription.colorimetry(), NamedTransferFunction::linear, colorDescription.sdrBrightness(), colorDescription.minHdrBrightness(), - colorDescription.maxFrameAverageBrightness(), colorDescription.maxHdrHighlightBrightness()); + colorDescription.maxFrameAverageBrightness(), colorDescription.maxHdrHighlightBrightness(), + colorDescription.sdrGamutWideness()); } else { m_surface->intermediaryColorDescription = colorDescription; } diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index c2b26925b1..f4664e132e 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -73,6 +73,7 @@ DrmOutput::DrmOutput(const std::shared_ptr &conn) const Edid *edid = conn->edid(); + const auto hdrData = edid->hdrMetadata(); setInformation(Information{ .name = conn->connectorName(), .manufacturer = edid->manufacturerString(), @@ -87,6 +88,9 @@ DrmOutput::DrmOutput(const std::shared_ptr &conn) .internal = conn->isInternal(), .nonDesktop = conn->isNonDesktop(), .mstPath = conn->mstPath(), + .maxPeakBrightness = hdrData && hdrData->hasValidBrightnessValues ? hdrData->desiredContentMaxLuminance : 0, + .maxAverageBrightness = hdrData && hdrData->hasValidBrightnessValues ? hdrData->desiredMaxFrameAverageLuminance : 0, + .minBrightness = hdrData && hdrData->hasValidBrightnessValues ? hdrData->desiredContentMinLuminance : 0, }); initialState.modes = getModes(); @@ -324,6 +328,10 @@ bool DrmOutput::queueChanges(const std::shared_ptr &props) m_pipeline->setBT2020(props->wideColorGamut.value_or(m_state.wideColorGamut)); m_pipeline->setNamedTransferFunction(props->highDynamicRange.value_or(m_state.highDynamicRange) ? NamedTransferFunction::PerceptualQuantizer : NamedTransferFunction::sRGB); m_pipeline->setSdrBrightness(props->sdrBrightness.value_or(m_state.sdrBrightness)); + m_pipeline->setSdrGamutWideness(props->sdrGamutWideness.value_or(m_state.sdrGamutWideness)); + m_pipeline->setBrightnessOverrides(props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride), + props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride), + props->minBrightnessOverride.value_or(m_state.minBrightnessOverride)); return true; } @@ -348,6 +356,10 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props next.sdrBrightness = props->sdrBrightness.value_or(m_state.sdrBrightness); next.wideColorGamut = props->wideColorGamut.value_or(m_state.wideColorGamut); next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy); + next.maxPeakBrightnessOverride = props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride); + next.maxAverageBrightnessOverride = props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride); + next.minBrightnessOverride = props->minBrightnessOverride.value_or(m_state.minBrightnessOverride); + next.sdrGamutWideness = props->sdrGamutWideness.value_or(m_state.sdrGamutWideness); if (props->iccProfilePath) { next.iccProfilePath = *props->iccProfilePath; next.iccProfile = IccProfile::load(*props->iccProfilePath); @@ -358,9 +370,6 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props // ICC profiles don't support HDR (yet) m_pipeline->setIccProfile(nullptr); } - if (m_state.highDynamicRange != next.highDynamicRange || m_state.sdrBrightness != next.sdrBrightness || m_state.wideColorGamut != next.wideColorGamut || m_state.iccProfile != next.iccProfile) { - m_renderLoop->scheduleRepaint(); - } next.colorDescription = m_pipeline->colorDescription(); setState(next); diff --git a/src/backends/drm/drm_pipeline.cpp b/src/backends/drm/drm_pipeline.cpp index 60852b4fee..aa04435478 100644 --- a/src/backends/drm/drm_pipeline.cpp +++ b/src/backends/drm/drm_pipeline.cpp @@ -718,6 +718,24 @@ void DrmPipeline::setSdrBrightness(double sdrBrightness) } } +void DrmPipeline::setSdrGamutWideness(double sdrGamutWideness) +{ + if (m_pending.sdrGamutWideness != sdrGamutWideness) { + m_pending.sdrGamutWideness = sdrGamutWideness; + m_pending.colorDescription = createColorDescription(); + } +} + +void DrmPipeline::setBrightnessOverrides(std::optional peakBrightnessOverride, std::optional averageBrightnessOverride, std::optional minBrightnessOverride) +{ + if (m_pending.peakBrightnessOverride != peakBrightnessOverride || m_pending.averageBrightnessOverride != averageBrightnessOverride || m_pending.minBrightnessOverride != minBrightnessOverride) { + m_pending.peakBrightnessOverride = peakBrightnessOverride; + m_pending.averageBrightnessOverride = averageBrightnessOverride; + m_pending.minBrightnessOverride = minBrightnessOverride; + m_pending.colorDescription = createColorDescription(); + } +} + void DrmPipeline::setIccProfile(const std::shared_ptr &profile) { if (m_pending.iccProfile != profile) { @@ -731,12 +749,14 @@ ColorDescription DrmPipeline::createColorDescription() const if (m_pending.transferFunction == NamedTransferFunction::PerceptualQuantizer && m_connector->edid()) { const auto colorimetry = m_pending.BT2020 ? NamedColorimetry::BT2020 : NamedColorimetry::BT709; if (const auto hdr = m_connector->edid()->hdrMetadata(); hdr && hdr->hasValidBrightnessValues) { - return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, hdr->desiredContentMinLuminance, hdr->desiredMaxFrameAverageLuminance, hdr->desiredContentMaxLuminance); + return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, hdr->desiredContentMinLuminance, hdr->desiredMaxFrameAverageLuminance, hdr->desiredContentMaxLuminance, m_pending.sdrGamutWideness); + } else if (m_pending.peakBrightnessOverride && m_pending.averageBrightnessOverride) { + return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, m_pending.minBrightnessOverride.value_or(0), *m_pending.averageBrightnessOverride, *m_pending.peakBrightnessOverride, m_pending.sdrGamutWideness); } else { - return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, 0, m_pending.sdrBrightness, m_pending.sdrBrightness); + return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, 0, m_pending.sdrBrightness, m_pending.sdrBrightness, m_pending.sdrGamutWideness); } } else if (m_pending.iccProfile) { - return ColorDescription(m_pending.iccProfile->colorimetry(), NamedTransferFunction::sRGB, 200, 0, 200, 200); + return ColorDescription(m_pending.iccProfile->colorimetry(), NamedTransferFunction::sRGB, 200, 0, 200, 200, 0); } else { return ColorDescription::sRGB; } diff --git a/src/backends/drm/drm_pipeline.h b/src/backends/drm/drm_pipeline.h index 5a4c4d3b7d..34326a8dc7 100644 --- a/src/backends/drm/drm_pipeline.h +++ b/src/backends/drm/drm_pipeline.h @@ -132,6 +132,8 @@ public: void setNamedTransferFunction(NamedTransferFunction tf); void setIccProfile(const std::shared_ptr &profile); void setSdrBrightness(double sdrBrightness); + void setSdrGamutWideness(double sdrGamutWideness); + void setBrightnessOverrides(std::optional peakBrightnessOverride, std::optional averageBrightnessOverride, std::optional minBrightnessOverride); enum class CommitMode { Test, @@ -189,8 +191,12 @@ private: bool BT2020 = false; NamedTransferFunction transferFunction = NamedTransferFunction::sRGB; double sdrBrightness = 200; + double sdrGamutWideness = 0; std::shared_ptr iccProfile; ColorDescription colorDescription = ColorDescription::sRGB; + std::optional peakBrightnessOverride; + std::optional averageBrightnessOverride; + std::optional minBrightnessOverride; // the transformation that buffers submitted to the pipeline should have DrmPlane::Transformations renderOrientation = DrmPlane::Transformation::Rotate0; diff --git a/src/core/colorspace.cpp b/src/core/colorspace.cpp index 33df149c2f..9791fa3614 100644 --- a/src/core/colorspace.cpp +++ b/src/core/colorspace.cpp @@ -134,25 +134,29 @@ bool Colorimetry::operator==(const Colorimetry &other) const : (red == other.red && green == other.green && blue == other.blue && white == other.white); } -constexpr Colorimetry Colorimetry::fromName(NamedColorimetry name) +static const Colorimetry BT709 = Colorimetry{ + .red = {0.64, 0.33}, + .green = {0.30, 0.60}, + .blue = {0.15, 0.06}, + .white = {0.3127, 0.3290}, + .name = NamedColorimetry::BT709, +}; + +static const Colorimetry BT2020 = Colorimetry{ + .red = {0.708, 0.292}, + .green = {0.170, 0.797}, + .blue = {0.131, 0.046}, + .white = {0.3127, 0.3290}, + .name = NamedColorimetry::BT2020, +}; + +Colorimetry Colorimetry::fromName(NamedColorimetry name) { switch (name) { case NamedColorimetry::BT709: - return Colorimetry{ - .red = {0.64, 0.33}, - .green = {0.30, 0.60}, - .blue = {0.15, 0.06}, - .white = {0.3127, 0.3290}, - .name = name, - }; + return BT709; case NamedColorimetry::BT2020: - return Colorimetry{ - .red = {0.708, 0.292}, - .green = {0.170, 0.797}, - .blue = {0.131, 0.046}, - .white = {0.3127, 0.3290}, - .name = name, - }; + return BT2020; } Q_UNREACHABLE(); } @@ -168,11 +172,23 @@ Colorimetry Colorimetry::fromXYZ(QVector3D red, QVector3D green, QVector3D blue, }; } -const ColorDescription ColorDescription::sRGB = ColorDescription(NamedColorimetry::BT709, NamedTransferFunction::sRGB, 100, 0, 100, 100); +const ColorDescription ColorDescription::sRGB = ColorDescription(NamedColorimetry::BT709, NamedTransferFunction::sRGB, 100, 0, 100, 100, 0); -ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness) +static Colorimetry sRGBColorimetry(double factor) +{ + return Colorimetry{ + .red = BT709.red * (1 - factor) + BT2020.red * factor, + .green = BT709.green * (1 - factor) + BT2020.green * factor, + .blue = BT709.blue * (1 - factor) + BT2020.blue * factor, + .white = BT709.white, // whitepoint is the same + }; +} + +ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, double sdrGamutWideness) : m_colorimetry(colorimety) , m_transferFunction(tf) + , m_sdrColorimetry(sRGBColorimetry(sdrGamutWideness)) + , m_sdrGamutWideness(sdrGamutWideness) , m_sdrBrightness(sdrBrightness) , m_minHdrBrightness(minHdrBrightness) , m_maxFrameAverageBrightness(maxFrameAverageBrightness) @@ -180,9 +196,11 @@ ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferF { } -ColorDescription::ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness) +ColorDescription::ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, double sdrGamutWideness) : m_colorimetry(Colorimetry::fromName(colorimetry)) , m_transferFunction(tf) + , m_sdrColorimetry(sRGBColorimetry(sdrGamutWideness)) + , m_sdrGamutWideness(sdrGamutWideness) , m_sdrBrightness(sdrBrightness) , m_minHdrBrightness(minHdrBrightness) , m_maxFrameAverageBrightness(maxFrameAverageBrightness) @@ -195,6 +213,11 @@ const Colorimetry &ColorDescription::colorimetry() const return m_colorimetry; } +const Colorimetry &ColorDescription::sdrColorimetry() const +{ + return m_sdrColorimetry; +} + NamedTransferFunction ColorDescription::transferFunction() const { return m_transferFunction; @@ -220,14 +243,20 @@ double ColorDescription::maxHdrHighlightBrightness() const return m_maxHdrHighlightBrightness; } +double ColorDescription::sdrGamutWideness() const +{ + return m_sdrGamutWideness; +} + bool ColorDescription::operator==(const ColorDescription &other) const { - return m_colorimetry == other.colorimetry() - && m_transferFunction == other.transferFunction() - && m_sdrBrightness == other.sdrBrightness() - && m_minHdrBrightness == other.minHdrBrightness() - && m_maxFrameAverageBrightness == other.maxFrameAverageBrightness() - && m_maxHdrHighlightBrightness == other.maxHdrHighlightBrightness(); + return m_colorimetry == other.m_colorimetry + && m_transferFunction == other.m_transferFunction + && m_sdrGamutWideness == other.m_sdrGamutWideness + && m_sdrBrightness == other.m_sdrBrightness + && m_minHdrBrightness == other.m_minHdrBrightness + && m_maxFrameAverageBrightness == other.m_maxFrameAverageBrightness + && m_maxHdrHighlightBrightness == other.m_maxHdrHighlightBrightness; } static float srgbToLinear(float sRGB) diff --git a/src/core/colorspace.h b/src/core/colorspace.h index 048ad0eecb..73633ce355 100644 --- a/src/core/colorspace.h +++ b/src/core/colorspace.h @@ -27,7 +27,7 @@ enum class NamedColorimetry { class KWIN_EXPORT Colorimetry { public: - static constexpr Colorimetry fromName(NamedColorimetry name); + static Colorimetry fromName(NamedColorimetry name); static Colorimetry fromXYZ(QVector3D red, QVector3D green, QVector3D blue, QVector3D white); /** * @returns the XYZ representation of the xyY color passed in. Y is assumed to be one @@ -88,16 +88,19 @@ public: * @param minHdrBrightness the minimum brightness of HDR content * @param maxFrameAverageBrightness the maximum brightness of HDR content, if the whole screen is white * @param maxHdrHighlightBrightness the maximum brightness of HDR content, for a small part of the screen only + * @param sdrGamutWideness the gamut wideness of sRGB content; 0 is rec.709, 1 is rec.2020, everything in between is interpolated */ - explicit ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness); - explicit ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness); + explicit ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, double sdrGamutWideness); + explicit ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, double sdrGamutWideness); const Colorimetry &colorimetry() const; + const Colorimetry &sdrColorimetry() const; NamedTransferFunction transferFunction() const; double sdrBrightness() const; double minHdrBrightness() const; double maxFrameAverageBrightness() const; double maxHdrHighlightBrightness() const; + double sdrGamutWideness() const; bool operator==(const ColorDescription &other) const; @@ -108,6 +111,8 @@ public: private: Colorimetry m_colorimetry; NamedTransferFunction m_transferFunction; + Colorimetry m_sdrColorimetry; + double m_sdrGamutWideness; double m_sdrBrightness; double m_minHdrBrightness; double m_maxFrameAverageBrightness; diff --git a/src/core/output.cpp b/src/core/output.cpp index de1e6e3901..05f1754d4b 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -431,6 +431,14 @@ void Output::setState(const State &state) if (oldState.iccProfilePath != state.iccProfilePath) { Q_EMIT iccProfilePathChanged(); } + if (oldState.maxPeakBrightnessOverride != state.maxPeakBrightnessOverride + || oldState.maxAverageBrightnessOverride != state.maxAverageBrightnessOverride + || oldState.minBrightnessOverride != state.minBrightnessOverride) { + Q_EMIT brightnessMetadataChanged(); + } + if (oldState.sdrGamutWideness != state.sdrGamutWideness) { + Q_EMIT sdrGamutWidenessChanged(); + } if (oldState.enabled != state.enabled) { Q_EMIT enabledChanged(); } @@ -604,6 +612,40 @@ const ColorDescription &Output::colorDescription() const return m_state.colorDescription; } +double Output::maxPeakBrightness() const +{ + return m_state.maxPeakBrightnessOverride.value_or(m_information.maxPeakBrightness); +} + +double Output::maxAverageBrightness() const +{ + return m_state.maxAverageBrightnessOverride.value_or(m_information.maxAverageBrightness); +} + +double Output::minBrightness() const +{ + return m_state.minBrightnessOverride.value_or(m_information.minBrightness); +} + +std::optional Output::maxPeakBrightnessOverride() const +{ + return m_state.maxPeakBrightnessOverride; +} + +std::optional Output::maxAverageBrightnessOverride() const +{ + return m_state.maxAverageBrightnessOverride; +} + +std::optional Output::minBrightnessOverride() const +{ + return m_state.minBrightnessOverride; +} + +double Output::sdrGamutWideness() const +{ + return m_state.sdrGamutWideness; +} } // namespace KWin #include "moc_output.cpp" diff --git a/src/core/output.h b/src/core/output.h index e998a875e2..a2d8db6b2d 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -335,6 +335,15 @@ public: virtual bool updateCursorLayer(); + double maxPeakBrightness() const; + double maxAverageBrightness() const; + double minBrightness() const; + std::optional maxPeakBrightnessOverride() const; + std::optional maxAverageBrightnessOverride() const; + std::optional minBrightnessOverride() const; + + double sdrGamutWideness() const; + const ColorDescription &colorDescription() const; Q_SIGNALS: @@ -396,6 +405,8 @@ Q_SIGNALS: void autoRotationPolicyChanged(); void iccProfileChanged(); void iccProfilePathChanged(); + void brightnessMetadataChanged(); + void sdrGamutWidenessChanged(); protected: struct Information @@ -414,6 +425,9 @@ protected: bool placeholder = false; bool nonDesktop = false; QByteArray mstPath; + double maxPeakBrightness = 0; + double maxAverageBrightness = 0; + double minBrightness = 0; }; struct State @@ -436,6 +450,10 @@ protected: QString iccProfilePath; std::shared_ptr iccProfile; ColorDescription colorDescription = ColorDescription::sRGB; + std::optional maxPeakBrightnessOverride; + std::optional maxAverageBrightnessOverride; + std::optional minBrightnessOverride; + double sdrGamutWideness = 0; }; void setInformation(const Information &information); diff --git a/src/core/outputconfiguration.h b/src/core/outputconfiguration.h index f5e4e6b19e..dbe0e3614c 100644 --- a/src/core/outputconfiguration.h +++ b/src/core/outputconfiguration.h @@ -37,6 +37,10 @@ public: std::optional wideColorGamut; std::optional autoRotationPolicy; std::optional iccProfilePath; + std::optional> maxPeakBrightnessOverride; + std::optional> maxAverageBrightnessOverride; + std::optional> minBrightnessOverride; + std::optional sdrGamutWideness; }; class KWIN_EXPORT OutputConfiguration diff --git a/src/opengl/glshader.cpp b/src/opengl/glshader.cpp index f87621a234..c7a8e64b0e 100644 --- a/src/opengl/glshader.cpp +++ b/src/opengl/glshader.cpp @@ -441,7 +441,8 @@ QMatrix4x4 GLShader::getUniformMatrix4x4(const char *name) bool GLShader::setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst) { - return setUniform(GLShader::MatrixUniform::ColorimetryTransformation, src.colorimetry().toOther(dst.colorimetry())) + const auto &srcColorimetry = src.colorimetry().name == NamedColorimetry::BT709 ? dst.sdrColorimetry() : src.colorimetry(); + return setUniform(GLShader::MatrixUniform::ColorimetryTransformation, srcColorimetry.toOther(dst.colorimetry())) && setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(src.transferFunction())) && setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(dst.transferFunction())) && setUniform(FloatUniform::SdrBrightness, dst.sdrBrightness()) @@ -455,7 +456,7 @@ bool GLShader::setColorspaceUniformsFromSRGB(const ColorDescription &dst) bool GLShader::setColorspaceUniformsToSRGB(const ColorDescription &src) { - return setUniform(GLShader::MatrixUniform::ColorimetryTransformation, src.colorimetry().toOther(ColorDescription::sRGB.colorimetry())) + return setUniform(GLShader::MatrixUniform::ColorimetryTransformation, src.colorimetry().toOther(src.sdrColorimetry())) && setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(src.transferFunction())) && setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(NamedTransferFunction::sRGB)) && setUniform(FloatUniform::SdrBrightness, src.sdrBrightness()) diff --git a/src/outputconfigurationstore.cpp b/src/outputconfigurationstore.cpp index fcd4ead952..e24590b624 100644 --- a/src/outputconfigurationstore.cpp +++ b/src/outputconfigurationstore.cpp @@ -209,6 +209,10 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .wideColorGamut = changeSet->wideColorGamut.value_or(output->wideColorGamut()), .autoRotation = changeSet->autoRotationPolicy.value_or(output->autoRotationPolicy()), .iccProfilePath = changeSet->iccProfilePath.value_or(output->iccProfilePath()), + .maxPeakBrightnessOverride = changeSet->maxPeakBrightnessOverride.value_or(output->maxPeakBrightnessOverride()), + .maxAverageBrightnessOverride = changeSet->maxAverageBrightnessOverride.value_or(output->maxAverageBrightnessOverride()), + .minBrightnessOverride = changeSet->minBrightnessOverride.value_or(output->minBrightnessOverride()), + .sdrGamutWideness = changeSet->sdrGamutWideness.value_or(output->sdrGamutWideness()), }; *outputIt = SetupState{ .outputIndex = *outputIndex, @@ -237,6 +241,10 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .wideColorGamut = output->wideColorGamut(), .autoRotation = output->autoRotationPolicy(), .iccProfilePath = output->iccProfilePath(), + .maxPeakBrightnessOverride = output->maxPeakBrightnessOverride(), + .maxAverageBrightnessOverride = output->maxAverageBrightnessOverride(), + .minBrightnessOverride = output->minBrightnessOverride(), + .sdrGamutWideness = output->sdrGamutWideness(), }; *outputIt = SetupState{ .outputIndex = *outputIndex, @@ -279,6 +287,10 @@ std::pair> OutputConfigurationStore::setupT .wideColorGamut = state.wideColorGamut, .autoRotationPolicy = state.autoRotation, .iccProfilePath = state.iccProfilePath, + .maxPeakBrightnessOverride = state.maxPeakBrightnessOverride, + .maxAverageBrightnessOverride = state.maxAverageBrightnessOverride, + .minBrightnessOverride = state.minBrightnessOverride, + .sdrGamutWideness = state.sdrGamutWideness, }; if (setupState.enabled) { priorities.push_back(std::make_pair(output, setupState.priority)); @@ -652,6 +664,18 @@ void OutputConfigurationStore::load() if (const auto it = data.find("iccProfilePath"); it != data.end()) { state.iccProfilePath = it->toString(); } + if (const auto it = data.find("maxPeakBrightnessOverride"); it != data.end() && it->isDouble()) { + state.maxPeakBrightnessOverride = it->toDouble(); + } + if (const auto it = data.find("maxAverageBrightnessOverride"); it != data.end() && it->isDouble()) { + state.maxAverageBrightnessOverride = it->toDouble(); + } + if (const auto it = data.find("minBrightnessOverride"); it != data.end() && it->isDouble()) { + state.minBrightnessOverride = it->toDouble(); + } + if (const auto it = data.find("sdrGamutWideness"); it != data.end() && it->isDouble()) { + state.sdrGamutWideness = it->toDouble(); + } outputDatas.push_back(state); } @@ -846,6 +870,18 @@ void OutputConfigurationStore::save() if (output.iccProfilePath) { o["iccProfilePath"] = *output.iccProfilePath; } + if (output.maxPeakBrightnessOverride) { + o["maxPeakBrightnessOverride"] = *output.maxPeakBrightnessOverride; + } + if (output.maxAverageBrightnessOverride) { + o["maxAverageBrightnessOverride"] = *output.maxAverageBrightnessOverride; + } + if (output.minBrightnessOverride) { + o["minBrightnessOverride"] = *output.minBrightnessOverride; + } + if (output.sdrGamutWideness) { + o["sdrGamutWideness"] = *output.sdrGamutWideness; + } outputsData.append(o); } outputs["data"] = outputsData; diff --git a/src/outputconfigurationstore.h b/src/outputconfigurationstore.h index 0f100cdd9b..bc598bbe62 100644 --- a/src/outputconfigurationstore.h +++ b/src/outputconfigurationstore.h @@ -76,6 +76,10 @@ private: std::optional wideColorGamut; std::optional autoRotation; std::optional iccProfilePath; + std::optional maxPeakBrightnessOverride; + std::optional maxAverageBrightnessOverride; + std::optional minBrightnessOverride; + std::optional sdrGamutWideness; }; struct SetupState { diff --git a/src/wayland/frog_colormanagement_v1.cpp b/src/wayland/frog_colormanagement_v1.cpp index c0187b1b26..2519600569 100644 --- a/src/wayland/frog_colormanagement_v1.cpp +++ b/src/wayland/frog_colormanagement_v1.cpp @@ -148,7 +148,7 @@ void FrogColorManagementSurfaceV1::updateColorDescription() if (m_surface) { // TODO make brightness values optional in ColorDescription SurfaceInterfacePrivate *priv = SurfaceInterfacePrivate::get(m_surface); - priv->pending->colorDescription = ColorDescription(m_colorimetry, m_transferFunction, 0, 0, m_maxFrameAverageBrightness, m_maxPeakBrightness); + priv->pending->colorDescription = ColorDescription(m_colorimetry, m_transferFunction, 0, 0, m_maxFrameAverageBrightness, m_maxPeakBrightness, 0); priv->pending->colorDescriptionIsSet = true; } } diff --git a/src/wayland/outputdevice_v2.cpp b/src/wayland/outputdevice_v2.cpp index eacd31722a..7f5737cc3e 100644 --- a/src/wayland/outputdevice_v2.cpp +++ b/src/wayland/outputdevice_v2.cpp @@ -22,7 +22,7 @@ namespace KWin { -static const quint32 s_version = 5; +static const quint32 s_version = 6; static QtWaylandServer::kde_output_device_v2::transform kwinTransformToOutputDeviceTransform(OutputTransform transform) { @@ -102,6 +102,9 @@ public: void sendWideColorGamut(Resource *resource); void sendAutoRotationPolicy(Resource *resource); void sendIccProfilePath(Resource *resource); + void sendBrightnessMetadata(Resource *resource); + void sendBrightnessOverrides(Resource *resource); + void sendSdrGamutWideness(Resource *resource); OutputDeviceV2Interface *q; QPointer m_display; @@ -130,6 +133,13 @@ public: bool m_wideColorGamut = false; auto_rotate_policy m_autoRotation = auto_rotate_policy::auto_rotate_policy_in_tablet_mode; QString m_iccProfilePath; + double m_maxPeakBrightness = 0; + double m_maxAverageBrightness = 0; + double m_minBrightness = 0; + double m_sdrGamutWideness = 0; + std::optional m_maxPeakBrightnessOverride; + std::optional m_maxAverageBrightnessOverride; + std::optional m_minBrightnessOverride; protected: void kde_output_device_v2_bind_resource(Resource *resource) override; @@ -212,6 +222,9 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl updateWideColorGamut(); updateAutoRotate(); updateIccProfilePath(); + updateBrightnessMetadata(); + updateBrightnessOverrides(); + updateSdrGamutWideness(); connect(handle, &Output::geometryChanged, this, &OutputDeviceV2Interface::updateGlobalPosition); @@ -238,6 +251,8 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl connect(handle, &Output::wideColorGamutChanged, this, &OutputDeviceV2Interface::updateWideColorGamut); connect(handle, &Output::autoRotationPolicyChanged, this, &OutputDeviceV2Interface::updateAutoRotate); connect(handle, &Output::iccProfileChanged, this, &OutputDeviceV2Interface::updateIccProfilePath); + connect(handle, &Output::brightnessMetadataChanged, this, &OutputDeviceV2Interface::updateBrightnessMetadata); + connect(handle, &Output::sdrGamutWidenessChanged, this, &OutputDeviceV2Interface::updateSdrGamutWideness); } OutputDeviceV2Interface::~OutputDeviceV2Interface() @@ -293,6 +308,8 @@ void OutputDeviceV2InterfacePrivate::kde_output_device_v2_bind_resource(Resource sendWideColorGamut(resource); sendAutoRotationPolicy(resource); sendIccProfilePath(resource); + sendBrightnessMetadata(resource); + sendSdrGamutWideness(resource); sendDone(resource); } @@ -425,6 +442,27 @@ void OutputDeviceV2InterfacePrivate::sendIccProfilePath(Resource *resource) } } +void OutputDeviceV2InterfacePrivate::sendBrightnessMetadata(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_METADATA_SINCE_VERSION) { + send_brightness_metadata(resource->handle, std::round(m_maxPeakBrightness), std::round(m_maxAverageBrightness), std::round(m_minBrightness * 10'000)); + } +} + +void OutputDeviceV2InterfacePrivate::sendBrightnessOverrides(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_OVERRIDES_SINCE_VERSION) { + send_brightness_overrides(resource->handle, std::round(m_maxPeakBrightnessOverride.value_or(-1)), std::round(m_maxAverageBrightnessOverride.value_or(-1)), std::round(m_minBrightnessOverride.value_or(-0.000'1) * 10'000)); + } +} + +void OutputDeviceV2InterfacePrivate::sendSdrGamutWideness(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_SDR_GAMUT_WIDENESS_SINCE_VERSION) { + send_sdr_gamut_wideness(resource->handle, std::clamp(m_sdrGamutWideness * 10'000, 0, 10'000)); + } +} + void OutputDeviceV2Interface::updateGeometry() { const auto clientResources = d->resourceMap(); @@ -706,6 +744,46 @@ void OutputDeviceV2Interface::updateIccProfilePath() } } +void OutputDeviceV2Interface::updateBrightnessMetadata() +{ + if (d->m_maxPeakBrightness != d->m_handle->maxPeakBrightness() || d->m_maxAverageBrightness != d->m_handle->maxAverageBrightness() || d->m_minBrightness != d->m_handle->minBrightness()) { + d->m_maxPeakBrightness = d->m_handle->maxPeakBrightness(); + d->m_maxAverageBrightness = d->m_handle->maxAverageBrightness(); + d->m_minBrightness = d->m_handle->minBrightness(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendBrightnessMetadata(resource); + d->sendDone(resource); + } + } +} + +void OutputDeviceV2Interface::updateBrightnessOverrides() +{ + if (d->m_maxPeakBrightnessOverride != d->m_handle->maxPeakBrightnessOverride() || d->m_maxAverageBrightnessOverride != d->m_handle->maxAverageBrightnessOverride() || d->m_minBrightnessOverride != d->m_handle->minBrightnessOverride()) { + d->m_maxPeakBrightnessOverride = d->m_handle->maxPeakBrightnessOverride(); + d->m_maxAverageBrightnessOverride = d->m_handle->maxAverageBrightnessOverride(); + d->m_minBrightnessOverride = d->m_handle->minBrightnessOverride(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendBrightnessOverrides(resource); + d->sendDone(resource); + } + } +} + +void OutputDeviceV2Interface::updateSdrGamutWideness() +{ + if (d->m_sdrGamutWideness != d->m_handle->sdrGamutWideness()) { + d->m_sdrGamutWideness = d->m_handle->sdrGamutWideness(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendSdrGamutWideness(resource); + d->sendDone(resource); + } + } +} + OutputDeviceV2Interface *OutputDeviceV2Interface::get(wl_resource *native) { if (auto devicePrivate = resource_cast(native); devicePrivate && !devicePrivate->isGlobalRemoved()) { diff --git a/src/wayland/outputdevice_v2.h b/src/wayland/outputdevice_v2.h index 6bdfa87f05..06801f62b4 100644 --- a/src/wayland/outputdevice_v2.h +++ b/src/wayland/outputdevice_v2.h @@ -74,6 +74,9 @@ private: void updateWideColorGamut(); void updateAutoRotate(); void updateIccProfilePath(); + void updateBrightnessMetadata(); + void updateBrightnessOverrides(); + void updateSdrGamutWideness(); std::unique_ptr d; }; diff --git a/src/wayland/outputmanagement_v2.cpp b/src/wayland/outputmanagement_v2.cpp index 4f8c7ce507..859ad8741a 100644 --- a/src/wayland/outputmanagement_v2.cpp +++ b/src/wayland/outputmanagement_v2.cpp @@ -23,7 +23,7 @@ namespace KWin { -static const quint32 s_version = 6; +static const quint32 s_version = 7; class OutputManagementV2InterfacePrivate : public QtWaylandServer::kde_output_management_v2 { @@ -64,6 +64,8 @@ protected: void kde_output_configuration_v2_set_wide_color_gamut(Resource *resource, wl_resource *outputdevice, uint32_t enable_wcg) override; void kde_output_configuration_v2_set_auto_rotate_policy(Resource *resource, wl_resource *outputdevice, uint32_t auto_rotation_policy) override; void kde_output_configuration_v2_set_icc_profile_path(Resource *resource, wl_resource *outputdevice, const QString &profile_path) override; + void kde_output_configuration_v2_set_brightness_overrides(Resource *resource, wl_resource *outputdevice, int32_t max_peak_brightness, int32_t max_average_brightness, int32_t min_brightness) override; + void kde_output_configuration_v2_set_sdr_gamut_wideness(Resource *resource, wl_resource *outputdevice, uint32_t gamut_wideness) override; }; OutputManagementV2InterfacePrivate::OutputManagementV2InterfacePrivate(Display *display) @@ -295,6 +297,28 @@ void OutputConfigurationV2Interface::kde_output_configuration_v2_set_icc_profile } } +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_brightness_overrides(Resource *resource, wl_resource *outputdevice, int32_t max_peak_brightness, int32_t max_average_brightness, int32_t min_brightness) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->maxPeakBrightnessOverride = max_peak_brightness == -1 ? std::nullopt : std::optional(max_peak_brightness); + config.changeSet(output->handle())->maxAverageBrightnessOverride = max_average_brightness == -1 ? std::nullopt : std::optional(max_average_brightness); + config.changeSet(output->handle())->minBrightnessOverride = min_brightness == -1 ? std::nullopt : std::optional(min_brightness / 10'000.0); + } +} + +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_sdr_gamut_wideness(Resource *resource, wl_resource *outputdevice, uint32_t gamut_wideness) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->sdrGamutWideness = gamut_wideness / 10'000.0; + } +} + void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy(Resource *resource) { wl_resource_destroy(resource->handle);