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:
parent
e9680f6425
commit
833476a111
40 changed files with 155 additions and 68 deletions
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
bool hasAlpha = false;
|
||||
TextureCoordinateType coordinateType = UnnormalizedCoordinates;
|
||||
ColorDescription colorDescription;
|
||||
RenderingIntent renderingIntent;
|
||||
std::shared_ptr<SyncReleasePoint> bufferReleasePoint;
|
||||
};
|
||||
|
||||
|
|
|
@ -201,6 +201,7 @@ void SurfaceItemWayland::freeze()
|
|||
void SurfaceItemWayland::handleColorDescriptionChanged()
|
||||
{
|
||||
setColorDescription(m_surface->colorDescription());
|
||||
setRenderingIntent(m_surface->renderingIntent());
|
||||
}
|
||||
|
||||
void SurfaceItemWayland::handlePresentationModeHintChanged()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -340,6 +340,7 @@ public:
|
|||
void setLastTransaction(Transaction *transaction);
|
||||
|
||||
const ColorDescription &colorDescription() const;
|
||||
RenderingIntent renderingIntent() const;
|
||||
|
||||
void setPreferredColorDescription(const ColorDescription &descr);
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue