backends/drm: support applying icc profiles with color management

While applications are still restricted to sRGB, this allows working on sRGB
content on displays with a wide color gamut as the whole profile gets applied,
instead of just the VCGT.

CCBUG: 439135
master
Xaver Hugl 12 months ago
parent 7d0a3dcd1e
commit 8d25550c22

@ -26,6 +26,7 @@ set(mockDRM_SRCS
../../src/backends/drm/drm_qpainter_layer.cpp
../../src/backends/drm/drm_virtual_egl_layer.cpp
../../src/backends/drm/drm_virtual_output.cpp
../../src/backends/drm/icc_shader.cpp
)
include_directories(${Libdrm_INCLUDE_DIRS})

@ -25,6 +25,8 @@ target_sources(kwin PRIVATE
drm_qpainter_layer.cpp
drm_virtual_egl_layer.cpp
drm_virtual_output.cpp
icc.qrc
icc_shader.cpp
)
target_link_libraries(kwin PRIVATE Libdrm::Libdrm gbm::gbm PkgConfig::Libxcvt)

@ -7,6 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_egl_cursor_layer.h"
#include "core/iccprofile.h"
#include "drm_buffer.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
@ -51,7 +52,7 @@ std::optional<OutputLayerBeginFrameInfo> EglGbmCursorLayer::beginFrame()
// TODO for hardware cursors to work with color management, KWin needs to offload post-blending color management steps to KMS
return std::nullopt;
}
return m_surface.startRendering(m_pipeline->gpu()->cursorSize(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->cursorFormats(), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->output()->needsColormanagement());
return m_surface.startRendering(m_pipeline->gpu()->cursorSize(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->cursorFormats(), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement());
}
bool EglGbmCursorLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)

@ -7,6 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_egl_layer.h"
#include "core/iccprofile.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_egl_backend.h"
@ -58,7 +59,7 @@ std::optional<OutputLayerBeginFrameInfo> EglGbmLayer::beginFrame()
m_scanoutBuffer.reset();
m_dmabufFeedback.renderingSurface();
return m_surface.startRendering(m_pipeline->mode()->size(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->formats(), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->output()->needsColormanagement());
return m_surface.startRendering(m_pipeline->mode()->size(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->formats(), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement());
}
bool EglGbmLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
@ -104,7 +105,7 @@ bool EglGbmLayer::scanout(SurfaceItem *surfaceItem)
return false;
}
// TODO use GAMMA_LUT, CTM and DEGAMMA_LUT to allow direct scanout with HDR
if (m_pipeline->colorimetry() != NamedColorimetry::BT709 || m_pipeline->transferFunction() != NamedTransferFunction::sRGB) {
if (m_pipeline->output()->needsColormanagement()) {
return false;
}

@ -9,10 +9,14 @@
#include "drm_egl_layer_surface.h"
#include "config-kwin.h"
#include "core/colortransformation.h"
#include "core/graphicsbufferview.h"
#include "core/iccprofile.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "icc_shader.h"
#include "libkwineffects/gllut.h"
#include "platformsupport/scenes/opengl/eglnativefence.h"
#include "platformsupport/scenes/opengl/eglswapchain.h"
#include "platformsupport/scenes/opengl/glrendertimequery.h"
@ -69,7 +73,7 @@ void EglGbmLayerSurface::destroyResources()
m_oldSurface = {};
}
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, bool enableColormanagement)
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement)
{
if (!checkSurface(bufferSize, formats)) {
return std::nullopt;
@ -90,11 +94,20 @@ std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(cons
slot->framebuffer()->colorAttachment()->setContentTransform(transformation);
m_surface->currentSlot = slot;
if (m_surface->targetColorDescription != colorDescription || m_surface->channelFactors != channelFactors || m_surface->colormanagementEnabled != enableColormanagement) {
if (m_surface->targetColorDescription != colorDescription || m_surface->channelFactors != channelFactors
|| m_surface->colormanagementEnabled != enableColormanagement || m_surface->iccProfile != iccProfile) {
m_surface->damageJournal.clear();
m_surface->colormanagementEnabled = enableColormanagement;
m_surface->targetColorDescription = colorDescription;
m_surface->channelFactors = channelFactors;
m_surface->iccProfile = iccProfile;
if (iccProfile) {
if (!m_surface->iccShader) {
m_surface->iccShader = std::make_unique<IccShader>();
}
} else {
m_surface->iccShader.reset();
}
if (enableColormanagement) {
m_surface->intermediaryColorDescription = ColorDescription(colorDescription.colorimetry(), NamedTransferFunction::linear,
colorDescription.sdrBrightness(), colorDescription.minHdrBrightness(),
@ -135,19 +148,25 @@ bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion)
{
if (m_surface->colormanagementEnabled) {
GLFramebuffer *fbo = m_surface->currentSlot->framebuffer();
GLTexture *texture = fbo->colorAttachment();
GLFramebuffer::pushFramebuffer(fbo);
ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
QMatrix4x4 mat = texture->contentTransformMatrix();
ShaderBinder binder = m_surface->iccShader ? ShaderBinder(m_surface->iccShader->shader()) : ShaderBinder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
if (m_surface->iccShader) {
m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription.sdrBrightness());
} else {
QMatrix3x3 ctm;
ctm(0, 0) = m_surface->channelFactors.x();
ctm(1, 1) = m_surface->channelFactors.y();
ctm(2, 2) = m_surface->channelFactors.z();
binder.shader()->setUniform(GLShader::MatrixUniform::ColorimetryTransformation, ctm);
binder.shader()->setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(m_surface->intermediaryColorDescription.transferFunction()));
binder.shader()->setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(m_surface->targetColorDescription.transferFunction()));
binder.shader()->setUniform(GLShader::IntUniform::SdrBrightness, m_surface->intermediaryColorDescription.sdrBrightness());
binder.shader()->setUniform(GLShader::FloatUniform::MaxHdrBrightness, m_surface->intermediaryColorDescription.maxHdrHighlightBrightness());
}
QMatrix4x4 mat = fbo->colorAttachment()->contentTransformMatrix();
mat.ortho(QRectF(QPointF(), fbo->size()));
binder.shader()->setUniform(GLShader::MatrixUniform::ModelViewProjectionMatrix, mat);
QMatrix3x3 ctm;
ctm(0, 0) = m_surface->channelFactors.x();
ctm(1, 1) = m_surface->channelFactors.y();
ctm(2, 2) = m_surface->channelFactors.z();
binder.shader()->setUniform(GLShader::MatrixUniform::ColorimetryTransformation, ctm);
binder.shader()->setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(m_surface->intermediaryColorDescription.transferFunction()));
binder.shader()->setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(m_surface->targetColorDescription.transferFunction()));
glDisable(GL_BLEND);
m_surface->shadowTexture->render(m_surface->gbmSwapchain->size(), 1);
GLFramebuffer::popFramebuffer();
}

@ -36,6 +36,10 @@ class GraphicsBuffer;
class SurfaceItem;
class GLTexture;
class GLRenderTimeQuery;
class ColorTransformation;
class GlLookUpTable;
class IccProfile;
class IccShader;
class EglGbmLayerSurface : public QObject
{
@ -53,7 +57,7 @@ public:
EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target = BufferTarget::Normal, FormatOption formatOption = FormatOption::PreferAlpha);
~EglGbmLayerSurface();
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, bool enableColormanagement);
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement);
bool endRendering(const QRegion &damagedRegion);
std::chrono::nanoseconds queryRenderTime() const;
@ -79,12 +83,6 @@ private:
~Surface();
std::shared_ptr<EglContext> context;
bool colormanagementEnabled = false;
std::shared_ptr<GLTexture> shadowTexture;
std::unique_ptr<GLFramebuffer> shadowBuffer;
ColorDescription targetColorDescription = ColorDescription::sRGB;
ColorDescription intermediaryColorDescription = ColorDescription::sRGB;
QVector3D channelFactors = {1, 1, 1};
std::shared_ptr<EglSwapchain> gbmSwapchain;
std::shared_ptr<EglSwapchainSlot> currentSlot;
DamageJournal damageJournal;
@ -97,6 +95,16 @@ private:
std::shared_ptr<DrmFramebuffer> currentFramebuffer;
bool forceLinear = false;
// for color management
bool colormanagementEnabled = false;
std::shared_ptr<GLTexture> shadowTexture;
std::unique_ptr<GLFramebuffer> shadowBuffer;
ColorDescription targetColorDescription = ColorDescription::sRGB;
ColorDescription intermediaryColorDescription = ColorDescription::sRGB;
QVector3D channelFactors = {1, 1, 1};
std::unique_ptr<IccShader> iccShader;
std::shared_ptr<IccProfile> iccProfile;
// for render timing
std::unique_ptr<GLRenderTimeQuery> timeQuery;
std::unique_ptr<GLRenderTimeQuery> importTimeQuery;

