15

I'm attempting to solve what I thought would be a very simple problem. I want to keep a QPixmap updated with the entire screen contents. You can get such a pixmap by doing this:

QDesktopWidget *w = QApplication::desktop();
if (w)
{
    QRect r = w->screenGeometry();
    QPixmap p = QPixmap::grabWindow(w->winId(), 0, 0, r.width(), r.height())
    QByteArray bitmap;
}

The problem with this is that QDesktopWidget ends up re-grabbing the entire screen pixmap from the X11 server every time you ask for it, even if nothing has changed.

I need this code to be fast, so I'm trying to do this myself. My starting point was the qx11mirror demo, however, that basically does the same thing. It uses the XDamage extension to work out when something has changed, but instead of using the damaged rectangle information to just update that part of the cached pixmap, it just sets a "dirty" flag, which triggers an entire refresh anyway.

So I'm trying to modify the qx11mirror example to just update the damaged portion of the windows, but I can't seem to get anything to work - all I get is a blank (black) pixmap. The code I'm using is:

void QX11Mirror::x11Event(XEvent *event)
{
    if (event->type == m_damageEvent + XDamageNotify)
    {
        XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);

        XWindowAttributes attr;
        XGetWindowAttributes(QX11Info::display(), m_window, &attr);
        XRenderPictFormat *format = XRenderFindVisualFormat(QX11Info::display(), attr.visual);
        bool hasAlpha             = ( format->type == PictTypeDirect && format->direct.alphaMask );
        int x                     = attr.x;
        int y                     = attr.y;
        int width                 = attr.width;
        int height                = attr.height;

            // debug output so I can see the window pos vs the damaged area:
        qDebug() << "repainting dirty area:" << x << y << width << height << "vs" << e->area.x << e->area.y << e->area.width << e->area.height;

        XRenderPictureAttributes pa;
        pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets    
        Picture picture = XRenderCreatePicture(QX11Info::display(),
                                               m_window,
                                               format,
                                               CPSubwindowMode,
                                               &pa);

        XserverRegion region = XFixesCreateRegionFromWindow(QX11Info::display(),
                                                            m_window, WindowRegionBounding);

        XFixesTranslateRegion(QX11Info::display(), region, -x, -y);
        XFixesSetPictureClipRegion(QX11Info::display(), picture, 0, 0, region);
        XFixesDestroyRegion(QX11Info::display(), region);


        //QPixmap dest(width, height);
        XRenderComposite(QX11Info::display(),                       // display
                         hasAlpha ? PictOpOver : PictOpSrc,         // operation mode
                         picture,                                   // src drawable
                         None,                                      // src mask
                         dest.x11PictureHandle(),                   // dest drawable
                         e->area.x,                                 // src X
                         e->area.y,                                 // src Y
                         0,                                         // mask X
                         0,                                         // mask Y
                         e->area.x,                                 // dest X
                         e->area.y,                                 // dest Y
                         e->area.width,                             // width
                         e->area.height);                           // height

            m_px = dest;
        XDamageSubtract(QX11Info::display(), e->damage, None, None);
            emit windowChanged();

    }
    else if (event->type == ConfigureNotify)
    {
        XConfigureEvent *e = &event->xconfigure;
        m_position = QRect(e->x, e->y, e->width, e->height);
        emit positionChanged(m_position);
    }
}

Can anyone point me in the right direction? The documetnation for XRender, XDamage, and the other X11 extensions is pretty bad.

Reasons for using XRender over XCopyArea

The following text taken from here.

It is perfectly possible to create a GC for a window and use XCopyArea() to copy the contents of the window if you want to use the core protocol, but since the Composite extension exposes new visuals (ones with alpha channels e.g.), there's no guarantee that the format of the source drawable will match that of the destination. With the core protocol that situation will result in a match error, something that won't happen with the Xrender extension.

In addition the core protocol has no understanding of alpha channels, which means that it can't composite windows that use the new ARGB visual. When the source and destination have the same format, there's also no performance advantage to using the core protocol as of X11R6.8. That release is also the first to support the new Composite extension.

So in conclusion there are no drawbacks, and only advantages to choosing Xrender over the core protocol for these operations.

Thomi
  • 11,647
  • 13
  • 72
  • 110
  • Why use XRender instead of XCopyArea? XRender is error prone (if you don't do it exactly the right way, it won't work..) – ypnos Sep 22 '09 at 10:02
  • I have found that trying to update the screen only updating damaged parts does not work so well in practice. For example, OpenGL applications never seem to report damage. I think your best bet is to keep recapturing the screen. – ldog Oct 16 '09 at 23:03
  • You can recapture the screen in near real time (10-20 frames per seconds) if you do it smart. – ldog Oct 16 '09 at 23:03
  • 2
    gmatt - any advances on "smart"? How is what I'm doing above not smart? It should certainly be possible, but I'm not sure where I'm going wrong, that's all... – Thomi Oct 19 '09 at 06:54

1 Answers1

3

First you need to change the DamageReportLevel in the call to DamageCreate in QX11Mirror::setWindow from DamageReportNotEmpty to XDamageReportBoundingBox.

Next you need to call dest.detach() before the call to XRenderComposite. You don't really need both m_px and dest as member variables though - you can just use m__px.

There is also a missing call to XRenderFreePicture in that example which should go after the call to XRenderComposite:

XRenderFreePicture(QX11Info::display(), picture);

You don't need to duplicate all the code QX11Mirror::pixmap in QX11Mirror::x11Event. Instead change the type of m_dirty from bool to QRect, then have x11Event update m_dirty with the rectangle from the XDamageNotifyEvent,i.e. e->area. Then in QX11Mirror:pixmap rather than checking if m_dirty is true, check if m_dirty is not an empty rectangle. You would then pass the rectangle from m_dirty to XRenderComposite.

Edit:

The dest.detach is the key bit - without that you'll never get it to work.

atomice
  • 3,062
  • 17
  • 23