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.
This commit is contained in:
Xaver Hugl 2024-07-03 22:44:34 +02:00
parent 5aaab715b0
commit 2799c270b4
12 changed files with 406 additions and 145 deletions

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,252 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
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<ColorTransferFunction>(op.operation)
|| std::holds_alternative<InverseColorTransferFunction>(op.operation);
};
// first, only check if the pipeline can be programmed in the first place
// don't calculate LUTs just yet
std::optional<ColorOp> 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<DrmAtomicCommit>(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<ColorTransferFunction>(op.operation) || std::holds_alternative<InverseColorTransferFunction>(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<ColorMultiplier>(op.operation)) {
return true;
}
return false;
}
void LegacyLutColorOp::program(DrmAtomicCommit *commit, const ColorOp &op, double inputScale, double outputScale)
{
if (auto tf = std::get_if<ColorTransferFunction>(&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<uint16_t>::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<InverseColorTransferFunction>(&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<uint16_t>::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<ColorMultiplier>(&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<uint16_t>::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<uint16_t>::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<uint16_t>::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<ColorMatrix>(&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<ColorMultiplier>(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<ColorMatrix>(&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<ColorMultiplier>(&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);
}
}

View file

@ -0,0 +1,70 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorpipeline.h"
#include <drm.h>
#include <memory>
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<ColorPipeline> m_cachedPipeline;
std::unique_ptr<DrmAtomicCommit> 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<drm_color_lut> 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;
};
}

View file

@ -39,6 +39,11 @@ DrmGpu *DrmCommit::gpu() const
return m_gpu;
}
DrmAtomicCommit::DrmAtomicCommit(DrmGpu *gpu)
: DrmCommit(gpu)
{
}
DrmAtomicCommit::DrmAtomicCommit(const QList<DrmPipeline *> &pipelines)
: DrmCommit(pipelines.front()->gpu())
, m_pipelines(pipelines)

View file

@ -51,8 +51,9 @@ protected:
class DrmAtomicCommit : public DrmCommit
{
public:
DrmAtomicCommit(const QList<DrmPipeline *> &pipelines);
DrmAtomicCommit(const DrmAtomicCommit &copy) = default;
explicit DrmAtomicCommit(DrmGpu *gpu);
explicit DrmAtomicCommit(const QList<DrmPipeline *> &pipelines);
explicit DrmAtomicCommit(const DrmAtomicCommit &copy) = default;
void addProperty(const DrmProperty &prop, uint64_t value);
template<typename T>

View file

@ -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<LegacyLutColorOp>(next, &gammaLut, gammaLutSize.value()));
next = m_postBlendingColorOps.back().get();
}
if (ctm.isValid()) {
m_postBlendingColorOps.push_back(std::make_unique<LegacyMatrixColorOp>(next, &ctm));
next = m_postBlendingColorOps.back().get();
}
if (degammaLut.isValid() && degammaLutSize.isValid() && degammaLutSize.value() > 0) {
m_postBlendingColorOps.push_back(std::make_unique<LegacyLutColorOp>(next, &degammaLut, 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();

View file

@ -8,6 +8,7 @@
*/
#pragma once
#include "drm_colorop.h"
#include "drm_object.h"
#include <QPoint>
@ -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<drmModeCrtc> m_crtc;
std::shared_ptr<DrmFramebuffer> m_currentBuffer;
int m_pipeIndex;
DrmPlane *m_primaryPlane;
DrmPlane *m_cursorPlane;
std::vector<std::unique_ptr<DrmAbstractColorOp>> m_postBlendingColorOps;
};
}

View file

@ -345,8 +345,7 @@ bool DrmOutput::queueChanges(const std::shared_ptr<OutputChangeSet> &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;

View file

@ -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<uint32_t, QList<uint64_t>> 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<ColorTransformation> &transformation)
: m_lut(transformation, crtc->gammaRampSize())
{
if (crtc->gpu()->atomicModeSetting()) {
QList<drm_color_lut> 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<DrmBlob> DrmGammaRamp::blob() const
{
return m_blob;
}
DrmCrtc *DrmPipeline::crtc() const
{
return m_pending.crtc;
@ -625,9 +587,6 @@ const std::shared_ptr<IccProfile> &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<DrmGammaRamp>(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<ColorTransformation> &transformation)
void DrmPipeline::setCrtcColorPipeline(const ColorPipeline &pipeline)
{
m_pending.colorTransformation = transformation;
if (transformation) {
m_pending.gamma = std::make_shared<DrmGammaRamp>(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)

View file

@ -17,6 +17,7 @@
#include <xf86drmMode.h>
#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<ColorTransformation> &transformation);
const ColorLUT &lut() const;
std::shared_ptr<DrmBlob> blob() const;
private:
const ColorLUT m_lut;
std::shared_ptr<DrmBlob> m_blob;
};
class DrmPipeline
{
public:
@ -90,8 +77,6 @@ public:
void resetModesetPresentPending();
QHash<uint32_t, QList<uint64_t>> 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<ColorTransformation> &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<IccProfile> &profile);
@ -167,7 +151,7 @@ private:
bool m_modesetPresentPending = false;
bool m_didLegacyScanoutHack = false;
std::shared_ptr<DrmGammaRamp> 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> colorTransformation;
std::shared_ptr<DrmGammaRamp> gamma;
std::shared_ptr<DrmBlob> ctm;
ColorPipeline crtcColorPipeline;
DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics;
std::shared_ptr<IccProfile> iccProfile;

View file

@ -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<ColorMultiplier>(&m_pending.crtcColorPipeline.ops.front().operation);
if (!mult) {
return DrmPipeline::Error::InvalidArguments;
}
factors = mult->factors;
}
QList<uint16_t> red(m_pending.crtc->gammaRampSize());
QList<uint16_t> green(m_pending.crtc->gammaRampSize());
QList<uint16_t> 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<uint16_t>::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;
}