Skip to content

Specialized layers

Beyond the plain Layer, insomni ships a handful of purpose-built drawables and pure helpers. Each one is exported from the package barrel and is composed into the same renderer.render([...]) call as ordinary layers (or, for the pure helpers, used to feed it).

Preview of the Minimap (mirror layer) demo
Minimap (mirror layer) A corner minimap mirrors the whole scene at small scale and tracks the camera's viewport as you pan and zoom.

mirrorLayer(primary, options?) (class MirrorLayer) re-renders another layer’s packed shapes through a different viewport / clip rect — minimaps, overview panes, picture-in-picture. By default the mirror shares the primary’s pack by reference, so any push to the primary appears in the mirror next frame at zero CPU cost. The mirror is read-only: its push* / clear methods throw — mutate the primary instead.

Pass simplify to trade detail for upload/draw-call savings at overview scale. The mirror then owns a derived pack built by filtering the primary’s draw commands; call mirror.refresh() after mutating the primary to rebuild (it no-ops when the primary’s version is unchanged).

MirrorLayerOptions:

| Option | Type | Default | Notes | | ---------- | ------------------------ | ------- | ------------------------------------------ | | clip | FrameRect | — | CSS-pixel scissor for the mirror. | | simplify | MirrorSimplify|true | — | LOD filtering. true = a sensible preset. |

MirrorSimplify (a bare true means { skipTriangles: true, skipSprites: true }):

| Field | Type | Default | Notes | | --------------- | ------- | ------- | ------------------------------------------------------------ | | skipTriangles | boolean | false | Drop polygon fills / polyline tessellations / filled lines. | | skipSprites | boolean | false | Drop sprite (and MSDF-glyph) commands. | | stripStrokes | boolean | false | Render SDF shapes fill-only; outline-only shapes are kept. | | outlineBoost | number | 1 | Multiply preserved outline-only stroke widths (readability). |

import { createLayer, mirrorLayer } from "insomni";
const main = createLayer();
// … push content into `main` …
const minimap = mirrorLayer(main, {
clip: { x: 0, y: 0, width: 160, height: 120 },
simplify: { skipTriangles: true, stripStrokes: true },
});
renderer.render([main, minimap]);

createNavigator(options) builds a minimap/overview with an indicator rectangle that tracks the source camera’s visible region, plus optional drag/click interaction. It returns a Navigator handle:

  • layers — the layers to include in your render([...]) call, in z-order.
  • interactingtrue while the user is dragging the indicator.
  • refresh() — re-derive content + indicator (auto-runs on viewport changes; call manually when the data your render callback iterates changes).
  • dispose() — tear down listeners and the interaction node.

NavigatorOptions:

| Option | Type | Notes | | ----------------- | ----------------------------- | ------------------------------------------------------------------------------------ | | source | CameraViewport | The camera the user navigates in the main view. | | overview | CameraViewport | Hosts the navigator; positioned via its frame. | | content | Layer | readonly Layer[] | Layers to mirror through overview (cheap re-emit). | | render | (target, project) => void | Alt. content path: populate a navigator-owned UI layer; project maps world→CSS px. | | indicator | NavigatorIndicatorOptions | Indicator styling. | | interaction | NavigatorInteractionOptions | Drag/click behavior. | | contentSimplify | MirrorSimplify | true | Simplify mirrored content (requires the content path). | | cache | NavigatorCacheOptions | Retain the render-callback overview in a stable GPU buffer. |

Provide either content (mirror existing layers) or render (emit a fixed-weight schematic) — supplying neither throws.

  • NavigatorIndicatorOptionsfill, stroke, strokeWidth (default 1), cornerRadius (default 0).
  • NavigatorInteractionOptionsmanager (an InteractionManager), drag (default true), click ("center" | false, default "center"), bounds, zIndex (default 50), and onMove.
  • NavigatorCacheOptionsrenderer (a NavigatorRetainHost exposing retainLayer / unretainLayer) and spatialIndex (default false). Only the render-callback content path supports caching.

createLodLayer(levels) (class LodLayer) picks one of several pre-built layer sets based on the camera zoom — swap coarse/medium/fine representations without rebuilding per frame. It is a pure picker; resolve the active level in your frame loop and pass the result to the renderer.

A LodLevel is { minZoom, maxZoom, layers }. The active band is the first whose half-open range [minZoom, maxZoom) contains the zoom (use 0 / Infinity to open-end). resolve(zoom) returns that band’s layers, or [] when none match; selectLevel(zoom) returns the band (or null) for debugging.

import { createLodLayer } from "insomni";
const lod = createLodLayer([
{ minZoom: 0, maxZoom: 1, layers: [coarse] },
{ minZoom: 1, maxZoom: Infinity, layers: [fine, labels] },
]);
// per frame:
renderer.render(lod.resolve(camera.zoom));

LabelCuller is a greedy 2D overlap culler backed by a uniform hash grid. Feed AABBs in priority order (most-important first); each place(aabb) returns true and records the box when it does not overlap a previously placed one, or false (leaving the grid unchanged) when it does. Use it to thin a large label set down to a non-overlapping subset in O(N) expected time.

LabelCullerOptions: cellSize (default 64 — pick a value near the typical label size). The AABB type is { minX, minY, maxX, maxY }. Units are opaque: pass screen-space AABBs to cull per frame, or world-space AABBs to pre-cull at a fixed zoom. Call reset() between frames; placeRect(x, y, w, h) is a corner convenience.

import { LabelCuller } from "insomni";
const culler = new LabelCuller({ cellSize: 48 });
culler.reset();
for (const label of labelsByPriority) {
if (culler.placeRect(label.x, label.y, label.w, label.h)) {
layer.pushText(label.shape);
}
}

createBufferLayer(options) (class BufferLayer) draws data that lives entirely in a GPU storage buffer — e.g. the output of a compute pass — with no CPU round-trip. It is the “bring-your-own-GPU-buffer” seam.

BufferLayerInstancesOptions (kind: "instances") draws v3 uber instances — SDF rects/circles/ellipses decoded with the live per-kind geometry + SDF. The buffer holds the 64 B InstanceStruct (schema/instance.ts), the SAME record the live render() concat packs. A compute kernel emits one with the writeRectInstance WGSL helper (on insomni/advanced); allocate the buffer as root.createBuffer(d.arrayOf(INSTANCE_LAYOUT.tgpuStruct, N)).$usage("storage").

| Option | Type | Default | Notes | | ---------- | -------------------- | --------- | ----------------------------------------- | | buffer | TgpuBuffer (storage) | — | Array of 64 B InstanceStruct records. | | count | number | — | Instances to draw this frame (mutable). | | group | Group | — | Transform applied to all instances. | | space | LayerSpace | "world" | Coordinate space. | | clipRect | FrameRect | — | CSS-pixel scissor. | | opaque | boolean | false | Opaque pass; caller guarantees alpha = 1. |

An instance buffer stacks ABOVE all live geometry + bakes by submission order (no z-interleave) and carries a constant per-buffer near-z. There is no textured variant — instances are SDF primitives, not textured quads. To draw textured quads from a compute texture, use a normal layer’s pushSprite instead (see the heatmap geom in insomni-plot).

createCustomDrawable(space, record, options?) is the escape hatch: a raw render-pass hook for bringing your own pipelines, bind groups, and draw calls into the renderer’s frame. The renderer hands your record an active GPURenderPassEncoder mid-pass — do not begin or end passes.

record(pass, ctx) receives a CustomDrawableContext: device, encoder, scissor (the applied device-pixel rect, or null), space, and cameraBindGroup (bind at @group(0)).

CustomDrawableOptions: clipRect (device-pixel scissor the renderer applies for you), opaque (default false — route to the opaque pass), and onDestroy. isCustomDrawable(node) is the runtime brand-tag guard.

import { createCustomDrawable } from "insomni";
const overlay = createCustomDrawable("ui", (pass, ctx) => {
pass.setBindGroup(0, ctx.cameraBindGroup);
pass.setPipeline(myPipeline);
pass.draw(6);
});
renderer.render([content, overlay]);

These lower-level pieces back partial-redraw and overzoom caching. Most apps never touch them directly, but they are exported for advanced integrations.

  • RetainedTier — manages stable, WeakMap-keyed GPU instance buffers for packs whose paint/data damage is clear, so an undamaged layer is never re-uploaded. has(pack) / get(pack) / build(pack) / tickShrink(pack) drive the per-frame lifecycle; RetainedRecord holds the buffer + shrink bookkeeping.
  • TileGrid + dirtyTileSet — a row-major grid of command buckets over screen device-px space for tile-bucketed replay. new TileGrid(w, h, cellSize) builds the grid; dirtyTileSet(grid, regions, dpr) maps damage regions to the tile-id set to replay, returning null (fall back to a full frame) when the dirty fraction exceeds FULL_FRAME_TILE_FRACTION (0.5). ScreenAABB is the projected-pixel AABB shape.
  • buildSpatialGrid + queryRanges — a cell-binned index over a retained pack’s per-instance AABBs (the overzoom-cache spatial index). buildSpatialGrid(aabbs, count, options?) returns a SpatialGridResult (index + a newOrder permutation) or null when not worth building; queryRanges(index, view, out) returns the contiguous [offset, count) slot ranges intersecting the view. SpatialGridOptions: cellsPerAxis (default 32) and minItems (default 1024).