Previous posts in this topic:
- Whats Hot and whats Not
- An Overview
- The Raster Engine
- The OpenVG Engine
- The OpenGL Engine
- The Cost of Convenience
- Fast Text
In this series that we’ve been doing, I wanted to cover threading, a topic that has been actively discussed amongst some of the trolls over the last few months. We’ve had support for rendering into QImage’s from non-GUI threads since the early Qt 4.0 days, but its only in recent versions of Qt, I think, 4.4 that we got support for rendering text into images. Now that support is there, it begs the question how to make proper use of it. Generating the actual content in a thread is one usecase, here is an example of it.
What it means is that instead of rendering all the content of a certain view in the QWidget::paintEvent() or in the QGraphicsItem::paint() function, we use a background thread which produces the cache. The benefit is that even though drawing the actual content can be quite costly, drawing a pre-rendered image is fast, making it possible for the UI to stay 100% responsive while the heavy loading is happening in the background. It does imply that not all content is available at all times, but for many scenarios this is perfectly fine. There is nothing novel about this approach, I just think its a nice way to solve a problem that often comes up when dealing with user experience.
This approach is used by Google Maps (actually, what the server does I don’t know, but it sends individual tiles to the browser at least), iPhone and N900 web browsers, and I’ve talked to customers in the past that use this approach for usecases where generating the content is costly, but the user interface needs to stay responsive. In fact, this approach applies to pretty much anything where it is ok that the content is not immediately there, such as data tables like an mp3-index or a contact list, images in a data folder, etc.
The Task
Lets first look at the task. I’ve done a trivial implementation which looks in a directory and displays all the images in there. Each image is a separate content piece and I’ve put a background, a small frame around it and a drop shadow under it. Just so that there is a bit of active work going on. If you are into it, here is the Source Code
The content pieces could have been tiles in a map of Norway or tiles composing a webpage, but I choose images, because I already had some images around and I figured it made for an ok example. The demo is run on an N900 with compositor disabled using the following command lines:
- Non-Threaded:
./threaded_tile_generation -no-thread -graphicssystem opengl MyImageFolder - Threaded:
./threaded_tile_generation -graphicssystem opengl MyImageFolder
Here’s how it looks when the content is generated in the GUI thread:
The UI is running super-smooth as long as I show only the content that is already loaded. Once work is needed, the entire UI stops and the user experience is really bad. Here is how it looks if we move the work into a background thread.
The algorithm
Don’t use this particular algorithm. It is very crude and written to show an idea. First of all, because I was lazy, I used queued connections rather than a synchronized queue to schedule the pieces to be rendered. This means that the queue is managed by Qt’s event loop, out of my control. So if I pan far out, I will schedule a lot of images to be rendered, then pan beyond them before they are done. In a decent implementation, I would dequeue these and make sure that only the pieces that are directly visible are being processed.
The other thing is that there is no logic to “peek ahead”. I schedule images to be generated only when I need them. If I instead scheduled them based on the current panning direction, in addition to not discarding so aggressively, it would probably result in a situation most images are rendered ahead of time.
QGraphicsView
It would be kinda cool if this could be applied directly to QGraphicsView. You set a flag on the item and instead of generating its cache pixmap in the GUI thread, it was offloaded to the worker thread. This is not straight forward however, because the GUI thread can, pr today at least, continue to modify the state of the item, while its being rendered in the worker thread. Synching these two becomes a bit of a mess, and how to solve it, if at all, is not something we have a plan for. That doesn’t prevent people from doing this kind of work in their own custom paint() functions of course.
No related posts.
14 comments
I managed to build and run the example code.
But I think it’s slightly buggy respect to what you show in the video:
1) The tiles are not distributed on the entire screen: it’s just one row on the bottom
2) The FPS is not draw, even though I can see a white rectangle on the bottom-left
I could check and fix the code myself, but in the meantime I though you are the best to ask for advices
Anyway, great post and great example of how Qt can bring next-level UI experience
detro: which platform / Qt version is this?
Hi gunnar: This is pretty much the same approach as I use in my experimental image viewer.
I’m using a cache that stores images in multiple resoultions (thumbnail, preview, medium, full, …).
If a image is not cached in the requested resolution, it tries to find an image in cache that fits best and schedules a loading job.
If you watch my video at http://www.youtube.com/watch?v=wtFo9w73Az0 you will notice that the currently selected image appears in low-resolution first and automaticaly replaces with a high resolution image after a few seconds.
To speed things further up, I’m also try to recycle QGraphicsItems.
Is threaded use of QSVGRenderer::render supported if rendering to a QImage? In the past I have had sporadic crashes while trying to do that and had to keep SVG rendering in the GUI thread. Does QSVGRenderer use QPixmaps internally or anything?
yes this is basically the same i did here:
http://invalidmagic.wordpress.com/2010/01/12/libnoise-viewer/
it would be nice to just set a flag ‘can be processed in a thread’ but using the signal&slot mechanism which is thread safe is enough convenience for me.
nice to see that you run this code on a n900
The eplanation way using videos is very good
Stephen: There is no use of pixmaps in the svg renderer. Maybe this was back before text drawing was threadsafe for QImages and the svgs contained a bit of text?
In case anyone is interested, Brian at hostilefork.com has been working on a general solution to the multithreaded-rendering problem. His prototype Qt implementation is here:
http://hostilefork.com/thinker-qt/
Or for those in a hurry, a slide-show overview is here:
http://docs.google.com/present/view?id=dkjh9v7_12gxsn7bch
His design has some nice features, in that it hides the threading implementation from the user while retaining the features you’d want to have (n-processor thread pool, incremental updates, the ability to cancel renders-in-progress that are no longer relevant, copy-on-write for efficiency, etc)
Both this article, and the presentation jfriesne refers to are very interesting. Thanks!
I use threading extensively, though usually not for UI purposes but for calculations only. Still, solutions like this are inspiring.
Gunnar, I’m interested in seeing benchmarks of different implementations of raster vs OpenGL/VG engines.
What can I do to generate your graphs on the hardware that I have available? I’d like to know if I should recommend a high Mhz dual-core, or a low-Mhz dualcore+GPU. And also whose implementation is best (ATI/Nvidia/Inhell)
It would be cool if there was a database of this stuff available.
@gunnar: Sorry for the delay.
When I tried was the latest build of Qt 4.6 for Maemo (latest at January 21, 2010).
We also compiled and executed it on Samsung i8910 (disabling OpenGL): there the tiles were still “only the bottom row”, but the FPS indicator was there (not fitting the white rectangle though).
Surprisingly, on Samsung S60 without OpenGL was smoother!!! O_o
A question without relation to this article,you have mentioned QWindowSurface in “Qt Graphics and Performance – An Overview.” but when I could use it by include rather than by #include
.
A question without relation to this article,you have mentioned QWindowSurface in “Qt Graphics and Performance – An Overview.” but when I could use it by include “QWindowSurface” rather than by #include “Qt/private/qwindowsurface_p.h”
Is it eventually possible to publish a complete project (Have some little problems to get the source file running). Thanks in advance.
Comments on this entry are closed.