Храните QPixmap копию содержимого экрана с помощью X11, XDamage, XRender и других трюков.

Я пытаюсь решить то, что, по моему мнению, будет очень простой проблемой. Я хочу, чтобы QPixmap обновлялся со всем содержимым экрана. Вы можете получить такую ​​pixmap, сделав это:

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

Проблема заключается в том, что QDesktopWidget заканчивает повторное захват всей пиксельной карты экрана с сервера X11 каждый раз, когда вы ее запрашиваете, даже если ничего не изменилось.

Мне нужен этот код, чтобы быть быстрым, поэтому я пытаюсь сделать это сам. Моя начальная точка была qx11mirror demo, однако, в основном делает то же самое. Он использует расширение XDamage для разработки, когда что-то изменилось, но вместо того, чтобы использовать поврежденную информацию о прямоугольниках, чтобы просто обновить эту часть кэшированной pixmap, она просто устанавливает "грязный" флаг, который в любом случае запускает полное обновление.

Итак, я пытаюсь изменить пример qx11mirror, чтобы просто обновить поврежденную часть окон, но я не могу заставить ничего работать - все, что я получаю, - это пустая (черная) pixmap. Код, который я использую:

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

Может ли кто-нибудь указать мне в правильном направлении? Документация для XRender, XDamage и других расширений X11 довольно плохая.

Причины использования XRender над XCopyArea

Следующий текст, взятый из здесь.

Вполне возможно создать GC для окна и использовать XCopyArea() для копирования содержимого окна, если вы хотите использовать основной протокол, но поскольку расширение Composite предоставляет новые визуальные эффекты (например, с альфа-каналами), нет никакой гарантии, что формат исходного ресурса будет соответствовать назначению адресата. С основным протоколом эта ситуация приведет к ошибке совпадения, что не произойдет с расширением Xrender.

Кроме того, основной протокол не имеет понимания альфа-каналов, а это значит, что он не может создавать сложные окна, которые используют новый визуальный ARGB. Когда источник и получатель имеют одинаковый формат, также нет преимуществ по производительности для использования основного протокола с X11R6.8. Этот релиз также является первым, кто поддерживает новое расширение Composite.

Таким образом, в заключение нет недостатков, и только преимущества выбора Xrender по основному протоколу для этих операций.

Ответы

Ответ 1

Сначала вам нужно изменить DamageReportLevel в вызове DamageCreate в QX11Mirror:: setWindow из DamageReportNotEmpty в XDamageReportBoundingBox.

Затем вам нужно вызвать dest.detach() перед вызовом XRenderComposite. Вам действительно не нужны как m_px, так и dest как членные переменные, хотя вы можете просто использовать m__px.

В этом примере также отсутствует пропущенный вызов XRenderFreePicture, который должен идти после вызова XRenderComposite:

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

Вам не нужно дублировать весь код QX11Mirror:: pixmap в QX11Mirror:: x11Event. Вместо этого измените тип m_dirty с bool на QRect, а затем обновите x11Event m_dirty с прямоугольником из XDamageNotifyEvent, т.е. e- > область. Затем в QX11Mirror: pixmap вместо проверки, является ли m_dirty истинным, проверьте, не является ли m_dirty пустым прямоугольником. Затем вы передадите прямоугольник из m_dirty в XRenderComposite.

Edit:

Dest.detach - это бит ключа - без этого вы никогда не сможете его работать.