Qt Contributor Summit 2019 - Refurbishing Qt Widgets internals
Session notes
Goal of session: Present pain points in current Qt Widgets architecture and get other people's ideas. This was specifically about internals and implementation, not about adding new widgets or other user facing APIs.
The main driver is performance on desktop, but also consistency of behavior and APIs.
Problems identified
Backing store: As an example, many applications consist of a frame of widgets (toolbars etc.) with a large unused area in the middle (used by some third-party renderer). We allocate memory for one or more buffers for the whole screen, only to fill most of it with gray and then paint over it with some other renderer. There can also be the additional problem that we cannot do partial flushes of the buffer, so the entire screen gets updated every time a widget changes. The fact that there is a huge backing store backing all widgets also makes it hard to mix and match different graphics system (an RHI/OpenGL widget inside a raster tree for instance).
Update scheduling: While Qt Widgets do respect the update schedule of the window manager in most cases (when update() and paintEvent() is used), it is possible to call repaint() to get a synchronous call to paintEvent() and the implicit idea is that you can schedule painting whenever you want. This does not work well on modern systems.
QPaintEngine invocation: While some paint engines may already be addressing this under the hood, the idea that QPainter::begin() and end() is called per widget makes it complicated for the paint engine to know when the painting of a frame is actually starting and ending. In the RHI paint engine, this is making things difficult.
Windows: We are still using GDI/D2D, so we are missing out on features, since those APIs are not being extended to support new things.
Event delivery/Properties: When setting something like the geometry of a top-level widget, this is actually an asychronous request to the window manager. It may lead to a resize later on, or the window manager may deny the request (in which case we will get a resize to the original size instead). This means that the geometry you get from accessors on the widget are actually the request and not the actual size, so if a paint event is scheduled before the resize event, then the size retrieved there and used for painting might be wrong.
QWidget/QWindow: When we made QWindow, we duplicated a lot of the functionality in QWidget, but without widget-specific tweaks. This means that if you call certain functions on QWidgetWindow they will give different results than when calling the same functions on a top-level QWidget.
Expose event: Originally created to tell the window that a certain part which was obscured before had now been exposed, but is now being used as a shown/hidden event.
Parent-relationship of QWidget: The QWidget parent is also the QObject parent, so it handles both the visual hierarchy and memory ownership. In some cases, this can cause problems, e.g. with a modal menu that has a transient parent. For instance, there are hacks in Qt Creator to work around this.
Native widgets: Widgets with a native window handle will enable the same for other widgets in the same branch. This is to make sure we handle all cases consistently, but we should probably optimize for speed and let the user make the decision when a widget needs a window handle instead.
Backing store
Tor Arne is working on this. It was clarified that the main performance issue (lack of partial updates) is mainly on macOS with layer-backed windows. The widget backing store is being renamed to something like "paint manager" to better explain what it does.
Update scheduling
- AP*: repaint() has to be deprecated in Qt 5.15 and then removed in Qt 6. It is already documented as "not recommended", but it should be removed. [[1]]
Event delivery
This was discussed at length. It was suggested that we deprecate the setters/getters and instead introduce a new read-only property for things like the geometry, and call the setFoo() accessors requestFooChange() (or something) instead.
We could also remove the getters entirely and leave it to the user to save the current value from the event handler.
In the end, the consensus seemed to be that the problem is largely theoretical. The use cases where you resize and paint before the resize event has been received do not seem to happen in practice. So we should not break all source compatibility in Qt Gui in order to make it more consistent as long as no one is really complaining about this.
Request: Document better which events are delivered on different platforms in different cases, and in what order. If possible, make this consistent so that it is possible to write cross-platform code which does not take it into consideration at all.
Other
In general, there seems to be a need to document the subtleties of how Qt behaves in certain cases. A lot of what is brought up are low-level APIs and most things have very few practical implications for most users, so it is okay if it is not always 100% consistent or predictable. But it should be well documented so that it is possible to find out which behavior to expect, what patterns to follow.