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);