Valo
Valo is a WebGPU-native 2D rendering engine built with Rust and wgpu. It follows the architecture of Flutter's Impeller with no shader compilation at draw time — behind a user-facing API in the shape of Canvas2D and Skia.
What WebGPU brings
-
On browsers
- Modern-GPU rendering without WebGL's overhead
- One shared context drives every
<canvas>on the page, no per-canvas resource allocation - Advanced rendering features without hacks needed
-
On native platforms
- Small binary, small memory footprint, fast rendering
- The same code on Windows, macOS, Linux, Android and iOS
Playground valo.im/playground
Install
For browsers:
For native platforms:
What it looks like
Recording touches no GPU: a DisplayListBuilder is pure CPU work you can run on any thread, keep, nest inside other lists, and replay every frame.
use ;
let mut builder = new;
builder.draw_rrect_radii;
builder.draw_circle;
// Frosted glass over whatever is already recorded beneath.
builder.backdrop_blur;
let display_list = builder.build;
Rendering is where the GPU comes in — a Context wraps the wgpu device you own, and a Surface wraps your window's swapchain:
let mut context = new;
// Each frame: acquire, render, present.
if let Some = surface.acquire
Gradients, image filters, blend modes, clips, layers and shaped paragraphs are all paints and draws on the same builder — the examples walk through each.
From TypeScript
The raw API is the engine's own vocabulary — display lists, paints, shaders, paragraphs — with explicit resource lifetimes:
import { initializeValo, createRenderer, DisplayListBuilder, Paint } from "@valo/web/raw";
await initializeValo();
const renderer = await createRenderer(document.querySelector("canvas")!);
const builder = new DisplayListBuilder();
const paint = new Paint(0.78, 1, 0.24, 1);
builder.drawRoundedRect(24, 24, 240, 140, new Float32Array([28, 8, 28, 8]), paint);
const list = builder.build();
renderer.render(list, true, 1, 1, 1, 1);
list.free(); builder.free(); paint.free();
The Canvas2D adapter runs existing drawing code on the same renderer:
import { createValoCanvas } from "@valo/web";
const context = await createValoCanvas(document.querySelector("canvas")!);
context.fillStyle = "#c8ff3d";
context.roundRect(24, 24, 240, 140, [28, 8, 28, 8]);
context.fill();
context.backdropBlur(48, 72, 192, 56, 12); // Valo extension
It covers Canvas2D state, paths, transforms, clips, gradients, images, shadows, blends and text, and is checked against Chrome's own Canvas2D by a differential conformance suite. Browsers without WebGPU can use @valo/web/compat, which falls back to WebGL2 (raw API, one canvas per renderer).
From C
Valo ships a C API — every function null-safe, every object an opaque handle with a matching dispose. The header is crates/valo-capi/include/valo.h:
ValoContext *context = ;
ValoDisplayListBuilder *builder = ;
ValoPaint paint = ;
paint.color = ;
;
ValoDisplayList *list = ;
;
;
;
The complete version — build, run, write an image — is examples/c/hello.c, run by ./examples/c/run.sh.
Quick start (Rust)
Valo draws onto a device you own — a surface you configured, or a headless one:
use ;
Or run the shipped version straight to a PNG:
In a window
With winit, valo's Surface wraps the window's swapchain and the frame loop is acquire → render → present:
use Arc;
use ;
use ApplicationHandler;
use WindowEvent;
use ;
use ;
>);
The animated version with a retained list and a live HUD title is crates/valo/examples/window.rs.
Examples
Every example renders headless to target/examples/<name>.png:
Two run interactively in a native window, and the browser playground builds from the same engine:
Testing
VALO_BLESS=1
Failed conformance comparisons write both renders, the diff and the exact scene to packages/valo-conformance/artifacts/.
License
MIT. See LICENSE.
The fonts under assets/ are third-party test and example assets under their own licenses — see assets/fonts/README.md. No crate embeds or ships a font.