kwin/effectloader.h
Martin Gräßlin 055e2b3bb6 [autotests] Add a new test case which can verify the rendering of QPainter Scene
Summary:
The idea behind this autotest is inspired by bug 356328 which produced
incorrect rendering results. Also it's inspired by openQA which performs
image reference comparisons.

This test case tries to go further. It creates reference images which
must match the rendering result exactly. So far the test case verifies
the start condition - kwin started and one frame is rendered with default
cursor in the middle of the screen. And it verifies the moving of the
cursor without any windows shown. Whenever the cursor moves a repaint
should be triggered and the old and new area should be properly
repainted.

To support this the test needs some minor changes in KWin:
* Scene provides a frameRendered signal - needed for waiting on frame
* Scene and SceneQPainter are exported
* SceneQPainter provides access to it's Backend, so that we get to the
 backbuffer
* ScriptedEffectLoader is exported for getting a list of all scripted
 effects - (we don't want fade to manipulate the rendering)

Reviewers: #kwin, #plasma_on_wayland

Subscribers: plasma-devel, kwin

Tags: #plasma_on_wayland, #kwin

Differential Revision: https://phabricator.kde.org/D2046
2016-07-01 09:00:19 +02:00

379 lines
14 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_EFFECT_LOADER_H
#define KWIN_EFFECT_LOADER_H
#include <kwin_export.h>
// KDE
#include <KPluginMetaData>
#include <KSharedConfig>
// Qt
#include <QObject>
#include <QFlags>
#include <QMap>
#include <QPair>
#include <QQueue>
namespace KWin
{
class Effect;
class EffectPluginFactory;
enum class BuiltInEffect;
/**
* @brief Flags defining how a Loader should load an Effect.
*
* These Flags are only used internally when querying the configuration on whether
* an Effect should be loaded.
*
* @see AbstractEffectLoader::readConfig()
*/
enum class LoadEffectFlag {
Load = 1 << 0, ///< Effect should be loaded
CheckDefaultFunction = 1 << 2 ///< The Check Default Function needs to be invoked if the Effect provides it
};
Q_DECLARE_FLAGS(LoadEffectFlags, LoadEffectFlag);
/**
* @brief Interface to describe how an effect loader has to function.
*
* The AbstractEffectLoader specifies the methods a concrete loader has to implement and how
* those methods are expected to perform. Also it provides an interface to the outside world
* (that is EffectsHandlerImpl).
*
* The abstraction is used because there are multiple types of Effects which need to be loaded:
* @li Built-In Effects
* @li Scripted Effects
* @li Binary Plugin Effects
*
* Serving all of them with one Effect Loader is rather complex given that different stores need
* to be queried at the same time. Thus the idea is to have one implementation per type and one
* implementation which makes use of all of them and combines the loading.
*/
class AbstractEffectLoader : public QObject
{
Q_OBJECT
public:
virtual ~AbstractEffectLoader();
/**
* @brief The KSharedConfig this EffectLoader should operate on.
*
* Important: a valid KSharedConfig must be provided before trying to load any effects!
*
* @param config
* @internal
*/
virtual void setConfig(KSharedConfig::Ptr config);
/**
* @brief Whether this Effect Loader can load the Effect with the given @p name.
*
* The Effect Loader determines whether it knows or can find an Effect called @p name,
* and thus whether it can attempt to load the Effect.
*
* @param name The name of the Effect to look for.
* @return bool @c true if the Effect Loader knows this effect, false otherwise
*/
virtual bool hasEffect(const QString &name) const = 0;
/**
* @brief All the Effects this loader knows of.
*
* The implementation should re-query its store whenever this method is invoked.
* It's possible that the store of effects changed (e.g. a new one got installed)
*
* @return QStringList The internal names of the known Effects
*/
virtual QStringList listOfKnownEffects() const = 0;
/**
* @brief Synchronous loading of the Effect with the given @p name.
*
* Loads the Effect without checking any configuration value or any enabled by default
* function provided by the Effect.
*
* The loader is expected to apply the following checks:
* If the Effect is already loaded, the Effect should not get loaded again. Thus the loader
* is expected to track which Effects it has loaded, and which of those have been destroyed.
* The loader should check whether the Effect is supported. If the Effect indicates it is
* not supported, it should not get loaded.
*
* If the Effect loaded successfully the signal effectLoaded(KWin::Effect*,const QString&)
* must be emitted. Otherwise the user of the loader is not able to get the loaded Effect.
* It's not returning the Effect as queryAndLoadAll() is working async and thus the users
* of the loader are expected to be prepared for async loading.
*
* @param name The internal name of the Effect which should be loaded
* @return bool @c true if the effect could be loaded, @c false in error case
* @see queryAndLoadAll()
* @see effectLoaded(KWin::Effect*,const QString&)
*/
virtual bool loadEffect(const QString &name) = 0;
/**
* @brief The Effect Loader should query its store for all available effects and try to load them.
*
* The Effect Loader is supposed to perform this operation in a highly async way. If there is
* IO which needs to be performed this should be done in a background thread and a queue should
* be used to load the effects. The loader should make sure to not load more than one Effect
* in one event cycle. Loading the Effect has to be performed in the Compositor thread and
* thus blocks the Compositor. Therefore after loading one Effect all events should get
* processed first, so that the Compositor can perform a painting pass if needed. To simplify
* this operation one can use the EffectLoadQueue. This requires to add another loadEffect
* method with the custom loader specific type to refer to an Effect and LoadEffectFlags.
*
* The LoadEffectFlags have to be determined by querying the configuration with readConfig().
* If the Load flag is set the loading can proceed and all the checks from
* loadEffect(const QString &) have to be applied.
* In addition if the CheckDefaultFunction flag is set and the Effect provides such a method,
* it should be queried to determine whether the Effect is enabled by default. If such a method
* returns @c false the Effect should not get loaded. If the Effect does not provide a way to
* query whether it's enabled by default at runtime the flag can get ignored.
*
* If the Effect loaded successfully the signal effectLoaded(KWin::Effect*,const QString&)
* must be emitted.
*
* @see loadEffect(const QString &)
* @see effectLoaded(KWin::Effect*,const QString&)
*/
virtual void queryAndLoadAll() = 0;
/**
* @brief Whether the Effect with the given @p name is supported by the compositing backend.
*
* @param name The name of the Effect to check.
* @return bool @c true if it is supported, @c false otherwise
*/
virtual bool isEffectSupported(const QString &name) const = 0;
/**
* @brief Clears the load queue, that is all scheduled Effects are discarded from loading.
**/
virtual void clear() = 0;
Q_SIGNALS:
/**
* @brief The loader emits this signal when it successfully loaded an effect.
*
* @param effect The created Effect
* @param name The internal name of the loaded Effect
* @return void
*/
void effectLoaded(KWin::Effect *effect, const QString &name);
protected:
explicit AbstractEffectLoader(QObject *parent = nullptr);
/**
* @brief Checks the configuration for the Effect identified by @p effectName.
*
* For each Effect there could be a key called "<effectName>Enabled". If there is such a key
* the returned flags will contain Load in case it's @c true. If the key does not exist the
* @p defaultValue determines whether the Effect should be loaded. A value of @c true means
* that Load | CheckDefaultFunction is returned, in case of @c false no Load flags are returned.
*
* @param effecName The name of the Effect to look for in the configuration
* @param defaultValue Whether the Effect is enabled by default or not.
* @returns Flags indicating whether the Effect should be loaded and how it should be loaded
*/
LoadEffectFlags readConfig(const QString &effectName, bool defaultValue) const;
private:
KSharedConfig::Ptr m_config;
};
/**
* @brief Helper class to queue the loading of Effects.
*
* Loading an Effect has to be done in the compositor thread and thus the Compositor is blocked
* while the Effect loads. To not block the compositor for several frames the loading of all
* Effects need to be queued. By invoking the slot dequeue() through a QueuedConnection the queue
* can ensure that events are processed between the loading of two Effects and thus the compositor
* doesn't block.
*
* As it needs to be a slot, the queue must subclass QObject, but it also needs to be templated as
* the information to load an Effect is specific to the Effect Loader. Thus there is the
* AbstractEffectLoadQueue providing the slots as pure virtual functions and the templated
* EffectLoadQueue inheriting from AbstractEffectLoadQueue.
*
* The queue operates like a normal queue providing enqueue and a scheduleDequeue instead of dequeue.
*
*/
class AbstractEffectLoadQueue : public QObject
{
Q_OBJECT
public:
explicit AbstractEffectLoadQueue(QObject *parent = nullptr)
: QObject(parent)
{
}
protected Q_SLOTS:
virtual void dequeue() = 0;
};
template <typename Loader, typename QueueType>
class EffectLoadQueue : public AbstractEffectLoadQueue
{
public:
explicit EffectLoadQueue(Loader *parent)
: AbstractEffectLoadQueue(parent)
, m_effectLoader(parent)
, m_dequeueScheduled(false)
{
}
void enqueue(const QPair<QueueType, LoadEffectFlags> value)
{
m_queue.enqueue(value);
scheduleDequeue();
}
void clear()
{
m_queue.clear();
m_dequeueScheduled = false;
}
protected:
void dequeue() override
{
if (m_queue.isEmpty()) {
return;
}
m_dequeueScheduled = false;
const auto pair = m_queue.dequeue();
m_effectLoader->loadEffect(pair.first, pair.second);
scheduleDequeue();
}
private:
void scheduleDequeue()
{
if (m_queue.isEmpty() || m_dequeueScheduled) {
return;
}
m_dequeueScheduled = true;
QMetaObject::invokeMethod(this, "dequeue", Qt::QueuedConnection);
}
Loader *m_effectLoader;
bool m_dequeueScheduled;
QQueue<QPair<QueueType, LoadEffectFlags>> m_queue;
};
/**
* @brief Can load the Built-In-Effects
*
*/
class BuiltInEffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit BuiltInEffectLoader(QObject *parent = nullptr);
~BuiltInEffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
void clear() override;
void queryAndLoadAll() override;
bool loadEffect(const QString& name) override;
bool loadEffect(BuiltInEffect effect, LoadEffectFlags flags);
private:
bool loadEffect(const QString &name, BuiltInEffect effect, LoadEffectFlags flags);
QString internalName(const QString &name) const;
EffectLoadQueue<BuiltInEffectLoader, BuiltInEffect> *m_queue;
QMap<BuiltInEffect, Effect*> m_loadedEffects;
};
/**
* @brief Can load scripted Effects
*
*/
class KWIN_EXPORT ScriptedEffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit ScriptedEffectLoader(QObject* parent = nullptr);
~ScriptedEffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
void clear() override;
void queryAndLoadAll() override;
bool loadEffect(const QString &name) override;
bool loadEffect(const KPluginMetaData &effect, LoadEffectFlags flags);
private:
QList<KPluginMetaData> findAllEffects() const;
KPluginMetaData findEffect(const QString &name) const;
QStringList m_loadedEffects;
EffectLoadQueue< ScriptedEffectLoader, KPluginMetaData > *m_queue;
QMetaObject::Connection m_queryConnection;
};
class PluginEffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit PluginEffectLoader(QObject *parent = nullptr);
~PluginEffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
void clear() override;
void queryAndLoadAll() override;
bool loadEffect(const QString &name) override;
bool loadEffect(const KPluginMetaData &info, LoadEffectFlags flags);
void setPluginSubDirectory(const QString &directory);
private:
QVector<KPluginMetaData> findAllEffects() const;
KPluginMetaData findEffect(const QString &name) const;
EffectPluginFactory *factory(const KPluginMetaData &info) const;
QStringList m_loadedEffects;
EffectLoadQueue< PluginEffectLoader, KPluginMetaData> *m_queue;
QString m_pluginSubDirectory;
QMetaObject::Connection m_queryConnection;
};
class EffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit EffectLoader(QObject *parent = nullptr);
~EffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
bool loadEffect(const QString &name) override;
void queryAndLoadAll() override;
void setConfig(KSharedConfig::Ptr config) override;
void clear() override;
private:
QList<AbstractEffectLoader*> m_loaders;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags)
#endif