diff --git a/docs/configuration.md b/docs/configuration.md
index ef265d2dee..bbf8b9c844 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,42 +1,148 @@
-# General
-### Window opacity affects blur
-Since Plasma 6, window opacity now affects blur opacity with no option to disable it in the stock blur effect.
+# Simple
+A simple configuration system with a UI. For more complex configuration (for example blur menus but not firefox's menus), use window rules.
-Enabled (default):
-![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/525a3611-62f0-4c7e-b01c-253a05cbd3ca)
+Obvious options are not explained here.
-Disabled:
-![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/b4f35a24-e288-4c51-9707-494942abdaa0)
+## General
-# Force blur
-### Blur window decorations
-Whether to blur window decorations, including borders. Enable this if your window decoration doesn't support blur, or you want rounded top corners.
+| Option | Description |
+|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Noise strength** | Noise is used to mask color banding. |
+| **Window opacity affects blur** | Whether the blur opacity should be affected by the window's opacity. Window fading animations will still affect the blur opacity.
Enabled:
Disabled:
|
-This option will override the blur region specified by the decoration.
+## Force blur
+| Option | Description |
+|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Window classes** | A list of classes of windows to force blur, separated by new line.
Regular expressions are supported and can be specified by using the ``$regex:`` prefix. Example: ``$regex:org\.kde\..*`` |
+| **Blur window decorations as well** | Whether to blur the window decorations (titlebar and borders) in addition to the content. This will override the decoration's blur region, possibly breaking rounded corners, in which case you need to manually specify the corner radius in the *Rounded corners* section. |
+| **Treat windows as transparent** | Whether force-blurred windows should be considered as transparent in order to fix transparency for applications that don't mark their windows as transparent. See #38 for an example. |
-### Paint windows as non-opaque
-Fixes transparency for some applications by marking their windows as transparent. This will only be done for force-blurred windows.
+## Rounded corners
+| Option | Description |
+|------------------------------|----------------------------------------------------------------------------------------|
+| **Window top corner radius** | Requires *Blur window decorations as well* to be enabled for windows with decorations. |
+| **Anti-aliasing** | Makes rounded corners appear smoother. |
-# Static blur
-When enabled, the blur texture will be cached and reused. The blurred areas of the window will be marked as opaque, resulting in KWin not painting anything behind them.
-Only one image per screen is cached at a time.
+## Static blur
+When enabled, a cached texture will be painted behind the window instead of actually blurring the background (dynamic blur).
+The blurred areas of the window will also be marked as opaque, causing KWin to not paint anything behind the window.
+Both things result in lower resource usage.
Static blur is mainly intended for laptop users who want longer battery life while still having blur everywhere.
-### Use real blur for windows that are in front of other windows
-By default, when two windows overlap, you won't be able to see the window behind.
-![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/e581b5c1-7b2c-41c4-b180-4da5306747e1)
+| Option | Description |
+|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Use dynamic blur for windows with another window behind** | Whether to switch to dynamic blur when a window has another window behind it, allowing you to see the one behind.
Enabled:
Disabled:
|
+| **Texture source** | The image to use for static blur.
- Desktop wallpaper - A screenshot of the desktop is taken for every screen. Icons and widgets will be included. The cached texture is invalidated when the entire desktop is repainted, which can happen when the wallpaper changes, icons are interacted with or when widgets update.
- Custom - The specified image is scaled for every screen without respecting the aspect ratio. Supported formats are JPEG and PNG. |
+| **Blur texture** | Whether to blur the texture. This is only done once during texture creation. |
-If this option is enabled, the effect will automatically switch to real blur when necessary. At very high blur strengths, there may be a slight difference in the texture.
+## Window rules
-https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/7bae6a16-6c78-4889-8df1-feb24005dabc
+| Option | Description |
+|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Convert simple config to window rules** | Whether to automatically convert simple configuration into window rules. Disabling this will cause most options in the simple configuration UI to not have an effect anymore. |
-### Image source
-The image to use for static blur.
+# Window rules
+Configuration UI for window rules will be added in v2. The configuration file is located at ``~/.config/kwinbetterblurrc`` and isn't created automatically.
-- Desktop wallpaper - A screenshot of the desktop is taken for every screen. Icons and widgets will be included. The cached texture is invalidated when the entire desktop is repainted,
-which can happen when the wallpaper changes, icons are interacted with or when widgets update.
-- Custom - The specified image is scaled for every screen without respecting the aspect ratio. Supported formats are JPEG and PNG.
+Simple configuration is converted into multiple rules with priorities lower than 0. They are not added into the configuration file. This behavior can be disabled in the configuration UI in the *Window Rules* tab.
-### Blur image
-Whether to blur the image used for static blur. This is only done once.
+An example is provided at the end.
+
+## File structure
+### Window rule
+**Group**: [WindowRules][\$RuleName], where **$RuleName (string)** is the unique name of this rule.
+
+Rules are not evaluated in order as they appear in the file.
+
+| Property | Type | Description |
+|--------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| **Priority** | ``int (>=0)`` | Properties of rules with higher priority override properties of rules with lower priority.
Priorities lower than 0 are reserved. |
+
+### Conditions
+**Group**: [WindowRules][\$RuleName][Conditions][\$ConditionName], where **$ConditionName (string)** is the unique name of this condition in the rule.
+
+At least 1 (or 0 if none specified) condition must be satisfied in order for this rule to be applied. All specified subconditions in a condition must be satisfied.
+
+| Property | Type | Description |
+|-----------------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Negate** | ``enum (WindowClass, WindowState, WindowType)``
Multiple values are allowed. | Which subconditions should be negated.
Separated by space. |
+| **HasWindowBehind** | ``bool`` | Whether only windows that (don't) have another window (excluding desktops) behind them should satisfy this condition. |
+| **Internal** | ``bool`` | Internal KWin windows. |
+| **WindowClass** | ``string`` or ``regex`` | A regular expression executed on the window class. Only a partial match is required.
For an exact match, use ``^class$``.
To specify multiple classes, separate them by ``\|``: ``class1\|class2``.
A dot matches any character, which can be prevented by escaping it: ``\\.``. |
+| **WindowState** | ``enum (Fullscreen, Maximized)``
Multiple values are allowed. | Separated by space. |
+| **WindowType** | ``enum (Dialog, Dock, Normal, Menu, Toolbar, Tooltip, Utility)``
Multiple values are allowed. | Separated by space. |
+
+### Properties
+**Group**: [WindowRules][\$RuleName][Properties]
+
+Properties don't have default values. Unset properties don't override properties of other rules.
+
+| Property | Type | Description |
+|:-----------------------------|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **BlurContent** | ``bool`` | Whether to force blur the window's content, excluding decorations. |
+| **BlurDecorations** | ``bool`` | See *Blur window decorations as well* in the *Simple* section. The difference is that in window rules it's possible to blur only the decorations and the corner radius is specified in the *CornerRadius* property. |
+| **BlurStrength** | ``int (1 - 15)`` | |
+| **CornerRadius** | ``float (>= 0)`` | Corner radius for the blur region.
A single number specifies the same radius for both top and bottom corners. Example: ``12.5``
Two numbers separated by a space specify different radii for top and bottom corners respectively. Example: ``0 12.5``
Top corner radius will only be applied to windows with force-blurred decorations or no decorations. |
+| **CornerAntialiasing** | ``float (>= 0)`` | Makes rounded corners appear smoother. Recommended value is 1. |
+| **ForceTransparency** | ``bool`` | See *Treat windows as transparent* in the *Simple* section. |
+| **StaticBlur** | ``bool`` | Whether to use static blur instead of dynamic blur for the window. |
+| **WindowOpacityAffectsBlur** | ``bool`` | See *Window opacity affects blur* in the *Simple* section. |
+
+## Example
+```
+[WindowRules][Global][Properties]
+BlurStrength = 9
+CornerAntialiasing = 1
+WindowOpacityAffectsBlur = false
+
+
+[WindowRules][ForceBlur][Conditions][0]
+WindowClass = clementine|kate|kwrite|org\\.freedesktop\\.impl\\.portal\\.desktop\\.kde|plasmashell
+WindowType = Dialog Normal Menu Toolbar Tooltip Utility
+
+[WindowRules][ForceBlur][Conditions][1]
+WindowClass = firefox
+WindowType = Normal
+
+[WindowRules][ForceBlur][Properties]
+BlurContent = true
+BlurDecorations = true
+
+
+[WindowRules][StaticBlur][Conditions][0]
+HasWindowBehind = false
+
+[WindowRules][StaticBlur][Properties]
+StaticBlur = true
+
+
+# Reduce blur strength for windows on top of other windows and panels
+[WindowRules][LowBlurStrength]
+Priority = 1
+
+[WindowRules][LowBlurStrength][Conditions][0]
+HasWindowBehind = true
+
+[WindowRules][LowBlurStrength][Conditions][1]
+WindowType = Dock
+
+[WindowRules][LowBlurStrength][Properties]
+BlurStrength = 3
+
+
+[WindowRules][WindowCorners][Conditions][0]
+Negate = WindowState
+WindowState = Fullscreen Maximized
+WindowType = Normal
+
+[WindowRules][WindowCorners][Properties]
+CornerRadius = 15
+
+
+[WindowRules][MenuCorners][Conditions][0]
+WindowType = Menu
+
+[WindowRules][MenuCorners][Properties]
+CornerRadius = 10
+```
\ No newline at end of file
diff --git a/flake.lock b/flake.lock
index 71f15f8dba..0be498cd7b 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1730200266,
- "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=",
+ "lastModified": 1735834308,
+ "narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd",
+ "rev": "6df24922a1400241dae323af55f30e4318a6ca65",
"type": "github"
},
"original": {
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e659aa2a5e..6a0e660a5d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -16,11 +16,15 @@ set(forceblur_SOURCES
blur.cpp
blur.qrc
main.cpp
- settings.cpp
+ windowrules/windowrulecondition.cpp
+ windowrules/windowrule.cpp
+ windowrules/windowrulelist.cpp
+ windowrules/windowproperties.cpp
+ blurwindow.cpp
)
kconfig_add_kcfg_files(forceblur_SOURCES
- blurconfig.kcfgc
+ config/config.kcfgc
)
add_library(forceblur MODULE ${forceblur_SOURCES})
@@ -29,6 +33,8 @@ target_link_libraries(forceblur PRIVATE
KF6::ConfigGui
)
+
+target_compile_definitions(forceblur PRIVATE CONFIG_KWIN)
if (${KDecoration3_FOUND})
target_link_libraries(forceblur PRIVATE KDecoration3::KDecoration)
else()
diff --git a/src/blur.cpp b/src/blur.cpp
index 8771028e94..1eed7166a4 100644
--- a/src/blur.cpp
+++ b/src/blur.cpp
@@ -8,12 +8,13 @@
#include "blur.h"
// KConfigSkeleton
-#include "blurconfig.h"
+#include "config.h"
#include "core/pixelgrid.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "effect/effecthandler.h"
+#include "internalwindow.h"
#include "opengl/glutils.h"
#include "opengl/glplatform.h"
#include "utils.h"
@@ -21,12 +22,7 @@
#include "wayland/blur.h"
#include "wayland/display.h"
#include "wayland/surface.h"
-
-#ifdef KWIN_6_2_OR_GREATER
-#include "scene/decorationitem.h"
-#include "scene/surfaceitem.h"
-#include "scene/windowitem.h"
-#endif
+#include "kwin/window.h"
#include
#include
@@ -41,12 +37,6 @@
#include
#include
-#ifdef KDECORATION3
-#include
-#else
-#include
-#endif
-
#include
Q_LOGGING_CATEGORY(KWIN_BLUR, "kwin_better_blur", QtWarningMsg)
@@ -67,7 +57,8 @@ QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr;
BlurEffect::BlurEffect()
{
- BlurConfig::instance(effects->config());
+ m_windowRules = std::make_unique();
+ BetterBlur::Config::instance(effects->config());
ensureResources();
m_downsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture,
@@ -234,141 +225,38 @@ void BlurEffect::initBlurStrengthValues()
void BlurEffect::reconfigure(ReconfigureFlags flags)
{
- m_settings.read();
+ Q_UNUSED(flags)
- m_iterationCount = blurStrengthValues[m_settings.general.blurStrength].iteration;
- m_offset = blurStrengthValues[m_settings.general.blurStrength].offset;
- m_expandSize = blurOffsets[m_iterationCount - 1].expandSize;
+ BetterBlur::Config::self()->read();
+ m_windowRules->load();
+ m_colorMatrix = colorMatrix(BetterBlur::Config::brightness(), BetterBlur::Config::saturation(), BetterBlur::Config::contrast());
+
+ m_noiseStrength = BetterBlur::Config::noiseStrength();
+ m_staticBlurImage = QImage(BetterBlur::Config::fakeBlurImage());
m_staticBlurTextures.clear();
- m_colorMatrix = colorMatrix(m_settings.general.brightness, m_settings.general.saturation, m_settings.general.contrast);
- for (EffectWindow *w : effects->stackingOrder()) {
- updateBlurRegion(w);
+ for (const auto &[_, w] : m_windows) {
+ w->updateProperties();
+ w->updateBlurRegion();
}
// Update all windows for the blur to take effect
effects->addRepaintFull();
}
-void BlurEffect::updateBlurRegion(EffectWindow *w, bool geometryChanged)
-{
- std::optional content;
- std::optional frame;
-
- if (net_wm_blur_region != XCB_ATOM_NONE) {
- const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32);
- QRegion region;
- if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) {
- const uint32_t *cardinals = reinterpret_cast(value.constData());
- for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) {
- int x = cardinals[i++];
- int y = cardinals[i++];
- int w = cardinals[i++];
- int h = cardinals[i++];
- region += Xcb::fromXNative(QRect(x, y, w, h)).toRect();
- }
- }
- if (!value.isNull()) {
- content = region;
- }
- }
-
- SurfaceInterface *surf = w->surface();
-
- if (surf && surf->blur()) {
- content = surf->blur()->region();
- }
-
- if (auto internal = w->internalWindow()) {
- const auto property = internal->property("kwin_blur");
- if (property.isValid()) {
- content = property.value();
- }
- }
-
- if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) {
- frame = decorationBlurRegion(w);
- }
-
- // Don't override blur region for menus that already have one. The window geometry could include shadows.
- if (shouldForceBlur(w) && !((isMenu(w) || w->isTooltip()) && (content.has_value() || geometryChanged))) {
- // On X11, EffectWindow::contentsRect() includes GTK's client-side shadows, while on Wayland, it doesn't.
- // The content region is translated by EffectWindow::contentsRect() in BlurEffect::blurRegion, causing the
- // blur region to be off on X11. The frame region is not translated, so it is used instead.
- const auto isX11WithCSD = w->isX11Client() && w->frameGeometry() != w->bufferGeometry();
- if (!isX11WithCSD) {
- content = w->contentsRect().translated(-w->contentsRect().topLeft()).toRect();
- }
- if (isX11WithCSD || (m_settings.forceBlur.blurDecorations && w->decoration())) {
- frame = w->frameGeometry().translated(-w->x(), -w->y()).toRect();
- }
- }
-
- if (content.has_value() || frame.has_value()) {
- BlurEffectData &data = m_windows[w];
- data.content = content;
- data.frame = frame;
-#ifdef KWIN_6_2_OR_GREATER
- data.windowEffect = ItemEffect(w->windowItem());
-#endif
- } else if (!geometryChanged) { // Blur may disappear if this method is called when window geometry changes
- if (auto it = m_windows.find(w); it != m_windows.end()) {
- effects->makeOpenGLContextCurrent();
- m_windows.erase(it);
- }
- }
-}
-
-bool BlurEffect::hasStaticBlur(EffectWindow *w)
-{
- if (!m_settings.staticBlur.enable) {
- return false;
- }
-
- if (m_settings.staticBlur.disableWhenWindowBehind) {
- if (auto it = m_windows.find(w); it != m_windows.end()) {
- return !it->second.hasWindowBehind;
- }
- }
-
- return true;
-}
-
void BlurEffect::slotWindowAdded(EffectWindow *w)
{
- SurfaceInterface *surf = w->surface();
-
- if (surf) {
- windowBlurChangedConnections[w] = connect(surf, &SurfaceInterface::blurChanged, this, [this, w]() {
- if (w) {
- updateBlurRegion(w);
- }
- });
- }
+ m_windows[w] = std::make_unique(w, m_windowRules.get(), &net_wm_blur_region);
windowFrameGeometryChangedConnections[w] = connect(w, &EffectWindow::windowFrameGeometryChanged, this, [this,w]() {
- if (!w) {
- return;
- }
-
- if (w->isDesktop() && !effects->waylandDisplay()) {
- m_staticBlurTextures.erase(nullptr);
- return;
+ if (w && w->isDesktop() && !effects->waylandDisplay()) {
+ m_staticBlurTextures.clear();
}
-
- updateBlurRegion(w, true);
});
if (auto internal = w->internalWindow()) {
internal->installEventFilter(this);
}
-
- connect(w, &EffectWindow::windowDecorationChanged, this, &BlurEffect::setupDecorationConnections);
- setupDecorationConnections(w);
-
- updateBlurRegion(w);
-
- m_allWindows.push_back(w);
}
void BlurEffect::slotWindowDeleted(EffectWindow *w)
@@ -377,27 +265,18 @@ void BlurEffect::slotWindowDeleted(EffectWindow *w)
effects->makeOpenGLContextCurrent();
m_windows.erase(it);
}
- if (auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) {
- disconnect(*it);
- windowBlurChangedConnections.erase(it);
- }
if (auto it = windowFrameGeometryChangedConnections.find(w); it != windowFrameGeometryChangedConnections.end()) {
disconnect(*it);
windowFrameGeometryChangedConnections.erase(it);
}
- if (auto it = std::find(m_allWindows.begin(), m_allWindows.end(), w); it != m_allWindows.end()) {
- m_allWindows.erase(it);
- }
- if (m_blurWhenTransformed.contains(w)) {
- m_blurWhenTransformed.removeOne(w);
- }
+ m_windows.erase(w);
}
void BlurEffect::slotScreenAdded(KWin::Output *screen)
{
screenChangedConnections[screen] = connect(screen, &Output::changed, this, [this, screen]() {
- if (!m_settings.staticBlur.enable) {
+ if (m_staticBlurTextures.empty()) {
return;
}
@@ -408,10 +287,10 @@ void BlurEffect::slotScreenAdded(KWin::Output *screen)
void BlurEffect::slotScreenRemoved(KWin::Output *screen)
{
- for (auto &[window, data] : m_windows) {
- if (auto it = data.render.find(screen); it != data.render.end()) {
+ for (auto &[_, w] : m_windows) {
+ if (auto it = w->render.find(screen); it != w->render.end()) {
effects->makeOpenGLContextCurrent();
- data.render.erase(it);
+ w->render.erase(it);
}
}
@@ -424,27 +303,10 @@ void BlurEffect::slotScreenRemoved(KWin::Output *screen)
void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
{
if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) {
- updateBlurRegion(w);
+ m_windows[w]->updateBlurRegion();
}
}
-void BlurEffect::setupDecorationConnections(EffectWindow *w)
-{
- if (!w->decoration()) {
- return;
- }
-
- connect(w->decoration(),
-#ifdef KDECORATION3
- &KDecoration3::Decoration::blurRegionChanged
-#else
- &KDecoration2::Decoration::blurRegionChanged
-#endif
- , this, [this, w]() {
- updateBlurRegion(w);
- });
-}
-
bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
{
auto internal = qobject_cast(watched);
@@ -452,7 +314,7 @@ bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
QDynamicPropertyChangeEvent *pe = static_cast(event);
if (pe->propertyName() == "kwin_blur") {
if (auto w = effects->findWindow(internal)) {
- updateBlurRegion(w);
+ m_windows[w]->updateBlurRegion();
}
}
}
@@ -473,57 +335,11 @@ bool BlurEffect::supported()
#endif
}
-bool BlurEffect::decorationSupportsBlurBehind(const EffectWindow *w) const
-{
- return w->decoration() && !w->decoration()->blurRegion().isNull();
-}
-
-QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const
-{
- if (!decorationSupportsBlurBehind(w)) {
- return QRegion();
- }
-
- QRect decorationRect = w->decoration()->rect()
-#ifdef KDECORATION3
- .toAlignedRect()
-#endif
- ;
- QRegion decorationRegion = QRegion(decorationRect) - w->contentsRect().toRect();
- //! we return only blurred regions that belong to decoration region
- return decorationRegion.intersected(w->decoration()->blurRegion());
-}
-
-QRegion BlurEffect::blurRegion(EffectWindow *w) const
-{
- QRegion region;
-
- if (auto it = m_windows.find(w); it != m_windows.end()) {
- const std::optional &content = it->second.content;
- const std::optional &frame = it->second.frame;
- if (content.has_value()) {
- if (content->isEmpty()) {
- // An empty region means that the blur effect should be enabled
- // for the whole window.
- region = w->rect().toRect();
- } else {
- if (frame.has_value()) {
- region = frame.value();
- }
- region += content->translated(w->contentsRect().topLeft().toPoint()) & w->contentsRect().toRect();
- }
- } else if (frame.has_value()) {
- region = frame.value();
- }
- }
-
- return region;
-}
-
void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
{
- m_paintedArea = QRegion();
- m_currentBlur = QRegion();
+ m_paintedArea = {};
+ m_currentBlur = {};
+ m_windowGeometriesSum = {};
m_currentScreen = effects->waylandDisplay() ? data.screen : nullptr;
effects->prePaintScreen(data, presentTime);
@@ -533,26 +349,29 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::
{
// this effect relies on prePaintWindow being called in the bottom to top order
- // in case this window has regions to be blurred
- const QRegion blurArea = blurRegion(w).translated(w->pos().toPoint());
+ const auto &blurWindow = m_windows[w];
- bool staticBlur = hasStaticBlur(w) && m_staticBlurTextures.contains(m_currentScreen) && !blurArea.isEmpty();
- if (staticBlur) {
- if (!m_settings.general.windowOpacityAffectsBlur) {
- data.opaque += blurArea;
+ if (!w->isDesktop()) {
+ const auto hadWindowBehind = blurWindow->hasWindowBehind();
+ blurWindow->setHasWindowBehind(m_windowGeometriesSum.intersects(w->frameGeometry().toRect()));
+ if (hadWindowBehind != blurWindow->hasWindowBehind()) {
+ data.paint += w->expandedGeometry().toRect();
}
+ }
- int topCornerRadius;
- int bottomCornerRadius;
- if (isMenu(w)) {
- topCornerRadius = bottomCornerRadius = std::ceil(m_settings.roundedCorners.menuRadius);
- } else if (w->isDock()) {
- topCornerRadius = bottomCornerRadius = std::ceil(m_settings.roundedCorners.dockRadius);
- } else {
- topCornerRadius = std::ceil(m_settings.roundedCorners.windowTopRadius);
- bottomCornerRadius = std::ceil(m_settings.roundedCorners.windowBottomRadius);
+ const QRegion blurArea = blurWindow->blurRegion().translated(w->pos().toPoint());
+
+ bool staticBlur = blurWindow->properties()->staticBlur()
+ && !blurArea.isEmpty()
+ && m_staticBlurTextures.contains(m_currentScreen)
+ && m_staticBlurTextures[m_currentScreen].contains(blurWindow->properties()->blurStrength());
+ if (staticBlur) {
+ if (!blurWindow->properties()->windowOpacityAffectsBlur()) {
+ data.opaque += blurArea;
}
+ const int topCornerRadius = std::ceil(blurWindow->properties()->topCornerRadius());
+ const int bottomCornerRadius = std::ceil(blurWindow->properties()->bottomCornerRadius());
if (!w->isDock() || (w->isDock() && isDockFloating(w, blurArea))) {
const QRect blurRect = blurArea.boundingRect();
data.opaque -= QRect(blurRect.x(), blurRect.y(), topCornerRadius, topCornerRadius);
@@ -562,40 +381,14 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::
}
}
- if (m_settings.staticBlur.enable) {
- if (m_settings.staticBlur.disableWhenWindowBehind) {
- if (auto it = m_windows.find(w); it != m_windows.end()) {
- const bool hadWindowBehind = it->second.hasWindowBehind;
- it->second.hasWindowBehind = false;
- for (EffectWindow *other : m_allWindows) {
- if (w->window()->stackingOrder() <= other->window()->stackingOrder()
- || other->isDesktop()
- || !other->isOnCurrentDesktop()
- || !other->isOnCurrentActivity()
- || other->window()->resourceClass() == "xwaylandvideobridge"
- || other->isMinimized()) {
- continue;
- }
-
- if (w->frameGeometry().intersects(other->frameGeometry())) {
- it->second.hasWindowBehind = true;
- break;
- }
- }
-
- if (hadWindowBehind != it->second.hasWindowBehind) {
- data.paint += blurArea;
- data.opaque -= blurArea;
- }
- }
- }
-
- if (m_settings.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper && w->isDesktop() && w->frameGeometry() == data.paint.boundingRect()) {
- m_staticBlurTextures.erase(m_currentScreen);
- }
+ if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()
+ && !m_staticBlurTextures.empty()
+ && w->isDesktop()
+ && w->frameGeometry() == data.paint.boundingRect()) {
+ m_staticBlurTextures.erase(m_currentScreen);
}
- if (m_settings.forceBlur.markWindowAsTranslucent && !staticBlur && shouldForceBlur(w)) {
+ if (!staticBlur && blurWindow->properties()->forceTransparency()) {
data.setTranslucent();
}
@@ -606,8 +399,10 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::
if (data.opaque.intersects(m_currentBlur)) {
// to blur an area partially we have to shrink the opaque area of a window
QRegion newOpaque;
+ auto iterationCount = blurStrengthValues[blurWindow->properties()->blurStrength() - 1].iteration;
+ auto expandSize = blurOffsets[iterationCount - 1].expandSize;
for (const QRect &rect : data.opaque) {
- newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize);
+ newOpaque += rect.adjusted(expandSize, expandSize, -expandSize, -expandSize);
}
data.opaque = newOpaque;
@@ -637,67 +432,26 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::
m_paintedArea -= data.opaque;
m_paintedArea += data.paint;
-}
-
-bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data)
-{
- const bool hasForceBlurRole = w->data(WindowForceBlurRole).toBool();
- if ((effects->activeFullScreenEffect() && !hasForceBlurRole) || w->isDesktop()) {
- return false;
+ if (!w->isDesktop() && w->window()->resourceClass() != "xwaylandvideobridge") {
+ m_windowGeometriesSum += w->frameGeometry().toRect();
}
-
- bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
- bool translated = data.xTranslation() || data.yTranslation();
- if (!(scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED)))) {
- if (m_blurWhenTransformed.contains(w)) {
- m_blurWhenTransformed.removeOne(w);
- }
-
- return true;
- }
-
- // The force blur role may be removed while the window is still transformed, causing the blur to disappear for
- // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore.
- if (m_blurWhenTransformed.contains(w)) {
- return true;
- } else if (hasForceBlurRole) {
- m_blurWhenTransformed.append(w);
- }
-
- return hasForceBlurRole;
-}
-
-bool BlurEffect::shouldForceBlur(const EffectWindow *w) const
-{
- if (w->isDesktop() || (!m_settings.forceBlur.blurDocks && w->isDock()) || (!m_settings.forceBlur.blurMenus && isMenu(w))) {
- return false;
- }
-
- bool matches = m_settings.forceBlur.windowClasses.contains(w->window()->resourceName())
- || m_settings.forceBlur.windowClasses.contains(w->window()->resourceClass());
- return (matches && m_settings.forceBlur.windowClassMatchingMode == WindowClassMatchingMode::Whitelist)
- || (!matches && m_settings.forceBlur.windowClassMatchingMode == WindowClassMatchingMode::Blacklist);
}
void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data)
{
- auto it = m_windows.find(w);
- if (it != m_windows.end()) {
- BlurEffectData &blurInfo = it->second;
- BlurRenderData &renderInfo = blurInfo.render[m_currentScreen];
- if (shouldBlur(w, mask, data)) {
- blur(renderInfo, renderTarget, viewport, w, mask, region, data);
- }
+ auto blurWindow = m_windows[w].get();
+ if (blurWindow->shouldBlur(mask, data)) {
+ blur(blurWindow, blurWindow->properties()->blurStrength() - 1, blurWindow->render[m_currentScreen], renderTarget, viewport, mask, region, data);
}
// Draw the window over the blurred area
effects->drawWindow(renderTarget, viewport, w, mask, region, data);
}
-GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const RenderTarget &renderTarget)
+GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const int &strength, const RenderTarget &renderTarget)
{
- if (m_staticBlurTextures.contains(output)) {
- return m_staticBlurTextures[output].get();
+ if (m_staticBlurTextures.contains(output) && m_staticBlurTextures[output].contains(strength)) {
+ return m_staticBlurTextures[output][strength].get();
}
if (effects->waylandDisplay() && !output) {
@@ -709,23 +463,23 @@ GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const Rende
textureFormat = renderTarget.texture()->internalFormat();
}
GLTexture *texture = effects->waylandDisplay()
- ? createStaticBlurTextureWayland(output, renderTarget, textureFormat)
- : createStaticBlurTextureX11(textureFormat);
+ ? createStaticBlurTextureWayland(output, strength, renderTarget, textureFormat)
+ : createStaticBlurTextureX11(strength, textureFormat);
if (!texture) {
return nullptr;
}
- return (m_staticBlurTextures[output] = std::unique_ptr(texture)).get();
+ return (m_staticBlurTextures[output][strength] = std::unique_ptr(texture)).get();
}
GLTexture *BlurEffect::ensureNoiseTexture()
{
- if (m_settings.general.noiseStrength == 0) {
+ if (m_noiseStrength == 0) {
return nullptr;
}
const qreal scale = std::max(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0);
- if (!noiseTexture || noiseTextureScale != scale || noiseTextureStength != m_settings.general.noiseStrength) {
+ if (!noiseTexture || noiseTextureScale != scale || noiseTextureStength != m_noiseStrength) {
// Init randomness based on time
std::srand((uint)QTime::currentTime().msec());
@@ -735,7 +489,7 @@ GLTexture *BlurEffect::ensureNoiseTexture()
uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y);
for (int x = 0; x < noiseImage.width(); x++) {
- noiseImageLine[x] = std::rand() % m_settings.general.noiseStrength;
+ noiseImageLine[x] = std::rand() % m_noiseStrength;
}
}
@@ -748,16 +502,18 @@ GLTexture *BlurEffect::ensureNoiseTexture()
noiseTexture->setFilter(GL_NEAREST);
noiseTexture->setWrapMode(GL_REPEAT);
noiseTextureScale = scale;
- noiseTextureStength = m_settings.general.noiseStrength;
+ noiseTextureStength = m_noiseStrength;
}
return noiseTexture.get();
}
-void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data)
+void BlurEffect::blur(BetterBlur::Window *w, const int &strength, BetterBlur::BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, WindowPaintData &data)
{
+ Q_UNUSED(mask)
+
// Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape.
- QRegion blurShape = w ? blurRegion(w).translated(w->pos().toPoint()) : region;
+ QRegion blurShape = w ? w->blurRegion().translated(w->window()->pos().toPoint()) : region;
if (data.xScale() != 1 || data.yScale() != 1) {
QPoint pt = blurShape.boundingRect().topLeft();
QRegion scaledShape;
@@ -775,8 +531,8 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
const QRect backgroundRect = blurShape.boundingRect();
const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale()));
- const auto opacity = w && m_settings.general.windowOpacityAffectsBlur
- ? w->opacity() * data.opacity()
+ const auto opacity = w && w->properties()->windowOpacityAffectsBlur()
+ ? w->window()->opacity() * data.opacity()
: data.opacity();
QList effectiveShape;
@@ -803,20 +559,11 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
float topCornerRadius = 0;
float bottomCornerRadius = 0;
- if (w && !(w->isDock() && !isDockFloating(w, blurShape))) {
- const bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry();
- if (isMenu(w)) {
- topCornerRadius = bottomCornerRadius = m_settings.roundedCorners.menuRadius;
- } else if (w->isDock()) {
- topCornerRadius = bottomCornerRadius = m_settings.roundedCorners.dockRadius;
- } else if ((!w->isFullScreen() && !isMaximized) || m_settings.roundedCorners.roundMaximized) {
- if (!w->decoration() || (w->decoration() && m_settings.forceBlur.blurDecorations)) {
- topCornerRadius = m_settings.roundedCorners.windowTopRadius;
- }
- bottomCornerRadius = m_settings.roundedCorners.windowBottomRadius;
+ if (w && !(w->window()->isDock() && !isDockFloating(w->window(), blurShape))) {
+ if (!w->window()->decoration() || (w->window()->decoration() && w->properties()->blurDecorations())) {
+ topCornerRadius = w->properties()->topCornerRadius() * viewport.scale();
}
- topCornerRadius = topCornerRadius * viewport.scale();
- bottomCornerRadius = bottomCornerRadius * viewport.scale();
+ bottomCornerRadius = w->properties()->bottomCornerRadius() * viewport.scale();
}
// Maybe reallocate offscreen render targets. Keep in mind that the first one contains
@@ -829,22 +576,25 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
// Since the VBO is shared, the texture needs to be blurred before the geometry is uploaded, otherwise it will be
// reset.
GLTexture *staticBlurTexture = nullptr;
- if (w && hasStaticBlur(w)) {
- staticBlurTexture = ensureStaticBlurTexture(m_currentScreen, renderTarget);
+ if (w && w->properties()->staticBlur()) {
+ staticBlurTexture = ensureStaticBlurTexture(m_currentScreen, strength, renderTarget);
if (staticBlurTexture) {
renderInfo.textures.clear();
renderInfo.framebuffers.clear();
}
}
+ size_t iterationCount = blurStrengthValues[strength].iteration;
+ auto offset = blurStrengthValues[strength].offset;
+
if (!staticBlurTexture
- && (renderInfo.framebuffers.size() != (m_iterationCount + 1)
+ && (renderInfo.framebuffers.size() != (iterationCount + 1)
|| renderInfo.textures[0]->size() != backgroundRect.size()
|| renderInfo.textures[0]->internalFormat() != textureFormat)) {
renderInfo.framebuffers.clear();
renderInfo.textures.clear();
- for (size_t i = 0; i <= m_iterationCount; ++i) {
+ for (size_t i = 0; i <= iterationCount; ++i) {
auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i));
if (!texture) {
qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture";
@@ -994,7 +744,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
m_texture.shader->setUniform(m_texture.scaleLocation, (float)viewport.scale());
m_texture.shader->setUniform(m_texture.topCornerRadiusLocation, topCornerRadius);
m_texture.shader->setUniform(m_texture.bottomCornerRadiusLocation, bottomCornerRadius);
- m_texture.shader->setUniform(m_texture.antialiasingLocation, m_settings.roundedCorners.antialiasing);
+ m_texture.shader->setUniform(m_texture.antialiasingLocation, w->properties()->cornerAntialiasing());
m_texture.shader->setUniform(m_texture.opacityLocation, static_cast(opacity));
staticBlurTexture->bind();
@@ -1015,7 +765,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix);
- m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset));
+ m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, offset);
m_downsamplePass.shader->setUniform(m_downsamplePass.colorMatrixLocation, m_colorMatrix);
m_downsamplePass.shader->setUniform(m_downsamplePass.transformColorsLocation, true);
@@ -1050,7 +800,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
m_upsamplePass.shader->setUniform(m_upsamplePass.bottomCornerRadiusLocation, static_cast(0));
m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
m_upsamplePass.shader->setUniform(m_upsamplePass.noiseLocation, false);
- m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset));
+ m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, offset);
for (size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) {
GLFramebuffer::popFramebuffer();
@@ -1069,7 +819,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
GLFramebuffer::popFramebuffer();
const auto &read = renderInfo.framebuffers[1];
- if (m_settings.general.noiseStrength > 0) {
+ if (m_noiseStrength > 0) {
if (auto *noiseTexture = ensureNoiseTexture()) {
m_upsamplePass.shader->setUniform(m_upsamplePass.noiseLocation, true);
m_upsamplePass.shader->setUniform(m_upsamplePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height()));
@@ -1086,7 +836,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
m_upsamplePass.shader->setUniform(m_upsamplePass.topCornerRadiusLocation, topCornerRadius);
m_upsamplePass.shader->setUniform(m_upsamplePass.bottomCornerRadiusLocation, bottomCornerRadius);
- m_upsamplePass.shader->setUniform(m_upsamplePass.antialiasingLocation, m_settings.roundedCorners.antialiasing);
+ m_upsamplePass.shader->setUniform(m_upsamplePass.antialiasingLocation, static_cast(w ? w->properties()->cornerAntialiasing() : 0.0));
m_upsamplePass.shader->setUniform(m_upsamplePass.blurSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height()));
m_upsamplePass.shader->setUniform(m_upsamplePass.opacityLocation, static_cast(opacity));
@@ -1110,18 +860,18 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg
vbo->unbindArrays();
}
-void BlurEffect::blur(GLTexture *texture)
+void BlurEffect::blur(GLTexture *texture, const int &strength)
{
const QRect textureRect = QRect(0, 0, texture->width(), texture->height());
auto blurredFramebuffer = std::make_unique(texture);
- BlurRenderData renderData;
+ BetterBlur::BlurRenderData renderInfo;
const RenderTarget renderTarget(blurredFramebuffer.get());
const RenderViewport renderViewport(textureRect, 1.0, renderTarget);
WindowPaintData data;
GLFramebuffer::pushFramebuffer(blurredFramebuffer.get());
- blur(renderData, renderTarget, renderViewport, nullptr, 0, textureRect, data);
+ blur(nullptr, strength, renderInfo, renderTarget, renderViewport, 0, textureRect, data);
GLFramebuffer::popFramebuffer();
}
@@ -1157,7 +907,7 @@ GLTexture *BlurEffect::wallpaper(EffectWindow *desktop, const qreal &scale, cons
return texture.release();
}
-GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat)
+GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, const int &strength, const RenderTarget &renderTarget, const GLenum &textureFormat)
{
EffectWindow *desktop = nullptr;
for (EffectWindow *w : effects->stackingOrder()) {
@@ -1171,10 +921,10 @@ GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, cons
}
std::unique_ptr texture;
- if (m_settings.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper) {
+ if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) {
texture.reset(wallpaper(desktop, output->scale(), textureFormat));
- } else if (m_settings.staticBlur.imageSource == StaticBlurImageSource::Custom) {
- texture = GLTexture::upload(m_settings.staticBlur.customImage.scaled(output->pixelSize(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation));
+ } else if (BetterBlur::Config::fakeBlurImageSourceCustom()) {
+ texture = GLTexture::upload(m_staticBlurImage.scaled(output->pixelSize(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation));
}
if (!texture) {
return nullptr;
@@ -1209,14 +959,14 @@ GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, cons
GLFramebuffer::popFramebuffer();
ShaderManager::instance()->popShader();
- if (m_settings.staticBlur.blurCustomImage) {
- blur(texture.get());
+ if (BetterBlur::Config::fakeBlurCustomImageBlur()) {
+ blur(texture.get(), strength);
}
return texture.release();
}
-GLTexture *BlurEffect::createStaticBlurTextureX11(const GLenum &textureFormat)
+GLTexture *BlurEffect::createStaticBlurTextureX11(const int &strength, const GLenum &textureFormat)
{
std::vector desktops;
QRegion desktopGeometries;
@@ -1243,17 +993,17 @@ GLTexture *BlurEffect::createStaticBlurTextureX11(const GLenum &textureFormat)
const auto geometry = desktop->frameGeometry();
std::unique_ptr texture;
- if (m_settings.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper) {
+ if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) {
texture.reset(wallpaper(desktop, 1, textureFormat));
- } else if (m_settings.staticBlur.imageSource == StaticBlurImageSource::Custom) {
- texture = GLTexture::upload(m_settings.staticBlur.customImage.scaled(geometry.width(), geometry.height(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation));
+ } else if (BetterBlur::Config::fakeBlurImageSourceCustom()) {
+ texture = GLTexture::upload(m_staticBlurImage.scaled(geometry.width(), geometry.height(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation));
}
if (!texture) {
return nullptr;
}
- if (m_settings.staticBlur.blurCustomImage) {
- blur(texture.get());
+ if (BetterBlur::Config::fakeBlurCustomImageBlur()) {
+ blur(texture.get(), strength);
}
QMatrix4x4 projectionMatrix;
diff --git a/src/blur.h b/src/blur.h
index bc32511794..c9b96faa33 100644
--- a/src/blur.h
+++ b/src/blur.h
@@ -7,51 +7,21 @@
#pragma once
+#include "blurwindow.h"
+#include "windowrules/windowrulelist.h"
+
#include "effect/effect.h"
#include "opengl/glutils.h"
-#ifdef KWIN_6_2_OR_GREATER
-#include "scene/item.h"
-#endif
-
-#include "settings.h"
-#include "window.h"
#include
#include
-
namespace KWin
{
class BlurManagerInterface;
-struct BlurRenderData
-{
- /// Temporary render targets needed for the Dual Kawase algorithm, the first texture
- /// contains not blurred background behind the window, it's cached.
- std::vector> textures;
- std::vector> framebuffers;
-};
-
-struct BlurEffectData
-{
- /// The region that should be blurred behind the window
- std::optional content;
-
- /// The region that should be blurred behind the frame
- std::optional frame;
-
- /// The render data per screen. Screens can have different color spaces.
- std::unordered_map