Wednesday, 23 November 2011

Avoiding graphics flicker in Qt / QML

It's very common when writing QML applications to write a small stub, something like the following:


int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    QDeclarativeView view;
    view.setSource(QUrl("qrc:/qml/main.qml"));
    view.showFullScreen();
    return a.exec();
}

What's wrong with this? It's a very subtle problem. I'll give you a moment to think about it, and a video to see if you notice the problem. Make sure you don't cheat.

(demonstrating removal of flicker in QML)

Back already? Have you figured it out? That's right, it flickers. Horrifically.

So what causes this? By default, QWidgets are drawn parent first, with parents drawing children. When a widget is drawn, first, it draws its background, then it draws the actual content. That background proves to be a problem, in this case.

If we add the following lines to the above example, the flicker goes away, and my eyes no longer want to bleed:
    view.setAttribute(Qt::WA_OpaquePaintEvent);
    view.setAttribute(Qt::WA_NoSystemBackground);
    view.viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
    view.viewport()->setAttribute(Qt::WA_NoSystemBackground);

NB: I'm not completely sure that adding it to both the view, and the viewport is completely necessary, but it can't harm at least. Make sure to re-set it if you change viewports.

For completeness, here's the full, fixed example:

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    QDeclarativeView view;
    view.setSource(QUrl("qrc:/qml/main.qml"));
    view.setAttribute(Qt::WA_OpaquePaintEvent);

    view.setAttribute(Qt::WA_NoSystemBackground);
    view.viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
    view.viewport()->setAttribute(Qt::WA_NoSystemBackground);

    view.showFullScreen();
    return a.exec();
}

(If you're curious, Qt::WA_OpaquePaintEvent basically implies that you'll repaint everything as necessary yourself (which QML is well behaved with), and Qt::WA_NoSystemBackground tells Qt to nicely not paint the background.)

NB: on Harmattan (and Nemo Mobile) at least, make sure you always use QWidget::showFullScreen(). The compositor in use there unredirects fullscreen windows (meaning no compositor in the way), so you get faster drawing performance, and every frame counts.

(obligatory thanks to Daniel Stone of X and Collabora fame, for telling me to stop blaming X, and start blaming the crappy toolkits ☺)

7 comments:

  1. For performance, depending on how many objects your scene has, the following could also help improve performance:

    view.setViewportUpdateMode(QGraphicsView::FullViewportUpdate);

    The background drawing avoidance code is actually in the docs, too:

    http://doc.qt.nokia.com/stable/qdeclarativeperformance.html

    I hope that for Qt 5, there will be sane defaults for these things to avoid having this boilerplate code in every single application (e.g. one has to explicitly enable background drawing if the common case becomes using QML UIs, which can handle the background drawing well). On the other hand, you can do QML UIs that do not have any background, so you still have to make sure that you have at least a screen-filling Rectangle or some other element as the background if you are doing totally custom QML stuff (not using Qt Components).

    ReplyDelete
  2. Does it make a difference which rendering engine is being used and is the result the same if the app is run on a standard Linux desktop system? I would have assumed that with the raster engine and alien widgets enabled, Qt would do a top-down render of the whole widget stack into the image buffer for the top-level window and then only blit that to the screen once drawing was complete.

    ReplyDelete
  3. Graphics system doesn't matter. This video was actually taken using raster for the applications.

    As for precisely why this happens, I'm not completely sure. To take a semi-educated guess, I'd say that it may be to do with the content of the QDeclarativeView not being ready to paint (remember, components aren't necessarily instantly available), thus, the background is all there actually is for that instant - the background is painted, the compositor gets damage events and paints the pixmap of nothing, then the load finishes, it repaints (background first) and then content is actually painted.

    That aside, though - there's no point painting something just to paint over it, so even if this wasn't causing a noticeable flicker, it's still a good idea to switch it off if you don't need it.

    ReplyDelete
  4. Please DO NOT showFullScreen on the desktop! It's already annoying enough when a game does that, and outright unacceptable when any other application does it. Your application is not the only one in the world, please do not monopolize my screen. Thanks in advance.

    ReplyDelete
  5. @Kevin: this is not a desktop application. did you miss the video?

    ReplyDelete
  6. hey any idea how to set the same params on QQuickView in QT5.3 ?

    ReplyDelete
  7. Nick, QtQuick2 has a completely different graphics stack that does not suffer from the same problems. You shouldn't need to do anything.

    ReplyDelete