Text Rendering With Glyphon
30 Jun 2025Text rasterization is a complicated process that can absolutely tank the performance of your application if not optimized properly. It has been a pain point in my past rendering adventures. In timaeus, a Doom-like 2.5D engine that I wrote last summer, I tried to roll my own glyph rasterizer with very little success; it halved the framerate of my app and the glyphs were still jagged.
Having learned from this experience, I decided to look for a crate that would provide a high-quality text rendering solution that plays nicely with wgpu. Luckily, I found glyphon, which fits this use case perfectly. Glyphon provides an API for rendering text with GPU acceleration via wgpu. It also appears to have support for custom glyphs making it a versatile solution that will hopefully fit the needs of Mirador as the project continues to expand.
However, nothing can ever be as easy as it seems. Our current application does not invoke wgpu directly, but instead accesses it through egui_wgpu in order to integrate smoothly with the egui user interface panels, which are used to provide a realtime developer interface with the program as it runs. However, glyphon traditionally expects wgpu to be defined as its own crate, and if there is a difference between the version of wgpu provided by egui_wgpu and the version of wgpu expected by glyphon, the application will not compile. It is for this reason that is absolutely crucial that we use version 0.8 of the glyphon crate. This version of glyphon looks for wgpu 24 which is the same version of wgpu provided by importing egui_wgpu::wgpu
.
Due to the large number of parameters that glyphon needs in order to accurately place and style a piece of text I decided to create a wrapper around the glyphon API that would provide a simpler interface for our application. This wrapper streamlines the process of rendering text by splitting the glyphon API into a more manageable set of nested structs that together provide glyphon with all the information it needs to render a given piece of text.
The first of these structs is TextStyle
which controls the font, size, weight, color, and style of the text.
pub struct TextStyle {
pub font_family: String,
pub font_size: f32,
pub line_height: f32,
pub color: Color,
pub weight: Weight,
pub style: Style,
}
The second of which is TextPosition
which controls the position and max width and height of the text.
pub struct TextPosition {
pub x: f32,
pub y: f32,
pub max_width: Option<f32>,
pub max_height: Option<f32>,
}
Both of these structs are then contained within the TextBuffer
struct which completely specifies the text to be rendered, and how exactly it should be rendered.
pub struct TextBuffer {
pub buffer: Buffer,
pub style: TextStyle,
pub position: TextPosition,
pub scale: f32,
pub visible: bool,
pub text_content: String,
}
The nuts and bolts of the glyphon API are kept in yet another renderer struct this time aptly named TextRenderer
which is responsible for rendering the text buffer. However, since glyphon needs access to the window in which it is rendering the TextRenderer
struct is contained within the top level AppState
rather than being nested within the WgpuRenderer
struct.
pub struct TextRenderer {
pub font_system: FontSystem,
pub swash_cache: SwashCache,
pub viewport: Viewport,
pub atlas: TextAtlas,
pub text_renderer: GlyphonTextRenderer,
pub text_buffers: HashMap<String, TextBuffer>,
pub window_scale_factor: f32,
pub window_size: winit::dpi::PhysicalSize<u32>,
pub loaded_fonts: Vec<String>,
}