backends/drm: support CTM for simple color transformations

In order to work around hardware and drivers that aren't capable of applying
a LUT, calculate a per-channel factor for brightness and color temperature
modification. While this ignores color calibration, this makes night color
work until a proper shader based color pipeline is implemented.

BUG: 455720
This commit is contained in:
Xaver Hugl 2023-02-14 14:52:56 +01:00
parent f4a35ed619
commit f2417a8523
11 changed files with 118 additions and 18 deletions

View file

@ -19,7 +19,7 @@ namespace KWin
{
DrmCrtc::DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane)
: DrmObject(gpu, crtcId, {PropertyDefinition(QByteArrayLiteral("MODE_ID"), Requirement::Required), PropertyDefinition(QByteArrayLiteral("ACTIVE"), Requirement::Required), PropertyDefinition(QByteArrayLiteral("VRR_ENABLED"), Requirement::Optional), PropertyDefinition(QByteArrayLiteral("GAMMA_LUT"), Requirement::Optional), PropertyDefinition(QByteArrayLiteral("GAMMA_LUT_SIZE"), Requirement::Optional)}, DRM_MODE_OBJECT_CRTC)
: DrmObject(gpu, crtcId, {PropertyDefinition(QByteArrayLiteral("MODE_ID"), Requirement::Required), PropertyDefinition(QByteArrayLiteral("ACTIVE"), Requirement::Required), PropertyDefinition(QByteArrayLiteral("VRR_ENABLED"), Requirement::Optional), PropertyDefinition(QByteArrayLiteral("GAMMA_LUT"), Requirement::Optional), PropertyDefinition(QByteArrayLiteral("GAMMA_LUT_SIZE"), Requirement::Optional), PropertyDefinition(QByteArrayLiteral("CTM"), Requirement::Optional)}, DRM_MODE_OBJECT_CRTC)
, m_crtc(drmModeGetCrtc(gpu->fd(), crtcId))
, m_pipeIndex(pipeIndex)
, m_primaryPlane(primaryPlane)

View file

@ -34,6 +34,7 @@ public:
VrrEnabled,
Gamma_LUT,
Gamma_LUT_Size,
CTM,
Count
};

View file

@ -469,15 +469,30 @@ DrmOutputLayer *DrmOutput::cursorLayer() const
return m_pipeline->cursorLayer();
}
void DrmOutput::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
bool DrmOutput::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
{
m_pipeline->setColorTransformation(transformation);
m_pipeline->setGammaRamp(transformation);
m_pipeline->setCTM(QMatrix3x3());
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
m_renderLoop->scheduleRepaint();
return true;
} else {
m_pipeline->revertPendingChanges();
return false;
}
}
bool DrmOutput::setCTM(const QMatrix3x3 &ctm)
{
m_pipeline->setCTM(ctm);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
m_renderLoop->scheduleRepaint();
return true;
} else {
m_pipeline->revertPendingChanges();
return false;
}
}
}

View file

@ -58,7 +58,8 @@ public:
void leased(DrmLease *lease);
void leaseEnded();
void setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation) override;
bool setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation) override;
bool setCTM(const QMatrix3x3 &ctm) override;
private:
bool setDrmDpmsMode(DpmsMode mode);

View file

@ -238,7 +238,14 @@ void DrmPipeline::prepareAtomicPresentation()
}
m_pending.crtc->setPending(DrmCrtc::PropertyIndex::VrrEnabled, m_pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive || m_pending.syncMode == RenderLoopPrivate::SyncMode::AdaptiveAsync);
m_pending.crtc->setPending(DrmCrtc::PropertyIndex::Gamma_LUT, m_pending.gamma ? m_pending.gamma->blobId() : 0);
// use LUT if available, CTM if not
if (const auto gamma = m_pending.crtc->getProp(DrmCrtc::PropertyIndex::Gamma_LUT)) {
gamma->setPending(m_pending.gamma ? m_pending.gamma->blobId() : 0);
m_pending.crtc->setPending(DrmCrtc::PropertyIndex::CTM, 0);
} else {
m_pending.crtc->setPending(DrmCrtc::PropertyIndex::CTM, m_pending.ctm ? m_pending.ctm->blobId() : 0);
}
const auto fb = m_pending.layer->currentBuffer().get();
m_pending.crtc->primaryPlane()->set(QPoint(0, 0), fb->buffer()->size(), centerBuffer(orientateSize(fb->buffer()->size(), m_pending.bufferOrientation), m_pending.mode->size()));
m_pending.crtc->primaryPlane()->setBuffer(fb);
@ -759,14 +766,57 @@ void DrmPipeline::setRgbRange(Output::RgbRange range)
m_pending.rgbRange = range;
}
void DrmPipeline::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
void DrmPipeline::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
{
m_pending.colorTransformation = transformation;
m_pending.gamma = std::make_shared<DrmGammaRamp>(m_pending.crtc, transformation);
}
void DrmPipeline::setCTM(const QMatrix3x3 &ctm)
{
if (ctm.isIdentity()) {
m_pending.ctm.reset();
} else {
m_pending.ctm = std::make_shared<DrmCTM>(gpu(), ctm);
}
}
void DrmPipeline::setContentType(DrmConnector::DrmContentType type)
{
m_pending.contentType = type;
}
static uint64_t doubleToFixed(double value)
{
// ctm values are in S31.32 sign-magnitude format
uint64_t ret = std::abs(value) * (1ul << 32);
if (value < 0) {
ret |= 1ul << 63;
}
return ret;
}
DrmCTM::DrmCTM(DrmGpu *gpu, const QMatrix3x3 &ctm)
: m_gpu(gpu)
{
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))},
};
drmModeCreatePropertyBlob(m_gpu->fd(), &blob, sizeof(drm_color_ctm), &m_blobId);
}
DrmCTM::~DrmCTM()
{
if (m_blobId) {
drmModeDestroyPropertyBlob(m_gpu->fd(), m_blobId);
}
}
uint32_t DrmCTM::blobId() const
{
return m_blobId;
}
}

