Text Rendering in the QML Scene Graph

Published Friday July 15th, 2011 | by

Some time ago, Gunnar presented to you what the new QML Scene Graph is all about. As mentioned in that article, one of the new features is a new technique for text rendering based on distance field alpha testing. This technique allows us to leverage all the power of OpenGL and have text like we never had before in Qt: scalable, sub-pixel positioned and sub-pixel antialiased… and at almost no cost.

First, here is a video showing the improvements this technique brings:

Distance Field… Huh?

Now, most of you are probably wondering what a distance field is. Also known as distance transform or distance map, it is a derived representation of a digital image that maps each pixel to a value representing the distance to the closest edge of the glyph. On a range from 0.0 to 1.0, a pixel on the edge of the glyph should have a value of 0.5, a pixel outside the shape should have a value going towards 0.0 as the distance from the nearest edge increases, a pixel inside the shape should have a value going towards 1.0 as the distance from the nearest edge increases.

A distance field can be generated either from a high-resolution image or from a vector-based representation of the glyph. The first solution is typically based on a brute-force method and can potentially be very slow, thus it is hardly conceivable to use this solution in our case when we have dozens of glyphs (or even hundreds in the case of Chinese) to generate at once. Some tools (like this one) allow to pre-render a set of glyphs from a given font into a file to be then used at run-time, but we didn’t choose this solution as it gives poor flexibility to developers.

We chose instead to use a vector-based representation of the glyph to generate the distance data. Put simply, we extrude the outline of the glyph by a fixed distance on the inside and on the outside and fill the area between the extruded outlines with a distance gradient. We store the result in a single 8-bit channel of a 64×64 pixels cell contained in a bigger texture. By doing so we manage, thanks to Kim, to generate a distance field in less than a millisecond per glyph on a mobile device (on average), making any vector font usable dynamically at run-time.

How Does It Work

This technique allows to take advantage of the native bilinear interpolation performed by the GPU on the texture. The distance from the edge can be accurately interpolated, allowing to reconstruct the glyph at any scale factor. All we need to do is alpha-testing: pixels are shown or discarded depending on a threshold, typically 0.5 as it is the value at the edge of the glyph. The result is a glyph with sharp outlines at any level of zoom, as if they were vector graphics. The only flaw is that it cuts off sharp corners, but this is negligible considering how bad a magnified glyph looks when this technique is not used.

This technique provides a great visual improvement while not affecting performance at runtime as everything is done “for free” by the graphics hardware.

Improvements

High-Quality Anti-Aliasing

Using the same distance field representation of the glyph, we can also do high-quality anti-aliasing using a single line of shader code:

varying highp vec2 sampleCoord;
uniform sampler2D texture;
uniform lowp vec4 color;
uniform highp float distMin;
uniform highp float distMax;
void main() {
    gl_FragColor = color * smoothstep(distMin, distMax, texture2D(texture, sampleCoord).a);
}

 

Instead of using a single threshold to do alpha-testing, we now use two distance thresholds that the shader uses to soften the edges. The input distance field value is interpolated between the two thresholds with the smoothstep function to remove aliasing artifacts. The width of the soft region can be adjusted by changing the distance between the two thresholds. The more the glyph is minified, the wider the soft region is. The more the glyph is magnified, the thinner the soft region is.

When the GPU is powerful enough (meaning desktop GPUs) we can even do sub-pixel anti-aliasing, it is just about adding some lines of shader code. Instead of using the distance data to compute the output pixel’s alpha, we use the data of the neighboring pixels to compute each color component of the output pixel separately. Five texture samples are then needed instead of one. The red component averages the three left-most distance field values, the green component averages the three middle distance field values and the blue component averages the three right-most distance field values. Because this requires more processing power, sub-pixel anti-aliasing is currently disabled on mobile platforms. Anyway, the high pixel density displays that equips mobile devices nowadays make the use of sub-pixel anti-aliasing pointless.

Special Effects

In addition to anti-aliasing, the distance field can be used, again with just a few lines of shader code, to do some special effects like outlining, glows or drop shadows. We are then able to implement the three styles provided by the QML Text element (outline, raised and sunken) using that technique. For example to do outlining, we just have to use a different color for the texels which are between two distance values. Effectively, it makes the use of these effects faster, nicer and scalable (scaling a styled text in QtQuick 1.0, not Scene Graph based, gives very poor quality).

Implementation Details

For a given font, we rasterize and cache each glyph (or actually its distance field representation) only once at a size of 64×64 pixels. The same texture is then used to render the glyph at any size by scaling it appropriately. In comparison, the native font rendering used by QPainter implies caching a separate version of the glyph for each size used in the application. This leads to a lower graphics memory consumption when using different sizes of the same font.

To be able to scale freely the glyphs, we need to disable font hinting to have correct glyph positions at any level of zoom. As a consequence glyphs are also sub-pixel positioned (i.e. not pixel aligned).

Hack It Your Way

It you haven’t already, grab the Qt 5 repositories and look for the files starting with qsgdistancefield in the QtDeclarative module.

  • For debugging purposes you can disable distance field text rendering with the QML_DISABLE_DISTANCEFIELD environment variable.
  • On desktop platforms sub-pixel antialiasing is enabled by default, pass the –text-gray-antialiasing option to qmlscene to use the standard gray antialiasing.

For more reading on the topic have a look at this paper from Valve.

Did you like this? Share it:

Posted in Graphics, OpenGL, Painting, Performance

26 comments to Text Rendering in the QML Scene Graph

aportale says:

Thumbs up! Awesome stuff, quality and speed wise. And I am really happy about the on-the-fly creation of the Distance field data.

Laurent says:

The lack of hinting is a showstopper for widgets on Windows. Is it possible to enable high quality text with hinting per widget?

amer says:

what anew?

Yoann Lopes says:

@Laurent
Normal QWidgets will continue to use the native text rendering provided by QPainter.
This technique is used only for QML text elements and there will be also a way to use native text rendering for those.

Kurt K. says:

F**king sub-pixel anti-aliasing. Who in his right mind would want black text to look like a rainbow?

JTN says:

You guys are awesome, as usual.

aportale says:

@Kurt K.: “On desktop platforms sub-pixel antialiasing is enabled by default, pass the –text-gray-antialiasing option to qmlscene to use the standard gray antialiasing.” In other words: You can turn it off. And I will deactivate it too, because I like rainbows in the wilderness, but I dislike them between my text ;)

Donald says:

There is an evident clipping issue on the top of the “m”s when you render QML 1 with the OpenGL graphics system in your video.

New font rendering looks really great

Donald says:

I see you addressed the existing clipping issue in the course of the text:

“The only flaw is that it cuts off sharp corners, but this is negligible considering how bad a magnified glyph looks when this technique is not used.”

sorry about the spammy comment above.

Font’s look incomparably good now; it is really quite wonderful

minimoog77 says:

Thank you! Thank you! My request about how distance map was implemented was fulfilled…

Anonymous says:

If the generation of the distance field for one glyph is done on-demand and takes as long as ~1 millisecond on a mobile device (or is this a typo?), that means with a target 60 FPS, and a few dozen unique glyphs to render on screen, you might not have the distance fields ready until several frames (60 FPS ~= 16.6.. ms). If it’s indeed not a typo, is the distance field computation spread across several frames, and is there any fallback until the distance field are ready? Anyway, it looks nice for glyphs rendered with a big size, is there any improvement to expect for the lack of hinting / tiny render sizes?

minimoog77 says:

@Anonymous Distance fields are generated only once.

Yoann Lopes says:

@ comment #11
It’s not a typo, note that the distance field for a given glyph is generated only once in the lifetime of the application then cached. So in the case of latin characters, a couple of frames might be skipped in the beginning but it doesn’t happen anymore after that. On the over side, for Chinese characters the situation is more problematic and we are currently trying to address that. A possible solution would be to generate a set of glyphs at application start-up, before rendering any frame. We are also planning to implement a cross-process glyph cache.
About the lack of hinting for small font sizes, it is a requirement to be able to scale the glyphs. I have currently no solution to fix that…

Fazer says:

My inner geek makes me proud you linked to Valve, my favorite game developer company :-)

Marcel says:

When there was no hinting on Linux ten years ago (patent issues etc.) fonts looked horrible. No it seems hinting is switched off with this rendering but apparently fonts still look good, as people state above. How does that work?

bq says:

Thanks for this great technique. In my QtQuick-based application I use a lot of text scaling and both of the two aproaches (raster, OpenGL) was horrible – slow rendering or ugly artefacts. QtQuick 2.0 with distance field map saved my day! Thanks a lot! :)

ary says:

Nice!

Smoothly scalable text with the possibility of applying high quality outlining and drop shadows effects – it’s like a UI designer’s dream come true… :-)

ary says:

If only there were the possibility of “sandboxing” the execution of a QML script, one could use QML not only as application source code, but also as a document format for (potentially user-supplied) on-screen presentations…

It would easily beat PowerPoint presentations in terms of visual animation quality, performance, and flexibility (custom shader effects etc.)…

aportale says:

@ary: Gunnar demonstrated a QML based presentation system like that: http://labs.qt.nokia.com/2011/05/30/a-qml-presentation-system/ (assuming you did not read that post)

ary says:

@aportale: ah I must have missed that one, thanks.

Edzaa says:

Hello, I try build Qt5 using instructions on wiki page http://developer.qt.nokia.com/wiki/Building_Qt_5_from_Git but it fails when trying “git submodule foreach ‘git fetch –all’” step. It appears that when it tries to download ‘Gerrit’ it cannot download from http://codereview.qt.nokia.com . The error I get is:

Fetching gerrit
error: Failed connect to codereview.qt.nokia.com:80; Connection timed out while accessing http://codereview.qt.nokia.com/p/qt/qtbase/info/refs

Can someone give that server a kick – or should I just skip that step in the build?

@Edzaa, codereview.qt.nokia.com is not public yet .. could you please try https://qt.gitorious.org/qt/qt5/blobs/master/README ?

git clone git://gitorious.org/qt/qt5.git
cd qt5
./init-repository
./configure -prefix $PWD/qtbase -opensource -confirm-license -nomake tests
make -j4

Mel says:

@Sergio

Tried that, I think building Qt5 at the moment is a bit too soon. I can not get the example in this blogpost to compile without it throwing up include errors, I think they are still heavily rearranging the source between modules atm. (To warn others if you ./inti-repository use the -nowebkit option as webkit comes down as several Gigs. Also the latest Qt-Creator beta does not work with Qt5, it calls it an invalid version).

All in all I think I’ll watch from the sidelines and wait till at least 4.8 is out before trying out Qt5 again, which will be in Beta state by then hopefully, just too eager to see if this scenegraph stuff is as good as it seems!

Tom Cooksey says:

That’s pretty awesome! I wonder if an OpenCL kernel could be used to generate distance field maps?

Jérémie Delaitre says:

I’ve played a little bit with Qt5′s scene graph and tried to create my own item with its own sg node in a plugin. It worked well but then, I take a look at the text/glyph code in Qt to figure out how I could add some text/glyph nodes as children of my own node to add some text to my item.
It seems that all the text stuff is in private headers. Am I missing something or we can’t create custom item which display text (without rewriting what’s already done in Qt)? Is it planned to add some public API for that?

Yoann Lopes says:

@Jérémie
There’s no plan to make the text, rectangle and image nodes public. If you want to put text in your custom item, you can do it in QML ;)

Commenting closed.