Support frame callback in Wayland backend

The egl wayland backend registers for the callback for a rendered frame.
This allows to throttle KWin's compositor so that we don't render frames
which wouldn't end up on the screen.

For this the Scene provides a method to query whether the last frame got
rendered. By default this returns true in all backends. The Egl Wayland
backend returns true or false depending on whether the callback for the
last frame was recieved.

In case the last frame has not been renderd when performCompositing is
tried to be called, the method returns just like in the case when the
overlay window is not visible. Once the frame callback has been recieved
performCompositing is invoked again.
This commit is contained in:
Martin Gräßlin 2013-06-19 11:12:57 +02:00
parent cfa1ead9e1
commit c9779825d1
9 changed files with 98 additions and 0 deletions

View file

@ -87,6 +87,7 @@ Compositor::Compositor(QObject* workspace)
, m_finishing(false)
, m_timeSinceLastVBlank(0)
, m_scene(NULL)
, m_waitingForFrameRendered(false)
{
qRegisterMetaType<Compositor::SuspendReason>("Compositor::SuspendReason");
new CompositingAdaptor(this);
@ -527,10 +528,23 @@ void Compositor::timerEvent(QTimerEvent *te)
QObject::timerEvent(te);
}
void Compositor::lastFrameRendered()
{
if (!m_waitingForFrameRendered) {
return;
}
m_waitingForFrameRendered = false;
performCompositing();
}
void Compositor::performCompositing()
{
if (!isOverlayWindowVisible())
return; // nothing is visible anyway
if (!m_scene->isLastFrameRendered()) {
m_waitingForFrameRendered = true;
return; // frame wouldn't make it on the screen
}
// Create a list of all windows in the stacking order
ToplevelList windows = Workspace::self()->xStackingOrder();

View file

@ -118,6 +118,13 @@ public:
* Set's the Scene's Overlay X Window visibility to @p visible.
**/
void setOverlayWindowVisibility(bool visible);
/**
* @brief Hook for the Scene to notify about a that the last frame got rendered.
*
* In case the Compositor was waiting for the frame being rendered, the next rendering process
* is triggered.
*/
void lastFrameRendered();
Scene *scene() {
return m_scene;
@ -296,6 +303,7 @@ private:
bool m_starting; // start() sets this variable while starting
qint64 m_timeSinceLastVBlank;
Scene *m_scene;
bool m_waitingForFrameRendered;
KWIN_SINGLETON_VARIABLE(Compositor, s_compositor)
};

View file

@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define WL_EGL_PLATFORM 1
#include "egl_wayland_backend.h"
// kwin
#include "composite.h"
#include "options.h"
#include "wayland_backend.h"
#include "xcbutils.h"
@ -33,6 +34,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace KWin
{
static void handleFrameCallback(void *data, wl_callback *callback, uint32_t time)
{
Q_UNUSED(data)
Q_UNUSED(time)
reinterpret_cast<EglWaylandBackend*>(data)->lastFrameRendered();
if (callback) {
wl_callback_destroy(callback);
}
}
static const struct wl_callback_listener s_surfaceFrameListener = {
handleFrameCallback
};
EglWaylandBackend::EglWaylandBackend()
: QObject(NULL)
, OpenGLBackend()
@ -40,6 +56,7 @@ EglWaylandBackend::EglWaylandBackend()
, m_bufferAge(0)
, m_wayland(Wayland::WaylandBackend::self())
, m_overlay(NULL)
, m_lastFrameRendered(true)
{
if (!m_wayland) {
setFailed("Wayland Backend has not been created");
@ -236,6 +253,9 @@ void EglWaylandBackend::present()
// need to dispatch pending events as eglSwapBuffers can block
m_wayland->dispatchEvents();
m_lastFrameRendered = false;
wl_callback *callback = wl_surface_frame(m_wayland->surface());
wl_callback_add_listener(callback, &s_surfaceFrameListener, this);
if (supportsBufferAge()) {
eglSwapBuffers(m_display, m_surface);
eglQuerySurface(m_display, m_surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
@ -338,6 +358,17 @@ void EglWaylandBackend::overlaySizeChanged(const QSize &size)
wl_egl_window_resize(m_overlay, size.width(), size.height(), 0, 0);
}
bool EglWaylandBackend::isLastFrameRendered() const
{
return m_lastFrameRendered;
}
void EglWaylandBackend::lastFrameRendered()
{
m_lastFrameRendered = true;
Compositor::self()->lastFrameRendered();
}
/************************************************
* EglTexture
************************************************/

View file

@ -66,7 +66,9 @@ public:
virtual void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion);
virtual bool makeCurrent() override;
virtual void doneCurrent() override;
virtual bool isLastFrameRendered() const override;
Xcb::Shm *shm();
void lastFrameRendered();
protected:
virtual void present();
@ -88,6 +90,7 @@ private:
Wayland::WaylandBackend *m_wayland;
wl_egl_window *m_overlay;
QScopedPointer<Xcb::Shm> m_shm;
bool m_lastFrameRendered;
friend class EglWaylandTexture;
};

View file

@ -121,6 +121,15 @@ public:
virtual bool makeOpenGLContextCurrent();
virtual void doneOpenGLContextCurrent();
/**
* @brief Allows the Compositor to delay the rendering of the next frame until the last one
* has been rendered. This is mostly interesting in case that the system compositor is not able
* to keep up with KWin's frame rate.
*
* @return bool @c true if the next frame should be rendered, @c false otherwise
*/
virtual bool isLastFrameRendered() const = 0;
public Q_SLOTS:
// a window has been destroyed
void windowDeleted(KWin::Deleted*);

View file

@ -133,6 +133,11 @@ QRegion OpenGLBackend::accumulatedDamageHistory(int bufferAge) const
return region;
}
bool OpenGLBackend::isLastFrameRendered() const
{
return true;
}
/************************************************
* SceneOpenGL
***********************************************/

View file

@ -56,6 +56,7 @@ public:
virtual bool syncsToVBlank() const;
virtual bool makeOpenGLContextCurrent() override;
virtual void doneOpenGLContextCurrent() override;
virtual bool isLastFrameRendered() const override;
void idle();
@ -475,6 +476,13 @@ public:
virtual void endRenderingFrame(const QRegion &damage, const QRegion &damagedRegion) = 0;
virtual bool makeCurrent() = 0;
virtual void doneCurrent() = 0;
/**
* @brief Backend specific code to determine whether the last frame got rendered.
*
* Default implementation always returns @c true. That is it's always assumed that the last
* frame got rendered. If a backend needs more control it needs to implement this method.
*/
virtual bool isLastFrameRendered() const;
/**
* @brief Compositor is going into idle mode, flushes any pending paints.
**/
@ -670,6 +678,11 @@ inline bool SceneOpenGL::hasPendingFlush() const
return m_backend->hasPendingFlush();
}
inline bool SceneOpenGL::isLastFrameRendered() const
{
return m_backend->isLastFrameRendered();
}
inline SceneOpenGL::Texture* OpenGLWindowPixmap::texture() const
{
return m_texture.data();

View file

@ -137,6 +137,11 @@ void XRenderBackend::screenGeometryChanged(const QSize &size)
Q_UNUSED(size)
}
bool XRenderBackend::isLastFrameRendered() const
{
return true;
}
//****************************************
// X11XRenderBackend
//****************************************

View file

@ -67,6 +67,13 @@ public:
* @param size The new screen size
*/
virtual void screenGeometryChanged(const QSize &size);
/**
* @brief Backend specific code to determine whether the last frame got rendered.
*
* Default implementation always returns @c true. That is it's always assumed that the last
* frame got rendered. If a backend needs more control it needs to implement this method.
*/
virtual bool isLastFrameRendered() const;
/**
* @brief The compositing buffer hold by this backend.
*
@ -156,6 +163,9 @@ public:
virtual OverlayWindow *overlayWindow() {
return m_backend->overlayWindow();
}
virtual bool isLastFrameRendered() const {
return m_backend->isLastFrameRendered();
}
static SceneXrender *createScene();
protected: