From 8d25550c2208b19c3cb2de46364a9bbc9678487e Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Wed, 4 Oct 2023 17:55:38 +0200 Subject: [PATCH] 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 --- autotests/drm/CMakeLists.txt | 1 + src/backends/drm/CMakeLists.txt | 2 + src/backends/drm/drm_egl_cursor_layer.cpp | 3 +- src/backends/drm/drm_egl_layer.cpp | 5 +- src/backends/drm/drm_egl_layer_surface.cpp | 43 +++-- src/backends/drm/drm_egl_layer_surface.h | 22 ++- src/backends/drm/drm_output.cpp | 8 +- src/backends/drm/drm_pipeline.cpp | 48 +++-- src/backends/drm/drm_pipeline.h | 9 +- src/backends/drm/icc.frag | 56 ++++++ src/backends/drm/icc.qrc | 6 + src/backends/drm/icc_core.frag | 59 ++++++ src/backends/drm/icc_shader.cpp | 207 +++++++++++++++++++++ src/backends/drm/icc_shader.h | 60 ++++++ src/libkwineffects/glshader.cpp | 8 + src/libkwineffects/glshader.h | 1 + 16 files changed, 488 insertions(+), 50 deletions(-) create mode 100644 src/backends/drm/icc.frag create mode 100644 src/backends/drm/icc.qrc create mode 100644 src/backends/drm/icc_core.frag create mode 100644 src/backends/drm/icc_shader.cpp create mode 100644 src/backends/drm/icc_shader.h diff --git a/autotests/drm/CMakeLists.txt b/autotests/drm/CMakeLists.txt index 590d10bcbe..1772fa3b55 100644 --- a/autotests/drm/CMakeLists.txt +++ b/autotests/drm/CMakeLists.txt @@ -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}) diff --git a/src/backends/drm/CMakeLists.txt b/src/backends/drm/CMakeLists.txt index ea497ab7e6..1cf281a381 100644 --- a/src/backends/drm/CMakeLists.txt +++ b/src/backends/drm/CMakeLists.txt @@ -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) diff --git a/src/backends/drm/drm_egl_cursor_layer.cpp b/src/backends/drm/drm_egl_cursor_layer.cpp index 1bb580877a..3d8e4ed718 100644 --- a/src/backends/drm/drm_egl_cursor_layer.cpp +++ b/src/backends/drm/drm_egl_cursor_layer.cpp @@ -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 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) diff --git a/src/backends/drm/drm_egl_layer.cpp b/src/backends/drm/drm_egl_layer.cpp index 10a9264fbb..08b852d712 100644 --- a/src/backends/drm/drm_egl_layer.cpp +++ b/src/backends/drm/drm_egl_layer.cpp @@ -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 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; } diff --git a/src/backends/drm/drm_egl_layer_surface.cpp b/src/backends/drm/drm_egl_layer_surface.cpp index 171d30b1b8..13287fc838 100644 --- a/src/backends/drm/drm_egl_layer_surface.cpp +++ b/src/backends/drm/drm_egl_layer_surface.cpp @@ -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 EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, bool enableColormanagement) +std::optional EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement) { if (!checkSurface(bufferSize, formats)) { return std::nullopt; @@ -90,11 +94,20 @@ std::optional 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(); + } + } 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(); } diff --git a/src/backends/drm/drm_egl_layer_surface.h b/src/backends/drm/drm_egl_layer_surface.h index d316e07719..984456554f 100644 --- a/src/backends/drm/drm_egl_layer_surface.h +++ b/src/backends/drm/drm_egl_layer_surface.h @@ -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 startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, bool enableColormanagement); + std::optional startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement); bool endRendering(const QRegion &damagedRegion); std::chrono::nanoseconds queryRenderTime() const; @@ -79,12 +83,6 @@ private: ~Surface(); std::shared_ptr context; - bool colormanagementEnabled = false; - std::shared_ptr shadowTexture; - std::unique_ptr shadowBuffer; - ColorDescription targetColorDescription = ColorDescription::sRGB; - ColorDescription intermediaryColorDescription = ColorDescription::sRGB; - QVector3D channelFactors = {1, 1, 1}; std::shared_ptr gbmSwapchain; std::shared_ptr currentSlot; DamageJournal damageJournal; @@ -97,6 +95,16 @@ private: std::shared_ptr currentFramebuffer; bool forceLinear = false; + // for color management + bool colormanagementEnabled = false; + std::shared_ptr shadowTexture; + std::unique_ptr shadowBuffer; + ColorDescription targetColorDescription = ColorDescription::sRGB; + ColorDescription intermediaryColorDescription = ColorDescription::sRGB; + QVector3D channelFactors = {1, 1, 1}; + std::unique_ptr iccShader; + std::shared_ptr iccProfile; + // for render timing std::unique_ptr timeQuery; std::unique_ptr importTimeQuery; diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index f9afab74ab..bb3690eab6 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -318,7 +318,7 @@ bool DrmOutput::queueChanges(const std::shared_ptr &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 &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 &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(); } } diff --git a/src/backends/drm/drm_pipeline.cpp b/src/backends/drm/drm_pipeline.cpp index dedce9522e..4a8223fa74 100644 --- a/src/backends/drm/drm_pipeline.cpp +++ b/src/backends/drm/drm_pipeline.cpp @@ -11,6 +11,7 @@ #include +#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 &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 &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; } diff --git a/src/backends/drm/drm_pipeline.h b/src/backends/drm/drm_pipeline.h index 4811398f96..ec843a01b4 100644 --- a/src/backends/drm/drm_pipeline.h +++ b/src/backends/drm/drm_pipeline.h @@ -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() const; void setCrtc(DrmCrtc *crtc); void setMode(const std::shared_ptr &mode); @@ -129,8 +128,9 @@ public: void setGammaRamp(const std::shared_ptr &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 &profile); void setSdrBrightness(double sdrBrightness); enum class CommitMode { @@ -185,9 +185,10 @@ private: std::shared_ptr 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; ColorDescription colorDescription = ColorDescription::sRGB; // the transformation that buffers submitted to the pipeline should have diff --git a/src/backends/drm/icc.frag b/src/backends/drm/icc.frag new file mode 100644 index 0000000000..8cdc892698 --- /dev/null +++ b/src/backends/drm/icc.frag @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// 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; +} diff --git a/src/backends/drm/icc.qrc b/src/backends/drm/icc.qrc new file mode 100644 index 0000000000..ef6d4589ef --- /dev/null +++ b/src/backends/drm/icc.qrc @@ -0,0 +1,6 @@ + + + icc.frag + icc_core.frag + + diff --git a/src/backends/drm/icc_core.frag b/src/backends/drm/icc_core.frag new file mode 100644 index 0000000000..3c9ffeea2a --- /dev/null +++ b/src/backends/drm/icc_core.frag @@ -0,0 +1,59 @@ +#version 140 +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// 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; +} diff --git a/src/backends/drm/icc_shader.cpp b/src/backends/drm/icc_shader.cpp new file mode 100644 index 0000000000..0f104b22e4 --- /dev/null +++ b/src/backends/drm/icc_shader.cpp @@ -0,0 +1,207 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + 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 &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 B; + QMatrix4x4 matrix2; + std::unique_ptr M; + std::unique_ptr C; + std::unique_ptr 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 &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); +} + +} diff --git a/src/backends/drm/icc_shader.h b/src/backends/drm/icc_shader.h new file mode 100644 index 0000000000..dccd56776f --- /dev/null +++ b/src/backends/drm/icc_shader.h @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include +#include +#include + +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 &profile, float sdrBrightness); + +private: + bool setProfile(const std::shared_ptr &profile); + + std::unique_ptr m_shader; + std::shared_ptr m_profile; + + QMatrix3x3 m_matrix1; + std::unique_ptr m_B; + QMatrix4x4 m_matrix2; + std::unique_ptr m_M; + std::unique_ptr m_C; + std::unique_ptr 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; +}; + +} diff --git a/src/libkwineffects/glshader.cpp b/src/libkwineffects/glshader.cpp index 4867d2f8bf..24985f2696 100644 --- a/src/libkwineffects/glshader.cpp +++ b/src/libkwineffects/glshader.cpp @@ -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) { diff --git a/src/libkwineffects/glshader.h b/src/libkwineffects/glshader.h index abe7a7c36f..1b40ff0620 100644 --- a/src/libkwineffects/glshader.h +++ b/src/libkwineffects/glshader.h @@ -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);