# nical/lyon: turning paths into triangles for GPU 2D rendering in Rust

> Lyon is a Rust workspace that tessellates fills and strokes into vertex and index buffers you upload yourself. It is not an SVG renderer, and the README says so plainly.

**nical/lyon** — 2D graphics rendering on the GPU in rust using path tessellation.

- Repository: https://github.com/nical/lyon
- Stars: 2,600 · Forks: 157
- Language: Rust
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/nical-lyon

## What lyon solves, and the projects it is written for

Graphics APIs consume triangles. Vector artwork arrives as paths: move-to, line-to, quadratic and cubic curves, closed or open. Someone has to convert one into the other, and lyon is that converter. The README frames the goal as providing "efficient SVG-compliant path tessellation tools to help with rendering vector graphics on the GPU", and describes the library as a way to turn complex paths into triangles for use in your own rendering engine.

The audience is narrow on purpose. The README names Servo and games as the kinds of projects the library is meant to serve. If you are building a UI toolkit, a map renderer, a CAD viewer or a game that draws vector shapes, you are in scope. If you want a component that takes an .svg file and paints pixels, you are not: the FAQ answers the SVG question with a flat no, calling lyon "not an SVG renderer" and leaving rendering entirely to the crate's user.

## Path building, tessellation and the buffer builders in between

The mechanism is a pipeline with three visible stages. First you build a Path through a builder: begin, line_to, quadratic_bezier_to, cubic_bezier_to, end. The path is a data structure, not geometry.

Second, a tessellator consumes that path together with an options struct. FillTessellator handles fills; the crate also covers strokes, per the README's description of tessellating "complex path fills and strokes". The options struct is where tolerance and similar decisions live, and the docs.rs page for lyon_tessellation is the place the README points to for the output geometry builders.

Third, the result is written into a VertexBuffers through a BuffersBuilder. This is the part worth understanding before adopting lyon: the vertex type is yours. You supply a closure that maps a FillVertex into your own struct, and the builder fills the vertex and index vectors. The README's example defines a two-float position vertex and derives Copy, Clone and Debug on it. Nothing forces a particular vertex layout, which is why the output can be fed to OpenGL, Vulkan, D3D or a WebGPU pipeline without an intermediate conversion step. The algorithms are designed to produce a vertex buffer and an index buffer, and the format of that output is customizable.

## Installing lyon and tessellating a path on the first run

The repository is a Cargo workspace, and the README badge points at the lyon crate on crates.io. The workspace members list crates/lyon, crates/path, crates/tessellation, crates/algorithms, crates/geom and crates/extra. The README's example is written against the umbrella crate, and the release list shows 1.0.0 dated 2022-07-12. If you prefer a narrower piece, docs.rs hosts lyon_tessellation separately.

The README example is the shortest path to a first real result. It builds a closed path from three segments:

```rust
use lyon::math::point;
use lyon::path::Path;
use lyon::tessellation::*;

let mut builder = Path::builder();
builder.begin(point(0.0, 0.0));
builder.line_to(point(1.0, 0.0));
builder.quadratic_bezier_to(point(2.0, 0.0), point(2.0, 1.0));
builder.cubic_bezier_to(point(1.0, 1.0), point(0.0, 1.0), point(0.0, 0.0));
builder.end(true);
let path = builder.build();
```

Then declare your own vertex type, allocate the buffers and run the tessellator. The README wraps the call in a block so the mutable borrow of the tessellator ends before the geometry is used:

```rust
#[derive(Copy, Clone, Debug)]
struct MyVertex { position: [f32; 2] }

let mut geometry: VertexBuffers<MyVertex, u16> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
{
    tessellator.tessellate_path(
        &path,
        &FillOptions::default(),
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
            MyVertex { position: vertex.position().to_array() }
        }),
    ).unwrap();
}
```

What you should see is what the example prints: a count of vertices and indices. Those two vectors are the thing you upload. The README notes the geometry is then ready for the GPU, and that is the end of lyon's involvement.

If you want a runnable target rather than a snippet, the repository carries examples/wgpu and examples/wgpu_svg at the top level, plus a cli member in the workspace. The README does not document how to run them, so read the files in those directories before assuming a command.

## No antialiasing, no renderer, and the SVG expectation problem

The sharpest limitation is stated in the FAQ: there is currently no built-in support for antialiasing in the tessellators. You get hard-edged triangles. The README suggests the usual game techniques (msaa, taa, fxaa) as user-side remedies, which means the quality question moves into your renderer and your pipeline configuration, not into lyon. For a project that exists to serve vector graphics, that is a real gap, and it is the first thing to plan around.

The second limitation is scope. Because lyon is not an SVG renderer, an SVG file still needs a parser and a mapping from SVG semantics to lyon paths before any of this is useful. The wgpu_svg example directory suggests the authors maintain a demonstration of that combination, but the README does not describe a supported SVG front end.

The third is that the library stops at buffers. How tessellated geometry is rendered is completely up to the crate's user, per the FAQ. That is a deliberate boundary and it is also the reason lyon is the wrong tool for anyone who wants a drawing API. If you need to draw a rounded rectangle on screen today with a working shader, lyon gives you the triangles and nothing else.

## How lyon differs from a full 2D rendering stack

The obvious alternative is a complete 2D renderer that owns the whole pipeline, of which the Rust ecosystem has several. The difference is architectural, not a matter of features. A full renderer takes a draw call or a scene description and produces pixels: it owns the GPU pipeline, the shaders, the batching and the antialiasing strategy. Lyon owns none of that. It hands you a vertex buffer and an index buffer and expects you to have opinions about everything downstream.

That split is the reason to pick lyon and the reason to avoid it. If you already have a renderer, lyon slots in as the geometry stage and does not fight your vertex format, because the BuffersBuilder closure lets you emit whatever struct your pipeline expects. If you do not have a renderer, you are adopting a geometry library and a rendering project at the same time, and the second one is larger. The README's own framing supports this reading: the intent is for the library to be useful in projects like Servo and games, both of which already have rendering machinery.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-22. The release history is sparser than the commit history: 1.0.0 on 2022-07-12, 0.15.0 on 2019-12-26, and v0.13.1 on 2019-02-24. A 1.0.0 tag followed by years without a newer release suggests the API is treated as settled rather than churning, but it also means fixes since 2022 arrive through the main branch, not through a version number. If you pin to 1.0.0, check whether a change you need is only on main.

Upgrade cost is mostly the workspace split. The Cargo.toml lists eleven members, so a breaking change in crates/geom or crates/path can propagate through tessellation and lyon. Depending on the umbrella lyon crate insulates you from some of that, at the price of pulling in more than you use.

The licence line in the README is worth reading carefully rather than skimming. It says licensed under either Apache License 2.0 or the MIT license, at your option, and then adds a third entry: Mozilla Public License 2.0. The README's own comment on that arrangement is that "Dual MIT/Apache2 is strictly more permissive". The repository root contains LICENSE-APACHE and LICENSE-MIT, and the metadata for the project carries a NOASSERTION licence identifier. That combination is a signal to read the files yourself and, if your organisation has rules about MPL-2.0, get an answer from whoever handles licensing before you depend on the crate. This is a description of what the files say, not legal advice.

## Conclusion

Adopt lyon if you are writing your own GPU renderer and need SVG-compliant path fills and strokes as triangles, with control over the vertex format. Do not adopt it if you want an SVG renderer or built-in antialiasing, because the README states lyon is not an SVG renderer and the tessellators have no antialiasing support. Before committing, check the tessellation options in the docs.rs pages for lyon_tessellation and confirm which crates in the workspace you actually need.

## FAQ

### Is nical/lyon an SVG renderer?

No. The README's FAQ states that lyon is not an SVG renderer, and that it mainly provides primitives to tessellate complex path fills and strokes for GPU APIs. How the tessellated geometry is rendered is left to the user of the crate.

### Does nical/lyon support antialiasing?

The FAQ says there is currently no built-in support for antialiasing in the tessellators. The README suggests achieving it with techniques commonly used in games, such as msaa, taa or fxaa, on the user's side.

### How do I add nical/lyon to a Rust project?

The README example is written against the lyon crate, which is published on crates.io, so you add it as a dependency in your Cargo.toml. The release list shows 1.0.0 dated 2022-07-12 as the most recent version.

### What does nical/lyon output?

The tessellators are designed to generate a vertex buffer and an index buffer, written into a VertexBuffers through a BuffersBuilder. The vertex type is customizable, since you supply a closure mapping a FillVertex to your own struct.

## Sources

- [Issues](https://github.com/nical/lyon/issues)
- [nical/lyon on GitHub](https://github.com/nical/lyon)
- [README](https://github.com/nical/lyon/blob/main/README.md)
- [Releases](https://github.com/nical/lyon/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nical-lyon
