diff --git a/src/backends/drm/drm_egl_layer.cpp b/src/backends/drm/drm_egl_layer.cpp index 689e5c897f..e15dfc5519 100644 --- a/src/backends/drm/drm_egl_layer.cpp +++ b/src/backends/drm/drm_egl_layer.cpp @@ -54,7 +54,7 @@ std::optional EglGbmLayer::doBeginFrame() // as the hardware cursor is more important than an incorrectly blended cursor edge m_scanoutBuffer.reset(); - return m_surface.startRendering(targetRect().size(), m_pipeline->output()->transform().combine(OutputTransform::FlipY), m_pipeline->formats(m_type), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement()); + return m_surface.startRendering(targetRect().size(), m_pipeline->output()->transform().combine(OutputTransform::FlipY), m_pipeline->formats(m_type), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement(), m_pipeline->output()->brightness()); } bool EglGbmLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) diff --git a/src/backends/drm/drm_egl_layer_surface.cpp b/src/backends/drm/drm_egl_layer_surface.cpp index 88a4eb7b4e..d763da5663 100644 --- a/src/backends/drm/drm_egl_layer_surface.cpp +++ b/src/backends/drm/drm_egl_layer_surface.cpp @@ -74,7 +74,7 @@ void EglGbmLayerSurface::destroyResources() m_oldSurface = {}; } -std::optional EglGbmLayerSurface::startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement) +std::optional EglGbmLayerSurface::startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement, double brightness) { if (!checkSurface(bufferSize, formats)) { return std::nullopt; @@ -96,12 +96,14 @@ std::optional EglGbmLayerSurface::startRendering(cons m_surface->currentSlot = slot; if (m_surface->targetColorDescription != colorDescription || m_surface->channelFactors != channelFactors - || m_surface->colormanagementEnabled != enableColormanagement || m_surface->iccProfile != iccProfile) { + || m_surface->colormanagementEnabled != enableColormanagement || m_surface->iccProfile != iccProfile + || m_surface->brightness != brightness) { m_surface->damageJournal.clear(); m_surface->colormanagementEnabled = enableColormanagement; m_surface->targetColorDescription = colorDescription; m_surface->channelFactors = channelFactors; m_surface->iccProfile = iccProfile; + m_surface->brightness = brightness; if (iccProfile) { if (!m_surface->iccShader) { m_surface->iccShader = std::make_unique(); @@ -185,10 +187,14 @@ bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion, OutputFrame if (m_surface->iccShader) { m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription.sdrBrightness(), m_surface->channelFactors); } else { + // enforce a 25 nits minimum sdr brightness + constexpr double minBrightness = 25; + const double sdrBrightness = m_surface->intermediaryColorDescription.sdrBrightness(); + const double brightnessFactor = (m_surface->brightness * (1 - (minBrightness / sdrBrightness))) + (minBrightness / sdrBrightness); QMatrix4x4 ctm; - ctm(0, 0) = m_surface->channelFactors.x(); - ctm(1, 1) = m_surface->channelFactors.y(); - ctm(2, 2) = m_surface->channelFactors.z(); + ctm(0, 0) = m_surface->channelFactors.x() * brightnessFactor; + ctm(1, 1) = m_surface->channelFactors.y() * brightnessFactor; + ctm(2, 2) = m_surface->channelFactors.z() * brightnessFactor; binder.shader()->setUniform(GLShader::Mat4Uniform::ColorimetryTransformation, ctm); binder.shader()->setUniform(GLShader::IntUniform::SourceNamedTransferFunction, int(m_surface->intermediaryColorDescription.transferFunction())); binder.shader()->setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, int(m_surface->targetColorDescription.transferFunction())); diff --git a/src/backends/drm/drm_egl_layer_surface.h b/src/backends/drm/drm_egl_layer_surface.h index 8683d4b627..c19ac5c03f 100644 --- a/src/backends/drm/drm_egl_layer_surface.h +++ b/src/backends/drm/drm_egl_layer_surface.h @@ -56,7 +56,7 @@ public: EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target = BufferTarget::Normal, FormatOption formatOption = FormatOption::PreferAlpha); ~EglGbmLayerSurface(); - std::optional startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement); + std::optional startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr &iccProfile, bool enableColormanagement, double brightness); bool endRendering(const QRegion &damagedRegion, OutputFrame *frame); bool doesSurfaceFit(const QSize &size, const QHash> &formats) const; @@ -101,6 +101,7 @@ private: ColorDescription targetColorDescription = ColorDescription::sRGB; ColorDescription intermediaryColorDescription = ColorDescription::sRGB; QVector3D channelFactors = {1, 1, 1}; + double brightness = 1.0; std::unique_ptr iccShader; std::shared_ptr iccProfile; diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 11e8d4f7c8..e4f7d0c69a 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -402,6 +402,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props next.colorDescription = m_pipeline->colorDescription(); next.vrrPolicy = props->vrrPolicy.value_or(m_state.vrrPolicy); next.colorProfileSource = props->colorProfileSource.value_or(m_state.colorProfileSource); + next.brightness = props->brightness.value_or(m_state.brightness); setState(next); if (!isEnabled() && m_pipeline->needsModeset()) { diff --git a/src/core/output.cpp b/src/core/output.cpp index 303ea3203d..e341c67d74 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -610,6 +610,9 @@ void Output::setState(const State &state) if (oldState.colorProfileSource != state.colorProfileSource) { Q_EMIT colorProfileSourceChanged(); } + if (oldState.brightness != state.brightness) { + Q_EMIT brightnessChanged(); + } if (oldState.enabled != state.enabled) { Q_EMIT enabledChanged(); } @@ -761,6 +764,11 @@ Output::ColorProfileSource Output::colorProfileSource() const { return m_state.colorProfileSource; } + +double Output::brightness() const +{ + return m_state.brightness; +} } // namespace KWin #include "moc_output.cpp" diff --git a/src/core/output.h b/src/core/output.h index 1c5a89f69f..e3d52bd62c 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -358,6 +358,8 @@ public: double sdrGamutWideness() const; ColorProfileSource colorProfileSource() const; + double brightness() const; + const ColorDescription &colorDescription() const; Q_SIGNALS: @@ -423,6 +425,7 @@ Q_SIGNALS: void sdrGamutWidenessChanged(); void colorDescriptionChanged(); void colorProfileSourceChanged(); + void brightnessChanged(); protected: struct Information @@ -472,6 +475,7 @@ protected: std::optional minBrightnessOverride; double sdrGamutWideness = 0; VrrPolicy vrrPolicy = VrrPolicy::Automatic; + double brightness = 1.0; }; void setInformation(const Information &information); diff --git a/src/core/outputconfiguration.h b/src/core/outputconfiguration.h index 45d207f5a1..512b181a49 100644 --- a/src/core/outputconfiguration.h +++ b/src/core/outputconfiguration.h @@ -43,6 +43,7 @@ public: std::optional> minBrightnessOverride; std::optional sdrGamutWideness; std::optional colorProfileSource; + std::optional brightness; }; class KWIN_EXPORT OutputConfiguration diff --git a/src/outputconfigurationstore.cpp b/src/outputconfigurationstore.cpp index e92b5c7ed7..b575917e2c 100644 --- a/src/outputconfigurationstore.cpp +++ b/src/outputconfigurationstore.cpp @@ -229,6 +229,7 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .maxAverageBrightnessOverride = changeSet->maxAverageBrightnessOverride.value_or(output->maxAverageBrightnessOverride()), .minBrightnessOverride = changeSet->minBrightnessOverride.value_or(output->minBrightnessOverride()), .sdrGamutWideness = changeSet->sdrGamutWideness.value_or(output->sdrGamutWideness()), + .brightness = changeSet->brightness.value_or(output->brightness()), }; *outputIt = SetupState{ .outputIndex = *outputIndex, @@ -263,6 +264,7 @@ void OutputConfigurationStore::storeConfig(const QList &allOutputs, bo .maxAverageBrightnessOverride = output->maxAverageBrightnessOverride(), .minBrightnessOverride = output->minBrightnessOverride(), .sdrGamutWideness = output->sdrGamutWideness(), + .brightness = output->brightness(), }; *outputIt = SetupState{ .outputIndex = *outputIndex, @@ -311,6 +313,7 @@ std::pair> OutputConfigurationStore::setupT .minBrightnessOverride = state.minBrightnessOverride, .sdrGamutWideness = state.sdrGamutWideness, .colorProfileSource = state.colorProfileSource, + .brightness = state.brightness, }; if (setupState.enabled) { priorities.push_back(std::make_pair(output, setupState.priority)); @@ -432,6 +435,7 @@ std::pair> OutputConfigurationStore::genera .wideColorGamut = existingData.wideColorGamut.value_or(false), .autoRotationPolicy = existingData.autoRotation.value_or(Output::AutoRotationPolicy::InTabletMode), .colorProfileSource = existingData.colorProfileSource.value_or(Output::ColorProfileSource::sRGB), + .brightness = existingData.brightness.value_or(1.0), }; if (enable) { const auto modeSize = changeset->transform->map(mode->size()); @@ -737,6 +741,9 @@ void OutputConfigurationStore::load() state.colorProfileSource = Output::ColorProfileSource::sRGB; } } + if (const auto it = data.find("brightness"); it != data.end() && it->isDouble()) { + state.brightness = std::clamp(it->toDouble(), 0.0, 1.0); + } outputDatas.push_back(state); } @@ -966,6 +973,9 @@ void OutputConfigurationStore::save() break; } } + if (output.brightness) { + o["brightness"] = *output.brightness; + } outputsData.append(o); } outputs["data"] = outputsData; diff --git a/src/outputconfigurationstore.h b/src/outputconfigurationstore.h index 7e71353482..3e4711780d 100644 --- a/src/outputconfigurationstore.h +++ b/src/outputconfigurationstore.h @@ -82,6 +82,7 @@ private: std::optional maxAverageBrightnessOverride; std::optional minBrightnessOverride; std::optional sdrGamutWideness; + std::optional brightness; }; struct SetupState { diff --git a/src/wayland/outputdevice_v2.cpp b/src/wayland/outputdevice_v2.cpp index aadb0cae76..6159db797a 100644 --- a/src/wayland/outputdevice_v2.cpp +++ b/src/wayland/outputdevice_v2.cpp @@ -22,7 +22,7 @@ namespace KWin { -static const quint32 s_version = 7; +static const quint32 s_version = 8; static QtWaylandServer::kde_output_device_v2::transform kwinTransformToOutputDeviceTransform(OutputTransform transform) { @@ -106,6 +106,7 @@ public: void sendBrightnessOverrides(Resource *resource); void sendSdrGamutWideness(Resource *resource); void sendColorProfileSource(Resource *resource); + void sendBrightness(Resource *resource); OutputDeviceV2Interface *q; QPointer m_display; @@ -142,6 +143,7 @@ public: std::optional m_maxAverageBrightnessOverride; std::optional m_minBrightnessOverride; color_profile_source m_colorProfile = color_profile_source::color_profile_source_sRGB; + double m_brightness = 1.0; protected: void kde_output_device_v2_bind_resource(Resource *resource) override; @@ -228,6 +230,7 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl updateBrightnessOverrides(); updateSdrGamutWideness(); updateColorProfileSource(); + updateBrightness(); connect(handle, &Output::geometryChanged, this, &OutputDeviceV2Interface::updateGlobalPosition); @@ -258,6 +261,7 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl connect(handle, &Output::brightnessMetadataChanged, this, &OutputDeviceV2Interface::updateBrightnessOverrides); connect(handle, &Output::sdrGamutWidenessChanged, this, &OutputDeviceV2Interface::updateSdrGamutWideness); connect(handle, &Output::colorProfileSourceChanged, this, &OutputDeviceV2Interface::updateColorProfileSource); + connect(handle, &Output::brightnessChanged, this, &OutputDeviceV2Interface::updateBrightness); } OutputDeviceV2Interface::~OutputDeviceV2Interface() @@ -317,6 +321,7 @@ void OutputDeviceV2InterfacePrivate::kde_output_device_v2_bind_resource(Resource sendBrightnessOverrides(resource); sendSdrGamutWideness(resource); sendColorProfileSource(resource); + sendBrightness(resource); sendDone(resource); } @@ -477,6 +482,13 @@ void OutputDeviceV2InterfacePrivate::sendColorProfileSource(Resource *resource) } } +void OutputDeviceV2InterfacePrivate::sendBrightness(Resource *resource) +{ + if (resource->version() >= KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_SINCE_VERSION) { + send_brightness(resource->handle, m_brightness); + } +} + void OutputDeviceV2Interface::updateGeometry() { const auto clientResources = d->resourceMap(); @@ -821,6 +833,19 @@ void OutputDeviceV2Interface::updateColorProfileSource() } } +void OutputDeviceV2Interface::updateBrightness() +{ + const uint32_t newBrightness = std::round(d->m_handle->brightness() * 10'000); + if (d->m_brightness != newBrightness) { + d->m_brightness = newBrightness; + const auto clientResources = d->resourceMap(); + for (const auto &resource : clientResources) { + d->sendBrightness(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.h b/src/wayland/outputdevice_v2.h index df2ed401f4..b664d24814 100644 --- a/src/wayland/outputdevice_v2.h +++ b/src/wayland/outputdevice_v2.h @@ -78,6 +78,7 @@ private: void updateBrightnessOverrides(); void updateSdrGamutWideness(); void updateColorProfileSource(); + void updateBrightness(); std::unique_ptr d; }; diff --git a/src/wayland/outputmanagement_v2.cpp b/src/wayland/outputmanagement_v2.cpp index 1886b9ba3e..c2020f28f8 100644 --- a/src/wayland/outputmanagement_v2.cpp +++ b/src/wayland/outputmanagement_v2.cpp @@ -23,7 +23,7 @@ namespace KWin { -static const quint32 s_version = 8; +static const quint32 s_version = 9; class OutputManagementV2InterfacePrivate : public QtWaylandServer::kde_output_management_v2 { @@ -67,6 +67,7 @@ protected: void kde_output_configuration_v2_set_brightness_overrides(Resource *resource, wl_resource *outputdevice, int32_t max_peak_brightness, int32_t max_average_brightness, int32_t min_brightness) override; void kde_output_configuration_v2_set_sdr_gamut_wideness(Resource *resource, wl_resource *outputdevice, uint32_t gamut_wideness) override; void kde_output_configuration_v2_set_color_profile_source(Resource *resource, wl_resource *outputdevice, uint32_t source) override; + void kde_output_configuration_v2_set_brightness(Resource *resource, wl_resource *outputdevice, uint32_t brightness) override; }; OutputManagementV2InterfacePrivate::OutputManagementV2InterfacePrivate(Display *display) @@ -342,6 +343,16 @@ void OutputConfigurationV2Interface::kde_output_configuration_v2_set_color_profi } } +void OutputConfigurationV2Interface::kde_output_configuration_v2_set_brightness(Resource *resource, wl_resource *outputdevice, uint32_t brightness) +{ + if (invalid) { + return; + } + if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) { + config.changeSet(output->handle())->brightness = brightness / 10'000.0; + } +} + void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy(Resource *resource) { wl_resource_destroy(resource->handle);