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:
parent
4ec8430a4b
commit
f3aaede382
11 changed files with 115 additions and 189 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue