Smooth Vector Graphics in Qt Quick

In the upcoming Qt 6.6.0 release we are including a tech preview of a new renderer for Qt Quick Shapes. Qt Quick Shapes is the hardware-accelerated 2D vector graphics API in Qt Quick. The API supports building arbitrary shapes from Bézier curves and lines, as well as higher level compositions such as circles and text. The new renderer expands this already powerful offering with high-quality anti-aliasing and smooth zooming. Here is the classic vector graphics tiger produced with the new renderer:

shapes-tiger-on-phone-2

The new Curve Renderer running on an Android phone.

Background

With Qt 5, basic Qt Quick rendering was moved from the CPU to the graphics processor, allowing applications to take advantage of the much higher rendering performance of the GPU. However, when it came to rendering more general vector graphics, the only available high level APIs (QtQuickPaintedItem and Canvas) were still based on CPU-bound QPainter rasterization into a framebuffer.

This changed in Qt version 5.10 with the introduction of the Qt Quick Shapes plugin.  The Shape API brought two major advantages over the earlier solutions:

  1. A fully declarative API, in style with QML, in contrast to the imperative API of the QPainter-based approaches. All the vector graphics data are QML properties that may be bound and animated in the usual way.
  2. The default Shapes rendering backend pushes the main rendering job onto the GPU. Known as the Geometry Renderer, it employs the curve flattening approach of Qt's OpenGL paint engine.

So far, so good. Over the years since the introduction, however, it has become clear that there are some areas where users are finding that the Shapes rendering is lacking. The most important ones are

  1.  The lack of anti-aliasing. Not only does this yield jagged edges, but also that thin outlines are rendered as discontinous segments. Enabling multi-sampling can alleviate this to some degree, but comes at a performance cost and may still not provide the desired visual quality.
  2. The curve flattening approach means that curves are rendered as a series of short straight lines. When zooming in on a curved shape, this tesselation becomes visible.

The Curve Renderer

Thus was born the idea of creating a new rendering backend for Shapes, a backend that would address both those issues and render smooth, anti-aliased shapes on the GPU. Since its main new feature is rendering true curves without flattening, it has been named the Curve Renderer.

Starting with Qt 6.6, the Curve Renderer can be selected using the new Shape property preferredRendererType. For experimentation, it can also be selected by setting QT_QUICKSHAPES_BACKEND to curve in the environment. Note that the curve renderer is considered tech preview in Qt 6.6, and behavior and APIs may still change.

Instead of flattening curves before passing them to the GPU for rendering, the new backend will preserve the curvature information, so that it can be reproduced faithfully at the target size. In addition, high-quality anti-aliasing is built in.

Demo: The weather forecast example

To demonstrate this, we have created a small example that uses Qt Quick Shapes in different ways: The Weather Forecast example.

Weather example

The example mimics a weather forecast app for Europe and is using Qt Quick Shapes for weather icons, UI icons, and also the map itself. By clicking on the gear icon in the app, you open a side panel where you can select between different rendering backends.

The example shows how shapes might fit in many different ways into a user interface. Clicking on a country will zoom into that country and show larger scale icons. Both the icons and map are the same assets as were used in the overview. The weather icons have an animated zoom effect on hover.

Weather example, zoomed in mode

The country name is also rendered with Qt Quick Shapes, using the PathText component.

Pixel comparison

Switching between different backends illustrates downsides and upsides to each. For instance, using the default Geometry Renderer will show aliased edges and uneven curves, but may be faster both at creating the GPU assets for the shapes the first time they are shown, and also at rendering them.

A compromise when using Geometry Renderer is to enable multi-sampling on the item rendering the shape. Producing the GPU assets will still be faster than Curve Renderer, but both the cost of rendering and memory consumption will increase. Multi-sampling  gives an approximation of anti-aliasing and will not get you pixel perfect results, as can be seen in this comparison:

Pixel zoom of the Ghostscript Tiger, rendered at normal scale. Left to right: curve renderer, geometry renderer and geometry renderer with multi-sampling.

In addition, using the Geometry Renderer will always produce flattening artifacts at some scale, regardless of whether it's anti-aliased or not. We have added a new "Magnify my tiger!" page in the main Qt Quick Shapes example (this will be part of the Qt 6.6.1 package, since we missed the release deadline) that demonstrates this very well:

The tiger rendered at approximately 20x scale. Left to right: curve renderer, geometry renderer and geometry renderer with multi-sampling.

So as with so many things in computer science, choosing a preferred renderer is all about trade offs. The Curve Renderer will give you beautiful curves with high quality anti-aliasing, but at some extra cost.

Future improvements

The Curve Renderer is released as a tech preview in Qt 6.6, and as such, it has known issues and limitations. The most important are

  1. Self-intersecting paths are not supported. The rendering algorithm assumes that no two edges in the same input path cross each other. If that happens, it results in spectacular rendering errors.
  2. Async mode is not implemented. That means that the preprocessing of the shape paths currently always happens on the gui thread, blocking it, even if the async property is enabled. It also means that the potential performance gain by spreading the preprocessing on multiple threads is not yet realized.

Independently of the Curve Renderer, Shapes currently also have another usability issue that one quickly runs into when starting to use them in an application. Namely, they do not set their implicit width and height properties. This often causes unexpected behavior when trying to lay out Shape items in a user interface. However, Qt 6.6 adds a new boundingRect property to Shape. It is computed by uniting the bounding rects of all the paths in the Shape and so can be useful for determining suitable sizes and layout of Shape items.

Another caveat of Qt Quick Shapes itself is the lack of support for standard vector graphics formats. In the Weather Forecast example, all icons are manually converted from SVG using a small specially crafted tool made specifically to support the source SVGs. This is an area we aim to expand in the future, but for now it is a rather manual process. PathSvg does provide a way to use SVG's path description syntax directly, so for most simple SVG shapes the conversion process is not complex.

Finally

While developing the Curve Renderer, we added debug visualization logic to help us figure out what was happening. That has produced many striking visuals, not least this fearsome beast which now adorns a wall in the Oslo office of The Qt Company:

Interior decoration in the Oslo office, courtesy of the Ghostscript Tiger and the geometry renderer.

 


Blog Topics:

Comments