@ -318,7 +318,7 @@ bool DrmOutput::queueChanges(const std::shared_ptr<OutputChangeSet> &props)
m_pipeline->setRgbRange(props->rgbRange.value_or(m_pipeline->rgbRange()));
m_pipeline->setRenderOrientation(outputToPlaneTransform(props->transform.value_or(transform())));
m_pipeline->setEnable(props->enabled.value_or(m_pipeline->enabled()));
m_pipeline->setColorimetry(props->wideColorGamut.value_or(m_state.wideColorGamut) ? NamedColorimetry::BT2020 : NamedColorimetry::BT709);
m_pipeline->setBT2020(props->wideColorGamut.value_or(m_state.wideColorGamut));
m_pipeline->setNamedTransferFunction(props->highDynamicRange.value_or(m_state.highDynamicRange) ? NamedTransferFunction::PerceptualQuantizer : NamedTransferFunction::sRGB);
m_pipeline->setSdrBrightness(props->sdrBrightness.value_or(m_state.sdrBrightness));
return true;
@ -347,6 +347,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy);
if (props->iccProfilePath) {
next.iccProfile = IccProfile::load(*props->iccProfilePath);
m_pipeline->setIccProfile(next.iccProfile);
}
if (m_state.highDynamicRange != next.highDynamicRange || m_state.sdrBrightness != next.sdrBrightness || m_state.wideColorGamut != next.wideColorGamut || m_state.iccProfile != next.iccProfile) {
m_renderLoop->scheduleRepaint();
@ -385,9 +386,6 @@ bool DrmOutput::setGammaRamp(const std::shared_ptr<ColorTransformation> &transfo
if (!m_pipeline->activePending() || needsColormanagement()) {
return false;
}
if (m_state.iccProfile && m_state.iccProfile->vcgt()) {
transformation->append(m_state.iccProfile->vcgt().get());
}
m_pipeline->setGammaRamp(transformation);
m_pipeline->setCTM(QMatrix3x3());
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
@ -436,7 +434,7 @@ QVector3D DrmOutput::channelFactors() const
bool DrmOutput::needsColormanagement() const
{
return m_pipeline->colorimetry() != NamedColorimetry::BT709 || m_pipeline->transferFunction() != NamedTransferFunction::sRGB || m_gpu->isNVidia();
return m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile || m_gpu->isNVidia();
}
}

@ -11,6 +11,7 @@
#include <errno.h>
#include "core/iccprofile.h"
#include "core/session.h"
#include "drm_backend.h"
#include "drm_buffer.h"
@ -289,10 +290,13 @@ bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
} else if (m_pending.transferFunction != NamedTransferFunction::sRGB) {
return false;
}
if (m_connector->colorspace.isValid() && m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
commit->addEnum(m_connector->colorspace, m_pending.colorimetry == NamedColorimetry::BT2020 ? DrmConnector::Colorspace::BT2020_RGB : DrmConnector::Colorspace::Default);
} else if (m_pending.colorimetry != NamedColorimetry::BT709) {
return false;
if (m_pending.BT2020) {
if (!m_connector->colorspace.isValid() || !m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
return false;
}
commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::BT2020_RGB);
} else if (m_connector->colorspace.isValid()) {
commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::Default);
}
if (m_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
@ -577,19 +581,14 @@ DrmConnector::DrmContentType DrmPipeline::contentType() const
return m_pending.contentType;
}
NamedColorimetry DrmPipeline::colorimetry() const
const ColorDescription &DrmPipeline::colorDescription() const
{
return m_pending.colorimetry;
return m_pending.colorDescription;
}
NamedTransferFunction DrmPipeline::transferFunction() const
const std::shared_ptr<IccProfile> &DrmPipeline::iccProfile() const
{
return m_pending.transferFunction;
}
const ColorDescription &DrmPipeline::colorDescription() const
{
return m_pending.colorDescription;
return m_pending.iccProfile;
}
void DrmPipeline::setCrtc(DrmCrtc *crtc)
@ -682,10 +681,10 @@ void DrmPipeline::setContentType(DrmConnector::DrmContentType type)
m_pending.contentType = type;
}
void DrmPipeline::setColorimetry(NamedColorimetry name)
void DrmPipeline::setBT2020(bool useBT2020)
{
if (m_pending.colorimetry != name) {
m_pending.colorimetry = name;
if (m_pending.BT2020 != useBT2020) {
m_pending.BT2020 = useBT2020;
m_pending.colorDescription = createColorDescription();
}
}
@ -706,14 +705,25 @@ void DrmPipeline::setSdrBrightness(double sdrBrightness)
}
}
void DrmPipeline::setIccProfile(const std::shared_ptr<IccProfile> &profile)
{
if (m_pending.iccProfile != profile) {
m_pending.iccProfile = profile;
m_pending.colorDescription = createColorDescription();
}
}
ColorDescription DrmPipeline::createColorDescription() const
{
if (m_connector->edid() && (m_pending.colorimetry != NamedColorimetry::BT709 || m_pending.transferFunction != NamedTransferFunction::sRGB)) {
if (m_pending.transferFunction == NamedTransferFunction::PerceptualQuantizer && m_connector->edid()) {
const auto colorimetry = m_pending.BT2020 ? NamedColorimetry::BT2020 : NamedColorimetry::BT709;
if (const auto hdr = m_connector->edid()->hdrMetadata(); hdr && hdr->hasValidBrightnessValues) {
return ColorDescription(m_pending.colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, hdr->desiredContentMinLuminance, hdr->desiredMaxFrameAverageLuminance, hdr->desiredContentMaxLuminance);
return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, hdr->desiredContentMinLuminance, hdr->desiredMaxFrameAverageLuminance, hdr->desiredContentMaxLuminance);
} else {
return ColorDescription(m_pending.colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, 0, m_pending.sdrBrightness, m_pending.sdrBrightness);
return ColorDescription(colorimetry, m_pending.transferFunction, m_pending.sdrBrightness, 0, m_pending.sdrBrightness, m_pending.sdrBrightness);
}
} else if (m_pending.iccProfile) {
return ColorDescription(m_pending.iccProfile->colorimetry(), NamedTransferFunction::sRGB, 200, 0, 200, 200);
} else {
return ColorDescription::sRGB;
}

@ -114,9 +114,8 @@ public:
uint32_t overscan() const;
Output::RgbRange rgbRange() const;
DrmConnector::DrmContentType contentType() const;
NamedColorimetry colorimetry() const;
NamedTransferFunction transferFunction() const;
const ColorDescription &colorDescription() const;
const std::shared_ptr<IccProfile> &iccProfile() const;
void setCrtc(DrmCrtc *crtc);
void setMode(const std::shared_ptr<DrmConnectorMode> &mode);
@ -129,8 +128,9 @@ public:
void setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation);
void setCTM(const QMatrix3x3 &ctm);
void setContentType(DrmConnector::DrmContentType type);
void setColorimetry(NamedColorimetry name);
void setBT2020(bool useBT2020);
void setNamedTransferFunction(NamedTransferFunction tf);
void setIccProfile(const std::shared_ptr<IccProfile> &profile);
void setSdrBrightness(double sdrBrightness);
enum class CommitMode {
@ -185,9 +185,10 @@ private:
std::shared_ptr<DrmBlob> ctm;
DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics;
NamedColorimetry colorimetry = NamedColorimetry::BT709;
bool BT2020 = false;
NamedTransferFunction transferFunction = NamedTransferFunction::sRGB;
double sdrBrightness = 200;
std::shared_ptr<IccProfile> iccProfile;
ColorDescription colorDescription = ColorDescription::sRGB;
// the transformation that buffers submitted to the pipeline should have

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
precision highp float;
in vec2 texcoord0;
uniform sampler2D src;
uniform float sdrBrightness;
uniform mat3 matrix1;
uniform int Bsize;
uniform sampler1D Bsampler;
uniform mat4 matrix2;
uniform int Msize;
uniform sampler1D Msamplrt;
uniform ivec3 Csize;
uniform sampler3D Csampler;
uniform int Asize;
uniform sampler1D Asampler;
vec3 sample1DLut(vec3 input, sampler1D lut, int lutSize) {
float lutOffset = 0.5 / lutSize;
float lutScale = 1 - lutOffset * 2;
float lutR = texture1D(lut, lutOffset + input.r * lutScale).r;
float lutG = texture1D(lut, lutOffset + input.g * lutScale).g;
float lutB = texture1D(lut, lutOffset + input.b * lutScale).b;
return vec3(lutR, lutG, lutB);
}
void main()
{
vec4 tex = texture2D(src, texcoord0);
tex.rgb /= sdrBrightness;
tex.rgb = matrix1 * tex.rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}
tex.rgb = (matrix2 * vec4(tex.rgb, 1.0)).rgb;
if (Msize > 0) {
tex.rgb = sample1DLut(tex.rgb, Msampler, Msize);
}
if (Csize > 0) {
vec3 lutOffset = vec3(0.5) / Csize;
vec3 lutScale = vec3(1) - lutOffset * 2;
tex.rgb = texture3D(Csampler, lutOffset + tex.rgb * lutScale).rgb;
}
if (Asize > 0) {
tex.rgb = sample1DLut(tex.rgb, Asampler, Asize);
}
gl_FragColor = tex;
}

