From afc55676515ebdd55a7e2fa9a79943d6057d7a50 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Sun, 30 Apr 2023 12:54:59 +0200 Subject: [PATCH] Implement initial support for color management and HDR This is done by converting from the sRGB + gamma 2.2 input from clients to linear with the color space of the output (BT.709 or BT2020 atm) in a shadow buffer, and then convert from the shadow buffer to the transfer function the output needs (sRGB or PQ). --- src/backends/drm/drm_egl_cursor_layer.cpp | 3 +- src/backends/drm/drm_egl_layer.cpp | 6 +- src/backends/drm/drm_egl_layer_surface.cpp | 47 ++++++- src/backends/drm/drm_egl_layer_surface.h | 7 +- src/backends/drm/drm_gbm_swapchain.cpp | 5 + src/backends/drm/drm_gbm_swapchain.h | 1 + src/backends/drm/drm_output.cpp | 53 +++++-- src/backends/drm/drm_output.h | 4 +- src/backends/drm/drm_pipeline.cpp | 85 +++++++++++- src/backends/drm/drm_pipeline.h | 10 +- src/colors/colordevice.cpp | 6 +- src/core/output.cpp | 28 +++- src/core/output.h | 21 ++- src/core/outputconfiguration.h | 3 + src/effects.cpp | 3 +- src/libkwineffects/CMakeLists.txt | 6 +- src/libkwineffects/colorspace.cpp | 129 ++++++++++++++++++ src/libkwineffects/colorspace.h | 73 ++++++++++ src/libkwineffects/kwinglutils.cpp | 77 +++++++++++ src/libkwineffects/kwinglutils.h | 11 ++ src/libkwineffects/kwinoffscreeneffect.cpp | 3 +- src/libkwineffects/rendertarget.cpp | 18 ++- src/libkwineffects/rendertarget.h | 9 +- .../scenes/opengl/eglcontext.cpp | 20 ++- src/plugins/blur/blur.cpp | 98 +++++++------ src/plugins/blur/blur.h | 2 +- src/plugins/mouseclick/mouseclick.cpp | 8 +- src/plugins/mouseclick/mouseclick.h | 2 +- src/plugins/mousemark/mousemark.cpp | 4 +- src/plugins/screenedge/screenedgeeffect.cpp | 4 +- src/plugins/snaphelper/snaphelper.cpp | 4 +- .../startupfeedback/startupfeedback.cpp | 7 +- src/plugins/touchpoints/touchpoints.cpp | 6 +- src/plugins/touchpoints/touchpoints.h | 2 +- src/plugins/trackmouse/trackmouse.cpp | 4 +- src/plugins/zoom/zoom.cpp | 14 +- src/plugins/zoom/zoom.h | 2 +- src/scene/cursordelegate_opengl.cpp | 4 +- src/scene/itemrenderer_opengl.cpp | 3 +- src/wayland/outputdevice_v2_interface.cpp | 80 ++++++++++- src/wayland/outputdevice_v2_interface.h | 3 + src/wayland/outputmanagement_v2_interface.cpp | 35 ++++- 42 files changed, 787 insertions(+), 123 deletions(-) create mode 100644 src/libkwineffects/colorspace.cpp create mode 100644 src/libkwineffects/colorspace.h diff --git a/src/backends/drm/drm_egl_cursor_layer.cpp b/src/backends/drm/drm_egl_cursor_layer.cpp index 580b5b8c0b..1b2a517056 100644 --- a/src/backends/drm/drm_egl_cursor_layer.cpp +++ b/src/backends/drm/drm_egl_cursor_layer.cpp @@ -10,6 +10,7 @@ #include "drm_buffer.h" #include "drm_egl_backend.h" #include "drm_gpu.h" +#include "drm_output.h" #include "drm_pipeline.h" #include @@ -45,7 +46,7 @@ EglGbmCursorLayer::EglGbmCursorLayer(EglGbmBackend *eglBackend, DrmPipeline *pip std::optional EglGbmCursorLayer::beginFrame() { - return m_surface.startRendering(m_pipeline->gpu()->cursorSize(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->cursorFormats()); + return m_surface.startRendering(m_pipeline->gpu()->cursorSize(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->cursorFormats(), Colorspace(m_pipeline->colorimetry(), m_pipeline->transferFunction()), m_pipeline->output()->sdrBrightness(), m_pipeline->output()->channelFactors()); } 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 b30ff23f83..dd38e06cb5 100644 --- a/src/backends/drm/drm_egl_layer.cpp +++ b/src/backends/drm/drm_egl_layer.cpp @@ -60,7 +60,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()); + return m_surface.startRendering(m_pipeline->mode()->size(), drmToTextureRotation(m_pipeline) | TextureTransform::MirrorY, m_pipeline->formats(), Colorspace(m_pipeline->colorimetry(), m_pipeline->transferFunction()), m_pipeline->output()->sdrBrightness(), m_pipeline->output()->channelFactors()); } bool EglGbmLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) @@ -100,6 +100,10 @@ bool EglGbmLayer::scanout(SurfaceItem *surfaceItem) if (directScanoutDisabled) { 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) { + return false; + } SurfaceItemWayland *item = qobject_cast(surfaceItem); if (!item || !item->surface()) { diff --git a/src/backends/drm/drm_egl_layer_surface.cpp b/src/backends/drm/drm_egl_layer_surface.cpp index 215c9c9942..71b1887e0e 100644 --- a/src/backends/drm/drm_egl_layer_surface.cpp +++ b/src/backends/drm/drm_egl_layer_surface.cpp @@ -60,7 +60,7 @@ void EglGbmLayerSurface::destroyResources() m_oldSurface = {}; } -std::optional EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats) +std::optional EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const Colorspace &colorspace, uint32_t sdrBrightness, const QVector3D &channelFactors) { if (!checkSurface(bufferSize, formats)) { return std::nullopt; @@ -69,7 +69,7 @@ std::optional EglGbmLayerSurface::startRendering(cons return std::nullopt; } - const auto [buffer, repaint] = m_surface.gbmSwapchain->acquire(); + auto [buffer, repaint] = m_surface.gbmSwapchain->acquire(); if (!buffer) { return std::nullopt; } @@ -90,14 +90,49 @@ std::optional EglGbmLayerSurface::startRendering(cons } m_surface.currentBuffer = buffer; - return OutputLayerBeginFrameInfo{ - .renderTarget = RenderTarget(fbo.get()), - .repaint = repaint, - }; + if (m_surface.colorspace != colorspace || m_surface.channelFactors != channelFactors || m_surface.sdrBrightness != sdrBrightness) { + m_surface.gbmSwapchain->resetDamage(); + repaint = infiniteRegion(); + m_surface.colorspace = colorspace; + m_surface.channelFactors = channelFactors; + m_surface.sdrBrightness = sdrBrightness; + } + if (colorspace != Colorspace::sRGB) { + if (!m_surface.shadowBuffer) { + m_surface.shadowTexture = std::make_shared(GL_RGBA16F, m_surface.gbmSwapchain->size()); + m_surface.shadowBuffer = std::make_shared(m_surface.shadowTexture.get()); + } + return OutputLayerBeginFrameInfo{ + .renderTarget = RenderTarget(m_surface.shadowBuffer.get(), Colorspace(colorspace.colorimetry(), NamedTransferFunction::linear), sdrBrightness), + .repaint = repaint, + }; + } else { + return OutputLayerBeginFrameInfo{ + .renderTarget = RenderTarget(fbo.get(), colorspace), + .repaint = repaint, + }; + } } bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion) { + if (m_surface.colorspace != Colorspace::sRGB) { + const auto &[texture, fbo] = m_surface.textureCache[m_surface.currentBuffer->bo()]; + GLFramebuffer::pushFramebuffer(fbo.get()); + ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); + QMatrix4x4 mat = texture->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(NamedTransferFunction::linear)); + binder.shader()->setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(m_surface.colorspace.transferFunction())); + m_surface.shadowTexture->render(m_surface.gbmSwapchain->size(), 1); + GLFramebuffer::popFramebuffer(); + } m_surface.gbmSwapchain->damage(damagedRegion); glFlush(); const auto buffer = importBuffer(m_surface, m_surface.currentBuffer); diff --git a/src/backends/drm/drm_egl_layer_surface.h b/src/backends/drm/drm_egl_layer_surface.h index 05454596d6..32d6a62462 100644 --- a/src/backends/drm/drm_egl_layer_surface.h +++ b/src/backends/drm/drm_egl_layer_surface.h @@ -54,7 +54,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); + std::optional startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap> &formats, const Colorspace &colorspace, uint32_t sdrBrightness, const QVector3D &channelFactors); bool endRendering(const QRegion &damagedRegion); bool doesSurfaceFit(const QSize &size, const QMap> &formats) const; @@ -74,6 +74,11 @@ private: }; struct Surface { + std::shared_ptr shadowTexture; + std::shared_ptr shadowBuffer; + Colorspace colorspace = Colorspace::sRGB; + QVector3D channelFactors = {1, 1, 1}; + float sdrBrightness = 200; std::shared_ptr gbmSwapchain; std::shared_ptr importDumbSwapchain; std::shared_ptr importGbmSwapchain; diff --git a/src/backends/drm/drm_gbm_swapchain.cpp b/src/backends/drm/drm_gbm_swapchain.cpp index cb7a369d75..4f62e1a3b9 100644 --- a/src/backends/drm/drm_gbm_swapchain.cpp +++ b/src/backends/drm/drm_gbm_swapchain.cpp @@ -72,6 +72,11 @@ void GbmSwapchain::damage(const QRegion &damage) m_damageJournal.add(damage); } +void GbmSwapchain::resetDamage() +{ + m_damageJournal.clear(); +} + void GbmSwapchain::releaseBuffer(GbmBuffer *buffer) { if (m_buffers.size() < 3) { diff --git a/src/backends/drm/drm_gbm_swapchain.h b/src/backends/drm/drm_gbm_swapchain.h index 5a6b375a3d..59ff8bf91a 100644 --- a/src/backends/drm/drm_gbm_swapchain.h +++ b/src/backends/drm/drm_gbm_swapchain.h @@ -32,6 +32,7 @@ public: std::pair, QRegion> acquire(); void damage(const QRegion &damage); + void resetDamage(); void releaseBuffer(GbmBuffer *buffer); DrmGpu *gpu() const; diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 6f3d866bfe..7bb7cb149f 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -67,6 +67,13 @@ DrmOutput::DrmOutput(const std::shared_ptr &conn) capabilities |= Capability::RgbRange; initialState.rgbRange = DrmConnector::broadcastRgbToRgbRange(conn->broadcastRGB.enumValue()); } + if (m_connector->hdrMetadata.isValid() && m_connector->edid() && m_connector->edid()->hdrMetadata() && m_connector->edid()->hdrMetadata()->supportsPQ) { + capabilities |= Capability::HighDynamicRange; + } + if (m_connector->colorspace.isValid() && m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB) + && m_connector->edid() && m_connector->edid()->hdrMetadata() && m_connector->edid()->hdrMetadata()->supportsBT2020) { + capabilities |= Capability::WideColorGamut; + } const Edid *edid = conn->edid(); @@ -409,6 +416,8 @@ 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->setNamedTransferFunction(props->highDynamicRange.value_or(m_state.highDynamicRange) ? NamedTransferFunction::PerceptualQuantizer : NamedTransferFunction::sRGB); return true; } @@ -428,6 +437,12 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props next.currentMode = m_pipeline->mode(); next.overscan = m_pipeline->overscan(); next.rgbRange = m_pipeline->rgbRange(); + next.highDynamicRange = props->highDynamicRange.value_or(m_state.highDynamicRange); + next.sdrBrightness = props->sdrBrightness.value_or(m_state.sdrBrightness); + next.wideColorGamut = props->wideColorGamut.value_or(m_state.wideColorGamut); + if (m_state.highDynamicRange != next.highDynamicRange || m_state.sdrBrightness != next.sdrBrightness || m_state.wideColorGamut != next.wideColorGamut) { + m_renderLoop->scheduleRepaint(); + } setState(next); setVrrPolicy(props->vrrPolicy.value_or(vrrPolicy())); @@ -463,7 +478,7 @@ DrmOutputLayer *DrmOutput::cursorLayer() const bool DrmOutput::setGammaRamp(const std::shared_ptr &transformation) { - if (!m_pipeline->active()) { + if (!m_pipeline->active() || m_pipeline->colorimetry() != NamedColorimetry::BT709 || m_pipeline->transferFunction() != NamedTransferFunction::sRGB) { return false; } m_pipeline->setGammaRamp(transformation); @@ -478,19 +493,37 @@ bool DrmOutput::setGammaRamp(const std::shared_ptr &transfo } } -bool DrmOutput::setCTM(const QMatrix3x3 &ctm) +bool DrmOutput::setChannelFactors(const QVector3D &rgb) { - if (!m_pipeline->active()) { - return false; + if (m_channelFactors == rgb) { + return true; } - m_pipeline->setCTM(ctm); - if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { - m_pipeline->applyPendingChanges(); + m_channelFactors = rgb; + if (m_pipeline->colorimetry() == NamedColorimetry::BT709 && m_pipeline->transferFunction() == NamedTransferFunction::sRGB) { + if (!m_pipeline->active()) { + return false; + } + QMatrix3x3 ctm; + ctm(0, 0) = rgb.x(); + ctm(1, 1) = rgb.y(); + ctm(2, 2) = rgb.z(); + m_pipeline->setCTM(ctm); + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { + m_pipeline->applyPendingChanges(); + m_renderLoop->scheduleRepaint(); + return true; + } else { + m_pipeline->revertPendingChanges(); + return false; + } + } else { m_renderLoop->scheduleRepaint(); return true; - } else { - m_pipeline->revertPendingChanges(); - return false; } } + +QVector3D DrmOutput::channelFactors() const +{ + return m_channelFactors; +} } diff --git a/src/backends/drm/drm_output.h b/src/backends/drm/drm_output.h index 07606c556d..32fac34cf4 100644 --- a/src/backends/drm/drm_output.h +++ b/src/backends/drm/drm_output.h @@ -60,7 +60,8 @@ public: void leaseEnded(); bool setGammaRamp(const std::shared_ptr &transformation) override; - bool setCTM(const QMatrix3x3 &ctm) override; + bool setChannelFactors(const QVector3D &rgb) override; + QVector3D channelFactors() const; private: bool setDrmDpmsMode(DpmsMode mode); @@ -80,6 +81,7 @@ private: QPointer source; QPointF position; } m_cursor; + QVector3D m_channelFactors = {1, 1, 1}; }; } diff --git a/src/backends/drm/drm_pipeline.cpp b/src/backends/drm/drm_pipeline.cpp index baa93c668c..5a397f9256 100644 --- a/src/backends/drm/drm_pipeline.cpp +++ b/src/backends/drm/drm_pipeline.cpp @@ -108,7 +108,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QVectorprepareAtomicModeset(commit.get()); + if (!pipeline->prepareAtomicModeset(commit.get())) { + return Error::InvalidArguments; + } } } else { pipeline->prepareAtomicDisable(commit.get()); @@ -229,7 +231,7 @@ void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit) } } -void DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit) +bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit) { commit->addProperty(m_connector->crtcId, m_pending.crtc->id()); if (m_connector->broadcastRGB.isValid()) { @@ -254,10 +256,14 @@ void DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit) commit->addProperty(m_connector->maxBpc, preferred); } if (m_connector->hdrMetadata.isValid()) { - commit->addProperty(m_connector->hdrMetadata, 0); + commit->addBlob(m_connector->hdrMetadata, createHdrMetadata(m_pending.transferFunction)); + } else if (m_pending.transferFunction != NamedTransferFunction::sRGB) { + return false; } - if (m_connector->colorspace.isValid()) { - commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::Default); + 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_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) { commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None); @@ -291,6 +297,7 @@ void DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit) commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied); } } + return true; } uint32_t DrmPipeline::calculateUnderscan() @@ -571,6 +578,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; +} + void DrmPipeline::setCrtc(DrmCrtc *crtc) { if (crtc && m_pending.crtc && crtc->gammaRampSize() != m_pending.crtc->gammaRampSize() && m_pending.colorTransformation) { @@ -660,4 +677,62 @@ void DrmPipeline::setContentType(DrmConnector::DrmContentType type) { m_pending.contentType = type; } + +void DrmPipeline::setColorimetry(NamedColorimetry name) +{ + m_pending.colorimetry = name; +} + +void DrmPipeline::setNamedTransferFunction(NamedTransferFunction tf) +{ + m_pending.transferFunction = tf; +} + +std::shared_ptr DrmPipeline::createHdrMetadata(NamedTransferFunction transferFunction) const +{ + if (transferFunction != NamedTransferFunction::PerceptualQuantizer) { + // for sRGB / gamma 2.2, don't send any metadata, to ensure the non-HDR experience stays the same + return nullptr; + } + if (!m_connector->edid() || !m_connector->edid()->hdrMetadata()) { + return nullptr; + } + const auto metadata = *m_connector->edid()->hdrMetadata(); + if (!metadata.supportsPQ) { + return nullptr; + } + const auto colorimetry = m_connector->edid()->colorimetry(); + const auto to16Bit = [](float value) { + return uint16_t(std::round(value / 0.00002)); + }; + hdr_output_metadata data{ + .metadata_type = 0, + .hdmi_metadata_type1 = hdr_metadata_infoframe{ + // eotf types (from CTA-861-G page 85): + // - 0: traditional gamma, SDR + // - 1: traditional gamma, HDR + // - 2: SMPTE ST2084 + // - 3: hybrid Log-Gamma based on BT.2100-0 + // - 4-7: reserved + .eotf = uint8_t(2), + // there's only one type. 1-7 are reserved for future use + .metadata_type = 0, + // in 0.00002 nits + .display_primaries = { + {to16Bit(colorimetry.redPrimary.x()), to16Bit(colorimetry.redPrimary.y())}, + {to16Bit(colorimetry.greenPrimary.x()), to16Bit(colorimetry.greenPrimary.y())}, + {to16Bit(colorimetry.bluePrimary.x()), to16Bit(colorimetry.bluePrimary.y())}, + }, + .white_point = {to16Bit(colorimetry.whitePoint.x()), to16Bit(colorimetry.whitePoint.y())}, + // in nits + .max_display_mastering_luminance = uint16_t(std::round(metadata.desiredContentMaxLuminance)), + // in 0.0001 nits + .min_display_mastering_luminance = uint16_t(std::round(metadata.desiredContentMinLuminance * 10000)), + // in nits + .max_cll = uint16_t(std::round(metadata.desiredContentMaxLuminance)), + .max_fall = uint16_t(std::round(metadata.desiredMaxFrameAverageLuminance)), + }, + }; + return DrmBlob::create(gpu(), &data, sizeof(data)); +} } diff --git a/src/backends/drm/drm_pipeline.h b/src/backends/drm/drm_pipeline.h index 7a2db9523f..c438d6523e 100644 --- a/src/backends/drm/drm_pipeline.h +++ b/src/backends/drm/drm_pipeline.h @@ -22,6 +22,7 @@ #include "drm_blob.h" #include "drm_connector.h" #include "drm_plane.h" +#include "libkwineffects/colorspace.h" namespace KWin { @@ -107,6 +108,8 @@ public: uint32_t overscan() const; Output::RgbRange rgbRange() const; DrmConnector::DrmContentType contentType() const; + NamedColorimetry colorimetry() const; + NamedTransferFunction transferFunction() const; void setCrtc(DrmCrtc *crtc); void setMode(const std::shared_ptr &mode); @@ -120,6 +123,8 @@ public: void setGammaRamp(const std::shared_ptr &transformation); void setCTM(const QMatrix3x3 &ctm); void setContentType(DrmConnector::DrmContentType type); + void setColorimetry(NamedColorimetry name); + void setNamedTransferFunction(NamedTransferFunction tf); enum class CommitMode { Test, @@ -134,6 +139,7 @@ private: bool isBufferForDirectScanout() const; uint32_t calculateUnderscan(); static Error errnoToError(); + std::shared_ptr createHdrMetadata(NamedTransferFunction transferFunction) const; // legacy only Error presentLegacy(); @@ -146,7 +152,7 @@ private: // atomic modesetting only void atomicCommitSuccessful(); void atomicModesetSuccessful(); - void prepareAtomicModeset(DrmAtomicCommit *commit); + bool prepareAtomicModeset(DrmAtomicCommit *commit); bool prepareAtomicPresentation(DrmAtomicCommit *commit); void prepareAtomicDisable(DrmAtomicCommit *commit); static Error commitPipelinesAtomic(const QVector &pipelines, CommitMode mode, const QVector &unusedObjects); @@ -172,6 +178,8 @@ private: std::shared_ptr gamma; std::shared_ptr ctm; DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics; + NamedColorimetry colorimetry = NamedColorimetry::BT709; + NamedTransferFunction transferFunction = NamedTransferFunction::sRGB; std::shared_ptr layer; std::shared_ptr cursorLayer; diff --git a/src/colors/colordevice.cpp b/src/colors/colordevice.cpp index 13ceb0b50a..d6aaf2ac34 100644 --- a/src/colors/colordevice.cpp +++ b/src/colors/colordevice.cpp @@ -319,11 +319,7 @@ void ColorDevice::update() { d->rebuildPipeline(); if (!d->output->setGammaRamp(d->transformation)) { - QMatrix3x3 ctm; - ctm(0, 0) = d->simpleTransformation.x(); - ctm(1, 1) = d->simpleTransformation.y(); - ctm(2, 2) = d->simpleTransformation.z(); - d->output->setCTM(ctm); + d->output->setChannelFactors(d->simpleTransformation); } } diff --git a/src/core/output.cpp b/src/core/output.cpp index 6151d66156..b4a4ce0ec0 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -297,6 +297,15 @@ void Output::setState(const State &state) if (oldState.rgbRange != state.rgbRange) { Q_EMIT rgbRangeChanged(); } + if (oldState.highDynamicRange != state.highDynamicRange) { + Q_EMIT highDynamicRangeChanged(); + } + if (oldState.sdrBrightness != state.sdrBrightness) { + Q_EMIT sdrBrightnessChanged(); + } + if (oldState.wideColorGamut != state.wideColorGamut) { + Q_EMIT wideColorGamutChanged(); + } if (oldState.enabled != state.enabled) { Q_EMIT enabledChanged(); } @@ -400,12 +409,12 @@ Output::RgbRange Output::rgbRange() const return m_state.rgbRange; } -bool Output::setGammaRamp(const std::shared_ptr &transformation) +bool Output::setChannelFactors(const QVector3D &rgb) { return false; } -bool Output::setCTM(const QMatrix3x3 &ctm) +bool Output::setGammaRamp(const std::shared_ptr &transformation) { return false; } @@ -425,6 +434,21 @@ Output::Transform Output::panelOrientation() const return m_information.panelOrientation; } +bool Output::wideColorGamut() const +{ + return m_state.wideColorGamut; +} + +bool Output::highDynamicRange() const +{ + return m_state.highDynamicRange; +} + +uint32_t Output::sdrBrightness() const +{ + return m_state.sdrBrightness; +} + bool Output::setCursor(CursorSource *source) { return false; diff --git a/src/core/output.h b/src/core/output.h index 0e0ebd6a3b..ebdcad3578 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -76,10 +76,12 @@ public: Q_ENUM(DpmsMode) enum class Capability : uint { - Dpms = 0x1, - Overscan = 0x2, - Vrr = 0x4, - RgbRange = 0x8, + Dpms = 1, + Overscan = 1 << 1, + Vrr = 1 << 2, + RgbRange = 1 << 3, + HighDynamicRange = 1 << 4, + WideColorGamut = 1 << 5, }; Q_DECLARE_FLAGS(Capabilities, Capability) @@ -254,9 +256,12 @@ public: bool isPlaceholder() const; bool isNonDesktop() const; Transform panelOrientation() const; + bool wideColorGamut() const; + bool highDynamicRange() const; + uint32_t sdrBrightness() const; virtual bool setGammaRamp(const std::shared_ptr &transformation); - virtual bool setCTM(const QMatrix3x3 &ctm); + virtual bool setChannelFactors(const QVector3D &rgb); virtual bool setCursor(CursorSource *source); virtual bool moveCursor(const QPointF &position); @@ -314,6 +319,9 @@ Q_SIGNALS: void overscanChanged(); void vrrPolicyChanged(); void rgbRangeChanged(); + void wideColorGamutChanged(); + void sdrBrightnessChanged(); + void highDynamicRangeChanged(); protected: struct Information @@ -345,6 +353,9 @@ protected: bool enabled = false; uint32_t overscan = 0; RgbRange rgbRange = RgbRange::Automatic; + bool wideColorGamut = false; + bool highDynamicRange = false; + uint32_t sdrBrightness = 200; }; void setInformation(const Information &information); diff --git a/src/core/outputconfiguration.h b/src/core/outputconfiguration.h index 34584257f3..ab3214a60f 100644 --- a/src/core/outputconfiguration.h +++ b/src/core/outputconfiguration.h @@ -29,6 +29,9 @@ public: std::optional overscan; std::optional rgbRange; std::optional vrrPolicy; + std::optional highDynamicRange; + std::optional sdrBrightness; + std::optional wideColorGamut; }; class KWIN_EXPORT OutputConfiguration diff --git a/src/effects.cpp b/src/effects.cpp index 02471892b3..109fecdfa9 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -1627,7 +1627,7 @@ void EffectsHandlerImpl::renderOffscreenQuickView(const RenderTarget &renderTarg return; } - ShaderTraits traits = ShaderTrait::MapTexture; + ShaderTraits traits = ShaderTrait::MapTexture | ShaderTrait::TransformColorspace; const qreal a = w->opacity(); if (a != 1.0) { traits |= ShaderTrait::Modulate; @@ -1643,6 +1643,7 @@ void EffectsHandlerImpl::renderOffscreenQuickView(const RenderTarget &renderTarg if (a != 1.0) { shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/libkwineffects/CMakeLists.txt b/src/libkwineffects/CMakeLists.txt index 7c387783d7..aa011f2011 100644 --- a/src/libkwineffects/CMakeLists.txt +++ b/src/libkwineffects/CMakeLists.txt @@ -17,8 +17,6 @@ set(kwin_EFFECTSLIB_SRCS kwinoffscreenquickview.cpp kwinquickeffect.cpp logging.cpp - rendertarget.cpp - renderviewport.cpp ) add_library(kwineffects SHARED ${kwin_EFFECTSLIB_SRCS}) @@ -45,6 +43,7 @@ install(TARGETS kwineffects EXPORT KWinEffectsTargets ${KDE_INSTALL_TARGETS_DEFA # kwingl(es)utils library set(kwin_GLUTILSLIB_SRCS + colorspace.cpp kwineglimagetexture.cpp kwinglplatform.cpp kwingltexture.cpp @@ -71,6 +70,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kwinconfig.h ${CMAKE_CURRENT_BINARY_DIR}/kwineffects_export.h ${CMAKE_CURRENT_BINARY_DIR}/kwinglutils_export.h + colorspace.h kwinanimationeffect.h kwineffects.h kwinglobals.h @@ -81,6 +81,8 @@ install(FILES kwinoffscreeneffect.h kwinoffscreenquickview.h kwinquickeffect.h + rendertarget.h + renderviewport.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kwin/libkwineffects COMPONENT Devel) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KWinEffects") diff --git a/src/libkwineffects/colorspace.cpp b/src/libkwineffects/colorspace.cpp new file mode 100644 index 0000000000..7cb5d0d568 --- /dev/null +++ b/src/libkwineffects/colorspace.cpp @@ -0,0 +1,129 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "colorspace.h" + +#include + +namespace KWin +{ + +static QMatrix3x3 inverse(const QMatrix3x3 &m) +{ + const double determinant = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) - m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0)) + m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0)); + QMatrix3x3 ret; + ret(0, 0) = (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) / determinant; + ret(0, 1) = (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2)) / determinant; + ret(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) / determinant; + ret(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) / determinant; + ret(1, 1) = (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0)) / determinant; + ret(1, 2) = (m(1, 0) * m(0, 2) - m(0, 0) * m(1, 2)) / determinant; + ret(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) / determinant; + ret(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) / determinant; + ret(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) / determinant; + return ret; +} + +static QMatrix3x3 matrixFromColumns(const QVector3D &first, const QVector3D &second, const QVector3D &third) +{ + QMatrix3x3 ret; + ret(0, 0) = first.x(); + ret(1, 0) = first.y(); + ret(2, 0) = first.z(); + ret(0, 1) = second.x(); + ret(1, 1) = second.y(); + ret(2, 1) = second.z(); + ret(0, 2) = third.x(); + ret(1, 2) = third.y(); + ret(2, 2) = third.z(); + return ret; +} + +static QVector3D operator*(const QMatrix3x3 &mat, const QVector3D &v) +{ + return QVector3D( + mat(0, 0) * v.x() + mat(0, 1) * v.y() + mat(0, 2) * v.z(), + mat(1, 0) * v.x() + mat(1, 1) * v.y() + mat(1, 2) * v.z(), + mat(2, 0) * v.x() + mat(2, 1) * v.y() + mat(2, 2) * v.z()); +} + +static QVector3D xyToXYZ(QVector2D xy) +{ + return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y()); +} + +QMatrix3x3 Colorimetry::toXYZ() const +{ + const auto r_xyz = xyToXYZ(red); + const auto g_xyz = xyToXYZ(blue); + const auto b_xyz = xyToXYZ(green); + const auto w_xyz = xyToXYZ(white); + const auto component_scale = inverse(matrixFromColumns(r_xyz, g_xyz, b_xyz)) * w_xyz; + return matrixFromColumns(r_xyz * component_scale.x(), g_xyz * component_scale.y(), b_xyz * component_scale.z()); +} + +QMatrix3x3 Colorimetry::toOther(const Colorimetry &other) const +{ + return toXYZ() * inverse(other.toXYZ()); +} + +bool Colorimetry::operator==(const Colorimetry &other) const +{ + return (name || other.name) ? (name == other.name) + : (red == other.red && green == other.green && blue == other.blue && white == other.white); +} + +constexpr Colorimetry Colorimetry::createFromName(NamedColorimetry name) +{ + switch (name) { + case NamedColorimetry::BT709: + return Colorimetry{ + .red = {0.64, 0.33}, + .green = {0.30, 0.60}, + .blue = {0.15, 0.06}, + .white = {0.3127, 0.3290}, + .name = name, + }; + case NamedColorimetry::BT2020: + return Colorimetry{ + .red = {0.708, 0.292}, + .green = {0.170, 0.797}, + .blue = {0.131, 0.046}, + .white = {0.3127, 0.3290}, + .name = name, + }; + } + Q_UNREACHABLE(); +} + +const Colorspace Colorspace::sRGB(Colorimetry::createFromName(NamedColorimetry::BT709), NamedTransferFunction::sRGB); + +Colorspace::Colorspace(NamedColorimetry colorimetry, NamedTransferFunction tf) + : m_colorimetry(Colorimetry::createFromName(colorimetry)) + , m_transferFunction(tf) +{ +} + +Colorspace::Colorspace(const Colorimetry &colorimetry, NamedTransferFunction tf) + : m_colorimetry(colorimetry) + , m_transferFunction(tf) +{ +} + +const Colorimetry &Colorspace::colorimetry() const +{ + return m_colorimetry; +} + +NamedTransferFunction Colorspace::transferFunction() const +{ + return m_transferFunction; +} + +bool Colorspace::operator==(const Colorspace &other) const +{ + return m_colorimetry == other.m_colorimetry && m_transferFunction == other.m_transferFunction; +} +} diff --git a/src/libkwineffects/colorspace.h b/src/libkwineffects/colorspace.h new file mode 100644 index 0000000000..0e1e313223 --- /dev/null +++ b/src/libkwineffects/colorspace.h @@ -0,0 +1,73 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once +#include + +#include +#include + +#include "libkwineffects/kwineffects_export.h" + +namespace KWin +{ + +enum class NamedColorimetry { + BT709, + BT2020, +}; + +/** + * Describes the definition of colors in a color space. + * Red, green and blue define the chromaticities ("absolute colors") of the red, green and blue LEDs on a display in xy coordinates + * White defines the the chromaticity of the reference white in xy coordinates + */ +class KWINEFFECTS_EXPORT Colorimetry +{ +public: + static constexpr Colorimetry createFromName(NamedColorimetry name); + + QMatrix3x3 toXYZ() const; + QMatrix3x3 toOther(const Colorimetry &colorimetry) const; + bool operator==(const Colorimetry &other) const; + + QVector2D red; + QVector2D green; + QVector2D blue; + QVector2D white; + std::optional name; +}; + +/** + * Describes an EOTF, that is, how encoded brightness values are converted to light + */ +enum class NamedTransferFunction { + sRGB = 0, + linear = 1, + PerceptualQuantizer = 2, +}; + +/** + * Describes the meaning of encoded color values + */ +class KWINEFFECTS_EXPORT Colorspace +{ +public: + explicit Colorspace(NamedColorimetry colorimetry, NamedTransferFunction tf); + explicit Colorspace(const Colorimetry &colorimety, NamedTransferFunction tf); + + bool operator==(const Colorspace &other) const; + + const Colorimetry &colorimetry() const; + NamedTransferFunction transferFunction() const; + + static const Colorspace sRGB; + +private: + Colorimetry m_colorimetry; + NamedTransferFunction m_transferFunction; +}; + +} diff --git a/src/libkwineffects/kwinglutils.cpp b/src/libkwineffects/kwinglutils.cpp index 7e2a30aeb6..09979585ab 100644 --- a/src/libkwineffects/kwinglutils.cpp +++ b/src/libkwineffects/kwinglutils.cpp @@ -426,6 +426,7 @@ void GLShader::resolveLocations() mMatrixLocation[ModelViewProjectionMatrix] = uniformLocation("modelViewProjectionMatrix"); mMatrixLocation[WindowTransformation] = uniformLocation("windowTransformation"); mMatrixLocation[ScreenTransformation] = uniformLocation("screenTransformation"); + mMatrixLocation[ColorimetryTransformation] = uniformLocation("colorimetryTransform"); mVec2Location[Offset] = uniformLocation("offset"); @@ -437,6 +438,9 @@ void GLShader::resolveLocations() mIntLocation[TextureWidth] = uniformLocation("textureWidth"); mIntLocation[TextureHeight] = uniformLocation("textureHeight"); + mIntLocation[SourceNamedTransferFunction] = uniformLocation("sourceNamedTransferFunction"); + mIntLocation[DestinationNamedTransferFunction] = uniformLocation("destinationNamedTransferFunction"); + mIntLocation[SdrBrightness] = uniformLocation("sdrBrightness"); mLocationsResolved = true; } @@ -447,6 +451,12 @@ int GLShader::uniformLocation(const char *name) return location; } +bool GLShader::setUniform(MatrixUniform uniform, const QMatrix3x3 &value) +{ + resolveLocations(); + return setUniform(mMatrixLocation[uniform], value); +} + bool GLShader::setUniform(GLShader::MatrixUniform uniform, const QMatrix4x4 &matrix) { resolveLocations(); @@ -571,6 +581,14 @@ bool GLShader::setUniform(int location, const QVector4D &value) return (location >= 0); } +bool GLShader::setUniform(int location, const QMatrix3x3 &value) +{ + if (location >= 0) { + glUniformMatrix3fv(location, 1, GL_FALSE, value.constData()); + } + return location >= 0; +} + bool GLShader::setUniform(int location, const QMatrix4x4 &value) { if (location >= 0) { @@ -619,6 +637,19 @@ QMatrix4x4 GLShader::getUniformMatrix4x4(const char *name) } } +bool GLShader::setColorspaceUniforms(const Colorspace &src, const Colorspace &dst) +{ + return setUniform(GLShader::MatrixUniform::ColorimetryTransformation, src.colorimetry().toOther(dst.colorimetry())) + && setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(src.transferFunction())) + && setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(dst.transferFunction())); +} + +bool GLShader::setColorspaceUniforms(const Colorspace &src, const RenderTarget &renderTarget) +{ + return setColorspaceUniforms(src, renderTarget.colorspace()) + && setUniform(IntUniform::SdrBrightness, renderTarget.sdrBrightness()); +} + //**************************************** // ShaderManager //**************************************** @@ -749,6 +780,38 @@ QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const } else if (traits & ShaderTrait::UniformColor) { stream << "uniform vec4 geometryColor;\n"; } + if (traits & ShaderTrait::TransformColorspace) { + stream << "uniform mat3 colorimetryTransform;\n"; + stream << "uniform int sourceNamedTransferFunction;\n"; + stream << "uniform int destinationNamedTransferFunction;\n"; + stream << "uniform int sdrBrightness;// in nits\n"; + stream << "\n"; + stream << "vec3 nitsToPq(vec3 nits) {\n"; + stream << " vec3 normalized = clamp(nits / 10000.0, vec3(0), vec3(1));\n"; + stream << " float c1 = 0.8359375;\n"; + stream << " float c2 = 18.8515625;\n"; + stream << " float c3 = 18.6875;\n"; + stream << " float m1 = 0.1593017578125;\n"; + stream << " float m2 = 78.84375;\n"; + stream << " vec3 num = vec3(c1) + c2 * pow(normalized, vec3(m1));\n"; + stream << " vec3 denum = vec3(1.0) + c3 * pow(normalized, vec3(m1));\n"; + stream << " return pow(num / denum, vec3(m2));\n"; + stream << "}\n"; + stream << "vec3 srgbToLinear(vec3 color) {\n"; + stream << " bvec3 isLow = lessThanEqual(color, vec3(0.04045f));\n"; + stream << " vec3 loPart = color / 12.92f;\n"; + stream << " vec3 hiPart = pow((color + 0.055f) / 1.055f, vec3(12.0f / 5.0f));\n"; + stream << " return mix(hiPart, loPart, isLow);\n"; + stream << "}\n"; + stream << "\n"; + stream << "vec3 linearToSrgb(vec3 color) {\n"; + stream << " bvec3 isLow = lessThanEqual(color, vec3(0.0031308f));\n"; + stream << " vec3 loPart = color * 12.92f;\n"; + stream << " vec3 hiPart = pow(color, vec3(5.0f / 12.0f)) * 1.055f - 0.055f;\n"; + stream << " return mix(hiPart, loPart, isLow);\n"; + stream << "}\n"; + stream << "\n"; + } if (output != QByteArrayLiteral("gl_FragColor")) { stream << "\nout vec4 " << output << ";\n"; @@ -774,6 +837,20 @@ QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const } else if (traits & ShaderTrait::UniformColor) { stream << " " << output << " = geometryColor;\n"; } + if (traits & ShaderTrait::TransformColorspace) { + // simple sRGB -> linear + stream << "if (sourceNamedTransferFunction == 0) {\n"; + stream << " " << output << ".rgb = sdrBrightness * srgbToLinear(" << output << ".rgb);\n"; + stream << "}\n"; + stream << " " << output << ".rgb = colorimetryTransform * " << output << ".rgb;\n"; + // nits -> simple sRGB + stream << "if (destinationNamedTransferFunction == 0) {\n"; + stream << " " << output << ".rgb = linearToSrgb(" << output << ".rgb / sdrBrightness);\n"; + // nits -> PQ + stream << "} else if (destinationNamedTransferFunction == 2) {\n"; + stream << " " << output << ".rgb = nitsToPq(" << output << ".rgb);\n"; + stream << "}\n"; + } stream << "}"; stream.flush(); diff --git a/src/libkwineffects/kwinglutils.h b/src/libkwineffects/kwinglutils.h index 9e43446408..d5f8f8a2c2 100644 --- a/src/libkwineffects/kwinglutils.h +++ b/src/libkwineffects/kwinglutils.h @@ -10,6 +10,7 @@ #pragma once // kwin +#include "libkwineffects/colorspace.h" #include "libkwineffects/kwingltexture.h" #include "libkwineffects/kwinglutils_export.h" #include "libkwineffects/kwinglutils_funcs.h" @@ -90,6 +91,7 @@ public: bool setUniform(int location, const QVector2D &value); bool setUniform(int location, const QVector3D &value); bool setUniform(int location, const QVector4D &value); + bool setUniform(int location, const QMatrix3x3 &value); bool setUniform(int location, const QMatrix4x4 &value); bool setUniform(int location, const QColor &value); @@ -108,6 +110,7 @@ public: ModelViewProjectionMatrix, WindowTransformation, ScreenTransformation, + ColorimetryTransformation, MatrixCount }; @@ -130,6 +133,9 @@ public: AlphaToOne, ///< @deprecated no longer used TextureWidth, TextureHeight, + SourceNamedTransferFunction, + DestinationNamedTransferFunction, + SdrBrightness, IntUniformCount }; @@ -138,6 +144,7 @@ public: ColorUniformCount }; + bool setUniform(MatrixUniform uniform, const QMatrix3x3 &value); bool setUniform(MatrixUniform uniform, const QMatrix4x4 &matrix); bool setUniform(Vec2Uniform uniform, const QVector2D &value); bool setUniform(Vec4Uniform uniform, const QVector4D &value); @@ -146,6 +153,9 @@ public: bool setUniform(ColorUniform uniform, const QVector4D &value); bool setUniform(ColorUniform uniform, const QColor &value); + bool setColorspaceUniforms(const Colorspace &src, const Colorspace &dst); + bool setColorspaceUniforms(const Colorspace &src, const RenderTarget &renderTarget); + protected: GLShader(unsigned int flags = NoFlags); bool loadFromFiles(const QString &vertexfile, const QString &fragmentfile); @@ -176,6 +186,7 @@ enum class ShaderTrait { UniformColor = (1 << 1), Modulate = (1 << 2), AdjustSaturation = (1 << 3), + TransformColorspace = (1 << 4), }; Q_DECLARE_FLAGS(ShaderTraits, ShaderTrait) diff --git a/src/libkwineffects/kwinoffscreeneffect.cpp b/src/libkwineffects/kwinoffscreeneffect.cpp index a54ed6625a..399538861f 100644 --- a/src/libkwineffects/kwinoffscreeneffect.cpp +++ b/src/libkwineffects/kwinoffscreeneffect.cpp @@ -150,8 +150,9 @@ void OffscreenData::setVertexSnappingMode(RenderGeometry::VertexSnappingMode mod void OffscreenData::paint(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *window, const QRegion ®ion, const WindowPaintData &data, const WindowQuadList &quads) { - GLShader *shader = m_shader ? m_shader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); + GLShader *shader = m_shader ? m_shader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation | ShaderTrait::TransformColorspace); ShaderBinder binder(shader); + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); const double scale = viewport.scale(); diff --git a/src/libkwineffects/rendertarget.cpp b/src/libkwineffects/rendertarget.cpp index 439d215f98..67949fe507 100644 --- a/src/libkwineffects/rendertarget.cpp +++ b/src/libkwineffects/rendertarget.cpp @@ -10,14 +10,18 @@ namespace KWin { -RenderTarget::RenderTarget(GLFramebuffer *fbo) +RenderTarget::RenderTarget(GLFramebuffer *fbo, const Colorspace &colorspace, uint32_t brightness) : m_framebuffer(fbo) , m_transformation(fbo->colorAttachment() ? fbo->colorAttachment()->contentTransformMatrix() : QMatrix4x4()) + , m_colorspace(colorspace) + , m_sdrBrightness(brightness) { } -RenderTarget::RenderTarget(QImage *image) +RenderTarget::RenderTarget(QImage *image, const Colorspace &colorspace, uint32_t brightness) : m_image(image) + , m_colorspace(colorspace) + , m_sdrBrightness(brightness) { } @@ -67,4 +71,14 @@ QImage *RenderTarget::image() const return m_image; } +const Colorspace &RenderTarget::colorspace() const +{ + return m_colorspace; +} + +uint32_t RenderTarget::sdrBrightness() const +{ + return m_sdrBrightness; +} + } // namespace KWin diff --git a/src/libkwineffects/rendertarget.h b/src/libkwineffects/rendertarget.h index a943ca70bc..cf49e527dc 100644 --- a/src/libkwineffects/rendertarget.h +++ b/src/libkwineffects/rendertarget.h @@ -6,6 +6,7 @@ #pragma once +#include "libkwineffects/colorspace.h" #include "libkwineffects/kwinglutils_export.h" #include @@ -21,11 +22,13 @@ class GLTexture; class KWINGLUTILS_EXPORT RenderTarget { public: - explicit RenderTarget(GLFramebuffer *fbo); - explicit RenderTarget(QImage *image); + explicit RenderTarget(GLFramebuffer *fbo, const Colorspace &colorspace = Colorspace::sRGB, uint32_t sdrBrightness = 200); + explicit RenderTarget(QImage *image, const Colorspace &colorspace = Colorspace::sRGB, uint32_t sdrBrightness = 200); QSize size() const; QMatrix4x4 transformation() const; + const Colorspace &colorspace() const; + uint32_t sdrBrightness() const; QRectF applyTransformation(const QRectF &rect, const QRectF &viewport) const; QRect applyTransformation(const QRect &rect, const QRect &viewport) const; @@ -37,6 +40,8 @@ private: QImage *m_image = nullptr; GLFramebuffer *m_framebuffer = nullptr; QMatrix4x4 m_transformation; + const Colorspace m_colorspace; + const uint32_t m_sdrBrightness; }; } // namespace KWin diff --git a/src/platformsupport/scenes/opengl/eglcontext.cpp b/src/platformsupport/scenes/opengl/eglcontext.cpp index 29764c6656..2176e8d85e 100644 --- a/src/platformsupport/scenes/opengl/eglcontext.cpp +++ b/src/platformsupport/scenes/opengl/eglcontext.cpp @@ -186,11 +186,29 @@ bool EglContext::isValid() const return EGL_NO_CONTEXT; } +static GLint glFormatForDrmFormat(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_ARGB16161616: + return GL_RGBA16; + case DRM_FORMAT_ARGB16161616F: + return GL_RGBA16F; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB2101010: + return GL_RGB10_A2; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + default: + return GL_RGBA8; + } +}; + std::shared_ptr EglContext::importDmaBufAsTexture(const DmaBufAttributes &attributes) const { EGLImageKHR image = m_display->importDmaBufAsImage(attributes); if (image != EGL_NO_IMAGE_KHR) { - return std::make_shared(m_display->handle(), image, GL_RGBA8, QSize(attributes.width, attributes.height)); + return std::make_shared(m_display->handle(), image, glFormatForDrmFormat(attributes.format), QSize(attributes.width, attributes.height)); } else { qCWarning(KWIN_OPENGL) << "Failed to record frame: Error creating EGLImageKHR - " << getEglErrorString(); return nullptr; diff --git a/src/plugins/blur/blur.cpp b/src/plugins/blur/blur.cpp index c5679db77e..5ea1a48d39 100644 --- a/src/plugins/blur/blur.cpp +++ b/src/plugins/blur/blur.cpp @@ -58,12 +58,11 @@ BlurEffect::BlurEffect() connect(effects, &EffectsHandler::virtualScreenGeometryChanged, this, [this]() { screenGeometryChanged(nullptr); }); - updateTexture(nullptr); } // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. - if (m_shader && m_shader->isValid() && !m_screenData.empty()) { + if (m_shader && m_shader->isValid()) { if (effects->xcbConnection()) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); } @@ -88,7 +87,7 @@ BlurEffect::BlurEffect() connect(effects, &EffectsHandler::windowDecorationChanged, this, &BlurEffect::setupDecorationConnections); connect(effects, &EffectsHandler::propertyNotify, this, &BlurEffect::slotPropertyNotify); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() { - if (m_shader && m_shader->isValid() && !m_screenData.empty()) { + if (m_shader && m_shader->isValid()) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); } }); @@ -113,8 +112,6 @@ void BlurEffect::screenAdded(EffectScreen *screen) connect(screen, &EffectScreen::geometryChanged, this, [this, screen]() { screenGeometryChanged(screen); }); - effects->makeOpenGLContextCurrent(); - updateTexture(screen); } void BlurEffect::screenRemoved(EffectScreen *screen) @@ -126,19 +123,54 @@ void BlurEffect::screenRemoved(EffectScreen *screen) void BlurEffect::screenGeometryChanged(EffectScreen *screen) { - effects->makeOpenGLContextCurrent(); - updateTexture(screen); - // Fetch the blur regions for all windows const auto stackingOrder = effects->stackingOrder(); for (EffectWindow *window : stackingOrder) { updateBlurRegion(window); } - effects->doneOpenGLContextCurrent(); } -void BlurEffect::updateTexture(EffectScreen *screen) +bool BlurEffect::updateTexture(EffectScreen *screen, const RenderTarget &renderTarget) { + GLenum textureFormat = GL_RGBA8; + if (renderTarget.colorspace() == Colorspace::sRGB) { + if (!GLPlatform::instance()->isGLES()) { + GLuint prevFbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast(&prevFbo)); + + if (prevFbo != 0) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } + + GLenum colorEncoding = GL_LINEAR; + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_BACK_LEFT, + GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, + reinterpret_cast(&colorEncoding)); + + if (prevFbo != 0) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevFbo); + } + + if (colorEncoding == GL_SRGB) { + textureFormat = GL_SRGB8_ALPHA8; + } + } + } else { + textureFormat = GL_RGBA16F; + } + + // Note that we currently render the entire blur effect in logical + // coordinates - this means that when using high DPI screens the underlying + // texture will be low DPI. This isn't really visible since we're blurring + // anyway. + const auto screenSize = screen ? screen->geometry().size() : effects->virtualScreenSize(); + + if (auto it = m_screenData.find(screen); it != m_screenData.end()) { + const auto &texture = it->second.renderTargetTextures.front(); + if (texture->internalFormat() == textureFormat && texture->size() == screenSize) { + return true; + } + } ScreenData data; /* Reserve memory for: * - The original sized texture (1) @@ -148,36 +180,6 @@ void BlurEffect::updateTexture(EffectScreen *screen) data.renderTargets.reserve(m_downSampleIterations + 2); data.renderTargetTextures.reserve(m_downSampleIterations + 2); - GLenum textureFormat = GL_RGBA8; - - // Check the color encoding of the default framebuffer - if (!GLPlatform::instance()->isGLES()) { - GLuint prevFbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast(&prevFbo)); - - if (prevFbo != 0) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - GLenum colorEncoding = GL_LINEAR; - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_BACK_LEFT, - GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, - reinterpret_cast(&colorEncoding)); - - if (prevFbo != 0) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevFbo); - } - - if (colorEncoding == GL_SRGB) { - textureFormat = GL_SRGB8_ALPHA8; - } - } - - // Note that we currently render the entire blur effect in logical - // coordinates - this means that when using high DPI screens the underlying - // texture will be low DPI. This isn't really visible since we're blurring - // anyway. - const auto screenSize = screen ? screen->geometry().size() : effects->virtualScreenSize(); for (int i = 0; i <= m_downSampleIterations; i++) { data.renderTargetTextures.push_back(std::make_unique(textureFormat, screenSize / (1 << i))); data.renderTargetTextures.back()->setFilter(GL_LINEAR); @@ -197,7 +199,7 @@ void BlurEffect::updateTexture(EffectScreen *screen) return fbo->valid(); }); if (!renderTargetsValid) { - return; + return false; } // Prepare the stack for the rendering @@ -217,6 +219,7 @@ void BlurEffect::updateTexture(EffectScreen *screen) data.renderTargetStack.push(data.renderTargets.front().get()); m_screenData[screen] = std::move(data); + return true; } void BlurEffect::initBlurStrengthValues() @@ -291,14 +294,6 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) // Invalidate noise texture m_noiseTexture.reset(); - if (effects->waylandDisplay()) { - for (const auto screen : effects->screens()) { - updateTexture(screen); - } - } else { - updateTexture(nullptr); - } - // Update all windows for the blur to take effect effects->addRepaintFull(); } @@ -609,7 +604,7 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const { - if (m_screenData.empty() || !m_shader || !m_shader->isValid()) { + if (!m_shader || !m_shader->isValid()) { return false; } @@ -634,6 +629,9 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) { if (shouldBlur(w, mask, data)) { + if (!updateTexture(m_currentScreen, renderTarget)) { + return; + } const QRect screen = viewport.renderRect().toRect(); QRegion shape = blurRegion(w).translated(w->pos().toPoint()); diff --git a/src/plugins/blur/blur.h b/src/plugins/blur/blur.h index dba82186ed..50adf9f1be 100644 --- a/src/plugins/blur/blur.h +++ b/src/plugins/blur/blur.h @@ -77,7 +77,7 @@ private: QRect expand(const QRect &rect) const; QRegion expand(const QRegion ®ion) const; void initBlurStrengthValues(); - void updateTexture(EffectScreen *screen); + bool updateTexture(EffectScreen *screen, const RenderTarget &renderTarget); QRegion blurRegion(const EffectWindow *w) const; QRegion decorationBlurRegion(const EffectWindow *w) const; bool decorationSupportsBlurBehind(const EffectWindow *w) const; diff --git a/src/plugins/mouseclick/mouseclick.cpp b/src/plugins/mouseclick/mouseclick.cpp index 48a72a3526..1c6c244358 100644 --- a/src/plugins/mouseclick/mouseclick.cpp +++ b/src/plugins/mouseclick/mouseclick.cpp @@ -11,6 +11,7 @@ // KConfigSkeleton #include "mouseclickconfig.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" #include @@ -101,7 +102,7 @@ void MouseClickEffect::paintScreen(const RenderTarget &renderTarget, const Rende effects->paintScreen(renderTarget, viewport, mask, region, screen); if (effects->isOpenGLCompositing()) { - paintScreenSetupGl(viewport.projectionMatrix()); + paintScreenSetupGl(renderTarget, viewport.projectionMatrix()); } for (const auto &click : m_clicks) { for (int i = 0; i < m_ringCount; ++i) { @@ -301,10 +302,11 @@ void MouseClickEffect::drawCircleQPainter(const QColor &color, float cx, float c painter->restore(); } -void MouseClickEffect::paintScreenSetupGl(const QMatrix4x4 &projectionMatrix) +void MouseClickEffect::paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix) { - GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor); + GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace); shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); glLineWidth(m_lineWidth); glEnable(GL_BLEND); diff --git a/src/plugins/mouseclick/mouseclick.h b/src/plugins/mouseclick/mouseclick.h index 4b6e50d6fa..6b0fd02ef8 100644 --- a/src/plugins/mouseclick/mouseclick.h +++ b/src/plugins/mouseclick/mouseclick.h @@ -135,7 +135,7 @@ private: void drawCircleGl(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r); void drawCircleQPainter(const QColor &color, float cx, float cy, float r); - void paintScreenSetupGl(const QMatrix4x4 &projectionMatrix); + void paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix); void paintScreenFinishGl(); QColor m_colors[BUTTON_COUNT]; diff --git a/src/plugins/mousemark/mousemark.cpp b/src/plugins/mousemark/mousemark.cpp index 2fc153dc2d..5339c9f851 100644 --- a/src/plugins/mousemark/mousemark.cpp +++ b/src/plugins/mousemark/mousemark.cpp @@ -15,6 +15,7 @@ #include "libkwineffects/kwinconfig.h" #include "libkwineffects/kwinglplatform.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" #include #include @@ -90,8 +91,9 @@ void MouseMarkEffect::paintScreen(const RenderTarget &renderTarget, const Render vbo->setUseColor(true); vbo->setColor(color); const auto scale = viewport.scale(); - ShaderBinder binder(ShaderTrait::UniformColor); + ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, viewport.projectionMatrix()); + binder.shader()->setColorspaceUniforms(Colorspace::sRGB, renderTarget); QVector verts; for (const Mark &mark : std::as_const(marks)) { verts.clear(); diff --git a/src/plugins/screenedge/screenedgeeffect.cpp b/src/plugins/screenedge/screenedgeeffect.cpp index 6d097b0d23..987f1bf4c9 100644 --- a/src/plugins/screenedge/screenedgeeffect.cpp +++ b/src/plugins/screenedge/screenedgeeffect.cpp @@ -10,6 +10,7 @@ // KWin #include "libkwineffects/kwingltexture.h" #include "libkwineffects/kwinglutils.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" // KDE #include @@ -80,7 +81,8 @@ void ScreenEdgeEffect::paintScreen(const RenderTarget &renderTarget, const Rende GLTexture *texture = glow->texture.get(); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate); + ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::TransformColorspace); + binder.shader()->setColorspaceUniforms(Colorspace::sRGB, renderTarget); const QVector4D constant(opacity, opacity, opacity, opacity); binder.shader()->setUniform(GLShader::ModulationConstant, constant); const auto scale = viewport.scale(); diff --git a/src/plugins/snaphelper/snaphelper.cpp b/src/plugins/snaphelper/snaphelper.cpp index 4b5783601e..d352f88b9d 100644 --- a/src/plugins/snaphelper/snaphelper.cpp +++ b/src/plugins/snaphelper/snaphelper.cpp @@ -11,6 +11,7 @@ #include "snaphelper.h" #include "libkwineffects/kwinglutils.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" #include @@ -105,8 +106,9 @@ void SnapHelperEffect::paintScreen(const RenderTarget &renderTarget, const Rende GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); - ShaderBinder binder(ShaderTrait::UniformColor); + ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, viewport.projectionMatrix()); + binder.shader()->setColorspaceUniforms(Colorspace::sRGB, renderTarget); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/plugins/startupfeedback/startupfeedback.cpp b/src/plugins/startupfeedback/startupfeedback.cpp index 0d567ab758..3af8b9bad4 100644 --- a/src/plugins/startupfeedback/startupfeedback.cpp +++ b/src/plugins/startupfeedback/startupfeedback.cpp @@ -213,17 +213,20 @@ void StartupFeedbackEffect::paintScreen(const RenderTarget &renderTarget, const } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GLShader *shader = nullptr; if (m_type == BlinkingFeedback && m_blinkingShader && m_blinkingShader->isValid()) { const QColor &blinkingColor = BLINKING_COLORS[FRAME_TO_BLINKING_COLOR[m_frame]]; ShaderManager::instance()->pushShader(m_blinkingShader.get()); + shader = m_blinkingShader.get(); m_blinkingShader->setUniform(GLShader::Color, blinkingColor); } else { - ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); } const auto scale = viewport.scale(); QMatrix4x4 mvp = viewport.projectionMatrix(); mvp.translate(m_currentGeometry.x() * scale, m_currentGeometry.y() * scale); - ShaderManager::instance()->getBoundShader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); + shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); texture->render(m_currentGeometry.size(), scale); ShaderManager::instance()->popShader(); glDisable(GL_BLEND); diff --git a/src/plugins/touchpoints/touchpoints.cpp b/src/plugins/touchpoints/touchpoints.cpp index 9b5c9e6687..d6d39c25f9 100644 --- a/src/plugins/touchpoints/touchpoints.cpp +++ b/src/plugins/touchpoints/touchpoints.cpp @@ -11,6 +11,7 @@ #include "touchpoints.h" #include "libkwineffects/kwinglutils.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" #include @@ -123,7 +124,7 @@ void TouchPointsEffect::paintScreen(const RenderTarget &renderTarget, const Rend effects->paintScreen(renderTarget, viewport, mask, region, screen); if (effects->isOpenGLCompositing()) { - paintScreenSetupGl(viewport.projectionMatrix()); + paintScreenSetupGl(renderTarget, viewport.projectionMatrix()); } for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) { for (int i = 0; i < m_ringCount; ++i) { @@ -227,10 +228,11 @@ void TouchPointsEffect::drawCircleQPainter(const QColor &color, float cx, float painter->restore(); } -void TouchPointsEffect::paintScreenSetupGl(const QMatrix4x4 &projectionMatrix) +void TouchPointsEffect::paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix) { GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor); shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); glLineWidth(m_lineWidth); glEnable(GL_BLEND); diff --git a/src/plugins/touchpoints/touchpoints.h b/src/plugins/touchpoints/touchpoints.h index d1eb7964fd..01df569f56 100644 --- a/src/plugins/touchpoints/touchpoints.h +++ b/src/plugins/touchpoints/touchpoints.h @@ -61,7 +61,7 @@ private: float computeRadius(int time, bool press, int ring); void drawCircleGl(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r); void drawCircleQPainter(const QColor &color, float cx, float cy, float r); - void paintScreenSetupGl(const QMatrix4x4 &projectionMatrix); + void paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix); void paintScreenFinishGl(); Qt::GlobalColor colorForId(quint32 id); diff --git a/src/plugins/trackmouse/trackmouse.cpp b/src/plugins/trackmouse/trackmouse.cpp index c78eb4650d..e81e3bb236 100644 --- a/src/plugins/trackmouse/trackmouse.cpp +++ b/src/plugins/trackmouse/trackmouse.cpp @@ -21,6 +21,7 @@ #include "libkwineffects/kwinconfig.h" #include "libkwineffects/kwinglutils.h" +#include "libkwineffects/rendertarget.h" #include "libkwineffects/renderviewport.h" #include @@ -103,11 +104,12 @@ void TrackMouseEffect::paintScreen(const RenderTarget &renderTarget, const Rende effects->paintScreen(renderTarget, viewport, mask, region, screen); // paint normal screen if (effects->isOpenGLCompositing() && m_texture[0] && m_texture[1]) { - ShaderBinder binder(ShaderTrait::MapTexture); + ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); GLShader *shader(binder.shader()); if (!shader) { return; } + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); QMatrix4x4 matrix(viewport.projectionMatrix()); diff --git a/src/plugins/zoom/zoom.cpp b/src/plugins/zoom/zoom.cpp index d732ef8f16..5fb6b5f2c2 100644 --- a/src/plugins/zoom/zoom.cpp +++ b/src/plugins/zoom/zoom.cpp @@ -255,7 +255,7 @@ void ZoomEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseco effects->prePaintScreen(data, presentTime); } -ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderViewport &viewport, EffectScreen *screen) +ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectScreen *screen) { const QRect rect = effects->waylandDisplay() ? screen->geometry() : effects->virtualScreenGeometry(); const qreal devicePixelRatio = effects->waylandDisplay() ? screen->devicePixelRatio() : 1; @@ -264,8 +264,9 @@ ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderViewport OffscreenData &data = m_offscreenData[effects->waylandDisplay() ? screen : nullptr]; data.viewport = rect; - if (!data.texture || data.texture->size() != nativeSize) { - data.texture.reset(new GLTexture(GL_RGBA8, nativeSize)); + const GLenum textureFormat = renderTarget.colorspace() == Colorspace::sRGB ? GL_RGBA8 : GL_RGBA16F; + if (!data.texture || data.texture->size() != nativeSize || data.texture->internalFormat() != textureFormat) { + data.texture.reset(new GLTexture(textureFormat, nativeSize)); data.texture->setFilter(GL_LINEAR); data.texture->setWrapMode(GL_CLAMP_TO_EDGE); data.framebuffer = std::make_unique(data.texture.get()); @@ -276,10 +277,10 @@ ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderViewport void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, EffectScreen *screen) { - OffscreenData *offscreenData = ensureOffscreenData(viewport, screen); + OffscreenData *offscreenData = ensureOffscreenData(renderTarget, viewport, screen); // Render the scene in an offscreen texture and then upscale it. - RenderTarget offscreenRenderTarget(offscreenData->framebuffer.get()); + RenderTarget offscreenRenderTarget(offscreenData->framebuffer.get(), renderTarget.colorspace(), renderTarget.sdrBrightness()); RenderViewport offscreenViewport(screen->geometry(), screen->devicePixelRatio(), offscreenRenderTarget); GLFramebuffer::pushFramebuffer(offscreenData->framebuffer.get()); effects->paintScreen(offscreenRenderTarget, offscreenViewport, mask, region, screen); @@ -382,7 +383,8 @@ void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewp glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); + s->setColorspaceUniforms(Colorspace::sRGB, renderTarget); QMatrix4x4 mvp = viewport.projectionMatrix(); mvp.translate(p.x() * scale, p.y() * scale); s->setUniform(GLShader::ModelViewProjectionMatrix, mvp); diff --git a/src/plugins/zoom/zoom.h b/src/plugins/zoom/zoom.h index cc19de4a9a..5237b55f10 100644 --- a/src/plugins/zoom/zoom.h +++ b/src/plugins/zoom/zoom.h @@ -93,7 +93,7 @@ private: }; GLTexture *ensureCursorTexture(); - OffscreenData *ensureOffscreenData(const RenderViewport &viewport, EffectScreen *screen); + OffscreenData *ensureOffscreenData(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectScreen *screen); void markCursorTextureDirty(); #if HAVE_ACCESSIBILITY diff --git a/src/scene/cursordelegate_opengl.cpp b/src/scene/cursordelegate_opengl.cpp index 6e277a6e4d..dda3f38478 100644 --- a/src/scene/cursordelegate_opengl.cpp +++ b/src/scene/cursordelegate_opengl.cpp @@ -42,11 +42,11 @@ void CursorDelegateOpenGL::paint(const RenderTarget &renderTarget, const QRegion // Render the cursor scene in an offscreen render target. const QSize bufferSize = (Cursors::self()->currentCursor()->rect().size() * scale).toSize(); if (!m_texture || m_texture->size() != bufferSize) { - m_texture = std::make_unique(GL_RGBA8, bufferSize); + m_texture = std::make_unique(renderTarget.framebuffer()->colorAttachment()->internalFormat(), bufferSize); m_framebuffer = std::make_unique(m_texture.get()); } - RenderTarget offscreenRenderTarget(m_framebuffer.get()); + RenderTarget offscreenRenderTarget(m_framebuffer.get(), renderTarget.colorspace()); RenderLayer renderLayer(layer()->loop()); renderLayer.setDelegate(std::make_unique(Compositor::self()->cursorScene(), m_output)); diff --git a/src/scene/itemrenderer_opengl.cpp b/src/scene/itemrenderer_opengl.cpp index 3bd102348a..d3b961c298 100644 --- a/src/scene/itemrenderer_opengl.cpp +++ b/src/scene/itemrenderer_opengl.cpp @@ -283,7 +283,7 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend const size_t size = totalVertexCount * sizeof(GLVertex2D); - ShaderTraits shaderTraits = ShaderTrait::MapTexture; + ShaderTraits shaderTraits = ShaderTrait::MapTexture | ShaderTrait::TransformColorspace; if (data.brightness() != 1.0) { shaderTraits |= ShaderTrait::Modulate; @@ -322,6 +322,7 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend GLShader *shader = ShaderManager::instance()->pushShader(shaderTraits); shader->setUniform(GLShader::Saturation, data.saturation()); + shader->setColorspaceUniforms(Colorspace::sRGB, renderTarget); if (renderContext.hardwareClipping) { glEnable(GL_SCISSOR_TEST); diff --git a/src/wayland/outputdevice_v2_interface.cpp b/src/wayland/outputdevice_v2_interface.cpp index f5f2e8faab..bd1bc93a9b 100644 --- a/src/wayland/outputdevice_v2_interface.cpp +++ b/src/wayland/outputdevice_v2_interface.cpp @@ -24,7 +24,7 @@ using namespace KWin; namespace KWaylandServer { -static const quint32 s_version = 2; +static const quint32 s_version = 3; static QtWaylandServer::kde_output_device_v2::transform kwinTransformToOutputDeviceTransform(Output::Transform transform) { @@ -48,6 +48,12 @@ static uint32_t kwinCapabilitiesToOutputDeviceCapabilities(Output::Capabilities if (caps & Output::Capability::RgbRange) { ret |= QtWaylandServer::kde_output_device_v2::capability_rgb_range; } + if (caps & Output::Capability::HighDynamicRange) { + ret |= QtWaylandServer::kde_output_device_v2::capability_high_dynamic_range; + } + if (caps & Output::Capability::WideColorGamut) { + ret |= QtWaylandServer::kde_output_device_v2::capability_wide_color_gamut; + } return ret; } @@ -82,6 +88,9 @@ public: void sendOverscan(Resource *resource); void sendVrrPolicy(Resource *resource); void sendRgbRange(Resource *resource); + void sendHighDynamicRange(Resource *resource); + void sendSdrBrightness(Resource *resource); + void sendWideColorGamut(Resource *resource); OutputDeviceV2Interface *q; QPointer m_display; @@ -105,6 +114,9 @@ public: uint32_t m_overscan = 0; vrr_policy m_vrrPolicy = vrr_policy_automatic; rgb_range m_rgbRange = rgb_range_automatic; + bool m_highDynamicRange = false; + uint32_t m_sdrBrightness = 200; + bool m_wideColorGamut = false; protected: void kde_output_device_v2_bind_resource(Resource *resource) override; @@ -182,6 +194,9 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, KWin::Output updateRgbRange(); updateName(); updateModes(); + updateHighDynamicRange(); + updateSdrBrightness(); + updateWideColorGamut(); connect(handle, &Output::geometryChanged, this, &OutputDeviceV2Interface::updateGlobalPosition); @@ -203,6 +218,9 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, KWin::Output this, &OutputDeviceV2Interface::updateModes); connect(handle, &Output::rgbRangeChanged, this, &OutputDeviceV2Interface::updateRgbRange); + connect(handle, &Output::highDynamicRangeChanged, this, &OutputDeviceV2Interface::updateHighDynamicRange); + connect(handle, &Output::sdrBrightnessChanged, this, &OutputDeviceV2Interface::updateSdrBrightness); + connect(handle, &Output::wideColorGamutChanged, this, &OutputDeviceV2Interface::updateWideColorGamut); } OutputDeviceV2Interface::~OutputDeviceV2Interface() @@ -253,6 +271,9 @@ void OutputDeviceV2InterfacePrivate::kde_output_device_v2_bind_resource(Resource sendOverscan(resource); sendVrrPolicy(resource); sendRgbRange(resource); + sendHighDynamicRange(resource); + sendSdrBrightness(resource); + sendWideColorGamut(resource); sendDone(resource); } @@ -350,6 +371,27 @@ void OutputDeviceV2InterfacePrivate::sendRgbRange(Resource *resource) send_rgb_range(resource->handle, m_rgbRange); } +void OutputDeviceV2InterfacePrivate::sendHighDynamicRange(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_HIGH_DYNAMIC_RANGE_SINCE_VERSION) { + send_high_dynamic_range(resource->handle, m_highDynamicRange ? 1 : 0); + } +} + +void OutputDeviceV2InterfacePrivate::sendSdrBrightness(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_SDR_BRIGHTNESS_SINCE_VERSION) { + send_sdr_brightness(resource->handle, m_sdrBrightness); + } +} + +void OutputDeviceV2InterfacePrivate::sendWideColorGamut(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_WIDE_COLOR_GAMUT_SINCE_VERSION) { + send_wide_color_gamut(resource->handle, m_wideColorGamut ? 1 : 0); + } +} + void OutputDeviceV2Interface::updateGeometry() { const auto clientResources = d->resourceMap(); @@ -570,6 +612,42 @@ void OutputDeviceV2Interface::updateRgbRange() } } +void OutputDeviceV2Interface::updateHighDynamicRange() +{ + if (d->m_highDynamicRange != d->m_handle->highDynamicRange()) { + d->m_highDynamicRange = d->m_handle->highDynamicRange(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendHighDynamicRange(resource); + d->sendDone(resource); + } + } +} + +void OutputDeviceV2Interface::updateSdrBrightness() +{ + if (d->m_sdrBrightness != d->m_handle->sdrBrightness()) { + d->m_sdrBrightness = d->m_handle->sdrBrightness(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendSdrBrightness(resource); + d->sendDone(resource); + } + } +} + +void OutputDeviceV2Interface::updateWideColorGamut() +{ + if (d->m_wideColorGamut != d->m_handle->wideColorGamut()) { + d->m_wideColorGamut = d->m_handle->wideColorGamut(); + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendWideColorGamut(resource); + d->sendDone(resource); + } + } +} + OutputDeviceV2Interface *OutputDeviceV2Interface::get(wl_resource *native) { if (auto devicePrivate = resource_cast(native); devicePrivate && !devicePrivate->isGlobalRemoved()) { diff --git a/src/wayland/outputdevice_v2_interface.h b/src/wayland/outputdevice_v2_interface.h index 398821e081..70ec8d9d2a 100644 --- a/src/wayland/outputdevice_v2_interface.h +++ b/src/wayland/outputdevice_v2_interface.h @@ -73,6 +73,9 @@ private: void updateVrrPolicy(); void updateRgbRange(); void updateGeometry(); + void updateHighDynamicRange(); + void updateSdrBrightness(); + void updateWideColorGamut(); std::unique_ptr d; }; diff --git a/src/wayland/outputmanagement_v2_interface.cpp b/src/wayland/outputmanagement_v2_interface.cpp index c14d24e390..5bd26bd01b 100644 --- a/src/wayland/outputmanagement_v2_interface.cpp +++ b/src/wayland/outputmanagement_v2_interface.cpp @@ -25,7 +25,7 @@ using namespace KWin; namespace KWaylandServer { -static const quint32 s_version = 3; +static const quint32 s_version = 4; class OutputManagementV2InterfacePrivate : public QtWaylandServer::kde_output_management_v2 { @@ -61,6 +61,9 @@ protected: void kde_output_configuration_v2_set_rgb_range(Resource *resource, wl_resource *outputdevice, uint32_t rgbRange) override; void kde_output_configuration_v2_set_primary_output(Resource *resource, struct ::wl_resource *output) override; void kde_output_configuration_v2_set_priority(Resource *resource, wl_resource *output, uint32_t priority) override; + void kde_output_configuration_v2_set_high_dynamic_range(Resource *resource, wl_resource *outputdevice, uint32_t enable_hdr) override; + void kde_output_configuration_v2_set_sdr_brightness(Resource *resource, wl_resource *outputdevice, uint32_t sdr_brightness) override; + void kde_output_configuration_v2_set_wide_color_gamut(Resource *resource, wl_resource *outputdevice, uint32_t enable_wcg) override; }; OutputManagementV2InterfacePrivate::OutputManagementV2InterfacePrivate(Display *display) @@ -241,6 +244,36 @@ void OutputConfigurationV2Interface::kde_output_configuration_v2_set_priority(Re } } +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_high_dynamic_range(Resource *resource, wl_resource *outputdevice, uint32_t enable_hdr) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->highDynamicRange = enable_hdr == 1; + } +} + +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_sdr_brightness(Resource *resource, wl_resource *outputdevice, uint32_t sdr_brightness) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->sdrBrightness = sdr_brightness; + } +} + +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_wide_color_gamut(Resource *resource, wl_resource *outputdevice, uint32_t enable_wcg) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->wideColorGamut = enable_wcg == 1; + } +} + void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy(Resource *resource) { wl_resource_destroy(resource->handle);