From 2799c270b4a7ac96971d6bcc889f347adfcaf558 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Wed, 3 Jul 2024 22:44:34 +0200 Subject: [PATCH] backends/drm: implement support for post blending color pipelines This exposes the degamma->ctm->gamma pipeline as a drm color op, which can be set to a generic color pipeline. The same code can later be adapted to program the upcoming per-plane color pipeline properties. --- autotests/drm/CMakeLists.txt | 1 + src/backends/drm/CMakeLists.txt | 1 + src/backends/drm/drm_colorop.cpp | 252 +++++++++++++++++++++++ src/backends/drm/drm_colorop.h | 70 +++++++ src/backends/drm/drm_commit.cpp | 5 + src/backends/drm/drm_commit.h | 5 +- src/backends/drm/drm_crtc.cpp | 17 ++ src/backends/drm/drm_crtc.h | 7 +- src/backends/drm/drm_output.cpp | 42 +--- src/backends/drm/drm_pipeline.cpp | 93 +-------- src/backends/drm/drm_pipeline.h | 26 +-- src/backends/drm/drm_pipeline_legacy.cpp | 32 ++- 12 files changed, 406 insertions(+), 145 deletions(-) create mode 100644 src/backends/drm/drm_colorop.cpp create mode 100644 src/backends/drm/drm_colorop.h diff --git a/autotests/drm/CMakeLists.txt b/autotests/drm/CMakeLists.txt index bc5d8c6d79..f4747c39c4 100644 --- a/autotests/drm/CMakeLists.txt +++ b/autotests/drm/CMakeLists.txt @@ -4,6 +4,7 @@ set(mockDRM_SRCS ../../src/backends/drm/drm_backend.cpp ../../src/backends/drm/drm_blob.cpp ../../src/backends/drm/drm_buffer.cpp + ../../src/backends/drm/drm_colorop.cpp ../../src/backends/drm/drm_commit.cpp ../../src/backends/drm/drm_commit_thread.cpp ../../src/backends/drm/drm_connector.cpp diff --git a/src/backends/drm/CMakeLists.txt b/src/backends/drm/CMakeLists.txt index dcffbb7767..5c1e9a854a 100644 --- a/src/backends/drm/CMakeLists.txt +++ b/src/backends/drm/CMakeLists.txt @@ -3,6 +3,7 @@ target_sources(kwin PRIVATE drm_backend.cpp drm_blob.cpp drm_buffer.cpp + drm_colorop.cpp drm_commit.cpp drm_commit_thread.cpp drm_connector.cpp diff --git a/src/backends/drm/drm_colorop.cpp b/src/backends/drm/drm_colorop.cpp new file mode 100644 index 0000000000..0c9fab376d --- /dev/null +++ b/src/backends/drm/drm_colorop.cpp @@ -0,0 +1,252 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "drm_colorop.h" +#include "drm_blob.h" +#include "drm_commit.h" +#include "drm_object.h" + +namespace KWin +{ + +DrmAbstractColorOp::DrmAbstractColorOp(DrmAbstractColorOp *next) + : m_next(next) +{ +} + +DrmAbstractColorOp::~DrmAbstractColorOp() +{ +} + +DrmAbstractColorOp *DrmAbstractColorOp::next() const +{ + return m_next; +} + +bool DrmAbstractColorOp::matchPipeline(DrmAtomicCommit *commit, const ColorPipeline &pipeline) +{ + if (m_cachedPipeline && *m_cachedPipeline == pipeline) { + commit->merge(m_cache.get()); + return true; + } + + DrmAbstractColorOp *currentOp = this; + const auto needsLimitedRange = [](const ColorOp &op) { + // KMS LUTs have an input and output range of [0, 1] + return std::holds_alternative(op.operation) + || std::holds_alternative(op.operation); + }; + + // first, only check if the pipeline can be programmed in the first place + // don't calculate LUTs just yet + std::optional initialOp; + double valueScaling = 1; + if (!pipeline.ops.empty() && needsLimitedRange(pipeline.ops.front()) && pipeline.ops.front().input.max > 1) { + valueScaling = 1.0 / pipeline.ops.front().input.max; + initialOp = ColorOp{ + .input = pipeline.ops.front().input, + .operation = ColorMultiplier{valueScaling}, + .output = ValueRange{ + .min = pipeline.ops.front().input.min * valueScaling, + .max = 1.0, + }, + }; + while (currentOp && !currentOp->canBeUsedFor(*initialOp)) { + currentOp = currentOp->next(); + } + if (!currentOp) { + return false; + } + currentOp = currentOp->next(); + } + for (auto it = pipeline.ops.begin(); it != pipeline.ops.end(); it++) { + while (currentOp && !currentOp->canBeUsedFor(*it)) { + currentOp = currentOp->next(); + } + if (!currentOp) { + return false; + } + currentOp = currentOp->next(); + } + + // now actually program the properties + currentOp = this; + m_cache = std::make_unique(commit->gpu()); + if (initialOp) { + while (!currentOp->canBeUsedFor(*initialOp)) { + currentOp->bypass(m_cache.get()); + currentOp = currentOp->next(); + } + currentOp->program(m_cache.get(), *initialOp, 1, 1); + currentOp = currentOp->next(); + } + for (auto it = pipeline.ops.begin(); it != pipeline.ops.end(); it++) { + while (!currentOp->canBeUsedFor(*it)) { + currentOp->bypass(m_cache.get()); + currentOp = currentOp->next(); + } + if (it == pipeline.ops.end() - 1) { + // this is the last op, we need to un-do the factor + // this assumes that the output is always limited range + currentOp->program(m_cache.get(), *it, valueScaling, 1.0); + valueScaling = 1.0; + } else if (needsLimitedRange(*it) || needsLimitedRange(*(it + 1))) { + // this op can only output limited range or the next op needs a limited range input, + // adjust the factor to make it happen + currentOp->program(m_cache.get(), *it, valueScaling, 1.0 / it->output.max); + valueScaling = 1.0 / it->output.max; + } else { + // this and the next op are both fine with extended range, set the factor to 1.0 to use all the resolution we can get + currentOp->program(m_cache.get(), *it, valueScaling, 1.0); + valueScaling = 1.0; + } + currentOp = currentOp->next(); + } + while (currentOp) { + currentOp->bypass(m_cache.get()); + currentOp = currentOp->next(); + } + commit->merge(m_cache.get()); + m_cachedPipeline = pipeline; + return true; +} + +LegacyLutColorOp::LegacyLutColorOp(DrmAbstractColorOp *next, DrmProperty *prop, uint32_t maxSize) + : DrmAbstractColorOp(next) + , m_prop(prop) + , m_maxSize(maxSize) + , m_components(m_maxSize) +{ +} + +bool LegacyLutColorOp::canBeUsedFor(const ColorOp &op) +{ + if (std::holds_alternative(op.operation) || std::holds_alternative(op.operation)) { + // the required resolution depends heavily on the function and on the input and output ranges / multipliers + // but this is good enough for now + return m_maxSize >= 1024; + } else if (std::holds_alternative(op.operation)) { + return true; + } + return false; +} + +void LegacyLutColorOp::program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale) +{ + if (auto tf = std::get_if(&op.operation)) { + for (uint32_t i = 0; i < m_maxSize; i++) { + const double nits = tf->tf.encodedToNits(i / double(m_maxSize - 1) / inputScale, tf->referenceLuminance); + const uint16_t output = std::round(std::clamp(nits * outputScale, 0.0, 1.0) * std::numeric_limits::max()); + m_components[i] = { + .red = output, + .green = output, + .blue = output, + .reserved = 0, + }; + } + commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), m_components.data(), sizeof(drm_color_lut) * m_components.size())); + } else if (auto tf = std::get_if(&op.operation)) { + for (uint32_t i = 0; i < m_maxSize; i++) { + const double nits = tf->tf.nitsToEncoded(i / double(m_maxSize - 1) / inputScale, tf->referenceLuminance); + const uint16_t output = std::round(std::clamp(nits * outputScale, 0.0, 1.0) * std::numeric_limits::max()); + m_components[i] = { + .red = output, + .green = output, + .blue = output, + .reserved = 0, + }; + } + commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), m_components.data(), sizeof(drm_color_lut) * m_components.size())); + } else if (auto mult = std::get_if(&op.operation)) { + for (uint32_t i = 0; i < m_maxSize; i++) { + m_components[i] = { + .red = uint16_t(std::round(std::clamp(mult->factors.x() * outputScale / inputScale * i / double(m_maxSize - 1), 0.0, 1.0) * std::numeric_limits::max())), + .green = uint16_t(std::round(std::clamp(mult->factors.y() * outputScale / inputScale * i / double(m_maxSize - 1), 0.0, 1.0) * std::numeric_limits::max())), + .blue = uint16_t(std::round(std::clamp(mult->factors.z() * outputScale / inputScale * i / double(m_maxSize - 1), 0.0, 1.0) * std::numeric_limits::max())), + .reserved = 0, + }; + } + commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), m_components.data(), sizeof(drm_color_lut) * m_maxSize)); + } else { + Q_ASSERT(false); + } +} + +void LegacyLutColorOp::bypass(DrmAtomicCommit *commit) +{ + commit->addBlob(*m_prop, nullptr); +} + +LegacyMatrixColorOp::LegacyMatrixColorOp(DrmAbstractColorOp *next, DrmProperty *prop) + : DrmAbstractColorOp(next) + , m_prop(prop) +{ +} + +bool LegacyMatrixColorOp::canBeUsedFor(const ColorOp &op) +{ + // this isn't necessarily true, but let's keep things simple for now + if (auto matrix = std::get_if(&op.operation)) { + return matrix->mat(3, 0) == 0 + && matrix->mat(3, 1) == 0 + && matrix->mat(3, 2) == 0 + && matrix->mat(3, 3) == 1 + && matrix->mat(0, 3) == 0 + && matrix->mat(1, 3) == 0 + && matrix->mat(2, 3) == 0; + } else if (std::holds_alternative(op.operation)) { + return true; + } + return false; +} + +static uint64_t doubleToFixed(double value) +{ + // ctm values are in S31.32 sign-magnitude format + uint64_t ret = std::abs(value) * (1ull << 32); + if (value < 0) { + ret |= 1ull << 63; + } + return ret; +} + +void LegacyMatrixColorOp::program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale) +{ + if (auto matrix = std::get_if(&op.operation)) { + QMatrix4x4 scaled = matrix->mat; + scaled.scale(outputScale / inputScale); + drm_color_ctm data = { + .matrix = { + doubleToFixed(scaled(0, 0)), doubleToFixed(scaled(0, 1)), doubleToFixed(scaled(0, 2)), // + doubleToFixed(scaled(1, 0)), doubleToFixed(scaled(1, 1)), doubleToFixed(scaled(1, 2)), // + doubleToFixed(scaled(2, 0)), doubleToFixed(scaled(2, 1)), doubleToFixed(scaled(2, 2)), // + }, + }; + commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), &data, sizeof(data))); + } else if (auto mult = std::get_if(&op.operation)) { + QVector3D scaled = mult->factors; + scaled *= outputScale / inputScale; + drm_color_ctm data = { + .matrix = { + doubleToFixed(scaled.x()), doubleToFixed(0), doubleToFixed(0), // + doubleToFixed(0), doubleToFixed(scaled.y()), doubleToFixed(0), // + doubleToFixed(0), doubleToFixed(0), doubleToFixed(scaled.z()), // + }, + }; + commit->addBlob(*m_prop, DrmBlob::create(m_prop->drmObject()->gpu(), &data, sizeof(data))); + } else { + Q_ASSERT(false); + } +} + +void LegacyMatrixColorOp::bypass(DrmAtomicCommit *commit) +{ + commit->addBlob(*m_prop, nullptr); +} + +} diff --git a/src/backends/drm/drm_colorop.h b/src/backends/drm/drm_colorop.h new file mode 100644 index 0000000000..ae8ab89c6f --- /dev/null +++ b/src/backends/drm/drm_colorop.h @@ -0,0 +1,70 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once +#include "core/colorpipeline.h" + +#include +#include + +namespace KWin +{ + +class DrmBlob; +class DrmProperty; +class DrmAtomicCommit; + +class DrmAbstractColorOp +{ +public: + explicit DrmAbstractColorOp(DrmAbstractColorOp *next); + virtual ~DrmAbstractColorOp(); + + bool matchPipeline(DrmAtomicCommit *commit, const ColorPipeline &pipeline); + virtual bool canBeUsedFor(const ColorOp &op) = 0; + virtual void program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale) = 0; + virtual void bypass(DrmAtomicCommit *commit) = 0; + + DrmAbstractColorOp *next() const; + +protected: + DrmAbstractColorOp *m_next = nullptr; + + std::optional m_cachedPipeline; + std::unique_ptr m_cache; +}; + +class LegacyLutColorOp : public DrmAbstractColorOp +{ +public: + explicit LegacyLutColorOp(DrmAbstractColorOp *next, DrmProperty *prop, uint32_t maxSize); + + bool canBeUsedFor(const ColorOp &op) override; + void program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale) override; + void bypass(DrmAtomicCommit *commit) override; + +private: + DrmProperty *const m_prop; + const uint32_t m_maxSize; + QList m_components; +}; + +class LegacyMatrixColorOp : public DrmAbstractColorOp +{ +public: + explicit LegacyMatrixColorOp(DrmAbstractColorOp *next, DrmProperty *prop); + + bool canBeUsedFor(const ColorOp &op) override; + void program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale) override; + void bypass(DrmAtomicCommit *commit) override; + +private: + DrmProperty *const m_prop; +}; + +} diff --git a/src/backends/drm/drm_commit.cpp b/src/backends/drm/drm_commit.cpp index bc2be4f7f9..7a0f260c41 100644 --- a/src/backends/drm/drm_commit.cpp +++ b/src/backends/drm/drm_commit.cpp @@ -39,6 +39,11 @@ DrmGpu *DrmCommit::gpu() const return m_gpu; } +DrmAtomicCommit::DrmAtomicCommit(DrmGpu *gpu) + : DrmCommit(gpu) +{ +} + DrmAtomicCommit::DrmAtomicCommit(const QList &pipelines) : DrmCommit(pipelines.front()->gpu()) , m_pipelines(pipelines) diff --git a/src/backends/drm/drm_commit.h b/src/backends/drm/drm_commit.h index 8469f5f789..ed9a3399e6 100644 --- a/src/backends/drm/drm_commit.h +++ b/src/backends/drm/drm_commit.h @@ -51,8 +51,9 @@ protected: class DrmAtomicCommit : public DrmCommit { public: - DrmAtomicCommit(const QList &pipelines); - DrmAtomicCommit(const DrmAtomicCommit ©) = default; + explicit DrmAtomicCommit(DrmGpu *gpu); + explicit DrmAtomicCommit(const QList &pipelines); + explicit DrmAtomicCommit(const DrmAtomicCommit ©) = default; void addProperty(const DrmProperty &prop, uint64_t value); template diff --git a/src/backends/drm/drm_crtc.cpp b/src/backends/drm/drm_crtc.cpp index ba5e1d9498..858d41cfb0 100644 --- a/src/backends/drm/drm_crtc.cpp +++ b/src/backends/drm/drm_crtc.cpp @@ -49,6 +49,23 @@ bool DrmCrtc::updateProperties() degammaLut.update(props); degammaLutSize.update(props); + if (!postBlendingPipeline) { + DrmAbstractColorOp *next = nullptr; + if (gammaLut.isValid() && gammaLutSize.isValid() && gammaLutSize.value() > 0) { + m_postBlendingColorOps.push_back(std::make_unique(next, &gammaLut, gammaLutSize.value())); + next = m_postBlendingColorOps.back().get(); + } + if (ctm.isValid()) { + m_postBlendingColorOps.push_back(std::make_unique(next, &ctm)); + next = m_postBlendingColorOps.back().get(); + } + if (degammaLut.isValid() && degammaLutSize.isValid() && degammaLutSize.value() > 0) { + m_postBlendingColorOps.push_back(std::make_unique(next, °ammaLut, degammaLutSize.value())); + next = m_postBlendingColorOps.back().get(); + } + postBlendingPipeline = next; + } + const bool ret = !gpu()->atomicModeSetting() || (modeId.isValid() && active.isValid()); if (!ret) { qCWarning(KWIN_DRM) << "Failed to update the basic crtc properties. modeId:" << modeId.isValid() << "active:" << active.isValid(); diff --git a/src/backends/drm/drm_crtc.h b/src/backends/drm/drm_crtc.h index eb53b83e3e..7fb374765f 100644 --- a/src/backends/drm/drm_crtc.h +++ b/src/backends/drm/drm_crtc.h @@ -8,6 +8,7 @@ */ #pragma once +#include "drm_colorop.h" #include "drm_object.h" #include @@ -25,7 +26,7 @@ class DrmPlane; class DrmCrtc : public DrmObject { public: - DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane); + explicit DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane); void disable(DrmAtomicCommit *commit) override; bool updateProperties() override; @@ -49,12 +50,16 @@ public: DrmProperty degammaLut; DrmProperty degammaLutSize; + DrmAbstractColorOp *postBlendingPipeline = nullptr; + private: DrmUniquePtr m_crtc; std::shared_ptr m_currentBuffer; int m_pipeIndex; DrmPlane *m_primaryPlane; DrmPlane *m_cursorPlane; + + std::vector> m_postBlendingColorOps; }; } diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 94661007a5..9ba4853459 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -345,8 +345,7 @@ bool DrmOutput::queueChanges(const std::shared_ptr &props) } if (bt2020 || hdr || props->colorProfileSource.value_or(m_state.colorProfileSource) != ColorProfileSource::sRGB) { // remove unused gamma ramp and ctm, if present - m_pipeline->setGammaRamp(nullptr); - m_pipeline->setCTM(QMatrix3x3{}); + m_pipeline->setCrtcColorPipeline(ColorPipeline{}); } return true; } @@ -458,36 +457,17 @@ bool DrmOutput::doSetChannelFactors(const QVector3D &rgb) if (!m_pipeline->activePending()) { return false; } + ColorPipeline pipeline{ValueRange{}}; const auto inOutputSpace = m_state.colorDescription.transferFunction().nitsToEncoded(rgb, 1); - if (m_pipeline->hasCTM()) { - QMatrix3x3 ctm; - ctm(0, 0) = inOutputSpace.x(); - ctm(1, 1) = inOutputSpace.y(); - ctm(2, 2) = inOutputSpace.z(); - m_pipeline->setCTM(ctm); - m_pipeline->setGammaRamp(nullptr); - if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { - m_pipeline->applyPendingChanges(); - m_channelFactorsNeedShaderFallback = false; - return true; - } else { - m_pipeline->setCTM(QMatrix3x3()); - m_pipeline->applyPendingChanges(); - } - } - if (m_pipeline->hasGammaRamp()) { - auto lut = ColorTransformation::createScalingTransform(inOutputSpace); - if (lut) { - m_pipeline->setGammaRamp(std::move(lut)); - if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { - m_pipeline->applyPendingChanges(); - m_channelFactorsNeedShaderFallback = false; - return true; - } else { - m_pipeline->setGammaRamp(nullptr); - m_pipeline->applyPendingChanges(); - } - } + pipeline.addMultiplier(inOutputSpace); + m_pipeline->setCrtcColorPipeline(pipeline); + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { + m_pipeline->applyPendingChanges(); + m_channelFactorsNeedShaderFallback = false; + return true; + } else { + m_pipeline->setCrtcColorPipeline(ColorPipeline{}); + m_pipeline->applyPendingChanges(); } m_channelFactorsNeedShaderFallback = m_channelFactors != QVector3D{1, 1, 1}; return true; diff --git a/src/backends/drm/drm_pipeline.cpp b/src/backends/drm/drm_pipeline.cpp index d7adfa7619..9bd15590d2 100644 --- a/src/backends/drm/drm_pipeline.cpp +++ b/src/backends/drm/drm_pipeline.cpp @@ -212,15 +212,15 @@ DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commi if (m_pending.crtc->vrrEnabled.isValid()) { commit->setVrr(m_pending.crtc, m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync); } - if (m_pending.crtc->gammaLut.isValid()) { - commit->addBlob(m_pending.crtc->gammaLut, m_pending.gamma ? m_pending.gamma->blob() : nullptr); - } else if (m_pending.gamma) { - return Error::InvalidArguments; - } - if (m_pending.crtc->ctm.isValid()) { - commit->addBlob(m_pending.crtc->ctm, m_pending.ctm); - } else if (m_pending.ctm) { - return Error::InvalidArguments; + + if (!m_pending.crtc->postBlendingPipeline) { + if (!m_pending.crtcColorPipeline.isIdentity()) { + return Error::InvalidArguments; + } + } else { + if (!m_pending.crtc->postBlendingPipeline->matchPipeline(commit, m_pending.crtcColorPipeline)) { + return Error::InvalidArguments; + } } if (!m_primaryLayer->checkTestBuffer()) { @@ -480,20 +480,6 @@ QHash> DrmPipeline::formats(DrmPlane::TypeIndex planeT Q_UNREACHABLE(); } -bool DrmPipeline::hasCTM() const -{ - return gpu()->atomicModeSetting() && m_pending.crtc && m_pending.crtc->ctm.isValid(); -} - -bool DrmPipeline::hasGammaRamp() const -{ - if (gpu()->atomicModeSetting()) { - return m_pending.crtc && m_pending.crtc->gammaLut.isValid(); - } else { - return m_pending.crtc && m_pending.crtc->gammaRampSize() > 0; - } -} - bool DrmPipeline::pruneModifier() { const DmaBufAttributes *dmabufAttributes = m_primaryLayer->currentBuffer() ? m_primaryLayer->currentBuffer()->buffer()->dmabufAttributes() : nullptr; @@ -539,30 +525,6 @@ void DrmPipeline::resetModesetPresentPending() m_modesetPresentPending = false; } -DrmGammaRamp::DrmGammaRamp(DrmCrtc *crtc, const std::shared_ptr &transformation) - : m_lut(transformation, crtc->gammaRampSize()) -{ - if (crtc->gpu()->atomicModeSetting()) { - QList atomicLut(m_lut.size()); - for (uint32_t i = 0; i < m_lut.size(); i++) { - atomicLut[i].red = m_lut.red()[i]; - atomicLut[i].green = m_lut.green()[i]; - atomicLut[i].blue = m_lut.blue()[i]; - } - m_blob = DrmBlob::create(crtc->gpu(), atomicLut.data(), sizeof(drm_color_lut) * atomicLut.size()); - } -} - -const ColorLUT &DrmGammaRamp::lut() const -{ - return m_lut; -} - -std::shared_ptr DrmGammaRamp::blob() const -{ - return m_blob; -} - DrmCrtc *DrmPipeline::crtc() const { return m_pending.crtc; @@ -625,9 +587,6 @@ const std::shared_ptr &DrmPipeline::iccProfile() const void DrmPipeline::setCrtc(DrmCrtc *crtc) { - if (crtc && m_pending.crtc && crtc->gammaRampSize() != m_pending.crtc->gammaRampSize() && m_pending.colorTransformation) { - m_pending.gamma = std::make_shared(crtc, m_pending.colorTransformation); - } m_pending.crtc = crtc; if (crtc) { m_pending.formats = crtc->primaryPlane() ? crtc->primaryPlane()->formats() : legacyFormats; @@ -672,39 +631,9 @@ void DrmPipeline::setRgbRange(Output::RgbRange range) m_pending.rgbRange = range; } -void DrmPipeline::setGammaRamp(const std::shared_ptr &transformation) +void DrmPipeline::setCrtcColorPipeline(const ColorPipeline &pipeline) { - m_pending.colorTransformation = transformation; - if (transformation) { - m_pending.gamma = std::make_shared(m_pending.crtc, transformation); - } else { - m_pending.gamma.reset(); - } -} - -static uint64_t doubleToFixed(double value) -{ - // ctm values are in S31.32 sign-magnitude format - uint64_t ret = std::abs(value) * (1ull << 32); - if (value < 0) { - ret |= 1ull << 63; - } - return ret; -} - -void DrmPipeline::setCTM(const QMatrix3x3 &ctm) -{ - if (ctm.isIdentity()) { - m_pending.ctm.reset(); - } else { - drm_color_ctm blob = { - .matrix = { - doubleToFixed(ctm(0, 0)), doubleToFixed(ctm(1, 0)), doubleToFixed(ctm(2, 0)), - doubleToFixed(ctm(0, 1)), doubleToFixed(ctm(1, 1)), doubleToFixed(ctm(2, 1)), - doubleToFixed(ctm(0, 2)), doubleToFixed(ctm(1, 2)), doubleToFixed(ctm(2, 2))}, - }; - m_pending.ctm = DrmBlob::create(gpu(), &blob, sizeof(blob)); - } + m_pending.crtcColorPipeline = pipeline; } void DrmPipeline::setColorDescription(const ColorDescription &description) diff --git a/src/backends/drm/drm_pipeline.h b/src/backends/drm/drm_pipeline.h index 4afb3528b5..2e734c2f92 100644 --- a/src/backends/drm/drm_pipeline.h +++ b/src/backends/drm/drm_pipeline.h @@ -17,6 +17,7 @@ #include #include "core/colorlut.h" +#include "core/colorpipeline.h" #include "core/colorspace.h" #include "core/output.h" #include "core/renderloop_p.h" @@ -30,25 +31,11 @@ namespace KWin class DrmGpu; class DrmConnector; class DrmCrtc; -class GammaRamp; class DrmConnectorMode; class DrmPipelineLayer; class DrmCommitThread; class OutputFrame; -class DrmGammaRamp -{ -public: - DrmGammaRamp(DrmCrtc *crtc, const std::shared_ptr &transformation); - - const ColorLUT &lut() const; - std::shared_ptr blob() const; - -private: - const ColorLUT m_lut; - std::shared_ptr m_blob; -}; - class DrmPipeline { public: @@ -90,8 +77,6 @@ public: void resetModesetPresentPending(); QHash> formats(DrmPlane::TypeIndex planeType) const; - bool hasCTM() const; - bool hasGammaRamp() const; bool pruneModifier(); void setOutput(DrmOutput *output); @@ -121,8 +106,7 @@ public: void setPresentationMode(PresentationMode mode); void setOverscan(uint32_t overscan); void setRgbRange(Output::RgbRange range); - void setGammaRamp(const std::shared_ptr &transformation); - void setCTM(const QMatrix3x3 &ctm); + void setCrtcColorPipeline(const ColorPipeline &pipeline); void setContentType(DrmConnector::DrmContentType type); void setColorDescription(const ColorDescription &description); void setIccProfile(const std::shared_ptr &profile); @@ -167,7 +151,7 @@ private: bool m_modesetPresentPending = false; bool m_didLegacyScanoutHack = false; - std::shared_ptr m_currentLegacyGamma; + ColorPipeline m_currentLegacyGamma; struct State { @@ -181,9 +165,7 @@ private: uint32_t overscan = 0; Output::RgbRange rgbRange = Output::RgbRange::Automatic; PresentationMode presentationMode = PresentationMode::VSync; - std::shared_ptr colorTransformation; - std::shared_ptr gamma; - std::shared_ptr ctm; + ColorPipeline crtcColorPipeline; DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics; std::shared_ptr iccProfile; diff --git a/src/backends/drm/drm_pipeline_legacy.cpp b/src/backends/drm/drm_pipeline_legacy.cpp index 63db14d5e2..3d099452b2 100644 --- a/src/backends/drm/drm_pipeline_legacy.cpp +++ b/src/backends/drm/drm_pipeline_legacy.cpp @@ -140,7 +140,7 @@ DrmPipeline::Error DrmPipeline::applyPendingChangesLegacy() return err; } } - if (m_pending.gamma != m_currentLegacyGamma) { + if (m_pending.crtcColorPipeline != m_currentLegacyGamma) { if (Error err = setLegacyGamma(); err != Error::None) { return err; } @@ -159,13 +159,31 @@ DrmPipeline::Error DrmPipeline::applyPendingChangesLegacy() DrmPipeline::Error DrmPipeline::setLegacyGamma() { - if (m_pending.gamma) { - if (drmModeCrtcSetGamma(gpu()->fd(), m_pending.crtc->id(), m_pending.gamma->lut().size(), m_pending.gamma->lut().red(), m_pending.gamma->lut().green(), m_pending.gamma->lut().blue()) != 0) { - qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno); - return errnoToError(); - } - m_currentLegacyGamma = m_pending.gamma; + if (m_pending.crtcColorPipeline.ops.size() > 1) { + return DrmPipeline::Error::InvalidArguments; } + QVector3D factors(1, 1, 1); + if (m_pending.crtcColorPipeline.ops.size() == 1) { + auto mult = std::get_if(&m_pending.crtcColorPipeline.ops.front().operation); + if (!mult) { + return DrmPipeline::Error::InvalidArguments; + } + factors = mult->factors; + } + QList red(m_pending.crtc->gammaRampSize()); + QList green(m_pending.crtc->gammaRampSize()); + QList blue(m_pending.crtc->gammaRampSize()); + for (int i = 0; i < m_pending.crtc->gammaRampSize(); i++) { + const double relative = i / double(m_pending.crtc->gammaRampSize() - 1) * std::numeric_limits::max(); + red[i] = std::round(relative * factors.x()); + green[i] = std::round(relative * factors.y()); + blue[i] = std::round(relative * factors.z()); + } + if (drmModeCrtcSetGamma(gpu()->fd(), m_pending.crtc->id(), m_pending.crtc->gammaRampSize(), red.data(), green.data(), blue.data()) != 0) { + qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno); + return errnoToError(); + } + m_currentLegacyGamma = m_pending.crtcColorPipeline; return DrmPipeline::Error::None; }