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.
This commit is contained in:
Fredrik Höglund 2012-03-28 20:29:33 +02:00
parent 5f220bef2e
commit bb9f59a89c
4 changed files with 152 additions and 118 deletions

View file

@ -76,6 +76,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrandr.h>
#include <xcb/damage.h>
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<EffectsHandlerImpl *>(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<GLTexture *>(texture.value<void*>());
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<Unmanaged*>(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<QRect> 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<void*>());
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<QRect> 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<void*>());
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)

View file

@ -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);

View file

@ -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()));
}

View file

@ -33,6 +33,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <X11/extensions/Xdamage.h>
#include <xcb/xfixes.h>
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()
};