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).
MirrorLayer
Section titled “MirrorLayer”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]);Navigator
Section titled “Navigator”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 yourrender([...])call, in z-order.interacting—truewhile the user is dragging the indicator.refresh()— re-derive content + indicator (auto-runs on viewport changes; call manually when the data yourrendercallback 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.
NavigatorIndicatorOptions—fill,stroke,strokeWidth(default 1),cornerRadius(default 0).NavigatorInteractionOptions—manager(anInteractionManager),drag(default true),click("center"| false, default"center"),bounds,zIndex(default 50), andonMove.NavigatorCacheOptions—renderer(aNavigatorRetainHostexposingretainLayer/unretainLayer) andspatialIndex(default false). Only therender-callback content path supports caching.
LodLayer
Section titled “LodLayer”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
Section titled “LabelCuller”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); }}BufferLayer
Section titled “BufferLayer”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).
CustomDrawable
Section titled “CustomDrawable”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]);Retained and tile infrastructure
Section titled “Retained and tile infrastructure”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;RetainedRecordholds 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, returningnull(fall back to a full frame) when the dirty fraction exceedsFULL_FRAME_TILE_FRACTION(0.5).ScreenAABBis 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 aSpatialGridResult(index+ anewOrderpermutation) ornullwhen not worth building;queryRanges(index, view, out)returns the contiguous[offset, count)slot ranges intersecting the view.SpatialGridOptions:cellsPerAxis(default 32) andminItems(default 1024).
See also
Section titled “See also”- Layers & groups — the base
Layerand coordinate spaces. - Drawing primitives — shapes, sprites, and textures.
- Packing — the buffer layouts
BufferLayerconsumes.