Andreas
Qt
KDE
Labs
Graphics View
Painting
Posted by Andreas
 in Qt, KDE, Labs, Graphics View, Painting
 on Monday, May 12, 2008 @ 21:44

I’ve always had a dream that Qt’s widget system would be based on a powerful 2D, or possibly even 3D, graphics engine, reaping all the benefits and optimizations that make games run fast. The reason is, coming from a 3D graphics background originally (alright, I was 16 at the time), I’ve always been puzzled by how poor application UIs perform, and how constrained they are, compared to the most basic 2D and 3D graphics engines out there. I think there are many reason for why graphics toolkits provide limited capabilities, and performance, and I’ve been studying this, hoping to help find ways for Qt to be better than the rest. If you ask me why oh why, be warned, I will talk all night. ;-)

I think I could get shot for saying this, but IMO widgets are monolithic beasts. Input, painting, clipping, geometry, events and all are almost always packed into just one class. And that class plugs into a framework that works in only one way. It’s hard to change the way a widget clips without introducing rendering artifacts (draw outside and try to update with the rendered region - oops, the dirty region is autoclipped to the widget rect). It’s impossible to know what a widget looks like without calling paintEvent(), which is a virtual function that might do something different every time it’s called. Multithreaded painting is extremely hard. It’s hard to make the widget paint outside paintEvent() in general. Couldn’t the widget just say what it looks like instead?

The main reason it’s like this, I think, is that UI toolkits’ graphics capabilities are just a hurdle in the path to the ultimate goal, which is to pull together UIs with a nice tool, perhaps targeting your favorite language, and with a cool style and the perfect widget ;-). IOW modeled vertically, after the concrete problem to be solved (which is generally speaking a good approach), and perhaps constrained by the capabilities of the primary target platform. I still think we can learn a lot from looking at UIs as a specialization of a 2D graphics scene API, rather than 2D graphics being an extension to a UI toolkit. We need to model our graphics the way that graphics works (both software and hardware), and not cling so much to a particular problem space. Make sense?

Qt provides both a high-level widget API, a low-level graphics API, and a “mid-level” canvas API called Graphics View. Graphics View is an example of loosening up the constraints of the high-level API, without exposing too many low-level problems such as geometry and dirty region handling. In a way it’s more a 2D graphics engine than anything else. It manages surfaces in 2D (or 2.5D, quasi-3D) space. Originally, we meant for it to be different from QWidget. Obviously, it’s a framework that’s meant for something completely different than widgets (vector graphics, charts, maps, IC design, large scrollable scenes, and so on). What we’ve learned, however, is that the two aren’t really that different. Why can’t I have 1000 widgets in a QScrollArea, for example.

So looking back a few years, you’ll see that we’ve made changes to improve QWidget. Without breaking compatibility of course (which is amazing in itself, shows how Qt’s architecture allows for significant internal changes).

Before Graphics View came out, in Qt 4.1, we loosened up one constraint in QWidget by enabling automatic background propagation. Now, widgets no longer had any default background (remember this change? how about QWidget::autoFillBackground oh, OK now you remember ;-)), and we were one step out of the box-model that widgets traditionally represent (btw when I use the term “box-model” I refer to a widget representing an independent rectangular region of actual screen real estate). Then, in Qt 4.2, we introduced delayed widget creation (DWC), which allows a widget to be constructed without an actual window handle. As part of the DWC work Paul, Matthias and a few others did for 4.2, they had to ensure that widgets had enough local state to independently represent what it otherwise had with a window handle, as without. Well, this had a ripple-effect. During the Qt 4.2 and 4.3 maintenance cycles, we discussed the fact that the only widget that really needs a window handle, is the top-level. With Qt 4.4, Bjørn Erik did some tough refactoring work (which some of us, me included, thought wouldn’t really be feasible), and gave birth to what we call Alien Widgets, the invisible behind-the-scenes beauty. Because of this, in Qt 4.4, a window and a widget are different, despite being the same class, in that the window signs a “contract” with the windowing system to register some screen real estate. This is the same now on all platforms.

Still, after this, you could see some strings that pull QWidget down. Painting outside paint event - using QWidget for screen shot captures, for example, required painter redirection. Each widget still constructed its own QPainter inside paintEvent(), and with it a separate paint engine, despite how all painting ended up in the same paint device: the QPixmap backingstore, which was associated with the same top-level window. Now in Qt 4.4, QWidget has a render-function, much like QGraphicsItem has a paint-function, and the window is, for a subtree of QWidgets, essentially the same as a QGraphicsView is for a scene. Puh.

