What it is
Vega ships a canvas renderer and an svg renderer. This is a third one, built on WebGPU. It registers itself with Vega's renderer module, so nothing about a spec changes: the same scenegraph is drawn with GPU pipelines instead of 2D canvas calls.
The canvas renderer is the reference. Every spec in the corpus is rendered both ways and compared pixel by pixel on every commit, so a difference is a tracked defect rather than a surprise. What is still off is listed under features and roadmap.
Needs a browser with WebGPU. If navigator.gpu is undefined the renderer will not register, so
keep canvas as the fallback.
Use it
Script tag
The renderer registers itself when loaded after Vega.
<script src="https://cdn.jsdelivr.net/npm/vega@6/build/vega.min.js"></script>
<script src="https://kanadaat.github.io/vega-webgpu/releases/1_2_0/vega-webgpu-renderer.js"></script>
<div id="vis"></div>
<script>
fetch('https://vega.github.io/vega/examples/bar-chart.vg.json')
.then(res => res.json())
.then(spec => {
new vega.View(vega.parse(spec), {
renderer: 'webgpu',
container: '#vis',
hover: true,
}).runAsync();
});
</script>
Every hosted build is listed under versions, each with its own page.
Match the Vega major version. 2.0.0 and later target Vega 6. Everything before it targets
Vega 5 and throws Class constructor cannot be invoked without 'new' when loaded next to Vega 6,
because a renderer registers itself against whichever Vega is already on the page.
| Renderer | Load this Vega |
|---|---|
| 2.0.0 and later | https://cdn.jsdelivr.net/npm/vega@6/build/vega.min.js |
| 1.2.0 and earlier | https://cdn.jsdelivr.net/npm/vega@5/build/vega.min.js |
npm
Not published yet. Until it is, take a hosted build with the script tag above.
npm install vega-webgpu-renderer
import 'vega-webgpu-renderer'; // registers the 'webgpu' renderer
const view = new vega.View(vega.parse(spec), {
renderer: 'webgpu',
container: '#vis',
});
Falling back
Pick the renderer once, at view construction.
const renderer = navigator.gpu ? 'webgpu' : 'canvas';
new vega.View(vega.parse(spec), { renderer, container: '#vis' }).runAsync();
Config
Options live on view._renderer.wgOptions and can be changed between frames.
const view = new vega.View(vega.parse(spec), { renderer: 'webgpu', container: '#vis' });
view._renderer.wgOptions.debugLog = true;
| Option | What it does | Default | Since |
|---|---|---|---|
sampleCount |
MSAA samples per pixel: 4 for antialiased, 1 for plain. Anything else falls
back to 4. Pipelines rebuild on the next render.
|
4 |
2.0.0 |
redrawOnZoom |
Follow browser zoom. Zoom changes devicePixelRatio, and the default resizes the canvas
and redraws so the view stays sharp. When false the canvas keeps the ratio it was first
sized at and the browser scales it, which is softer but skips the redraw and cannot grow past the
texture cap.
|
true |
2.0.0 |
renderLock |
Skip re-entrant render calls while a frame is in flight. The most recent request always runs, so an interactive chart stays responsive under a drag. | true |
1.1.1 |
cacheShapes |
Keep triangulated shape geometry between frames instead of rebuilding it. Costs memory on a scene whose geometry changes every frame. | true |
1.1.0 |
debugLog |
Log per-frame render timings to the console. | false |
1.0.0 |
offscreen |
Render into a texture the renderer owns and never touch the canvas swapchain. For a headless runner
with no compositor, where acquiring the swapchain destroys the device. The canvas stays blank, so the
frame is only reachable through captureFrame().
|
false |
2.0.0 |
renderBatch and simpleLine were removed in 2.0.0. The line mark batches its segments
unconditionally now, so neither had anything left to switch.
Feature completeness
All twelve of Vega's mark types are drawn: arc, area, group, image, line, path, rect, rule, shape, symbol, text and trail. Fill, stroke, opacity, clipping, corner radii, symbol shapes and rotation, and text layout all work on every mark that has them. What follows is the properties that do not yet reach every mark.
| Mark | Gradient fill | Gradient stroke | strokeDash | strokeCap | blend |
|---|
Every mark name is a link into the playground, where you can tick these properties on and off and watch canvas and WebGPU draw the same spec side by side.
Where a gradient is not supported the mark draws a flat colour from the gradient's first stop. Where
strokeDash is not read the stroke draws solid, which is most visible on a dashed axis grid, since
Vega renders those as rule marks.
strokeJoin and strokeMiterLimit are read by nobody. Triangulated marks miter at
Vega's default limit of 10 and the rest join round, so only a spec that asks for something other than the
default differs.
Known differences from canvas
-
No seam between abutting fills. Two polygons sharing an edge, a choropleth's counties for
instance, come out with no line between them. Canvas fills each polygon separately so about a quarter of the
background survives on the shared edge, which is the seam you are used to seeing. One multisampled pass
covers that edge completely. Ask for the border with a
strokeinstead, which is clearer about the intent and matches everywhere. Reproducing the seam is planned for a release after 2.0.0. - Edges of triangulated marks. arc, area, path and shape take their edge coverage from MSAA, which expresses quarter steps, so an edge landing on a pixel boundary can read a quarter light or heavy against canvas. rect, rule, symbol and line segments compute coverage analytically and do not have this.
- A blend mode needs an opaque backdrop. WebGPU's fixed function blending cannot express the term that leaves a source colour unchanged where the backdrop is empty, so a blended mark over a fully transparent background comes out dark where canvas leaves it alone. Charts normally set a background, and with one the four supported modes match canvas exactly.
- Downscaled images. The GPU samples bilinearly where canvas box filters a large reduction, so an icon shrunk about five times is softer than canvas draws it.
-
Maximum canvas size. WebGPU caps a texture at
maxTextureDimension2D, 8192 on most GPUs. A view whose device pixels exceed that drops its pixel ratio to fit and draws softer, with a warning. A view over the cap at ratio 1 cannot be drawn at all and falls back with a warning.
Roadmap
Broad strokes. The full list, with the measurement behind each item, is in ToDo.md.
2.0.0
In progress. Parity, not improvement.
- The properties still ignored above: dashes outside line and group, gradient strokes on the rest, blend on group, image and text, joins and miter limit, round caps on path, area and shape
- Analytic edge coverage for triangulated marks, the largest single difference from canvas and the cause of most of the rest
- A stroked path drawn as segments rather than a triangulated ribbon
- Widen the vega-typings renderer union to include
webgpu, then publish
After 2.0.0
Worth doing, not worth holding a release for.
- Viewport clipping, so a very tall view draws only what is on screen
- Tiled rendering for a canvas past the GPU's texture cap
- Per-item compositing, which is what would reproduce canvas's polygon seams
- The eleven blend modes WebGPU cannot express without reading the destination
- Mipmaps, so a heavily downscaled image matches canvas
- Performance: feed d3-geo a path sink, reuse buffers, cache bind groups
Set up locally
git clone https://github.com/KaNaDaAT/vega-webgpu.git
cd vega-webgpu
npm install
npm run build # UMD, minified and ESM bundles plus type declarations, into build/
npm run serve # serve the repo at http://localhost:5500
Then open
http://localhost:5500/test/?spec=bar&renderer=webgpu&version=dev to run the local build
against any spec in test/specs-valid. The page can show canvas and WebGPU side by side and a
pixel diff between them.
| Command | What it does |
|---|---|
npm run dev |
Rollup in watch mode |
npm test |
Render every spec both ways and compare |
npm run test:report |
Open the comparison gallery from the last run |
npm run typecheck |
Strict TypeScript, no emit |
npm run lint |
eslint |
Versions
Each build is hosted here and can be loaded straight from a script tag.
| Version | Changes | Files |
|---|---|---|
| 2.0.0-rc2 | Built for Vega 6. Every mark type is drawn, most edges are antialiased analytically rather than by MSAA, and text goes through a glyph atlas. (prerelease) | js min |
| 1.2.0 | Performance work on lines, clipping support and a path offset fix. (needs Vega 5) | js min |
| 1.1.1 | Performance work on paths, plus the renderLock option. (needs Vega 5) | js min |
| 1.1.0 | Overall improvements to performance, types and structure. (needs Vega 5) | js min |
| 1.0.0 | First WebGPU implementation. (needs Vega 5) | js min |