scene/item: add ItemEffect

Right now it's just a helper to mark items as being affected by some effect,
to prevent direct scanout of the relevant item without needing to block direct
scanout for the whole screen
This commit is contained in:
Xaver Hugl 2024-08-03 15:36:36 +02:00
parent 418143a45b
commit 18f1092728
17 changed files with 152 additions and 46 deletions

View file

@ -71,7 +71,7 @@ void DontCrashCancelAnimationFromAnimationEndedTest::cleanup()
void DontCrashCancelAnimationFromAnimationEndedTest::testScript()
{
// load a scripted effect which deletes animation data
ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10, QString(), true);
ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10, QString());
QVERIFY(effect);
const auto children = effects->children();

View file

@ -11,6 +11,7 @@
#include "effect/effecthandler.h"
#include "opengl/gltexture.h"
#include "opengl/glutils.h"
#include "scene/windowitem.h"
namespace KWin
{
@ -34,6 +35,7 @@ public:
GLShader *m_shader = nullptr;
RenderGeometry::VertexSnappingMode m_vertexSnappingMode = RenderGeometry::VertexSnappingMode::Round;
QMetaObject::Connection m_windowDamagedConnection;
ItemEffect m_windowEffect;
};
class OffscreenEffectPrivate
@ -65,7 +67,7 @@ void OffscreenEffect::redirect(EffectWindow *window)
}
offscreenData = std::make_unique<OffscreenData>();
offscreenData->setVertexSnappingMode(d->vertexSnappingMode);
offscreenData->m_windowEffect = ItemEffect(window->windowItem());
offscreenData->m_windowDamagedConnection =
connect(window, &EffectWindow::windowDamaged, this, &OffscreenEffect::handleWindowDamaged);
@ -274,6 +276,11 @@ void OffscreenEffect::setVertexSnappingMode(RenderGeometry::VertexSnappingMode m
}
}
bool OffscreenEffect::blocksDirectScanout() const
{
return false;
}
class CrossFadeWindowData : public OffscreenData
{
public:
@ -356,6 +363,7 @@ void CrossFadeEffect::redirect(EffectWindow *window)
return;
}
offscreenData = std::make_unique<CrossFadeWindowData>();
offscreenData->m_windowEffect = ItemEffect(window->windowItem());
// Avoid including blur and contrast effects. During a normal painting cycle they
// won't be included, but since we call effects->drawWindow() outside usual compositing
@ -393,6 +401,11 @@ void CrossFadeEffect::setShader(EffectWindow *window, GLShader *shader)
}
}
bool CrossFadeEffect::blocksDirectScanout() const
{
return false;
}
} // namespace KWin
#include "moc_offscreeneffect.cpp"

View file

@ -72,6 +72,8 @@ protected:
*/
void setVertexSnappingMode(RenderGeometry::VertexSnappingMode mode);
bool blocksDirectScanout() const override;
private Q_SLOTS:
void handleWindowDamaged(EffectWindow *window);
void handleWindowDeleted(EffectWindow *window);
@ -121,6 +123,8 @@ public:
**/
void setShader(EffectWindow *window, GLShader *shader);
bool blocksDirectScanout() const override;
static bool supported();
private:

View file

