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).
This commit is contained in:
Xaver Hugl 2023-04-30 12:54:59 +02:00
parent 644e31f389
commit afc5567651
42 changed files with 787 additions and 123 deletions

View file

@ -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 <gbm.h>
@ -45,7 +46,7 @@ EglGbmCursorLayer::EglGbmCursorLayer(EglGbmBackend *eglBackend, DrmPipeline *pip
std::optional<OutputLayerBeginFrameInfo> 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)

View file

@ -60,7 +60,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());
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<SurfaceItemWayland *>(surfaceItem);
if (!item || !item->surface()) {

View file

@ -60,7 +60,7 @@ void EglGbmLayerSurface::destroyResources()
m_oldSurface = {};
}
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QVector<uint64_t>> &formats)
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QVector<uint64_t>> &formats, const Colorspace &colorspace, uint32_t sdrBrightness, const QVector3D &channelFactors)
{
if (!checkSurface(bufferSize, formats)) {
return std::nullopt;
@ -69,7 +69,7 @@ std::optional<OutputLayerBeginFrameInfo> 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<OutputLayerBeginFrameInfo> 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<GLTexture>(GL_RGBA16F, m_surface.gbmSwapchain->size());
m_surface.shadowBuffer = std::make_shared<GLFramebuffer>(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);

View file

@ -54,7 +54,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, QVector<uint64_t>> &formats);
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, TextureTransforms transformation, const QMap<uint32_t, QVector<uint64_t>> &formats, const Colorspace &colorspace, uint32_t sdrBrightness, const QVector3D &channelFactors);
bool endRendering(const QRegion &damagedRegion);
bool doesSurfaceFit(const QSize &size, const QMap<uint32_t, QVector<uint64_t>> &formats) const;
@ -74,6 +74,11 @@ private:
};
struct Surface
{
std::shared_ptr<GLTexture> shadowTexture;
std::shared_ptr<GLFramebuffer> shadowBuffer;
Colorspace colorspace = Colorspace::sRGB;
QVector3D channelFactors = {1, 1, 1};
float sdrBrightness = 200;
std::shared_ptr<GbmSwapchain> gbmSwapchain;
std::shared_ptr<DumbSwapchain> importDumbSwapchain;
std::shared_ptr<GbmSwapchain> importGbmSwapchain;

View file

@ -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) {

View file

@ -32,6 +32,7 @@ public:
std::pair<std::shared_ptr<GbmBuffer>, QRegion> acquire();
void damage(const QRegion &damage);
void resetDamage();
void releaseBuffer(GbmBuffer *buffer);
DrmGpu *gpu() const;

View file

@ -67,6 +67,13 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &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<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->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<OutputChangeSet> &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<ColorTransformation> &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<ColorTransformation> &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;
}
}

View file

@ -60,7 +60,8 @@ public:
void leaseEnded();
bool setGammaRamp(const std::shared_ptr<ColorTransformation> &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<CursorSource> source;
QPointF position;
} m_cursor;
QVector3D m_channelFactors = {1, 1, 1};
};
}

View file

@ -108,7 +108,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline
return Error::InvalidArguments;
}
if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset) {
pipeline->prepareAtomicModeset(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<DrmBlob> 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));
}
}

View file

@ -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<DrmConnectorMode> &mode);
@ -120,6 +123,8 @@ public:
void setGammaRamp(const std::shared_ptr<ColorTransformation> &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<DrmBlob> 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<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects);
@ -172,6 +178,8 @@ private:
std::shared_ptr<DrmGammaRamp> gamma;
std::shared_ptr<DrmBlob> ctm;
DrmConnector::DrmContentType contentType = DrmConnector::DrmContentType::Graphics;
NamedColorimetry colorimetry = NamedColorimetry::BT709;
NamedTransferFunction transferFunction = NamedTransferFunction::sRGB;
std::shared_ptr<DrmPipelineLayer> layer;
std::shared_ptr<DrmOverlayLayer> cursorLayer;

View file

@ -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);
}
}

View file

@ -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<ColorTransformation> &transformation)
bool Output::setChannelFactors(const QVector3D &rgb)
{
return false;
}
bool Output::setCTM(const QMatrix3x3 &ctm)
bool Output::setGammaRamp(const std::shared_ptr<ColorTransformation> &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;

View file

@ -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<ColorTransformation> &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);

View file

