core/colorspace: add rendering intents

Rendering intents describe how to handle mapping between different colorspaces,
what to do with out of gamut values and what to do if the whitepoint doesn't match.
This way, clients can choose which behavior their content should get.
This commit is contained in:
Xaver Hugl 2024-08-12 01:33:06 +02:00
parent e9680f6425
commit 833476a111
40 changed files with 155 additions and 68 deletions

View file

@ -70,11 +70,16 @@ void TestColorspaces::roundtripConversion()
const QVector3D green(0, 1, 0);
const QVector3D blue(0, 0, 1);
const QVector3D white(1, 1, 1);
QVERIFY(compareVectors(dst.mapTo(src.mapTo(red, dst), src), red, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(green, dst), src), green, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(blue, dst), src), blue, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(white, dst), src), white, requiredAccuracy));
constexpr std::array renderingIntents = {
RenderingIntent::RelativeColorimetric,
RenderingIntent::AbsoluteColorimetric,
};
for (const RenderingIntent intent : renderingIntents) {
QVERIFY(compareVectors(dst.mapTo(src.mapTo(red, dst, intent), src, intent), red, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(green, dst, intent), src, intent), green, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(blue, dst, intent), src, intent), blue, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(white, dst, intent), src, intent), white, requiredAccuracy));
}
}
void TestColorspaces::nonNormalizedPrimaries()
@ -83,7 +88,7 @@ void TestColorspaces::nonNormalizedPrimaries()
const auto from = Colorimetry::fromName(NamedColorimetry::BT709);
const auto to = Colorimetry(Colorimetry::xyToXYZ(from.red()) * 2, Colorimetry::xyToXYZ(from.green()) * 2, Colorimetry::xyToXYZ(from.blue()) * 2, Colorimetry::xyToXYZ(from.white()) * 2);
const auto convertedWhite = from.toOther(to) * QVector3D(1, 1, 1);
const auto convertedWhite = from.toOther(to, RenderingIntent::RelativeColorimetric) * QVector3D(1, 1, 1);
QCOMPARE_LE(std::abs(1 - convertedWhite.x()), s_resolution10bit);
QCOMPARE_LE(std::abs(1 - convertedWhite.y()), s_resolution10bit);
QCOMPARE_LE(std::abs(1 - convertedWhite.z()), s_resolution10bit);
@ -110,11 +115,18 @@ void TestColorspaces::testIdentityTransformation()
QFETCH(TransferFunction::Type, transferFunction);
const ColorDescription color(colorimetry, TransferFunction(transferFunction), 100, 0, 100, 100);
const auto pipeline = ColorPipeline::create(color, color);
if (!pipeline.isIdentity()) {
qWarning() << pipeline;
constexpr std::array renderingIntents = {
RenderingIntent::Perceptual,
RenderingIntent::RelativeColorimetric,
RenderingIntent::AbsoluteColorimetric,
};
for (const RenderingIntent intent : renderingIntents) {
const auto pipeline = ColorPipeline::create(color, color, intent);
if (!pipeline.isIdentity()) {
qWarning() << pipeline;
}
QVERIFY(pipeline.isIdentity());
}
QVERIFY(pipeline.isIdentity());
}
void TestColorspaces::testColorPipeline_data()
@ -124,17 +136,22 @@ void TestColorspaces::testColorPipeline_data()
QTest::addColumn<QVector3D>("dstBlack");
QTest::addColumn<QVector3D>("dstGray");
QTest::addColumn<QVector3D>("dstWhite");
QTest::addColumn<RenderingIntent>("intent");
QTest::addRow("sRGB -> rec.2020") << ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer), 500, 0, std::nullopt, std::nullopt)
<< QVector3D(0.044, 0.044, 0.044)
<< QVector3D(0.517, 0.517, 0.517)
<< QVector3D(0.677, 0.677, 0.677);
QTest::addRow("sRGB -> scRGB") << ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 0, 80), 80, 0, std::nullopt, std::nullopt)
<< QVector3D(0.0001, 0.0001, 0.0001)
<< QVector3D(0.2177376408240310, 0.2177376408240310, 0.2177376408240310)
<< QVector3D(1, 1, 1);
QTest::addRow("sRGB -> rec.2020 relative colorimetric")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer), 500, 0, std::nullopt, std::nullopt)
<< QVector3D(0.044, 0.044, 0.044)
<< QVector3D(0.517, 0.517, 0.517)
<< QVector3D(0.677, 0.677, 0.677)
<< RenderingIntent::RelativeColorimetric;
QTest::addRow("sRGB -> scRGB relative colorimetric")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 0, 80), 80, 0, std::nullopt, std::nullopt)
<< QVector3D(0.0001, 0.0001, 0.0001)
<< QVector3D(0.2177376408240310, 0.2177376408240310, 0.2177376408240310)
<< QVector3D(1, 1, 1)
<< RenderingIntent::RelativeColorimetric;
}
void TestColorspaces::testColorPipeline()
@ -144,13 +161,14 @@ void TestColorspaces::testColorPipeline()
QFETCH(QVector3D, dstBlack);
QFETCH(QVector3D, dstGray);
QFETCH(QVector3D, dstWhite);
QFETCH(RenderingIntent, intent);
const auto pipeline = ColorPipeline::create(srcColor, dstColor);
const auto pipeline = ColorPipeline::create(srcColor, dstColor, intent);
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(0, 0, 0)), dstBlack, s_resolution10bit));
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(0.5, 0.5, 0.5)), dstGray, s_resolution10bit));
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(1, 1, 1)), dstWhite, s_resolution10bit));
const auto inversePipeline = ColorPipeline::create(dstColor, srcColor);
const auto inversePipeline = ColorPipeline::create(dstColor, srcColor, intent);
QVERIFY(compareVectors(inversePipeline.evaluate(dstBlack), QVector3D(0, 0, 0), s_resolution10bit));
QVERIFY(compareVectors(inversePipeline.evaluate(dstGray), QVector3D(0.5, 0.5, 0.5), s_resolution10bit));
QVERIFY(compareVectors(inversePipeline.evaluate(dstWhite), QVector3D(1, 1, 1), s_resolution10bit));

View file

@ -86,7 +86,7 @@ ColorDescription EglGbmLayer::colorDescription() const
return m_surface.colorDescription();
}
bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
@ -97,7 +97,7 @@ bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescriptio
// TODO make the icc profile output a color pipeline too?
return false;
}
ColorPipeline pipeline = ColorPipeline::create(color, m_pipeline->output()->scanoutColorDescription());
ColorPipeline pipeline = ColorPipeline::create(color, m_pipeline->output()->scanoutColorDescription(), intent);
if (m_pipeline->output()->needsChannelFactorFallback()) {
pipeline.addTransferFunction(m_pipeline->output()->scanoutColorDescription().transferFunction());
pipeline.addMultiplier(m_pipeline->output()->effectiveChannelFactors());

View file

@ -40,7 +40,7 @@ public:
const ColorPipeline &colorPipeline() const override;
private:
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<DrmFramebuffer> m_scanoutBuffer;
ColorPipeline m_colorPipeline;

View file

@ -193,7 +193,7 @@ bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion, OutputFrame
if (m_surface->iccShader) {
m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription, m_surface->channelFactors);
} else {
binder.shader()->setColorspaceUniforms(m_surface->intermediaryColorDescription, m_surface->targetColorDescription);
binder.shader()->setColorspaceUniforms(m_surface->intermediaryColorDescription, m_surface->targetColorDescription, RenderingIntent::RelativeColorimetric);
QMatrix4x4 ctm;
ctm(0, 0) = m_surface->channelFactors.x();
ctm(1, 1) = m_surface->channelFactors.y();

View file

@ -491,7 +491,7 @@ void DrmOutput::tryKmsColorOffloading()
const QVector3D channelFactors = effectiveChannelFactors();
const double maxLuminance = colorDescription().maxHdrLuminance().value_or(colorDescription().referenceLuminance());
const ColorDescription optimal = colorDescription().transferFunction().type == TransferFunction::gamma22 ? colorDescription() : colorDescription().withTransferFunction(TransferFunction(TransferFunction::gamma22, 0, maxLuminance));
ColorPipeline colorPipeline = ColorPipeline::create(optimal, colorDescription());
ColorPipeline colorPipeline = ColorPipeline::create(optimal, colorDescription(), RenderingIntent::RelativeColorimetric);
colorPipeline.addTransferFunction(colorDescription().transferFunction());
colorPipeline.addMultiplier(channelFactors);
colorPipeline.addInverseTransferFunction(colorDescription().transferFunction());
@ -517,7 +517,7 @@ bool DrmOutput::needsChannelFactorFallback() const
QVector3D DrmOutput::effectiveChannelFactors() const
{
QVector3D adaptedChannelFactors = Colorimetry::fromName(NamedColorimetry::BT709).toOther(m_state.colorDescription.containerColorimetry()) * m_channelFactors;
QVector3D adaptedChannelFactors = Colorimetry::fromName(NamedColorimetry::BT709).toOther(m_state.colorDescription.containerColorimetry(), RenderingIntent::RelativeColorimetric) * m_channelFactors;
// normalize red to be the original brightness value again
adaptedChannelFactors *= m_channelFactors.x() / adaptedChannelFactors.x();
if (m_state.highDynamicRange || !m_brightnessDevice) {

View file

@ -133,7 +133,7 @@ std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
return nullptr;
}
bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;

View file

@ -44,7 +44,7 @@ public:
const ColorDescription &colorDescription() const;
private:
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<EglSwapchain> createGbmSwapchain() const;
bool doesGbmSwapchainFit(EglSwapchain *swapchain) const;

View file

@ -111,7 +111,7 @@ bool WaylandEglPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRe
return true;
}
bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
Q_ASSERT(!m_presentationBuffer);
// TODO use viewporter to relax this check

View file

@ -44,7 +44,7 @@ public:
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;

View file

@ -287,7 +287,7 @@ static bool checkForBlackBackground(SurfaceItem *background)
}
const QRgb rgb = view.image()->pixel(0, 0);
const QVector3D encoded(qRed(rgb) / 255.0, qGreen(rgb) / 255.0, qBlue(rgb) / 255.0);
const QVector3D nits = background->colorDescription().mapTo(encoded, ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear), 100, 0, std::nullopt, std::nullopt));
const QVector3D nits = background->colorDescription().mapTo(encoded, ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear), 100, 0, std::nullopt, std::nullopt), background->renderingIntent());
// below 0.1 nits, it shouldn't be noticeable that we replace it with black
return nits.lengthSquared() <= (0.1 * 0.1);
}

View file

@ -11,7 +11,7 @@
namespace KWin
{
ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDescription &to)
ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDescription &to, RenderingIntent intent)
{
const auto range1 = ValueRange(from.minLuminance(), from.maxHdrLuminance().value_or(from.referenceLuminance()));
ColorPipeline ret(ValueRange{
@ -23,7 +23,7 @@ ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDes
// FIXME this assumes that the range stays the same with matrix multiplication
// that's not necessarily true, and figuring out the actual range could be complicated..
ret.addMatrix(from.containerColorimetry().toOther(to.containerColorimetry()), ret.currentOutputRange());
ret.addMatrix(from.containerColorimetry().toOther(to.containerColorimetry(), intent), ret.currentOutputRange());
ret.addInverseTransferFunction(to.transferFunction());
return ret;

View file

@ -79,7 +79,7 @@ public:
explicit ColorPipeline();
explicit ColorPipeline(const ValueRange &inputRange);
static ColorPipeline create(const ColorDescription &from, const ColorDescription &to);
static ColorPipeline create(const ColorDescription &from, const ColorDescription &to, RenderingIntent intent);
ColorPipeline merged(const ColorPipeline &onTop) const;

View file

@ -126,10 +126,16 @@ const QMatrix4x4 &Colorimetry::fromXYZ() const
return m_fromXYZ;
}
QMatrix4x4 Colorimetry::toOther(const Colorimetry &other) const
QMatrix4x4 Colorimetry::toOther(const Colorimetry &other, RenderingIntent intent) const
{
// rendering intent is relative colorimetric, so adapt to the different whitepoint
return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ();
switch (intent) {
case RenderingIntent::Perceptual:
case RenderingIntent::RelativeColorimetric:
return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ();
case RenderingIntent::AbsoluteColorimetric:
return other.fromXYZ() * toXYZ();
}
Q_UNREACHABLE();
}
Colorimetry Colorimetry::adaptedTo(QVector2D newWhitepoint) const
@ -267,11 +273,11 @@ std::optional<double> ColorDescription::maxHdrLuminance() const
return m_maxHdrLuminance;
}
QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const
QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst, RenderingIntent intent) const
{
rgb = m_transferFunction.encodedToNits(rgb);
rgb *= dst.referenceLuminance() / m_referenceLuminance;
rgb = m_containerColorimetry.toOther(dst.containerColorimetry()) * rgb;
rgb = m_containerColorimetry.toOther(dst.containerColorimetry(), intent) * rgb;
return dst.transferFunction().nitsToEncoded(rgb);
}

View file

@ -14,6 +14,22 @@
namespace KWin
{
/**
* rendering intents describe how colors should be mapped between different color spaces
*/
enum class RenderingIntent {
/* "vendor specific", preserves the overall color appearance */
Perceptual,
/* "vendor specific", maps saturated colors to be saturated in the target color space too */
// TODO Saturation,
/* colorimetric mapping between color spaces, with whitepoint adaptation */
RelativeColorimetric,
/* colorimetric mapping between color spaces, without whitepoint adaptation */
AbsoluteColorimetric,
/* colorimetric mapping between color spaces, with whitepoint adaptation and black point compensation */
// TODO RelativeColorimetricWithBPC,
};
enum class NamedColorimetry {
BT709,
BT2020,
@ -56,9 +72,8 @@ public:
const QMatrix4x4 &fromXYZ() const;
/**
* @returns a matrix that transforms from linear RGB in this colorimetry to linear RGB in the other colorimetry
* the rendering intent is relative colorimetric
*/
QMatrix4x4 toOther(const Colorimetry &colorimetry) const;
QMatrix4x4 toOther(const Colorimetry &colorimetry, RenderingIntent intent) const;
bool operator==(const Colorimetry &other) const;
bool operator==(NamedColorimetry name) const;
/**
@ -164,7 +179,7 @@ public:
bool operator==(const ColorDescription &other) const = default;
QVector3D mapTo(QVector3D rgb, const ColorDescription &other) const;
QVector3D mapTo(QVector3D rgb, const ColorDescription &other, RenderingIntent intent) const;
ColorDescription withTransferFunction(const TransferFunction &func) const;
/**

View file

@ -62,7 +62,7 @@ bool OutputLayer::needsRepaint() const
return !m_repaints.isEmpty();
}
bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
return false;
}
@ -91,7 +91,7 @@ bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem, const std::shared_ptr
m_bufferTransform = surfaceItem->bufferTransform();
const auto desiredTransform = m_output ? m_output->transform() : OutputTransform::Kind::Normal;
m_offloadTransform = m_bufferTransform.combine(desiredTransform.inverted());
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription(), frame);
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription(), surfaceItem->renderingIntent(), frame);
if (ret) {
surfaceItem->resetDamage();
// ensure the pixmap is updated when direct scanout ends

View file

@ -95,7 +95,7 @@ public:
OutputTransform bufferTransform() const;
protected:
virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame);
virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame);
virtual std::optional<OutputLayerBeginFrameInfo> doBeginFrame() = 0;
virtual bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) = 0;

View file

@ -1501,7 +1501,7 @@ void EffectsHandler::renderOffscreenQuickView(const RenderTarget &renderTarget,
if (a != 1.0) {
shader->setUniform(GLShader::Vec4Uniform::ModulationConstant, QVector4D(a, a, a, a));
}
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
const bool alphaBlending = w->hasAlphaChannel() || (a != 1.0);
if (alphaBlending) {

View file

@ -189,7 +189,7 @@ void OffscreenData::paint(const RenderTarget &renderTarget, const RenderViewport
shader->setUniform(GLShader::Vec3Uniform::PrimaryBrightness, QVector3D(toXYZ(1, 0), toXYZ(1, 1), toXYZ(1, 2)));
shader->setUniform(GLShader::IntUniform::TextureWidth, m_texture->width());
shader->setUniform(GLShader::IntUniform::TextureHeight, m_texture->height());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
const bool clipping = region != infiniteRegion();
const QRegion clipRegion = clipping ? viewport.mapToRenderTarget(region) : infiniteRegion();

View file

@ -469,10 +469,10 @@ QMatrix4x4 GLShader::getUniformMatrix4x4(const char *name)
}
}
bool GLShader::setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst)
bool GLShader::setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst, RenderingIntent intent)
{
const auto &srcColorimetry = src.containerColorimetry() == NamedColorimetry::BT709 ? dst.sdrColorimetry() : src.containerColorimetry();
return setUniform(Mat4Uniform::ColorimetryTransformation, srcColorimetry.toOther(dst.containerColorimetry()))
const auto &srcColorimetry = intent == RenderingIntent::Perceptual && src.containerColorimetry() == NamedColorimetry::BT709 ? dst.sdrColorimetry() : src.containerColorimetry();
return setUniform(Mat4Uniform::ColorimetryTransformation, srcColorimetry.toOther(dst.containerColorimetry(), intent))
&& setUniform(IntUniform::SourceNamedTransferFunction, src.transferFunction().type)
&& setUniform(Vec2Uniform::SourceTransferFunctionParams, QVector2D(src.transferFunction().minLuminance, src.transferFunction().maxLuminance - src.transferFunction().minLuminance))
&& setUniform(FloatUniform::SourceReferenceLuminance, src.referenceLuminance())

View file

@ -138,7 +138,7 @@ public:
bool setUniform(ColorUniform uniform, const QVector4D &value);
bool setUniform(ColorUniform uniform, const QColor &value);
bool setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst);
bool setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst, RenderingIntent intent);
protected:
GLShader(unsigned int flags = NoFlags);

View file

@ -67,7 +67,7 @@ void ColorPickerEffect::paintScreen(const RenderTarget &renderTarget, const Rend
const QPoint texturePosition = viewport.mapToRenderTarget(m_scheduledPosition).toPoint();
glReadPixels(texturePosition.x(), renderTarget.size().height() - texturePosition.y() - PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE, GL_RGBA, GL_FLOAT, data.data());
QVector3D sRGB = 255 * renderTarget.colorDescription().mapTo(QVector3D(data[0], data[1], data[2]), ColorDescription::sRGB);
QVector3D sRGB = 255 * renderTarget.colorDescription().mapTo(QVector3D(data[0], data[1], data[2]), ColorDescription::sRGB, RenderingIntent::RelativeColorimetric);
QDBusConnection::sessionBus().send(m_replyMessage.createReply(QColor(sRGB.x(), sRGB.y(), sRGB.z())));
setPicking(false);
m_scheduledPosition = QPoint(-1, -1);

View file

@ -300,7 +300,7 @@ void MouseClickEffect::paintScreenSetupGl(const RenderTarget &renderTarget, cons
{
GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
glLineWidth(m_lineWidth);
glEnable(GL_BLEND);

View file

@ -118,7 +118,7 @@ void MouseMarkEffect::paintScreen(const RenderTarget &renderTarget, const Render
const auto scale = viewport.scale();
ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix());
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
binder.shader()->setUniform(GLShader::ColorUniform::Color, color);
QList<QVector2D> verts;
for (const Mark &mark : std::as_const(marks)) {

View file

@ -72,7 +72,7 @@ void OutputScreenCastSource::render(GLFramebuffer *target)
projectionMatrix.scale(1, -1);
projectionMatrix.ortho(QRect(QPoint(), textureSize()));
shaderBinder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
shaderBinder.shader()->setColorspaceUniforms(colorDescription, ColorDescription::sRGB);
shaderBinder.shader()->setColorspaceUniforms(colorDescription, ColorDescription::sRGB, RenderingIntent::Perceptual);
GLFramebuffer::pushFramebuffer(target);
outputTexture->render(textureSize());

View file

@ -107,7 +107,7 @@ void RegionScreenCastSource::blit(Output *output)
projectionMatrix.translate(outputGeometry.left(), outputGeometry.top());
shaderBinder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
shaderBinder.shader()->setColorspaceUniforms(colorDescription, ColorDescription::sRGB);
shaderBinder.shader()->setColorspaceUniforms(colorDescription, ColorDescription::sRGB, RenderingIntent::Perceptual);
outputTexture->render(outputGeometry.size());
GLFramebuffer::popFramebuffer();

View file

@ -385,7 +385,7 @@ QImage ScreenShotEffect::blitScreenshot(const RenderTarget &renderTarget, const
if (renderTarget.texture()) {
GLFramebuffer::pushFramebuffer(&target);
ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
binder.shader()->setColorspaceUniforms(renderTarget.colorDescription(), ColorDescription::sRGB);
binder.shader()->setColorspaceUniforms(renderTarget.colorDescription(), ColorDescription::sRGB, RenderingIntent::Perceptual);
QMatrix4x4 projectionMatrix;
projectionMatrix.scale(1, -1);
projectionMatrix *= renderTarget.transform().toMatrix();

View file

@ -72,7 +72,7 @@ void ShowPaintEffect::paintGL(const RenderTarget &renderTarget, const QMatrix4x4
vbo->reset();
ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projection);
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
QColor color = s_colors[m_colorIndex];

View file

@ -113,7 +113,7 @@ void SnapHelperEffect::paintScreen(const RenderTarget &renderTarget, const Rende
vbo->reset();
ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix());
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

View file

@ -239,7 +239,7 @@ void StartupFeedbackEffect::paintScreen(const RenderTarget &renderTarget, const
QMatrix4x4 mvp = viewport.projectionMatrix();
mvp.translate(pixelGeometry.x(), pixelGeometry.y());
shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
texture->render(pixelGeometry.size());
ShaderManager::instance()->popShader();
glDisable(GL_BLEND);

View file

@ -232,7 +232,7 @@ void TouchPointsEffect::paintScreenSetupGl(const RenderTarget &renderTarget, con
{
GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor);
shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
shader->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
glLineWidth(m_lineWidth);
glEnable(GL_BLEND);

View file

@ -399,7 +399,7 @@ void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewp
matrix.translate(offscreen.viewport.x() * scale, offscreen.viewport.y() * scale);
shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix() * matrix);
shader->setColorspaceUniforms(offscreen.color, renderTarget.colorDescription());
shader->setColorspaceUniforms(offscreen.color, renderTarget.colorDescription(), RenderingIntent::Perceptual);
offscreen.texture->render(offscreen.viewport.size() * scale);
}
@ -423,7 +423,7 @@ void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewp
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
s->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription());
s->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
QMatrix4x4 mvp = viewport.projectionMatrix();
mvp.translate(p.x() * scale, p.y() * scale);
s->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);

View file

@ -491,11 +491,21 @@ const ColorDescription &Item::colorDescription() const
return m_colorDescription;
}
RenderingIntent Item::renderingIntent() const
{
return m_renderingIntent;
}
void Item::setColorDescription(const ColorDescription &description)
{
m_colorDescription = description;
}
void Item::setRenderingIntent(RenderingIntent intent)
{
m_renderingIntent = intent;
}
PresentationModeHint Item::presentationHint() const
{
return m_presentationHint;

View file

@ -113,6 +113,7 @@ public:
WindowQuadList quads() const;
virtual void preprocess();
const ColorDescription &colorDescription() const;
RenderingIntent renderingIntent() const;
PresentationModeHint presentationHint() const;
Q_SIGNALS:
@ -136,6 +137,7 @@ protected:
virtual WindowQuadList buildQuads() const;
void discardQuads();
void setColorDescription(const ColorDescription &description);
void setRenderingIntent(RenderingIntent intent);
void setPresentationHint(PresentationModeHint hint);
void setScene(Scene *scene);
@ -169,6 +171,7 @@ private:
mutable std::optional<WindowQuadList> m_quads;
mutable std::optional<QList<Item *>> m_sortedChildItems;
ColorDescription m_colorDescription = ColorDescription::sRGB;
RenderingIntent m_renderingIntent = RenderingIntent::Perceptual;
PresentationModeHint m_presentationHint = PresentationModeHint::VSync;
};

View file

@ -165,6 +165,7 @@ void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
.hasAlpha = true,
.coordinateType = UnnormalizedCoordinates,
.colorDescription = item->colorDescription(),
.renderingIntent = item->renderingIntent(),
.bufferReleasePoint = nullptr,
});
}
@ -179,6 +180,7 @@ void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
.hasAlpha = true,
.coordinateType = UnnormalizedCoordinates,
.colorDescription = item->colorDescription(),
.renderingIntent = item->renderingIntent(),
.bufferReleasePoint = nullptr,
});
}
@ -195,6 +197,7 @@ void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
.hasAlpha = pixmap->hasAlphaChannel(),
.coordinateType = NormalizedCoordinates,
.colorDescription = item->colorDescription(),
.renderingIntent = item->renderingIntent(),
.bufferReleasePoint = surfaceItem->bufferReleasePoint(),
});
}
@ -209,6 +212,7 @@ void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
.hasAlpha = imageItem->image().hasAlphaChannel(),
.coordinateType = NormalizedCoordinates,
.colorDescription = item->colorDescription(),
.renderingIntent = item->renderingIntent(),
.bufferReleasePoint = nullptr,
});
}
@ -369,7 +373,7 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend
shader->setUniform(GLShader::Vec4Uniform::ModulationConstant, modulate(renderNode.opacity, data.brightness()));
}
if (traits & ShaderTrait::TransformColorspace) {
shader->setColorspaceUniforms(renderNode.colorDescription, renderTarget.colorDescription());
shader->setColorspaceUniforms(renderNode.colorDescription, renderTarget.colorDescription(), renderNode.renderingIntent);
}
if (std::holds_alternative<GLTexture *>(renderNode.texture)) {

View file

@ -31,6 +31,7 @@ public:
bool hasAlpha = false;
TextureCoordinateType coordinateType = UnnormalizedCoordinates;
ColorDescription colorDescription;
RenderingIntent renderingIntent;
std::shared_ptr<SyncReleasePoint> bufferReleasePoint;
};

View file

@ -201,6 +201,7 @@ void SurfaceItemWayland::freeze()
void SurfaceItemWayland::handleColorDescriptionChanged()
{
setColorDescription(m_surface->colorDescription());
setRenderingIntent(m_surface->renderingIntent());
}
void SurfaceItemWayland::handlePresentationModeHintChanged()

View file

@ -1107,6 +1107,11 @@ const ColorDescription &SurfaceInterface::colorDescription() const
return d->current->colorDescription;
}
RenderingIntent SurfaceInterface::renderingIntent() const
{
return d->current->renderingIntent;
}
void SurfaceInterface::setPreferredColorDescription(const ColorDescription &descr)
{
if (d->preferredColorDescription == descr) {

View file

@ -340,6 +340,7 @@ public:
void setLastTransaction(Transaction *transaction);
const ColorDescription &colorDescription() const;
RenderingIntent renderingIntent() const;
void setPreferredColorDescription(const ColorDescription &descr);

View file

@ -72,6 +72,7 @@ struct SurfaceState
ContentType contentType = ContentType::None;
PresentationModeHint presentationHint = PresentationModeHint::VSync;
ColorDescription colorDescription = ColorDescription::sRGB;
RenderingIntent renderingIntent = RenderingIntent::Perceptual;
std::unique_ptr<PresentationTimeFeedback> presentationFeedback;
struct
{

View file

@ -39,7 +39,8 @@ void XXColorManagerV4::xx_color_manager_v4_bind_resource(Resource *resource)
send_supported_intent(resource->handle, render_intent::render_intent_perceptual);
send_supported_intent(resource->handle, render_intent::render_intent_relative);
// TODO implement the other rendering intents
send_supported_intent(resource->handle, render_intent::render_intent_absolute);
// TODO implement saturation and relative bpc intents
}
void XXColorManagerV4::xx_color_manager_v4_destroy(Resource *resource)
@ -143,15 +144,36 @@ void XXColorSurfaceV4::xx_color_management_surface_v4_destroy(Resource *resource
wl_resource_destroy(resource->handle);
}
static std::optional<RenderingIntent> waylandToKwinIntent(uint32_t intent)
{
switch (intent) {
case QtWaylandServer::xx_color_manager_v4::render_intent::render_intent_perceptual:
return RenderingIntent::Perceptual;
case QtWaylandServer::xx_color_manager_v4::render_intent::render_intent_relative:
return RenderingIntent::RelativeColorimetric;
case QtWaylandServer::xx_color_manager_v4::render_intent::render_intent_absolute:
return RenderingIntent::AbsoluteColorimetric;
case QtWaylandServer::xx_color_manager_v4::render_intent::render_intent_saturation:
case QtWaylandServer::xx_color_manager_v4::render_intent::render_intent_relative_bpc:
return std::nullopt;
}
return std::nullopt;
}
void XXColorSurfaceV4::xx_color_management_surface_v4_set_image_description(Resource *resource, struct ::wl_resource *image_description, uint32_t render_intent)
{
if (!m_surface) {
return;
}
const std::optional<RenderingIntent> intent = waylandToKwinIntent(render_intent);
if (!intent) {
wl_resource_post_error(resource->handle, XX_COLOR_MANAGER_V4_ERROR_UNSUPPORTED_FEATURE, "rendering intent is not supported");
return;
}
const auto priv = SurfaceInterfacePrivate::get(m_surface);
priv->pending->colorDescription = XXImageDescriptionV4::get(image_description)->description();
priv->pending->renderingIntent = *intent;
priv->pending->colorDescriptionIsSet = true;
// TODO render_intent
}
void XXColorSurfaceV4::xx_color_management_surface_v4_unset_image_description(Resource *resource)