Background propagation. DWC. Alien Widgets. Shared Painter. You see what’s happening? We’re on a mission. :-) We’re closing the gap between QWidget and Graphics View. And we’re not done, there’s still more to come. :-) There are some things that we cannot easily change, like QWidget’s clipping model for one (it’s opposite from Graphics View) [*]. And that Graphics View can’t make windows like QWidget (arguably, this is solvable though). Plus all our widgets are QWidget-based. Embedding the QWidget-based widgets into Graphics View using WoC is cool, but it’s just not good enough for full-blown exploitation…

Feature by feature, I must say the situation today looks surprisingly good. I’m looking forward to the day when I can simply assign a QGLWidget viewport as QWidget’s window. Or when I can load UIs from Qt Designer into Graphics View. Or in Qt 5, maybe the two are the same thing (the latter is usually only mentioned between some specially interested devs in Trolltech social events after consuming large amounts of beer).

That’s enough blabber for one blog post. I just felt like sharing what’s on my mind these days. This is btw all part of the research we’re doing in Development / Trolltech to support next generation UIs.

[*] QWidget is by default clipped to the intersect of its rect() and the localized exposed region before paintEvent() is called. QScrollArea has no explicit clipping features. Because most widgets don’t draw outside their bounds, item-imposed clipping should have been off by default (obviously expose-clipping is unavoidable for viewports that allow partial updates). And scroll areas should instead explicitly clip the widgets that intersect its edges (widgets outside shouldn’t be drawn, you don’t need clipping for that) (most 2D and 3D graphics APIs use subdivision instead, essentially real-time retesselation of the intersecting primitives to avoid clipping altogether). It’s extremely hard to change this in QWidget today without breaking compatibility. QGraphicsItem has the preferred model in place. But all our standard widgets are written using QWidget, not QGraphicsItem/QGraphicsWidget.

15 Responses to “QWidget vs. Graphics View (ding-ding-ding!)”

» Posted by sotn3m
 on Tuesday, May 13, 2008 @ 06:30

I’ve got the impression that putting QWidget on QGLWidget on a separate layer is possible since I saw this presentation with 1000 widgets…;) It must have been convincing with this “3d” transformations:)

Keep up the good work;)

» Posted by Paul Kolomiets
 on Tuesday, May 13, 2008 @ 09:54

Exceptionally good idea!

» Posted by Philippe
 on Tuesday, May 13, 2008 @ 12:24

In general, complexity can grow incremently and safely only when sitting on a hierarchy of well defined and steady concepts. The fact that you could move so transparently (from my perspective) from Native windows (

» Posted by Philippe
 on Tuesday, May 13, 2008 @ 12:26

In general, complexity can grow incremently and safely only when sitting on a hierarchy of well defined and steady concepts. The fact that you could move so transparently (from my perspective) from Native windows (4.3) to non-native windows (4.4) as default, is yet another sign about how well designed are the Qt internals. Hence the Qt future is very promising…

IMHO, you should not worry too much about breaking binary compatibilities (but for persistent data). I don’t really see the problem of having as requirement the one to rebuild ones application in order to use eg. Qt 4.5.

» Posted by Adam Higerd
 on Tuesday, May 13, 2008 @ 14:36

@Philippe: The binary compatibility issue is a lot more important than you’re giving it credit for. “Binary compatibility” means that your run-of-the-mill desktop Linux user can click on the “updates available” button in his package manager and NOT have to reinstall/recompile every application that contains Q or K. Breaking binary compatibility means that button suddenly crashes the entire environment unless you install TONS of updates all at once.

» Posted by Thiago Macieira
 on Tuesday, May 13, 2008 @ 14:53

In any case, Andreas wasn’t talking about binary compatibility (the first mention of “binary” in this page is in Philippe’s comment). He was talking about compatibility in general: source, binary as well as behaviour compatibility.

If binary compatibility is the only thing broken, a simple rebuild will resolve. If source compatibility is broken, you have to edit the source code and adapt to the changes. If behaviour compatibility is broken, you have to change your own code to adapt to the new behaviour. And the kind of change that Andreas is talking about are quite big: he’s talking about basically changing the QWidget system whose roots are in the pre-1.0 releases of Qt.

» Posted by David Johnson
 on Tuesday, May 13, 2008 @ 16:34

Just remember us little guys. Not everyone has the latest gamer’s video hardware. We are not always willing to upgrade our hardware (or that of hundreds or thousands of customers) just because a new feature got added to Qt. In the case of my new laptop, I couldn’t upgrade if I wanted to. Yet Widgets-on-Canvas doesn’t work for me. This is a driver problem, but that does not diminish the fact the feature does not work for me on fairly new hardware. When you mentioned making QGLWidget the viewport of every QWidget, I got nervous, because although OpenGL is fast on my system, it only looks as tenth as good at basic 2D.

» Posted by Adam D. Ruppe
 on Tuesday, May 13, 2008 @ 19:06

Will all this work hurt network performance over a remote X connection through the Internet?

» Reply from Andreas
 on Wednesday, May 14, 2008 @ 11:12
Andreas

David Johnson: We’re not forgetting about old hardware, in fact we do test Qt against a whole range of old equipment, including esotheric OS versions and archaic compilers. If we make changes in Qt, and they don’t work at all with older hardware that we support, that’s considered a bug for us. I suspect what you’re seeing is a results of running a Widgets-in-GraphicsView demo/example on either X11 without XRender, or without OpenGL. Features that rely on these things don’t work on old hardware/software platforms, but the rest should. In particular embedding widgets into Graphics View should work perfectly on all platforms.

» Posted by espenr
 on Wednesday, May 14, 2008 @ 21:50

Love the walkthough of what has happened in 4.x releases for QWidget. It’s even informative for us TT guys :D

» Posted by Jeremy Friesner
 on Friday, May 16, 2008 @ 22:30

Hi all,

I apologize if this is off topic, but since this blog has the Qt performance expert’s ear I thought I’d bring it up. Back in the Qt4.2 days I identified a performance problem with Qt/OSX and reported it along with a little test app… I just tried the same test application again under the Qt 4.4.0 release codebase and the problem is still there.

The test app is very simple (less than 100 lines) and it just creates a scroll view with a number of widgets in it and then starts a 10Hz QTimer, connected to a slot that does nothing. I’d expect this to take negligible CPU time, and on Linux it does. On MacOS/X, however, the app takes 50%+ of a 2GHz G5 CPU core just to do nothing except fire the no-op QTimer events… I can’t think of any reason why so many CPU cycles should be burned doing essentially nothing.

Example code is here, in case anyone wants to try it:

http://www.lcscanada.com/jaf/big6.zip

-Jeremy

» Posted by David Johnson
 on Monday, May 19, 2008 @ 17:42

Andreas, it doesn’t work on all platforms. My relatively new Thinkpad has a Radeon X1400, and the FireGL Linux driver apparently does not have hardware accelerated XRender. After three weeks of fidgeting with the xorg.conf, I still can’t get it to work right. This is very frustrating, and support is not helping. I thought Radeons were common cards, but apparently they are not.

» Reply from Andreas
 on Tuesday, May 20, 2008 @ 20:12
Andreas

Device or item cache on X11, which are both off-screen rendering models, requires XRender to get a transparent base to render off-screen content on, otherwise it’ll won’t work (4.4.0) or will have a black background (upcoming 4.4.1). If you disable the cache, it should work with no black background, but it will not perform very well. I hope Qt 4.4.1’s fallback behavior helps though. It’s unfortunate that X cannot create transparent off-screen pixmaps without XRender…

» Posted by Leo S
 on Tuesday, May 20, 2008 @ 22:20

@Jeremy Friesner

Not sure why your code would be causing high CPU usage. Certainly isn’t here on Linux.
But whatever the original purpose of this code was, it is almost certainly not the best way to achieve it. You are creating almost 4700 individual QWidgets and layouts. Either draw that pattern in the paintevent (cache to a pixmap) or use GraphicsView. QWidget is not meant to be used in the way you are using it.

» Posted by Jeremy Friesner
 on Wednesday, May 21, 2008 @ 19:08

Hi Leo,

The purpose of that program is simply to demonstrate the problem and make its effects as obvious as possible. I agree that few “real” programs would or should work that way, but the point remains that a 10Hz QTimer that calls a no-op slot shouldn’t be taking up a significant amount of CPU time.

As I said, the problem is seen only in Qt/OSX, not in Linux.

-Jeremy