Rendering SDF on GPU

Font generation on the GPU

What is the new feature?

It’s a new way to generate text atlases. So far we have been generating glyphs on the CPU. At a lower generation size, there were some visual quality problems. At a higher generation size, the cost was usually prohibitively expensive in terms of processing power. Furthermore, that approach was stopping us from implementing another important feature, which we will implement in the upcoming releases - text outlines used for stroked text. The new solution does most of the work on the GPU and that gives us the ability to generate even higher resolution text atlases without an increase in CPU usage. This allows users to choose the desired generation to best suit their use case and balance between text visual quality required and memory used.

Notice/Limitations:

The feature requires that the graphics API/hardware used supports 2 essential features, specified in the renoir::RendererCaps structure filled in the RendererBackend::FillCaps method. The 2 required features are:

  • renoir::RendererCaps::CanOutputDepthInPixelShader
  • renoir::RendererCaps::SupportsTwoSidedStencilOperations

If either of those capabilities is not supported, SDF rendering is done on the CPU as a fallback. The stock backends for D3D9 and GLES2 do not support these, so they are an example of CPU SDF rendering.

Comparison with the old algorithm

We have already mentioned that the new algorithm is very scalable in regard to glyph generation size as it relates to CPU usage, whereas the old one wasn’t. It also allows us to implement nice-looking outlines - again something that the old algorithm wasn’t capable of. A picture says a thousand words, so…

Before @ default(52px) generation size:

SDFGPU @ default(52px) generation size:

Outlines @ default(52px) generation size with 8px spread:

How to use it?

  • The feature is enabled by default and is automatically disabled on backends that don’t support the required capabilities. If you need to forcefully disable the GPU generation, you can use the --disableSDFonGPU developer option.
  • The main controller of visual quality to memory consumption is the generation size of glyphs. Bigger glyphs lead to better quality, but also consume more space in the texture atlas. The algorithm uses a default generation size unless the --sdfCharacterSize X, or the --sdfCharacterSize=X options override it.
  • Another controller of visual quality to memory consumption is the distance field spread. It also affects the maximum size of text outlines. A bigger spread leads to better quality and a bigger maximal outline width but also uses more memory. The algorithm uses a default spread unless the --sdfSpread X, or the --sdfSpread=X options are provided. Spread specifies the size of the distance field that is generated around the glyph and is used both for quality edges and for specifying the maximal outline width. The maximal size of the outline can be calculated in the following way - maxOutlineWidth = ((SPREAD * TEXT_SIZE) / GENERATION_SIZE) - 1.0, where all variables are measured in pixels and SPREAD is the spread that has been used to generate the glyphs, TEXT_SIZE is the size of the currently rendered text, and GENERATION_SIZE is the generation size of the font atlas.

Atlas with different generation size:

SDFGPU atlas @ default(52px) generation size:

SDFGPU atlas @ 96px generation size:

SDFGPU atlas @ 128px generation size:

Text quality with different generation size:

SDFGPU text quality @ default(52px) generation size:

SDFGPU text quality @ 96px generation size:

SDFGPU text quality @ 128px generation size:

Atlas with different spread:

SDFGPU atlas @ 128px generation size with 2px spread:

SDFGPU atlas @ 128px generation size with 8px spread:

SDFGPU atlas @ 128px generation size with 12px spread:

Outline width with different spread:

Outline width 52px - capped to maxOutlineWidth @ 128px generation size with 2px spread:

Outline width 52px - capped to maxOutlineWidth @ 128px generation size with 8px spread:

Outline width 52px - capped to maxOutlineWidth @ 128px generation size with 12px spread: