backends/drm: properly handle neither CTM and gamma being supported

Instead of hardcoding only NVidia, try to use CTM and GAMMA_LUT before falling
back to the shader path. This way it also works on other GPUs that lack
color management hardware, and only falls back to the color management path
on older NVidia drivers.

This commit also ensures that the color management hardware is set properly
after toggling color management on and off again, and simplifies ColorDevice
to only deal with rgb factors instead of always calculating luts. This should
improve performance of night color animations on hardware where CTMs are
supported

CCBUG: 453701
This commit is contained in:
Xaver Hugl 2023-11-25 19:19:23 +01:00
parent 4ec8430a4b
commit f3aaede382
11 changed files with 115 additions and 189 deletions

View file

@ -78,7 +78,6 @@ DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t device
// find out what driver this kms device is using
DrmUniquePtr<drmVersion> version(drmGetVersion(fd));
m_isNVidia = strstr(version->name, "nvidia-drm");
m_isI915 = strstr(version->name, "i915");
m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl")
|| strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo");
@ -691,11 +690,6 @@ bool DrmGpu::asyncPageflipSupported() const
return m_asyncPageflipSupported;
}
bool DrmGpu::isNVidia() const
{
return m_isNVidia;
}
bool DrmGpu::isI915() const
{
return m_isI915;

View file

@ -24,7 +24,6 @@ struct gbm_device;
namespace KWin
{
class DrmOutput;
class DrmObject;
class DrmCrtc;
@ -78,7 +77,6 @@ public:
bool atomicModeSetting() const;
bool addFB2ModifiersSupported() const;
bool asyncPageflipSupported() const;
bool isNVidia() const;
bool isI915() const;
gbm_device *gbmDevice() const;
EglDisplay *eglDisplay() const;

View file

@ -386,6 +386,11 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
m_renderLoop->setRefreshRate(refreshRate());
m_renderLoop->scheduleRepaint();
if (!next.wideColorGamut && !next.highDynamicRange && !m_pipeline->iccProfile()) {
// re-set the CTM and/or gamma lut
doSetChannelFactors(m_channelFactors);
}
Q_EMIT changed();
}
@ -404,50 +409,54 @@ DrmOutputLayer *DrmOutput::cursorLayer() const
return m_pipeline->cursorLayer();
}
bool DrmOutput::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
{
if (!m_pipeline->activePending() || needsColormanagement()) {
return false;
}
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::setChannelFactors(const QVector3D &rgb)
{
if (m_channelFactors == rgb) {
return m_channelFactors == rgb || doSetChannelFactors(rgb);
}
bool DrmOutput::doSetChannelFactors(const QVector3D &rgb)
{
m_renderLoop->scheduleRepaint();
m_channelFactors = rgb;
if (m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile) {
// the shader "fallback" is always active
return true;
}
m_channelFactors = rgb;
if (!needsColormanagement()) {
if (!m_pipeline->activePending()) {
return false;
}
if (m_pipeline->hasCTM()) {
QMatrix3x3 ctm;
ctm(0, 0) = rgb.x();
ctm(1, 1) = rgb.y();
ctm(2, 2) = rgb.z();
m_pipeline->setCTM(ctm);
m_pipeline->setGammaRamp(nullptr);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
m_pipeline->applyPendingChanges();
m_renderLoop->scheduleRepaint();
m_channelFactorsNeedShaderFallback = false;
return true;
} else {
m_pipeline->revertPendingChanges();
return false;
m_pipeline->setCTM(QMatrix3x3());
m_pipeline->applyPendingChanges();
}
} else {
m_renderLoop->scheduleRepaint();
}
if (m_pipeline->hasGammaRamp()) {
auto lut = ColorTransformation::createScalingTransform(rgb);
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();
}
}
}
m_channelFactorsNeedShaderFallback = m_channelFactors != QVector3D{1, 1, 1};
return true;
}
QVector3D DrmOutput::channelFactors() const
@ -457,7 +466,7 @@ QVector3D DrmOutput::channelFactors() const
bool DrmOutput::needsColormanagement() const
{
return m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile || m_gpu->isNVidia();
return m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile || m_channelFactorsNeedShaderFallback;
}
}

View file

@ -58,7 +58,6 @@ public:
void leased(DrmLease *lease);
void leaseEnded();
bool setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation) override;
bool setChannelFactors(const QVector3D &rgb) override;
QVector3D channelFactors() const;
bool needsColormanagement() const;
@ -66,6 +65,7 @@ public:
private:
bool setDrmDpmsMode(DpmsMode mode);
void setDpmsMode(DpmsMode mode) override;
bool doSetChannelFactors(const QVector3D &rgb);
QList<std::shared_ptr<OutputMode>> getModes() const;
@ -76,6 +76,7 @@ private:
DrmLease *m_lease = nullptr;
QVector3D m_channelFactors = {1, 1, 1};
bool m_channelFactorsNeedShaderFallback = false;
};
}

