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
This commit is contained in:
parent
7d0a3dcd1e
commit
8d25550c22
16 changed files with 489 additions and 51 deletions
|
@ -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,21 +581,16 @@ DrmConnector::DrmContentType DrmPipeline::contentType() const
|
|||
return m_pending.contentType;
|
||||
}
|
||||
|
||||
NamedColorimetry DrmPipeline::colorimetry() const
|
||||
{
|
||||
return m_pending.colorimetry;
|
||||
}
|
||||
|
||||
NamedTransferFunction DrmPipeline::transferFunction() const
|
||||
{
|
||||
return m_pending.transferFunction;
|
||||
}
|
||||
|
||||
const ColorDescription &DrmPipeline::colorDescription() const
|
||||
{
|
||||
return m_pending.colorDescription;
|
||||
}
|
||||
|
||||
const std::shared_ptr<IccProfile> &DrmPipeline::iccProfile() const
|
||||
{
|
||||
return m_pending.iccProfile;
|
||||
}
|
||||
|
||||
void DrmPipeline::setCrtc(DrmCrtc *crtc)
|
||||
{
|
||||
if (crtc && m_pending.crtc && crtc->gammaRampSize() != m_pending.crtc->gammaRampSize() && m_pending.colorTransformation) {
|
||||
|
@ -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
|
||||
|
|
56
src/backends/drm/icc.frag
Normal file
56
src/backends/drm/icc.frag
Normal file
|
@ -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;
|
||||
}
|
6
src/backends/drm/icc.qrc
Normal file
6
src/backends/drm/icc.qrc
Normal file
|
@ -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>
|
59
src/backends/drm/icc_core.frag
Normal file
59
src/backends/drm/icc_core.frag
Normal file
|
@ -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;
|
||||
}
|
207
src/backends/drm/icc_shader.cpp
Normal file
207
src/backends/drm/icc_shader.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
60
src/backends/drm/icc_shader.h
Normal file
60
src/backends/drm/icc_shader.h
Normal file
|
@ -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…
Reference in a new issue