npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

insomni

v0.2.0-alpha.2

Published

A WebGPU rendering engine.

Readme

insomni

insomni is an opinionated 2D WebGPU renderer. It is optimized for data-heavy, interactive scenes — geometry is packed CPU-side into flat float arrays and uploaded to the GPU each frame. SDF-based primitives cover the common cases; complex polygons fall back to earcut triangulation.

Alpha. The core rendering loop is stable but some APIs carry known limitations (see Alpha limitations below).

Installation

npm install insomni
# or
pnpm add insomni

WebGPU must be available in the environment. Chrome 113+ and Safari 18+ ship it behind no flag.

Quick start

import { initGPU, createRenderer, createLayer, rgba } from "insomni";

// 1. Acquire the GPU device.
const gpu = await initGPU();

// 2. Build a renderer wired to a canvas element.
const canvas = document.querySelector("canvas") as HTMLCanvasElement;
const renderer = createRenderer(gpu, canvas);
renderer.setBackground(rgba(0.05, 0.06, 0.09, 1));

// 3. Create a layer and push some primitives.
const scene = createLayer(); // "world" space by default

scene.pushRect({
  x: 40,
  y: 36,
  width: 240,
  height: 128,
  fill: rgba(0.13, 0.15, 0.2, 1),
  cornerRadius: 16,
});
scene.pushCircle({ cx: 160, cy: 100, radius: 24, fill: rgba(0.36, 0.7, 1, 1) });
scene.pushLine({ x1: 40, y1: 170, x2: 280, y2: 170, color: rgba(1, 1, 1, 0.2), width: 1 });

// 4. Render each frame.
function frame(now: DOMHighResTimeStamp) {
  renderer.render([scene]);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

// 5. Handle resize.
window.addEventListener("resize", () => {
  const dpr = window.devicePixelRatio;
  renderer.setDpr(dpr);
  renderer.resize(canvas.clientWidth, canvas.clientHeight);
});

// 6. Tear down.
function dispose() {
  renderer.destroy();
  gpu.destroy();
}

Coordinate spaces

Every Layer lives in one of two coordinate spaces, set once at construction:

| Space | Meaning | | ------------------- | ----------------------------------------------------------------------------------------------------------------- | | "world" (default) | Camera-transformed world coordinates. Pan, zoom, and fitCameraToBounds all apply. | | "ui" | CSS pixel coordinates fixed to the canvas. Unaffected by the camera. Useful for HUD overlays, axes, and tooltips. |

const hud = createLayer({ space: "ui" });
hud.pushRect({ x: 8, y: 8, width: 120, height: 32, fill: rgba(0, 0, 0, 0.6), cornerRadius: 6 });

Layer and Group model

Layer is the universal drawable. It owns a CPU-side pack of shape commands and an optional coordinate space. You create one with createLayer(), populate it with push* calls, and hand it to renderer.render([...layers]). Layer order in the array sets draw order — earlier layers are painted first.

const background = createLayer();
const foreground = createLayer();
// background is drawn first, foreground on top:
renderer.render([background, foreground]);

Group is a lightweight transform container. Any primitive whose group field references a Group instance is transformed by that group's transform matrix at draw time — no CPU repacking needed. Mutating group.transform and re-rendering is enough to move, rotate, or scale an entire cluster of shapes as a rigid body.

import { createGroup, rotation } from "insomni";

const orbit = createGroup();
orbit.transform = rotation(Date.now() * 0.001);

layer.pushRect({ x: -20, y: -20, width: 40, height: 40, fill: rgba(1, 0.5, 0), group: orbit });
layer.pushCircle({ cx: 80, cy: 0, radius: 10, fill: rgba(0, 0.8, 1), group: orbit });

Dynamic vs. static (cached) layers

By default every layer is dynamic: on each frame the CPU pack is re-uploaded to GPU. This is fine for most scenes.

For large, unchanging geometry (grids, axis decorations, terrain) you can bake a layer to a cached RTT texture:

// Bake the layer once — runs an MSAA render pass and freezes it into a texture.
renderer.cacheLayer(grid);

// Every subsequent render() composites the baked texture instead of re-drawing the geometry.
renderer.render([grid, dynamic]);

// Must be called before re-baking with new content.
renderer.uncacheLayer(grid);
grid.clear();
// ... rebuild grid ...
renderer.cacheLayer(grid);

cacheLayer requires createRenderer (not a bare new Renderer2D). The bake freezes the world-space camera; a subsequent pan or zoom will NOT re-bake automatically — call uncacheLayer + cacheLayer to refresh.

OIT — Order-independent transparency

OIT is on by default. Text (MSDF glyphs) and transparent sprites z-interleave with shapes correctly regardless of draw order. The A-buffer composites depth-sorted fragments per pixel at the end of each frame.

When OIT is off (config: { oit: false }), glyphs and sprites fall back to a painter-order depth stamp — they will not float unconditionally on top, but they will not depth-sort against transparent geometry either.

You can pass a partial RendererConfig to createRenderer:

const renderer = createRenderer(gpu, canvas, {
  config: { oit: false }, // disable OIT (saves ~16 MiB of A-buffer memory)
});

Primitive push methods

All methods live on Layer and return this for chaining (except pushText which returns glyph metrics):

| Method | Shape | | --------------------------- | ------------------------------------------- | | pushRect(shape) | Axis-aligned rounded rectangle | | pushCircle(shape) | Circle | | pushEllipse(shape) | Ellipse | | pushLine(shape) | Line segment (alias for pushSegment) | | pushSegment(shape) | Line segment | | pushCurve(shape) | Cubic Bézier curve | | pushArc(shape) | Arc | | pushTriangle(shape) | Single triangle | | pushPolygon(shape) | Polygon (triangulated via earcut) | | pushStroke(shape) | Analytic SDF stroke path (round joins only) | | pushSprite(sprite) | Textured sprite (atlas region) | | pushText(shape) | MSDF glyph text | | pushAnchoredString(shape) | World-anchored, screen-sized text |

Call layer.clear() to reset the pack before rebuilding each frame.

Camera and resize

// Fit the world camera to a bounding box each resize:
renderer.fitCameraToBounds({ minX: 0, minY: 0, maxX: 800, maxY: 600 }, { padding: 0.9 });

// Manual HiDPI setup:
renderer.setDpr(window.devicePixelRatio);
renderer.resize(canvas.clientWidth, canvas.clientHeight);

Frame timing

Pass onFrameTiming in the renderer config to receive per-frame CPU/GPU timing info:

const renderer = createRenderer(gpu, canvas, {
  config: {
    onFrameTiming: (t) => console.log("frame", t),
  },
});

Device loss

initGPU exposes two hooks for GPU error handling:

const gpu = await initGPU({
  onDeviceLost: (info) => {
    // Notified when device is lost unexpectedly (GPU reset, driver crash, TDR).
    // insomni does NOT auto-recreate. Tear down your renderer and call initGPU again.
    console.error("GPU lost:", info.message);
  },
  onUncapturedError: (err) => {
    // Notified for each uncaptured GPU error (validation, out-of-memory).
    console.error("GPU error:", err);
  },
  logger: myLogger, // optional; defaults to console
});

SVG export

The SVG backend consumes the same Layer[] as render():

import { createSVGRenderer } from "insomni";

const svg = createSVGRenderer({ width: renderer.width, height: renderer.height });
svg.setCamera(renderer.getCamera());
svg.setBackground(bg);
svg.render([scene]);
document.body.appendChild(svg.element());

Alpha limitations

  • Device-loss recovery is notify-only. onDeviceLost fires but insomni does not auto-recreate the device or rebuild GPU resources. Callers must tear down and call initGPU again.
  • OIT fragment budget is fixed at K=8 per pixel (configurable up to K=16 via config.oitFragmentsPerPixel). Dense overlapping transparent layers will drop the deepest fragments.
  • Clipping is rectangular scissor only. There is no stencil-based or path-based clip.
  • SDF stroke joins are round only. Passing join: "miter" or join: "bevel" to pushStroke falls back to tessellated polylines.
  • cacheLayer freezes the world-space camera. A camera move after baking does not re-bake automatically.

Subpath exports

| Import path | Contents | | -------------------- | ------------------------------------------------------ | | insomni | Core renderer, Layer, Group, math, interactions | | insomni/text-ttf | TTF/MSDF text with loadFont() (pulls in opentype.js) | | insomni/viewport | createViewport, CameraViewport, pan-bound types | | insomni/internal | Render-debug probes (unstable) | | insomni/reactivity | Reactive primitives (unstable) | | insomni/spatial | Spatial indexing helpers (unstable) | | insomni/particles | Particle system (unstable) |