View file

@ -474,6 +474,20 @@ QMap<uint32_t, QList<uint64_t>> DrmPipeline::cursorFormats() const
}
}
bool DrmPipeline::hasCTM() const
{
return 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;

View file

@ -95,6 +95,8 @@ public:
QMap<uint32_t, QList<uint64_t>> formats() const;
QMap<uint32_t, QList<uint64_t>> cursorFormats() const;
bool hasCTM() const;
bool hasGammaRamp() const;
bool pruneModifier();
void setOutput(DrmOutput *output);

View file

@ -8,6 +8,7 @@
*/
#include "x11_standalone_output.h"
#include "core/colorlut.h"
#include "core/colortransformation.h"
#include "main.h"
#include "x11_standalone_backend.h"
@ -40,12 +41,16 @@ void X11Output::setXineramaNumber(int number)
m_xineramaNumber = number;
}
bool X11Output::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
bool X11Output::setChannelFactors(const QVector3D &rgb)
{
if (m_crtc == XCB_NONE) {
return true;
}
ColorLUT lut(transformation, m_gammaRampSize);
auto transformation = ColorTransformation::createScalingTransform(rgb);
if (!transformation) {
return false;
}
ColorLUT lut(std::move(transformation), m_gammaRampSize);
xcb_randr_set_crtc_gamma(kwinApp()->x11Connection(), m_crtc, lut.size(), lut.red(), lut.green(), lut.blue());
return true;
}

View file

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

View file

@ -19,40 +19,17 @@
namespace KWin
{
struct CmsDeleter
{
void operator()(cmsToneCurve *toneCurve)
{
if (toneCurve) {
cmsFreeToneCurve(toneCurve);
}
}
};
using UniqueToneCurvePtr = std::unique_ptr<cmsToneCurve, CmsDeleter>;
class ColorDevicePrivate
{
public:
enum DirtyToneCurveBit {
DirtyTemperatureToneCurve = 0x1,
DirtyBrightnessToneCurve = 0x2,
};
Q_DECLARE_FLAGS(DirtyToneCurves, DirtyToneCurveBit)
void rebuildPipeline();
void updateTemperatureToneCurves();
void updateBrightnessToneCurves();
void recalculateFactors();
Output *output;
DirtyToneCurves dirtyCurves;
QTimer *updateTimer;
uint brightness = 100;
uint temperature = 6500;
std::unique_ptr<ColorPipelineStage> temperatureStage;
QVector3D temperatureFactors = QVector3D(1, 1, 1);
std::unique_ptr<ColorPipelineStage> brightnessStage;
QVector3D brightnessFactors = QVector3D(1, 1, 1);
std::shared_ptr<ColorTransformation> transformation;
@ -60,52 +37,18 @@ public:
QVector3D simpleTransformation = QVector3D(1, 1, 1);
};
void ColorDevicePrivate::rebuildPipeline()
{
if (dirtyCurves & DirtyBrightnessToneCurve) {
updateBrightnessToneCurves();
}
if (dirtyCurves & DirtyTemperatureToneCurve) {
updateTemperatureToneCurves();
}
dirtyCurves = DirtyToneCurves();
std::vector<std::unique_ptr<ColorPipelineStage>> stages;
if (brightnessStage) {
if (auto s = brightnessStage->dup()) {
stages.push_back(std::move(s));
} else {
return;
}
}
if (temperatureStage) {
if (auto s = temperatureStage->dup()) {
stages.push_back(std::move(s));
} else {
return;
}
}
const auto tmp = std::make_shared<ColorTransformation>(std::move(stages));
if (tmp->valid()) {
transformation = tmp;
simpleTransformation = brightnessFactors * temperatureFactors;
}
}
static qreal interpolate(qreal a, qreal b, qreal blendFactor)
{
return (1 - blendFactor) * a + blendFactor * b;
}
void ColorDevicePrivate::updateTemperatureToneCurves()
void ColorDevicePrivate::recalculateFactors()
{
temperatureStage.reset();
brightnessFactors = QVector3D(brightness / 100.0, brightness / 100.0, brightness / 100.0);
if (temperature == 6500) {
return;
}
temperatureFactors = QVector3D(1, 1, 1);
} else {
// Note that cmsWhitePointFromTemp() returns a slightly green-ish white point.
const int blackBodyColorIndex = ((temperature - 1000) / 100) * 3;
const qreal blendFactor = (temperature % 100) / 100.0;
@ -119,74 +62,9 @@ void ColorDevicePrivate::updateTemperatureToneCurves()
const qreal zWhitePoint = interpolate(blackbodyColor[blackBodyColorIndex + 2],
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};
UniqueToneCurvePtr redCurve(cmsBuildParametricToneCurve(nullptr, 2, redCurveParams));
if (!redCurve) {
qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the red channel";
return;
}
UniqueToneCurvePtr greenCurve(cmsBuildParametricToneCurve(nullptr, 2, greenCurveParams));
if (!greenCurve) {
qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the green channel";
return;
}
UniqueToneCurvePtr blueCurve(cmsBuildParametricToneCurve(nullptr, 2, blueCurveParams));
if (!blueCurve) {
qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the blue channel";
return;
}
// The ownership of the tone curves will be moved to the pipeline stage.
cmsToneCurve *toneCurves[] = {redCurve.release(), greenCurve.release(), blueCurve.release()};
temperatureStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
if (!temperatureStage) {
qCWarning(KWIN_CORE) << "Failed to create the color temperature pipeline stage";
}
}
void ColorDevicePrivate::updateBrightnessToneCurves()
{
brightnessStage.reset();
if (brightness == 100) {
return;
}
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) {
qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the red channel";
return;
}
UniqueToneCurvePtr greenCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
if (!greenCurve) {
qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the green channel";
return;
}
UniqueToneCurvePtr blueCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
if (!blueCurve) {
qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the blue channel";
return;
}
// The ownership of the tone curves will be moved to the pipeline stage.
cmsToneCurve *toneCurves[] = {redCurve.release(), greenCurve.release(), blueCurve.release()};
brightnessStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
if (!brightnessStage) {
qCWarning(KWIN_CORE) << "Failed to create the color brightness pipeline stage";
}
simpleTransformation = brightnessFactors * temperatureFactors;
}
ColorDevice::ColorDevice(Output *output, QObject *parent)
@ -230,7 +108,6 @@ void ColorDevice::setBrightness(uint brightness)
return;
}
d->brightness = brightness;
d->dirtyCurves |= ColorDevicePrivate::DirtyBrightnessToneCurve;
scheduleUpdate();
Q_EMIT brightnessChanged();
}
@ -250,17 +127,14 @@ void ColorDevice::setTemperature(uint temperature)
return;
}
d->temperature = temperature;
d->dirtyCurves |= ColorDevicePrivate::DirtyTemperatureToneCurve;
scheduleUpdate();
Q_EMIT temperatureChanged();
}
void ColorDevice::update()
{
d->rebuildPipeline();
if (!d->output->setGammaRamp(d->transformation)) {
d->recalculateFactors();
d->output->setChannelFactors(d->simpleTransformation);
}
}
void ColorDevice::scheduleUpdate()