View file

@ -48,6 +48,19 @@ private:
uint32_t m_blobId = 0;
};
class DrmCTM
{
public:
DrmCTM(DrmGpu *gpu, const QMatrix3x3 &ctm);
~DrmCTM();
uint32_t blobId() const;
private:
DrmGpu *const m_gpu;
uint32_t m_blobId = 0;
};
class DrmPipeline
{
public:
@ -125,7 +138,8 @@ public:
void setSyncMode(RenderLoopPrivate::SyncMode mode);
void setOverscan(uint32_t overscan);
void setRgbRange(Output::RgbRange range);
void setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation);
void setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation);
void setCTM(const QMatrix3x3 &ctm);
void setContentType(DrmConnector::DrmContentType type);
enum class CommitMode {
@ -188,6 +202,7 @@ private:
RenderLoopPrivate::SyncMode syncMode = RenderLoopPrivate::SyncMode::Fixed;
std::shared_ptr<ColorTransformation> colorTransformation;
std::shared_ptr<DrmGammaRamp> gamma;
std::shared_ptr<DrmCTM> ctm;
DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics;
std::shared_ptr<DrmPipelineLayer> layer;

View file

@ -39,13 +39,14 @@ void X11Output::setXineramaNumber(int number)
m_xineramaNumber = number;
}
void X11Output::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
bool X11Output::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
{
if (m_crtc == XCB_NONE) {
return;
return true;
}
ColorLUT lut(transformation, m_gammaRampSize);
xcb_randr_set_crtc_gamma(kwinApp()->x11Connection(), m_crtc, lut.size(), lut.red(), lut.green(), lut.blue());
return true;
}
void X11Output::setCrtc(xcb_randr_crtc_t crtc)

View file

@ -39,7 +39,7 @@ public:
int xineramaNumber() const;
void setXineramaNumber(int number);
void setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation) override;
bool setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation) override;
private:
void setCrtc(xcb_randr_crtc_t crtc);

View file

@ -54,10 +54,14 @@ public:
uint temperature = 6500;
std::unique_ptr<ColorPipelineStage> temperatureStage;
QVector3D temperatureFactors;
std::unique_ptr<ColorPipelineStage> brightnessStage;
QVector3D brightnessFactors;
std::unique_ptr<ColorPipelineStage> calibrationStage;
std::shared_ptr<ColorTransformation> transformation;
// used if only limited per-channel multiplication is available
QVector3D simpleTransformation;
};
void ColorDevicePrivate::rebuildPipeline()
@ -99,6 +103,7 @@ void ColorDevicePrivate::rebuildPipeline()
const auto tmp = std::make_shared<ColorTransformation>(std::move(stages));
if (tmp->valid()) {
transformation = tmp;
simpleTransformation = brightnessFactors * temperatureFactors;
}
}
@ -134,6 +139,8 @@ void ColorDevicePrivate::updateTemperatureToneCurves()
blackbodyColor[blackBodyColorIndex + 5],
blendFactor);
temperatureFactors = QVector3D(xWhitePoint, yWhitePoint, zWhitePoint);
const double redCurveParams[] = {1.0, xWhitePoint, 0.0};
const double greenCurveParams[] = {1.0, yWhitePoint, 0.0};
const double blueCurveParams[] = {1.0, zWhitePoint, 0.0};
@ -172,6 +179,7 @@ void ColorDevicePrivate::updateBrightnessToneCurves()
}
const double curveParams[] = {1.0, brightness / 100.0, 0.0};
brightnessFactors = QVector3D(brightness / 100.0, brightness / 100.0, brightness / 100.0);
UniqueToneCurvePtr redCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
if (!redCurve) {
@ -291,11 +299,6 @@ void ColorDevice::setTemperature(uint temperature)
Q_EMIT temperatureChanged();
}
// QString ColorDevice::profile() const
// {
// return d->profile;
// }
void ColorDevice::setProfile(const QString &profile)
{
if (d->profile == profile) {
@ -310,7 +313,13 @@ void ColorDevice::setProfile(const QString &profile)
void ColorDevice::update()
{
d->rebuildPipeline();
d->output->setColorTransformation(d->transformation);
if (!d->output->setGammaRamp(d->transformation)) {
QMatrix3x3 ctm;
ctm(0, 0) = d->simpleTransformation.x();
ctm(1, 1) = d->simpleTransformation.y();
ctm(2, 2) = d->simpleTransformation.z();
d->output->setCTM(ctm);
}
}
void ColorDevice::scheduleUpdate()

View file

@ -397,8 +397,14 @@ Output::RgbRange Output::rgbRange() const
return m_state.rgbRange;
}
void Output::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
bool Output::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
{
return false;
}
bool Output::setCTM(const QMatrix3x3 &ctm)
{
return false;
}
ContentType Output::contentType() const

View file

@ -13,6 +13,7 @@
#include "renderloop.h"
#include <QDebug>
#include <QMatrix3x3>
#include <QMatrix4x4>
#include <QObject>
#include <QRect>
@ -258,7 +259,8 @@ public:
bool isNonDesktop() const;
Transform panelOrientation() const;
virtual void setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation);
virtual bool setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation);
virtual bool setCTM(const QMatrix3x3 &ctm);
virtual bool setCursor(CursorSource *source);
virtual bool moveCursor(const QPointF &position);