Layers & groups
A Layer is the universal drawable: a bag of shapes backed by one
UberPack. A Group is a lightweight, shareable transform
container that shapes (or a whole layer) can be tagged with. Both mirror the
authoring surface the rest of insomni is built on.
Layer draws first, marks draw on top, and a shared Group rotates four rects as one rigid body. Layers
Section titled “Layers”import { createLayer, rgba } from "insomni";
const layer = createLayer({ space: "world", label: "marks" });
layer .pushRect({ x: 0, y: 0, width: 40, height: 24, fill: rgba(0.2, 0.6, 1, 1) }) .pushCircle({ cx: 60, cy: 12, radius: 8, fill: rgba(1, 0.4, 0.2, 1) });
renderer.render([layer]);createLayer(options?) mints a Layer over a fresh UberPack. (new Layer(pack, options?) is available if you want to supply your own pack.)
LayerOptions
Section titled “LayerOptions”| Option | Type | Default | Notes |
| ----------- | ------------------------- | ----------- | --------------------------------------------------------------- |
| space | LayerSpace | "world" | Coordinate space: "world" or "ui". |
| clip | FrameRect | — | Device-pixel scissor crop for this layer. |
| clipRect | FrameRect | — | v1 alias for clip (clip wins when both set). |
| atlas | GlyphAtlas | — | MSDF glyph atlas. Required to call any text method. |
| group | Group | — | Layer-wide default group (see Groups). |
| zIndex | number | undefined | Composite z-band (see Z-order). |
| cache | CacheHint | "auto" | Bake policy (see Cache hints). |
| label | string | — | Human-readable label for debug tooling. |
| visible | boolean | true | A false layer is excluded from drawing. |
| debugData | Record<string, unknown> | — | Static metadata surfaced in the debug probe. |
The same fields are exposed as mutable properties on the Layer (plus
read-only space, pack, and atlas).
Push methods
Section titled “Push methods”Every push appends one instance and returns this for chaining. Each accepts the
kind’s record plus the optional group / emphasisKey tags from
GroupedShape.
| Method | Record type |
| -------------- | ----------------------------------------------------------------------- |
| pushRect | GroupedRectRecord |
| pushCircle | GroupedCircleRecord |
| pushEllipse | GroupedEllipseRecord |
| pushSegment | GroupedSegmentRecord |
| pushLine | GroupedSegmentRecord (alias of pushSegment) |
| pushCurve | GroupedCurveRecord |
| pushArc | GroupedArcRecord |
| pushTriangle | GroupedTriangleRecord |
| pushPolyline | GroupedPolylineShape (CPU-tessellated into triangles) |
| pushPolygon | TriangulatedPolygon \| PolygonShape (earcut-triangulated, hole-aware) |
| pushSprite | SpriteShape (textured quad, post-main pass) |
Batch helpers
Section titled “Batch helpers”Loop the matching single push, applying a shared options.group to any element
that does not already carry its own group:
addRects, addCircles, addEllipses, addLines, addTriangles,
addPolygons, addSprites — each (shapes, options?: { group?: Group }).
For hot paths there are pre-packed bulk methods that repack a flat v1-stride
buffer and submit one draw command (see Packing):
addSegmentsPacked, addCurvesPacked, addArcsPacked — each
(data, opts: { count, opaque, group? }).
Text methods
Section titled “Text methods”Require a layer created with { atlas }, or they throw:
| Method | Returns | Notes |
| -------------------------------------- | ----------------- | -------------------------------------------------------------------------- |
| pushText(shape, flags?) | GlyphMetricsOut | Full shaper (kerning, multi-line, wrap). |
| pushString(shape) | this | Fast single-line, no-kerning append. |
| pushAnchoredString(shape, flags?) | this | World-anchored, screen-sized; re-projects per frame (a pan never repacks). |
| pushStringsBulkInto(batch, options?) | this | Structure-of-arrays bulk label fast path. |
Lifecycle & inspection
Section titled “Lifecycle & inspection”| Member | Notes |
| ---------------------- | ------------------------------------------------------------------------------- |
| clear() | Reset the pack (and glyph/sprite packs) for the next frame; capacity is reused. |
| destroy() | Drop CPU buffers; do not push or render again. |
| setClipRect(rect?) | Set/clear the device-pixel crop; returns this. |
| shapeCount | Packed rect/circle/ellipse instance count. |
| triangleCount | Packed triangle instance count (polygons + polylines). |
| effectiveLocalBounds | Union world AABB of all packed shapes + glyphs, or null. |
| pack | The underlying UberPack. |
| glyphPack | This layer’s glyph instances, or null. |
Z-order and visibility
Section titled “Z-order and visibility”A layer is a flat z-band — layer N is entirely above layer N−1 (painter’s order), with no per-shape interleave across layers.
zIndexsorts ascending (lower = drawn first = below).undefinedkeeps array insertion order and sorts above every explicit band. Equal keys preserve insertion order (the sort is stable).- A pure
zIndexchange between frames is a composite-only invalidation; the renderer folds the z-order into its view fingerprint, so a reorder demotes a partial frame to a clean full repaint instead of ghosting. visible: falseexcludes the layer entirely; flipping it forces a full repaint next frame so the hidden layer’s pixels do not linger.
To interleave shapes from two “layers,” put them in the same layer — intra- layer overlap resolves via transparency and depth.
Cache hints
Section titled “Cache hints”CacheHint controls whether the renderer backs a layer with a cached RTT
snapshot:
| Hint | Behavior |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| "auto" (default) | A cheap static heuristic (instanceCount + glyphs × 8) bakes expensive layers, keeps cheap ones live. |
| "always" | Always bake; re-bake only when content or view changes. Pinned (exempt from budget eviction). |
| "never" | Always rasterize live. |
The hint is subordinate to z-soundness: a bake can only composite under all
live geometry or over it, so a layer whose bake would sit between two live
layers stays live even under "always". Caching accelerates the bottom and top
of the z-stack. The renderer applies the hint each frame; the manual escape
hatches are cacheLayer / uncacheLayer.
Grouped shape records
Section titled “Grouped shape records”Every push record extends GroupedShape, which adds two optional tags:
| Field | Type | Notes |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| group | Group | The shape’s transform group (see below). A per-push group overrides the layer’s default group. |
| emphasisKey | number | Per-instance emphasis key. 0 (default) is the exempt sentinel — never dimmed. A key ≥ 1 opts into setEmphasis dimming and forces the transparent path. |
The exported grouped records are GroupedRectRecord, GroupedCircleRecord,
GroupedEllipseRecord, GroupedSegmentRecord, GroupedCurveRecord,
GroupedArcRecord, GroupedTriangleRecord, GroupedTextShape, and
GroupedAnchoredStringShape.
Groups
Section titled “Groups”A Group is a value object holding one Mat3 transform and an optional parent.
Groups chain: a child’s effective transform is the compounded product of every
ancestor’s transform, root-to-leaf.
import { createGroup, resolveGroupTransform, translation, scaling } from "insomni";
const root = createGroup({ transform: translation(100, 50) });const child = createGroup({ transform: scaling(2, 2), parent: root });
// Tag shapes with the group — geometry transforms live, no repack on a move.layer.pushRect({ x: 0, y: 0, width: 10, height: 10, fill, group: child });
// Mutating the transform between frames takes effect without re-packing:root.transform = translation(120, 50);
resolveGroupTransform(child); // => multiply(root.transform, child.transform)GroupOptions / Group
Section titled “GroupOptions / Group”| Member | Type | Notes |
| ----------- | --------------- | ----------------------------------------------------------------------------------------- |
| transform | Mat3 | Default IDENTITY. Mutable — update between frames; takes effect without re-packing. |
| parent | Group \| null | Default null. Immutable after creation; transforms compound up the chain. |
createGroup(options?) mints a fresh group.
resolveGroupTransform(group)
Section titled “resolveGroupTransform(group)”Walks the parent chain and returns the compounded transform (root applied first,
leaf last); returns IDENTITY for a null group.
How groups select a camera slot
Section titled “How groups select a camera slot”A Group is CPU-side draw metadata — it is never packed into the instance
bytes (no repack on a group move). Instead the renderer composes the group’s
resolved transform into the layer’s space view-projection
and routes the command to a dedicated per-(space × group) camera slot. A
group-identity change is a DrawCommand merge boundary, so two shapes in
different groups never coalesce into one draw (they need different slots). This
is why mutating group.transform between frames moves the geometry live with no
re-packing cost.