emilk/egui · error

No fonts loaded

Error message

No fonts loaded

What it means

In Context::tessellate (context.rs:3067), when the requested pixels_per_point has no exact font-size match, egui falls back to the first available font entry and expects at least one to exist. The panic 'No fonts loaded' means ctx.fonts is None or empty — fonts were never initialized because Context::run() has not run yet, so tessellation cannot proceed.

Solutions

  1. Ensure Context::run executes (at least one pass) before calling tessellate on shapes
  2. Register fonts explicitly via Context::set_fonts before tessellating outside the normal loop
  3. Tessellate the shapes returned within the same pass that produced them
  4. Check that your integration isn't re-creating the Context each frame, losing font state

Example fix

// before
let shapes = memory.shapes; // on a fresh Context
let prims = ctx.tessellate(shapes); // panics: no fonts loaded
// after
ctx.run(input, |ctx| {
    let prims = ctx.tessellate(ctx draining shapes);
});
Defensive patterns

Strategy: validation

Validate before calling

// Rust: ensure fonts exist before tessellating
let has_fonts = ctx.run(input, |ctx| ctx.tessellate(shapes)); // tessellate only post-run

Prevention

When it happens

Trigger: Calling ctx.tessellate/shapes-to-primitives APIs before the first Context::run()/pass; rendering shapes collected outside the frame loop on a fresh Context.

Common situations: Custom integration code that tessellates egui shapes in its own render loop without running the egui context; rendering buffered shapes from a previous pass on a newly constructed Context; backend drawing paths invoked before egui's first update.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of emilk/egui@8fb03c9ae9 (2026-09-12). Data as JSON: /api/errors/cb915b73dbb9bbff. Report an issue: GitHub.

Appendix: source

Thrown at crates/egui/src/context.rs:3222

        shapes: Vec<ClippedShape>,
        pixels_per_point: f32,
    ) -> Vec<ClippedPrimitive> {
        profiling::function_scope!();

        // A tempting optimization is to reuse the tessellation from last frame if the
        // shapes are the same, but just comparing the shapes takes about 50% of the time
        // it takes to tessellate them, so it is not a worth optimization.

        self.write(|ctx| {
            let tessellation_options = ctx.memory.options.tessellation_options;
            let texture_atlas = if let Some(fonts) = ctx.fonts.as_ref() {
                fonts.texture_atlas()
            } else {
                log::warn!("No font size matching {pixels_per_point} pixels per point found.");
                ctx.fonts
                    .iter()
                    .next()
                    .expect("No fonts loaded")
                    .texture_atlas()
            };

            let paint_stats = PaintStats::from_shapes(&shapes);
            let clipped_primitives = {
                profiling::scope!("tessellator::tessellate_shapes");
                tessellator::Tessellator::new(
                    pixels_per_point,
                    tessellation_options,
                    texture_atlas.size(),
                    texture_atlas.prepared_discs(),
                )
                .tessellate_shapes(shapes)
            };
            ctx.paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
            clipped_primitives
        })
    }

View on GitHub (pinned to 8fb03c9ae9)