@ -0,0 +1,6 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/backends/drm/">
<file>icc.frag</file>
<file>icc_core.frag</file>
</qresource>
</RCC>

@ -0,0 +1,59 @@
#version 140
// SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
precision highp float;
in vec2 texcoord0;
out vec4 fragColor;
uniform sampler2D src;
uniform float sdrBrightness;
uniform mat3 matrix1;
uniform int Bsize;
uniform sampler1D Bsampler;
uniform mat4 matrix2;
uniform int Msize;
uniform sampler1D Msampler;
uniform ivec3 Csize;
uniform sampler3D Csampler;
uniform int Asize;
uniform sampler1D Asampler;
vec3 sample1DLut(in vec3 srcColor, in sampler1D lut, in int lutSize) {
float lutOffset = 0.5 / lutSize;
float lutScale = 1 - lutOffset * 2;
float lutR = texture(lut, lutOffset + srcColor.r * lutScale).r;
float lutG = texture(lut, lutOffset + srcColor.g * lutScale).g;
float lutB = texture(lut, lutOffset + srcColor.b * lutScale).b;
return vec3(lutR, lutG, lutB);
}
void main()
{
vec4 tex = texture(src, texcoord0);
tex.rgb /= sdrBrightness;
tex.rgb = matrix1 * tex.rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}
tex.rgb = (matrix2 * vec4(tex.rgb, 1.0)).rgb;
if (Msize > 0) {
tex.rgb = sample1DLut(tex.rgb, Msampler, Msize);
}
if (Csize.x > 0) {
vec3 lutOffset = vec3(0.5) / Csize;
vec3 lutScale = vec3(1) - lutOffset * 2;
tex.rgb = texture(Csampler, lutOffset + tex.rgb * lutScale).rgb;
}
if (Asize > 0) {
tex.rgb = sample1DLut(tex.rgb, Asampler, Asize);
}
fragColor = tex;
}

@ -0,0 +1,207 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "icc_shader.h"
#include "core/colorlut3d.h"
#include "core/colortransformation.h"
#include "core/iccprofile.h"
#include "libkwineffects/gllut.h"
#include "libkwineffects/gllut3D.h"
#include "libkwineffects/glshader.h"
#include "libkwineffects/glshadermanager.h"
#include "libkwineffects/gltexture.h"
namespace KWin
{
static constexpr size_t lutSize = 1 << 12;
IccShader::IccShader()
: m_shader(ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, QString(), QStringLiteral(":/backends/drm/icc.frag")))
{
m_locations = {
.src = m_shader->uniformLocation("src"),
.sdrBrightness = m_shader->uniformLocation("sdrBrightness"),
.matrix1 = m_shader->uniformLocation("matrix1"),
.bsize = m_shader->uniformLocation("Bsize"),
.bsampler = m_shader->uniformLocation("Bsampler"),
.matrix2 = m_shader->uniformLocation("matrix2"),
.msize = m_shader->uniformLocation("Msize"),
.msampler = m_shader->uniformLocation("Msampler"),
.csize = m_shader->uniformLocation("Csize"),
.csampler = m_shader->uniformLocation("Csampler"),
.asize = m_shader->uniformLocation("Asize"),
.asampler = m_shader->uniformLocation("Asampler"),
};
}
IccShader::~IccShader()
{
}
static const QVector2D D50 = Colorimetry::xyzToXY(QVector3D(0.9642, 1.0, 0.8249));
bool IccShader::setProfile(const std::shared_ptr<IccProfile> &profile)
{
if (!profile) {
m_matrix1.setToIdentity();
m_B.reset();
m_matrix2.setToIdentity();
m_M.reset();
m_C.reset();
m_A.reset();
return false;
}
if (m_profile != profile) {
const auto vcgt = profile->vcgt();
QMatrix3x3 matrix1;
std::unique_ptr<GlLookUpTable> B;
QMatrix4x4 matrix2;
std::unique_ptr<GlLookUpTable> M;
std::unique_ptr<GlLookUpTable3D> C;
std::unique_ptr<GlLookUpTable> A;
if (const IccProfile::BToATagData *tag = profile->BtToATag()) {
matrix1 = Colorimetry::chromaticAdaptationMatrix(profile->colorimetry().white, D50) * profile->colorimetry().toXYZ();
if (tag->B) {
const auto sample = [&tag](size_t x) {
const float relativeX = x / double(lutSize - 1);
return tag->B->transform(QVector3D(relativeX, relativeX, relativeX));
};
B = GlLookUpTable::create(sample, lutSize);
if (!B) {
return false;
}
}
matrix2 = tag->matrix.value_or(QMatrix4x4());
if (tag->M) {
const auto sample = [&tag](size_t x) {
const float relativeX = x / double(lutSize - 1);
return tag->M->transform(QVector3D(relativeX, relativeX, relativeX));
};
M = GlLookUpTable::create(sample, lutSize);
if (!M) {
return false;
}
}
if (tag->CLut) {
const auto sample = [&tag](size_t x, size_t y, size_t z) {
return tag->CLut->sample(x, y, z);
};
C = GlLookUpTable3D::create(sample, tag->CLut->xSize(), tag->CLut->ySize(), tag->CLut->zSize());
if (!C) {
return false;
}
}
if (tag->A) {
const auto sample = [&tag, vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
QVector3D ret = tag->A->transform(QVector3D(relativeX, relativeX, relativeX));
if (vcgt) {
ret = vcgt->transform(ret);
}
return ret;
};
A = GlLookUpTable::create(sample, lutSize);
if (!A) {
return false;
}
} else if (vcgt) {
const auto sample = [&vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
return vcgt->transform(QVector3D(relativeX, relativeX, relativeX));
};
A = GlLookUpTable::create(sample, lutSize);
}
} else {
const auto inverseEOTF = profile->inverseEOTF();
const auto sample = [inverseEOTF, vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
QVector3D ret(relativeX, relativeX, relativeX);
ret = inverseEOTF->transform(ret);
if (vcgt) {
ret = vcgt->transform(ret);
}
return ret;
};
A = GlLookUpTable::create(sample, lutSize);
if (!A) {
return false;
}
}
m_matrix1 = matrix1;
m_B = std::move(B);
m_matrix2 = matrix2;
m_M = std::move(M);
m_C = std::move(C);
m_A = std::move(A);
m_profile = profile;
}
return true;
}
GLShader *IccShader::shader() const
{
return m_shader.get();
}
void IccShader::setUniforms(const std::shared_ptr<IccProfile> &profile, float sdrBrightness)
{
// this failing can be silently ignored, it should only happen with GPU resets and gets corrected later
setProfile(profile);
m_shader->setUniform(m_locations.sdrBrightness, sdrBrightness);
m_shader->setUniform(m_locations.matrix1, m_matrix1);
glActiveTexture(GL_TEXTURE1);
if (m_B) {
m_shader->setUniform(m_locations.bsize, int(m_B->size()));
m_shader->setUniform(m_locations.bsampler, 1);
m_B->bind();
} else {
m_shader->setUniform(m_locations.bsize, 0);
m_shader->setUniform(m_locations.bsampler, 1);
glBindTexture(GL_TEXTURE_1D, 0);
}
m_shader->setUniform(m_locations.matrix2, m_matrix2);
glActiveTexture(GL_TEXTURE2);
if (m_M) {
m_shader->setUniform(m_locations.msize, int(m_M->size()));
m_shader->setUniform(m_locations.msampler, 2);
m_M->bind();
} else {
m_shader->setUniform(m_locations.msize, 0);
m_shader->setUniform(m_locations.msampler, 1);
glBindTexture(GL_TEXTURE_1D, 0);
}
glActiveTexture(GL_TEXTURE3);
if (m_C) {
m_shader->setUniform(m_locations.csize, m_C->xSize(), m_C->ySize(), m_C->zSize());
m_shader->setUniform(m_locations.csampler, 3);
m_C->bind();
} else {
m_shader->setUniform(m_locations.csize, 0, 0, 0);
m_shader->setUniform(m_locations.csampler, 3);
glBindTexture(GL_TEXTURE_3D, 0);
}
glActiveTexture(GL_TEXTURE4);
if (m_A) {
m_shader->setUniform(m_locations.asize, int(m_A->size()));
m_shader->setUniform(m_locations.asampler, 4);
m_A->bind();
} else {
m_shader->setUniform(m_locations.asize, 0);
m_shader->setUniform(m_locations.asampler, 4);
glBindTexture(GL_TEXTURE_1D, 0);
}
glActiveTexture(GL_TEXTURE0);
m_shader->setUniform(m_locations.src, 0);
}
}

@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QMatrix4x4>
#include <QSizeF>
#include <memory>
namespace KWin
{
class IccProfile;
class GLShader;
class GlLookUpTable;
class GlLookUpTable3D;
class GLTexture;
class IccShader
{
public:
explicit IccShader();
~IccShader();
GLShader *shader() const;
void setUniforms(const std::shared_ptr<IccProfile> &profile, float sdrBrightness);
private:
bool setProfile(const std::shared_ptr<IccProfile> &profile);
std::unique_ptr<GLShader> m_shader;
std::shared_ptr<IccProfile> m_profile;
QMatrix3x3 m_matrix1;
std::unique_ptr<GlLookUpTable> m_B;
QMatrix4x4 m_matrix2;
std::unique_ptr<GlLookUpTable> m_M;
std::unique_ptr<GlLookUpTable3D> m_C;
std::unique_ptr<GlLookUpTable> m_A;
struct Locations
{
int src;
int sdrBrightness;
int matrix1;
int bsize;
int bsampler;
int matrix2;
int msize;
int msampler;
int csize;
int csampler;
int asize;
int asampler;
};
Locations m_locations;
};
}

@ -349,6 +349,14 @@ bool GLShader::setUniform(int location, int value)
return (location >= 0);
}
bool GLShader::setUniform(int location, int xValue, int yValue, int zValue)
{
if (location >= 0) {
glUniform3i(location, xValue, yValue, zValue);
}
return location >= 0;
}
bool GLShader::setUniform(int location, const QVector2D &value)
{
if (location >= 0) {

@ -55,6 +55,7 @@ public:
bool setUniform(int location, float value);
bool setUniform(int location, int value);
bool setUniform(int location, int xValue, int yValue, int zValue);
bool setUniform(int location, const QVector2D &value);
bool setUniform(int location, const QVector3D &value);
bool setUniform(int location, const QVector4D &value);

Loading…
Cancel
Save