@ -13,6 +13,8 @@
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "effect/effecthandler.h"
#include "scene/surfaceitem.h"
#include "scene/windowitem.h"
#include "wayland/contrast.h"
#include "wayland/display.h"
#include "wayland/surface.h"
@ -174,6 +176,7 @@ void ContrastEffect::updateContrastRegion(EffectWindow *w)
Data &data = m_windowData[w];
data.colorMatrix = matrix;
data.contrastRegion = region;
data.surfaceEffect = ItemEffect(w->windowItem()->surfaceItem());
} else {
if (auto it = m_windowData.find(w); it != m_windowData.end()) {
effects->makeOpenGLContextCurrent();

View file

@ -10,6 +10,7 @@
#include "effect/effect.h"
#include "opengl/glplatform.h"
#include "opengl/glutils.h"
#include "scene/item.h"
#include <QList>
#include <QVector2D>
@ -75,6 +76,7 @@ private:
QRegion contrastRegion;
std::unique_ptr<GLTexture> texture;
std::unique_ptr<GLFramebuffer> fbo;
ItemEffect surfaceEffect;
};
std::unordered_map<const EffectWindow *, Data> m_windowData;
static ContrastManagerInterface *s_contrastManager;

View file

@ -15,6 +15,9 @@
#include "core/renderviewport.h"
#include "effect/effecthandler.h"
#include "opengl/glplatform.h"
#include "scene/decorationitem.h"
#include "scene/surfaceitem.h"
#include "scene/windowitem.h"
#include "wayland/blur.h"
#include "wayland/display.h"
#include "wayland/surface.h"
@ -263,6 +266,12 @@ void BlurEffect::updateBlurRegion(EffectWindow *w)
BlurEffectData &data = m_windows[w];
data.content = content;
data.frame = frame;
if (content) {
data.surfaceEffect = ItemEffect(w->windowItem()->surfaceItem());
}
if (frame) {
data.surfaceEffect = ItemEffect(w->windowItem()->decorationItem());
}
} else {
if (auto it = m_windows.find(w); it != m_windows.end()) {
effects->makeOpenGLContextCurrent();

View file

@ -9,6 +9,7 @@
#include "effect/effect.h"
#include "opengl/glutils.h"
#include "scene/item.h"
#include <QList>
@ -31,9 +32,11 @@ struct BlurEffectData
{
/// The region that should be blurred behind the window
std::optional<QRegion> content;
ItemEffect surfaceEffect;
/// The region that should be blurred behind the frame
std::optional<QRegion> frame;
ItemEffect decorationEffect;
/// The render data per screen. Screens can have different color spaces.
std::unordered_map<Output *, BlurRenderData> render;

View file

@ -105,7 +105,6 @@
"Name[zh_CN]": "气泡显隐渐变动画",
"Name[zh_TW]": "淡化彈出視窗"
},
"X-KDE-BlocksDirectScanout": false,
"X-KDE-Ordering": 60,
"X-Plasma-API": "javascript"
}

View file

@ -19,6 +19,7 @@
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "effect/effecthandler.h"
#include "scene/windowitem.h"
// Qt
#include <QMatrix4x4>
@ -79,7 +80,7 @@ void GlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:
{
auto animationIt = m_animations.find(w);
if (animationIt != m_animations.end()) {
(*animationIt).timeLine.advance(presentTime);
animationIt->second.timeLine.advance(presentTime);
data.setTransformed();
}
@ -88,13 +89,13 @@ void GlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:
void GlideEffect::apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads)
{
auto animationIt = m_animations.constFind(window);
if (animationIt == m_animations.constEnd()) {
auto animationIt = m_animations.find(window);
if (animationIt == m_animations.end()) {
return;
}
const GlideParams params = window->isDeleted() ? m_outParams : m_inParams;
const qreal t = (*animationIt).timeLine.value();
const qreal t = animationIt->second.timeLine.value();
const QRectF rect = window->expandedGeometry().translated(-window->pos());
const float fovY = std::tan(qDegreesToRadians(60.0f) / 2);
@ -162,8 +163,8 @@ void GlideEffect::postPaintWindow(EffectWindow *w)
if (auto animationIt = m_animations.find(w); animationIt != m_animations.end()) {
w->addRepaintFull();
if ((*animationIt).timeLine.done()) {
unredirect(animationIt.key());
if (animationIt->second.timeLine.done()) {
unredirect(animationIt->first);
animationIt = m_animations.erase(animationIt);
} else {
++animationIt;
@ -175,7 +176,7 @@ void GlideEffect::postPaintWindow(EffectWindow *w)
bool GlideEffect::isActive() const
{
return !m_animations.isEmpty();
return !m_animations.empty();
}
bool GlideEffect::supported()
@ -210,6 +211,7 @@ void GlideEffect::windowAdded(EffectWindow *w)
animation.timeLine.setDirection(TimeLine::Forward);
animation.timeLine.setDuration(m_duration);
animation.timeLine.setEasingCurve(QEasingCurve::InCurve);
animation.effect = ItemEffect(w->windowItem());
redirect(w);
effects->addRepaintFull();
@ -255,7 +257,7 @@ void GlideEffect::windowDataChanged(EffectWindow *w, int role)
auto animationIt = m_animations.find(w);
if (animationIt != m_animations.end()) {
unredirect(animationIt.key());
unredirect(animationIt->first);
m_animations.erase(animationIt);
}
}
@ -301,6 +303,11 @@ bool GlideEffect::isGlideWindow(EffectWindow *w) const
|| w->isDialog();
}
bool GlideEffect::blocksDirectScanout() const
{
return false;
}
} // namespace KWin
#include "moc_glide.cpp"

View file

@ -16,6 +16,9 @@
#include "effect/effectwindow.h"
#include "effect/offscreeneffect.h"
#include "effect/timeline.h"
#include "scene/item.h"
#include <unordered_map>
namespace KWin
{
@ -24,6 +27,7 @@ struct GlideAnimation
{
EffectWindowDeletedRef deletedRef;
TimeLine timeLine;
ItemEffect effect;
};
class GlideEffect : public OffscreenEffect
@ -68,6 +72,7 @@ public:
qreal outRotationAngle() const;
qreal outDistance() const;
qreal outOpacity() const;
bool blocksDirectScanout() const override;
protected:
void apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) override;
@ -81,7 +86,7 @@ private:
bool isGlideWindow(EffectWindow *w) const;
std::chrono::milliseconds m_duration;
QHash<EffectWindow *, GlideAnimation> m_animations;
std::unordered_map<EffectWindow *, GlideAnimation> m_animations;
struct GlideParams
{

View file

@ -12,6 +12,7 @@
#include "slidingpopupsconfig.h"
#include "effect/effecthandler.h"
#include "scene/windowitem.h"
#include "wayland/display.h"
#include "wayland/slide.h"
#include "wayland/surface.h"
@ -107,13 +108,8 @@ void SlidingPopupsEffect::reconfigure(ReconfigureFlags flags)
m_slideOutDuration = std::chrono::milliseconds(
static_cast<int>(animationTime(SlidingPopupsConfig::slideOutTime() != 0 ? std::chrono::milliseconds(SlidingPopupsConfig::slideOutTime()) : 200ms)));
auto animationIt = m_animations.begin();
while (animationIt != m_animations.end()) {
const auto duration = ((*animationIt).kind == AnimationKind::In)
? m_slideInDuration
: m_slideOutDuration;
(*animationIt).timeLine.setDuration(duration);
++animationIt;
for (auto &[window, animation] : m_animations) {
animation.timeLine.setDuration(animation.kind == AnimationKind::In ? m_slideInDuration : m_slideOutDuration);
}
auto dataIt = m_animationsData.begin();
@ -132,7 +128,7 @@ void SlidingPopupsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &da
return;
}
(*animationIt).timeLine.advance(presentTime);
animationIt->second.timeLine.advance(presentTime);
data.setTransformed();
effects->prePaintWindow(w, data, presentTime);
@ -140,8 +136,8 @@ void SlidingPopupsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &da
void SlidingPopupsEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
{
auto animationIt = m_animations.constFind(w);
if (animationIt == m_animations.constEnd()) {
auto animationIt = m_animations.find(w);
if (animationIt == m_animations.end()) {
effects->paintWindow(renderTarget, viewport, w, mask, region, data);
return;
}
@ -152,7 +148,7 @@ void SlidingPopupsEffect::paintWindow(const RenderTarget &renderTarget, const Re
const QRectF screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop());
int splitPoint = 0;
const QRectF geo = w->expandedGeometry();
const qreal t = (*animationIt).timeLine.value();
const qreal t = animationIt->second.timeLine.value();
switch (animData.location) {
case Location::Left:
@ -197,7 +193,7 @@ void SlidingPopupsEffect::postPaintWindow(EffectWindow *w)
auto animationIt = m_animations.find(w);
if (animationIt != m_animations.end()) {
effects->addRepaint(w->expandedGeometry());
if ((*animationIt).timeLine.done()) {
if (animationIt->second.timeLine.done()) {
if (!w->isDeleted()) {
w->setData(WindowForceBackgroundContrastRole, QVariant());
w->setData(WindowForceBlurRole, QVariant());
@ -292,7 +288,7 @@ void SlidingPopupsEffect::slotPropertyNotify(EffectWindow *w, long atom)
if (w->data(WindowClosedGrabRole).value<void *>() == this) {
w->setData(WindowClosedGrabRole, QVariant());
}
m_animations.remove(w);
m_animations.erase(w);
m_animationsData.remove(w);
return;
}
@ -531,6 +527,7 @@ void SlidingPopupsEffect::slideIn(EffectWindow *w)
animation.timeLine.setDirection(TimeLine::Forward);
animation.timeLine.setDuration((*dataIt).slideInDuration);
animation.timeLine.setEasingCurve(QEasingCurve::OutCubic);
animation.windowEffect = ItemEffect(w->windowItem());
// If the opposite animation (Out) was active and it had shorter duration,
// at this point, the timeline can end up in the "done" state. Thus, we have
@ -586,12 +583,10 @@ void SlidingPopupsEffect::slideOut(EffectWindow *w)
void SlidingPopupsEffect::stopAnimations()
{
for (auto it = m_animations.constBegin(); it != m_animations.constEnd(); ++it) {
EffectWindow *w = it.key();
if (!w->isDeleted()) {
w->setData(WindowForceBackgroundContrastRole, QVariant());
w->setData(WindowForceBlurRole, QVariant());
for (const auto &[window, animation] : m_animations) {
if (!window->isDeleted()) {
window->setData(WindowForceBackgroundContrastRole, QVariant());
window->setData(WindowForceBlurRole, QVariant());
}
}
@ -600,7 +595,12 @@ void SlidingPopupsEffect::stopAnimations()
bool SlidingPopupsEffect::isActive() const
{
return !m_animations.isEmpty();
return !m_animations.empty();
}
bool SlidingPopupsEffect::blocksDirectScanout() const
{
return false;
}
} // namespace

View file

@ -16,6 +16,7 @@
#include "effect/effect.h"
#include "effect/effectwindow.h"
#include "effect/timeline.h"
#include "scene/item.h"
namespace KWin
{
@ -49,6 +50,7 @@ public:
int slideOutDuration() const;
bool eventFilter(QObject *watched, QEvent *event) override;
bool blocksDirectScanout() const override;
private Q_SLOTS:
void slotWindowAdded(EffectWindow *w);
@ -91,8 +93,9 @@ private:
EffectWindowVisibleRef visibleRef;
AnimationKind kind;
TimeLine timeLine;
ItemEffect windowEffect;
};
QHash<EffectWindow *, Animation> m_animations;
std::unordered_map<EffectWindow *, Animation> m_animations;
enum class Location {
Left,

View file

@ -12,6 +12,34 @@
namespace KWin
{
ItemEffect::ItemEffect(Item *item)
: m_item(item)
{
item->addEffect();
}
ItemEffect::ItemEffect(ItemEffect &&move)
: m_item(std::exchange(move.m_item, nullptr))
{
}
ItemEffect::ItemEffect()
{
}
ItemEffect::~ItemEffect()
{
if (m_item) {
m_item->removeEffect();
}
}
ItemEffect &ItemEffect::operator=(ItemEffect &&move)
{
std::swap(m_item, move.m_item);
return *this;
}
Item::Item(Item *parent)
{
setParentItem(parent);
@ -516,6 +544,22 @@ void Item::setPresentationHint(PresentationModeHint hint)
m_presentationHint = hint;
}
bool Item::hasEffects() const
{
return m_effectCount != 0;
}
void Item::addEffect()
{
m_effectCount++;
}
void Item::removeEffect()
{
Q_ASSERT(m_effectCount > 0);
m_effectCount--;
}
} // namespace KWin
#include "moc_item.cpp"

View file

@ -24,6 +24,23 @@ class SceneDelegate;
class Scene;
class SyncReleasePoint;
class DrmDevice;
class Item;
class KWIN_EXPORT ItemEffect
{
public:
explicit ItemEffect(Item *item);
explicit ItemEffect(const ItemEffect &copy) = delete;
explicit ItemEffect(ItemEffect &&move);
explicit ItemEffect();
virtual ~ItemEffect();
ItemEffect &operator=(const ItemEffect &copy) = delete;
ItemEffect &operator=(ItemEffect &&move);
private:
QPointer<Item> m_item;
};
/**
* The Item class is the base class for items in the scene.
@ -116,6 +133,10 @@ public:
RenderingIntent renderingIntent() const;
PresentationModeHint presentationHint() const;
bool hasEffects() const;
void addEffect();
void removeEffect();
Q_SIGNALS:
void childAdded(Item *item);
/**
@ -173,6 +194,7 @@ private:
ColorDescription m_colorDescription = ColorDescription::sRGB;
RenderingIntent m_renderingIntent = RenderingIntent::Perceptual;
PresentationModeHint m_presentationHint = PresentationModeHint::VSync;
int m_effectCount = 0;
};
} // namespace KWin

View file

@ -177,7 +177,7 @@ static bool addCandidates(SurfaceItem *item, QList<SurfaceItem *> &candidates, s
}
}
}
if (candidates.size() >= maxCount) {
if (candidates.size() >= maxCount || item->hasEffects()) {
return false;
}
candidates.push_back(item);
@ -202,7 +202,7 @@ QList<SurfaceItem *> WorkspaceScene::scanoutCandidates(ssize_t maxCount) const
WindowItem *windowItem = stacking_order[i];
Window *window = windowItem->window();
if (window->isOnOutput(painted_screen) && window->opacity() > 0 && windowItem->isVisible()) {
if (!window->isClient() || window->opacity() != 1.0 || !window->isFullScreen()) {
if (!window->isClient() || window->opacity() != 1.0 || !window->isFullScreen() || window->windowItem()->hasEffects()) {
return {};
}
if (!windowItem->surfaceItem()) {

View file

@ -165,10 +165,10 @@ ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
return nullptr;
}
return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering"), 0), effect.value(QStringLiteral("X-KWin-Exclusive-Category")), effect.value(QStringLiteral("X-KDE-BlocksDirectScanout"), true));
return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering"), 0), effect.value(QStringLiteral("X-KWin-Exclusive-Category")));
}
ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory, bool blocksDirectScanout)
ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
{
ScriptedEffect *effect = new ScriptedEffect();
effect->m_exclusiveCategory = exclusiveCategory;
@ -177,7 +177,6 @@ ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString
return nullptr;
}
effect->m_chainPosition = chainPosition;
effect->m_blocksDirectScanout = blocksDirectScanout;
return effect;
}
@ -307,11 +306,6 @@ bool ScriptedEffect::isActiveFullScreenEffect() const
return effects->activeFullScreenEffect() == this;
}
bool ScriptedEffect::blocksDirectScanout() const
{
return m_blocksDirectScanout;
}
QList<int> ScriptedEffect::touchEdgesForAction(const QString &action) const
{
QList<int> ret;

View file

@ -80,7 +80,7 @@ public:
}
QString activeConfig() const;
void setActiveConfig(const QString &name);
static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory, bool blocksDirectScanout);
static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory);
static ScriptedEffect *create(const KPluginMetaData &effect);
static bool supported();
~ScriptedEffect() override;
@ -182,7 +182,6 @@ public:
QString pluginId() const;
bool isActiveFullScreenEffect() const;
bool blocksDirectScanout() const override;
public Q_SLOTS:
bool borderActivated(ElectricBorder border) override;
@ -223,6 +222,5 @@ private:
Effect *m_activeFullScreenEffect = nullptr;
std::map<uint, std::unique_ptr<GLShader>> m_shaders;
uint m_nextShaderId{1u};
bool m_blocksDirectScanout = true;
};
}