View file

@ -77,4 +77,31 @@ QVector3D ColorTransformation::transform(QVector3D in) const
cmsPipelineEvalFloat(&in[0], &ret[0], m_pipeline);
return ret;
}
std::unique_ptr<ColorTransformation> ColorTransformation::createScalingTransform(const QVector3D &scale)
{
std::array<double, 3> curveParams = {1.0, scale.x(), 0.0};
auto r = cmsBuildParametricToneCurve(nullptr, 2, curveParams.data());
curveParams = {1.0, scale.y(), 0.0};
auto g = cmsBuildParametricToneCurve(nullptr, 2, curveParams.data());
curveParams = {1.0, scale.z(), 0.0};
auto b = cmsBuildParametricToneCurve(nullptr, 2, curveParams.data());
if (!r || !g || !b) {
qCWarning(KWIN_CORE) << "Failed to build tone curves";
return nullptr;
}
const std::array curves = {r, g, b};
const auto stage = cmsStageAllocToneCurves(nullptr, 3, curves.data());
if (!stage) {
qCWarning(KWIN_CORE) << "Failed to allocate tone curves";
return nullptr;
}
std::vector<std::unique_ptr<ColorPipelineStage>> stages;
stages.push_back(std::make_unique<ColorPipelineStage>(stage));
auto transform = std::make_unique<ColorTransformation>(std::move(stages));
if (!transform->valid()) {
return nullptr;
}
return transform;
}
}

View file

@ -36,6 +36,8 @@ public:
std::tuple<uint16_t, uint16_t, uint16_t> transform(uint16_t r, uint16_t g, uint16_t b) const;
QVector3D transform(QVector3D in) const;
static std::unique_ptr<ColorTransformation> createScalingTransform(const QVector3D &scale);
private:
cmsPipeline *const m_pipeline;
std::vector<std::unique_ptr<ColorPipelineStage>> m_stages;