@ -29,6 +29,9 @@ public:
std::optional<uint32_t> overscan;
std::optional<Output::RgbRange> rgbRange;
std::optional<RenderLoop::VrrPolicy> vrrPolicy;
std::optional<bool> highDynamicRange;
std::optional<uint32_t> sdrBrightness;
std::optional<bool> wideColorGamut;
};
class KWIN_EXPORT OutputConfiguration

View file

@ -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);

View file

@ -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")

View file

@ -0,0 +1,129 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "colorspace.h"
#include <qassert.h>
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;
}
}

View file

@ -0,0 +1,73 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <optional>
#include <QMatrix3x3>
#include <QVector2D>
#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<NamedColorimetry> 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;
};
}

View file

@ -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();

View file

@ -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)

View file

@ -150,8 +150,9 @@ void OffscreenData::setVertexSnappingMode(RenderGeometry::VertexSnappingMode mod
void OffscreenData::paint(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *window, const QRegion &region,
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();

View file

@ -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

View file

@ -6,6 +6,7 @@
#pragma once
#include "libkwineffects/colorspace.h"
#include "libkwineffects/kwinglutils_export.h"
#include <QImage>
@ -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

View file

@ -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<GLTexture> EglContext::importDmaBufAsTexture(const DmaBufAttributes &attributes) const
{
EGLImageKHR image = m_display->importDmaBufAsImage(attributes);
if (image != EGL_NO_IMAGE_KHR) {
return std::make_shared<EGLImageTexture>(m_display->handle(), image, GL_RGBA8, QSize(attributes.width, attributes.height));
return std::make_shared<EGLImageTexture>(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;

View file

@ -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<GLint *>(&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<GLint *>(&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<GLint *>(&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<GLint *>(&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<GLTexture>(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 &region, 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());

View file

@ -77,7 +77,7 @@ private:
QRect expand(const QRect &rect) const;
QRegion expand(const QRegion &region) 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;

View file

@ -11,6 +11,7 @@
// KConfigSkeleton
#include "mouseclickconfig.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include <QAction>
@ -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);

View file

@ -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];

View file

@ -15,6 +15,7 @@
#include "libkwineffects/kwinconfig.h"
#include "libkwineffects/kwinglplatform.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include <KGlobalAccel>
#include <KLocalizedString>
@ -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<float> verts;
for (const Mark &mark : std::as_const(marks)) {
verts.clear();

View file

@ -10,6 +10,7 @@
// KWin
#include "libkwineffects/kwingltexture.h"
#include "libkwineffects/kwinglutils.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
// KDE
#include <Plasma/Svg>
@ -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();

View file

@ -11,6 +11,7 @@
#include "snaphelper.h"
#include "libkwineffects/kwinglutils.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include <QPainter>
@ -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);

View file

@ -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);

View file

@ -11,6 +11,7 @@
#include "touchpoints.h"
#include "libkwineffects/kwinglutils.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include <QAction>
@ -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);

View file

@ -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);

View file

@ -21,6 +21,7 @@
#include "libkwineffects/kwinconfig.h"
#include "libkwineffects/kwinglutils.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include <KGlobalAccel>
@ -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());

View file

@ -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<GLFramebuffer>(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 &region, 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);

View file

@ -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

View file

@ -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<GLTexture>(GL_RGBA8, bufferSize);
m_texture = std::make_unique<GLTexture>(renderTarget.framebuffer()->colorAttachment()->internalFormat(), bufferSize);
m_framebuffer = std::make_unique<GLFramebuffer>(m_texture.get());
}
RenderTarget offscreenRenderTarget(m_framebuffer.get());
RenderTarget offscreenRenderTarget(m_framebuffer.get(), renderTarget.colorspace());
RenderLayer renderLayer(layer()->loop());
renderLayer.setDelegate(std::make_unique<SceneDelegate>(Compositor::self()->cursorScene(), m_output));

View file

@ -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);

View file

@ -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<Display> 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<OutputDeviceV2InterfacePrivate *>(native); devicePrivate && !devicePrivate->isGlobalRemoved()) {

View file

@ -73,6 +73,9 @@ private:
void updateVrrPolicy();
void updateRgbRange();
void updateGeometry();
void updateHighDynamicRange();
void updateSdrBrightness();
void updateWideColorGamut();
std::unique_ptr<OutputDeviceV2InterfacePrivate> d;
};

View file

@ -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);