From bb9f59a89ce1ea82be11ba8605c4fc1d6a586e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20H=C3=B6glund?= Date: Wed, 28 Mar 2012 20:29:33 +0200 Subject: [PATCH] kwin: Use xcb to optimize damage event handling Use XDamageReportNonEmpty instead of XDamageReportRawRectangles. In XDamageReportNonEmpty mode the server generates a single damage event when the damage state transitions from not-damaged to damaged. When the compositor is ready to paint the screen, it requests the damage region for each window and resets the state to not-damaged. With XCB we can request the damage regions for all windows in a single roundtrip, making this the preferred mode. This should reduce the number of wakeups and the time spent processing damage events between repaints. --- composite.cpp | 239 ++++++++++++++++++----------------- libkwineffects/kwineffects.h | 2 +- toplevel.cpp | 2 + toplevel.h | 27 +++- 4 files changed, 152 insertions(+), 118 deletions(-) diff --git a/composite.cpp b/composite.cpp index 626fac8de7..8eb5b3f994 100644 --- a/composite.cpp +++ b/composite.cpp @@ -76,6 +76,8 @@ along with this program. If not, see . #include #include +#include + namespace KWin { @@ -530,11 +532,47 @@ void Compositor::timerEvent(QTimerEvent *te) } static int s_pendingFlushes = 0; + void Compositor::performCompositing() { if (!isOverlayWindowVisible()) return; // nothing is visible anyway + // Create a list of all windows in the stacking order + ToplevelList windows = Workspace::self()->xStackingOrder(); + ToplevelList damaged; + + // Reset the damage state of each window and fetch the damage region + // without waiting for a reply + foreach (Toplevel *win, windows) { + if (win->resetAndFetchDamage()) + damaged << win; + } + + if (damaged.count() > 0) + xcb_flush(connection()); + + // Move elevated windows to the top of the stacking order + foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { + Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); + windows.removeAll(t); + windows.append(t); + } + + // Get the replies + foreach (Toplevel *win, damaged) { + // Discard the cached lanczos texture + if (win->effectWindow()) { + const QVariant texture = win->effectWindow()->data(LanczosCacheRole); + if (texture.isValid()) { + delete static_cast(texture.value()); + win->effectWindow()->setData(LanczosCacheRole, QVariant()); + } + } + + win->getDamageRegionReply(); + } + bool pending = !repaints_region.isEmpty() || windowRepaintsPending(); if (pending) s_pendingFlushes = 3; @@ -552,14 +590,6 @@ void Compositor::performCompositing() return; } - // create a list of all windows in the stacking order - ToplevelList windows = Workspace::self()->xStackingOrder(); - foreach (EffectWindow * c, static_cast< EffectsHandlerImpl* >(effects)->elevatedWindows()) { - Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); - windows.removeAll(t); - windows.append(t); - } - // skip windows that are not yet ready for being painted // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window @@ -573,9 +603,10 @@ void Compositor::performCompositing() repaints_region = QRegion(); m_timeSinceLastVBlank = m_scene->paint(repaints, windows); + // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and - // checkCompositeTime() would restart it again somewhen later, called from functions that + // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. scheduleRepaint(); } @@ -810,21 +841,32 @@ bool Toplevel::setupCompositing() { if (!compositing()) return false; - damageRatio = 0.0; + if (damage_handle != None) return false; - damage_handle = XDamageCreate(display(), frameId(), XDamageReportRawRectangles); + + damage_handle = xcb_generate_id(connection()); + xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); + damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); unredirect = false; + Compositor::self()->checkUnredirect(true); Compositor::self()->scene()->windowAdded(this); + + // With unmanaged windows there is a race condition between the client painting the window + // and us setting up damage tracking. If the client wins we won't get a damage event even + // though the window has been painted. To avoid this we mark the whole window as damaged + // and schedule a repaint immediately after creating the damage object. + if (dynamic_cast(this)) + addDamageFull(); + return true; } void Toplevel::finishCompositing() { - damageRatio = 0.0; if (damage_handle == None) return; Compositor::self()->checkUnredirect(true); @@ -832,8 +874,10 @@ void Toplevel::finishCompositing() discardWindowPixmap(); delete effect_window; } - XDamageDestroy(display(), damage_handle); - damage_handle = None; + + xcb_damage_destroy(connection(), damage_handle); + + damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; @@ -841,7 +885,6 @@ void Toplevel::finishCompositing() void Toplevel::discardWindowPixmap() { - damageRatio = 0.0; addDamageFull(); if (window_pix == None) return; @@ -856,7 +899,6 @@ Pixmap Toplevel::createWindowPixmap() assert(compositing()); if (unredirected()) return None; - damageRatio = 0.0; grabXServer(); KXErrorHandler err; Pixmap pix = XCompositeNameWindowPixmap(display(), frameId()); @@ -874,69 +916,16 @@ Pixmap Toplevel::createWindowPixmap() return pix; } -// We must specify that the two events are a union so the compiler doesn't -// complain about strict aliasing rules. -typedef union { - XEvent e; - XDamageNotifyEvent de; -} EventUnion; - -static QVector damageRects; - void Toplevel::damageNotifyEvent(XDamageNotifyEvent* e) { - if (damageRatio == 1.0) { // we know that we're completely damaged, no need to tell us again - while (XPending(display())) { // drop events - EventUnion e2; - if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent() && - e2.e.xany.window == frameId()) { - XNextEvent(display(), &e2.e); - continue; - } - break; - } + Q_UNUSED(e) - return; - } + m_isDamaged = true; - const float area = rect().width()*rect().height(); - damageRects.reserve(16); - damageRects.erase(damageRects.begin(), damageRects.end()); - damageRects << QRect(e->area.x, e->area.y, e->area.width, e->area.height); - - // we can not easily say anything about the overall ratio since the new rects may intersect the present - float newDamageRatio = damageRects.last().width()*damageRects.last().height() / area; - - // compress - while (XPending(display())) { - EventUnion e2; - if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent() - && e2.e.xany.window == frameId()) { - XNextEvent(display(), &e2.e); - if (damageRatio >= 0.8 || newDamageRatio > 0.8 || damageRects.count() > 15) { - // If there are too many damage events in the queue, just discard them - // and damage the whole window. Otherwise the X server can just overload - // us with a flood of damage events. Should be probably optimized - // in the X server, as this is rather lame. - newDamageRatio = 1.0; - damageRects.clear(); - continue; - } - damageRects << QRect(e2.de.area.x, e2.de.area.y, e2.de.area.width, e2.de.area.height); - newDamageRatio += damageRects.last().width()*damageRects.last().height() / area; - continue; - } - break; - } - - - if ((damageRects.count() == 1 && damageRects.last() == rect()) || - (damageRects.isEmpty() && newDamageRatio == 1.0)) { - addDamageFull(); - } else { - foreach (const QRect &r, damageRects) - addDamage(r); - } + // Note: The rect is supposed to specify the damage extents, + // but we dont't know it at this point. No one who connects + // to this signal uses the rect however. + emit damaged(this, QRect()); } bool Toplevel::compositing() const @@ -948,71 +937,94 @@ bool Toplevel::compositing() const void Client::damageNotifyEvent(XDamageNotifyEvent* e) { #ifdef HAVE_XSYNC - if (syncRequest.isPending && isResize()) + if (syncRequest.isPending && isResize()) { + emit damaged(this, QRect()); + m_isDamaged = true; return; + } + if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == None) // cannot detect complete redraw, consider done now setReadyForPainting(); } #else + if (!ready_for_painting) setReadyForPainting(); #endif Toplevel::damageNotifyEvent(e); } -void Toplevel::addDamage(const QRect& r) +bool Toplevel::resetAndFetchDamage() { - addDamage(r.x(), r.y(), r.width(), r.height()); + if (!m_isDamaged) + return false; + + xcb_connection_t *conn = connection(); + + // Create a new region and copy the damage region to it, + // resetting the damaged state. + xcb_xfixes_region_t region = xcb_generate_id(conn); + xcb_xfixes_create_region(conn, region, 0, 0); + xcb_damage_subtract(conn, damage_handle, 0, region); + + // Send a fetch-region request and destroy the region + m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); + xcb_xfixes_destroy_region(conn, region); + + m_isDamaged = false; + m_damageReplyPending = true; + + return m_damageReplyPending; } -void Toplevel::addDamage(int x, int y, int w, int h) +void Toplevel::getDamageRegionReply() { - if (!compositing()) + if (!m_damageReplyPending) return; - QRect r(x, y, w, h); - // resizing the decoration may lag behind a bit and when shrinking there - // may be a damage event coming with size larger than the current window size - r &= rect(); - if (r.isEmpty()) + + m_damageReplyPending = false; + + // Get the fetch-region reply + xcb_xfixes_fetch_region_reply_t *reply = + xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); + + if (!reply) return; - damage_region += r; - int damageArea = 0; - foreach (const QRect &r2, damage_region.rects()) - damageArea += r2.width()*r2.height(); - damageRatio = float(damageArea) / float(rect().width()*rect().height()); - repaints_region += r; - emit damaged(this, r); - // discard lanczos texture - if (effect_window) { - QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole); - if (cachedTextureVariant.isValid()) { - GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); - delete cachedTexture; - cachedTexture = 0; - effect_window->setData(LanczosCacheRole, QVariant()); - } - } + + // Convert the reply to a QRegion + int count = xcb_xfixes_fetch_region_rectangles_length(reply); + QRegion region; + + if (count > 1 && count < 16) { + xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); + + QVector qrects; + qrects.reserve(count); + + for (int i = 0; i < count; i++) + qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); + + region.setRects(qrects.constData(), count); + } else + region += QRect(reply->extents.x, reply->extents.y, + reply->extents.width, reply->extents.height); + + damage_region += region; + repaints_region += region; + + free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; + damage_region = rect(); repaints_region = rect(); - damageRatio = 1.0; + emit damaged(this, rect()); - // discard lanczos texture - if (effect_window) { - QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole); - if (cachedTextureVariant.isValid()) { - GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); - delete cachedTexture; - cachedTexture = 0; - effect_window->setData(LanczosCacheRole, QVariant()); - } - } } void Toplevel::resetDamage(const QRect& r) @@ -1021,7 +1033,6 @@ void Toplevel::resetDamage(const QRect& r) int damageArea = 0; foreach (const QRect &r2, damage_region.rects()) damageArea += r2.width()*r2.height(); - damageRatio = float(damageArea) / float(rect().width()*rect().height()); } void Toplevel::addRepaint(const QRect& r) diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index eb09d51f4b..6fd0205483 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1001,7 +1001,7 @@ Q_SIGNALS: * Signal emitted when an area of a window is scheduled for repainting. * Use this signal in an effect if another area needs to be synced as well. * @param w The window which is scheduled for repainting - * @param r The damaged rect + * @param r Always empty. * @since 4.7 **/ void windowDamaged(KWin::EffectWindow *w, const QRect &r); diff --git a/toplevel.cpp b/toplevel.cpp index 515de0700d..fdaf02a809 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -44,6 +44,8 @@ Toplevel::Toplevel(Workspace* ws) , wmClientLeaderWin(0) , unredirect(false) , unredirectSuspend(false) + , m_isDamaged(false) + , m_damageReplyPending(false) { connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); } diff --git a/toplevel.h b/toplevel.h index d1db93950c..371490891e 100644 --- a/toplevel.h +++ b/toplevel.h @@ -33,6 +33,8 @@ along with this program. If not, see . #include +#include + class NETWinInfo2; namespace KWin @@ -284,6 +286,22 @@ public: virtual Layer layer() const = 0; + /** + * Resets the damage state and sends a request for the damage region. + * A call to this function must be followed by a call to getDamageRegionReply(), + * or the reply will be leaked. + * + * Returns true if the window was damaged, and false otherwise. + */ + bool resetAndFetchDamage(); + + /** + * Gets the reply from a previous call to resetAndFetchDamage(). + * Calling this function is a no-op if there is no pending reply. + * Call damage() to return the fetched region. + */ + void getDamageRegionReply(); + signals: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); @@ -313,8 +331,6 @@ protected: virtual void damageNotifyEvent(XDamageNotifyEvent* e); Pixmap createWindowPixmap(); void discardWindowPixmap(); - void addDamage(const QRect& r); - void addDamage(int x, int y, int w, int h); void addDamageFull(); void getWmClientLeader(); void getWmClientMachine(); @@ -345,6 +361,10 @@ protected: bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; + +protected: + bool m_isDamaged; + private: static QByteArray staticWindowRole(WId); static QByteArray staticSessionId(WId); @@ -358,7 +378,6 @@ private: Pixmap window_pix; Damage damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs - float damageRatio; bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; @@ -368,7 +387,9 @@ private: QByteArray window_role; bool unredirect; bool unredirectSuspend; // when unredirected, but pixmap is needed temporarily + bool m_damageReplyPending; QRegion opaque_region; + xcb_xfixes_fetch_region_cookie_t m_regionCookie; // when adding new data members, check also copyToDeleted() };