Drawing primitives
Everything you draw with insomni goes into a Layer. A
layer exposes a pushXxx / addXxx method per primitive kind; each call packs
one record into the layer’s flat instance buffer and appends (or merges into) a
draw command. There is no scene graph of retained shape objects — you push
records every frame, or build a layer once and retain it.
pushRect to pushPolyline. Each kind below is one of these packed records. Stroke styling — caps, joins, and dashes — is where polylines and curves diverge from the SDF shapes:
pushPolyline ends, corners, and dashes a stroke. The renderer draws SDF primitives (rect / circle / ellipse / segment / curve / arc) directly from their packed parameters, so they stay pixel-crisp at any zoom. Polygons and polylines are CPU-tessellated into triangles. Sprites are a textured sibling pass drawn after the main geometry.
Shape kinds
Section titled “Shape kinds”Layer.pushRect(shape) / Layer.addRects(shapes, options?). A rounded,
optionally rotated rectangle. (x, y) is the top-left corner; width/height
are full extents.
| Field | Type | Default | Notes |
| ----------------- | ------ | ----------- | ------------------------------------- |
| x, y | number | — | Top-left corner. |
| width, height | number | — | Full extents. |
| fill | Color | transparent | Interior fill. |
| stroke | Color | transparent | Border color. |
| strokeWidth | number | 0 | Border thickness; straddles the edge. |
| cornerRadius | number | 0 | Rounded corners. |
| rotation | number | 0 | Radians, about the rect center. |
Circle
Section titled “Circle”Layer.pushCircle(shape) / Layer.addCircles(shapes, options?).
| Field | Type | Default | Notes |
| ------------- | ------ | ----------- | ----------------- |
| cx, cy | number | — | Center. |
| radius | number | — | Radius. |
| fill | Color | transparent | Interior fill. |
| stroke | Color | transparent | Border color. |
| strokeWidth | number | 0 | Border thickness. |
Ellipse
Section titled “Ellipse”Layer.pushEllipse(shape) / Layer.addEllipses(shapes, options?).
| Field | Type | Default | Notes |
| ------------- | ------ | ----------- | -------------------------- |
| cx, cy | number | — | Center. |
| rx, ry | number | — | Radii. |
| fill | Color | transparent | Interior fill. |
| stroke | Color | transparent | Border color. |
| strokeWidth | number | 0 | Border thickness. |
| rotation | number | 0 | Radians, about the center. |
Segment and line
Section titled “Segment and line”Layer.pushSegment(shape) is the dedicated straight-line primitive: a compact
record with butt caps and a 1D SDF — the cheapest stroke, preferred for
high-count edges (tree branches, edge bundles). Joins between segments are the
caller’s responsibility.
| Field | Type | Default | Notes |
| ---------- | ------ | ------- | ------------------------------- |
| x1, y1 | number | — | Start point. |
| x2, y2 | number | — | End point. |
| color | Color | — | Stroke color (required). |
| width | number | 1 | Thickness in layer-local units. |
Layer.pushLine(shape) is a back-compat alias for pushSegment — a LineShape
is just a straight stroked segment, so it collapses onto the segment path.
Layer.addLines(shapes, options?) bulk-appends segments.
Layer.pushCurve(shape). A cubic Bézier stroke through four control points,
with optional per-end taper. The fragment solves distance-to-curve, so it stays
crisp at any zoom.
| Field | Type | Default | Notes |
| ------------ | ------- | --------- | ------------------------------------------------------------------------ |
| p0–p3 | Vec2 | — | Cubic control points (p0 start, p3 end). |
| color | Color | — | Stroke color (required). |
| width | number | 1 | Uniform thickness; overridden by the taper widths. |
| widthStart | number | width | Thickness at p0. |
| widthEnd | number | width | Thickness at p3. |
| cap | LineCap | "round" | End cap. Round caps are free; butt/square discard past the endpoint. |
Layer.pushArc(shape). A thick-stroked circular arc with butt caps, rendered
SDF-based. theta0 / theta1 are absolute angles in radians; the rendered
geometry uses the absolute span (direction is irrelevant). For a full ring pass
theta1 = theta0 + 2π.
| Field | Type | Default | Notes |
| ---------- | ------ | ------- | ------------------------ |
| cx, cy | number | — | Center. |
| radius | number | — | Arc radius. |
| theta0 | number | — | Start angle (radians). |
| theta1 | number | — | End angle (radians). |
| color | Color | — | Stroke color (required). |
| width | number | 1 | Stroke thickness. |
Triangle
Section titled “Triangle”Layer.pushTriangle({ p1, p2, p3, fill }) / Layer.addTriangles(...). A
flat-filled triangle — the substrate every polygon and polyline tessellates
into. p1/p2/p3 are Vec2; fill is a Color.
Polygons
Section titled “Polygons”Layer.pushPolygon(shape) / Layer.addPolygons(shapes, options?) accept two
forms:
{ points, holes?, fill }— a hole-aware filled polygon.pointsis the outer ring;holesare inner rings. Rings are flattened and triangulated withearcuton the CPU, then each triangle is appended through the triangle kind.{ triangles }— a pre-triangulated polygon: the caller supplies the triangle list directly.
import { createLayer, rgba } from "insomni";
const layer = createLayer();layer.pushPolygon({ points: [ { x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 80 }, { x: 0, y: 80 }, ], holes: [ [ { x: 30, y: 20 }, { x: 70, y: 20 }, { x: 50, y: 60 }, ], ], fill: rgba(0.2, 0.5, 0.9, 1),});Polylines and line styling
Section titled “Polylines and line styling”Layer.pushPolyline(shape) tessellates a stroked polyline on the CPU into
triangles. This is the path with full cap / join / dash control.
| Field | Type | Default | Notes |
| ------------- | ----------------- | --------- | ------------------------------------- |
| points | readonly Vec2[] | — | Vertices. |
| color | Color | — | Stroke color (required). |
| width | number | 1 | Thickness. |
| cap | LineCap | "butt" | "butt" | "round" | "square". |
| join | LineJoin | "miter" | "miter" | "bevel" | "round". |
| closed | boolean | false | Close the loop. |
| miterLimit | number | 4 | Miter falls back to bevel past this. |
| dashPattern | readonly number[] | — (solid) | Alternating [dash, gap, …] lengths. |
| dashOffset | number | 0 | Offset into the dash pattern. |
LineCap ("butt" | "round" | "square") and LineJoin
("miter" | "bevel" | "round") are the polyline styling unions. The curve kind
expresses its cap via the cap field (also a LineCap); the integer constants
CURVE_CAP_ROUND (0), CURVE_CAP_BUTT (1), and CURVE_CAP_SQUARE (2) are the
packed-buffer encoding of those names, exported for callers that build curve
buffers by hand.
Polyline rings
Section titled “Polyline rings”Two helpers generate ring point lists you can stroke through pushPolyline —
handy for dashed/dotted SDF-shape borders (the SDF rect/circle/ellipse pipeline
does not support dashes natively, but a tessellated ring does):
polylineEllipseRing({ cx, cy, rx, ry, rotation?, segments? })→Vec2[]polylineRectRing({ x, y, width, height, cornerRadius?, cornerSegments? })→Vec2[]
Both return an unclosed loop — pass closed: true to the polyline:
import { polylineEllipseRing, rgba } from "insomni";
const ring = polylineEllipseRing({ cx: 50, cy: 50, rx: 40, ry: 25 });layer.pushPolyline({ points: ring, color: rgba(0.9, 0.3, 0.3, 1), width: 2, closed: true, dashPattern: [6, 4],});Sprites and textures
Section titled “Sprites and textures”A textured quad cannot join the shape instance buffer (it carries an external
texture binding), so sprites accumulate in a per-layer SpritePack and draw in
a sibling pass after the main geometry, stacked by submission order. Adjacent
sprites sharing a texture coalesce into one draw command.
Layer.pushSprite(sprite) / Layer.addSprites(sprites):
| Field | Type | Default | Notes |
| ---------- | ---------------------------- | -------------------- | -------------------------------------------------------------------- |
| pos | Vec2 | — | Position of the sprite anchor. |
| size | Vec2 | — | Size in the layer’s space. |
| texture | Texture | TextureRegion | — | Source image or sub-region. |
| tint | Color | opaque white | Multiplicative tint. |
| rotation | number | 0 | Radians, about the anchor. |
| anchor | Vec2 | { x: 0.5, y: 0.5 } | Normalized anchor within the sprite. |
| opaque | boolean | false | Route to the opaque pass; set only when the texture is fully opaque. |
The SpritePack (SPRITE_FLOATS = 16, SPRITE_BYTES = 64) plus the
SpriteShape and SpriteCommand types are exported for callers who pack sprite
buffers directly.
Loading textures
Section titled “Loading textures”loadTexture(owner, src, options?) loads an image from a URL or any
ImageBitmapSource into a caller-owned Texture (premultiplied alpha, matching
the renderer’s blend state). owner is the renderer / root. LoadTextureOptions:
format (default "rgba8unorm") and label. The returned Texture is never
destroyed for you — call texture.destroy() when done.
import { loadTexture } from "insomni";
const tex = await loadTexture(renderer, "/icons.png");layer.pushSprite({ pos: { x: 0, y: 0 }, size: { x: 32, y: 32 }, texture: tex });Texture regions and atlases
Section titled “Texture regions and atlases”A TextureRegion is a borrowed UV sub-rectangle of a Texture:
texture.region(x, y, w, h)— by pixel coordinates.texture.regionUV(u0, v0, u1, v1)— by normalized UV.
Two atlas helpers wrap region lookup:
SpriteAtlas— named regions.new SpriteAtlas(texture, { name: { x, y, w, h }, … }), thenatlas.region("name"). Region definitions use theSpriteAtlasRegionDefshape.GridAtlas— a uniform grid.new GridAtlas(texture, { cell: [w, h], rows?, cols? })(GridAtlasOptions), thenatlas.frame(index)oratlas.frame(row, col).
import { GridAtlas } from "insomni";
const atlas = new GridAtlas(tex, { cell: [16, 16] });layer.pushSprite({ pos: { x: 0, y: 0 }, size: { x: 16, y: 16 }, texture: atlas.frame(3),});SamplerDescriptor (filter / addressMode / mipmapFilter) is exported for layers
that override the default sprite sampler.
Packed (zero-copy) batch paths
Section titled “Packed (zero-copy) batch paths”For very high counts, Layer exposes pre-packed bulk uploads that skip
per-record object allocation:
addSegmentsPacked(data, { count, opaque, group? }),
addCurvesPacked(...), and addArcsPacked(...). Each consumes a flat buffer at
the stride exported as SEGMENT_FLOATS (6), CURVE_FLOATS (12), and
ARC_FLOATS (8) floats per record, and is byte-identical to looping the matching
single push. See Packing for the buffer layouts.
f16 packing helpers
Section titled “f16 packing helpers”Several per-shape scalars (size, rotation, stroke width, corner radius) pack into IEEE-754 half-precision to keep the instance record small. The packing helpers are exported for callers who build instance buffers directly:
| Function | Purpose |
| -------------------------- | ------------------------------------------------------------- |
| packF16(value) | Pack a float to its 16-bit half pattern (ties-to-even). |
| unpackF16(bits) | Inverse of packF16. |
| pack2xF16(lo, hi) | Pack two floats into one little-endian u32 (pack2x16float). |
| packUnorm8x4(r, g, b, a) | Pack four [0,1] components into a unorm8x4 u32. |
See also
Section titled “See also”- Layers & groups — the
Layersurface and coordinate spaces. - Packing — instance buffer layout and the
SpritePack. - Text — glyph rendering.
- Specialized layers — mirror, navigator, LOD, buffer layers.