Shipping a Player
A compiled PNGine payload needs a player on the page. The payload is PNGB,
PNGine’s compact bytecode, usually travelling inside a PNG’s pNGb chunk
together with the executor, the small WASM interpreter that turns it into GPU
commands. There are three player tiers plus a zero-dependency escape hatch;
they trade capability for bytes. Sizes are production measurements (2026-08-18,
gzip in parentheses).
| Tier | Page cost | Payload | Capability |
|---|---|---|---|
Viewer (pngine) |
49.7 KB (17.0) | PNG with embedded executor | Everything |
Mini (pngine/mini) |
7.1 KB (3.3) | PNG with a pNGf chunk (flat command buffers, --flat) |
Command subset, main thread |
--html |
~1 KB, self-contained | Inlined | What the document uses |
Core (pngine/core) |
26.6 KB (9.0) | Extracted bytecode | Full dispatcher, no wrapper |
The viewer
Section titled “The viewer”The default. Runs the embedded executor in a worker over an OffscreenCanvas and plays every command the format defines:
import { pngine, play } from "pngine";
const p = await pngine("art.png", { canvas });play(p);It accepts only self-contained payloads, the default output of
pngine shader.sjon. A payload compiled with --no-executor needs
pngine/dev and its wasmUrl fallback instead; that combination is for
development, not shipping.
Full API: JavaScript API.
The mini player
Section titled “The mini player”pngine/mini plays a pre-flattened command stream on the main thread: no
worker, no WASM, 3.3 KB on the page (3.0 KB as pngine/mini-no-audio).
The payload is different: --flat writes a pNGf chunk (init and frame
command buffers with inline data) and omits pNGb entirely:
pngine shader.sjon --flat -o art.pngimport { miniPngine } from "pngine/mini";
await miniPngine(canvas, "art.png", { autoplay: true });The measured cost for simple_triangle (2026-08-20): 405 bytes of PNG, against
4,659 for the self-contained default.
Three consequences of the format:
- A
--flatPNG plays only in the mini player; the viewer needspNGb. Ship both chunks by exporting twice if both players matter. - The mini player decodes a subset of the command set by design, and the flat writer refuses to export a document the subset cannot express, so the failure happens at export time, not on the page.
- The chunk states its own version, currently 2, and the player refuses any
other. A
pNGfexported before clear values became onef32per channel encodes its pass commands differently, so an old flat PNG needs re-exporting rather than a fallback path.
Standalone HTML
Section titled “Standalone HTML”--html emits one self-contained HTML file: generated WebGPU JavaScript plus
the payload, no npm dependency at all.
pngine shader.sjon --html -o art.htmlMeasured (2026-08-20): simple_triangle 884 bytes, pass_shader_art 1,189
bytes, both complete files with the canvas included.
| Option | Effect |
|---|---|
--fullscreen / --fixed |
Canvas sizing (default: fullscreen) |
--unpack |
No deflate: larger output, readable JS for debugging |
--minify |
WGSL minified by wgslender (the WGSL minifier and reflection library PNGine uses) before packing |
Dark mode
Section titled “Dark mode”A uniform struct member named dark: f32 receives a dark-mode flag, resolved
per frame: the pngine-dark localStorage key ('1'/'0') when present, else
prefers-color-scheme. Both are live: a storage event from a same-origin
embedding page flips it without a reload. --minify preserves uniform member
names, so the convention survives minification. The landing page of this site
runs on it: its theme toggle mirrors into pngine-dark, and the background
shader follows.
pngine/core + pngine/executor are the pieces the viewer is built from
(dispatcher, loader, executor instantiation), for hosts that need their own
main loop, device, or canvas handling. The wiring walkthrough is in the
WASM API.
Bundling apps: vite-plugin-pngine
Section titled “Bundling apps: vite-plugin-pngine”For an app (not a bare page), vite-plugin-pngine configures dev-mode
aliases, WASM serving and single-file production output in one plugin call:
import { defineConfig } from "vite";import { pnginePlugin } from "vite-plugin-pngine";
export default defineConfig({ plugins: [pnginePlugin()] });vite build then produces a single index.html with JS and PNG assets
inlined.
Payload ↔ player matrix
Section titled “Payload ↔ player matrix”| Artifact | Produced by | Played by |
|---|---|---|
PNG, pNGb + executor |
pngine x.sjon (default) |
Viewer, dev, core |
PNG, pNGb, no executor |
--no-executor |
pngine/dev with wasmUrl |
PNG, pNGf |
--flat |
Mini player only |
.html |
--html |
Itself |
.pngb |
compile |
Core, or embed into a PNG later |
--audio <path> adds a pNGa chunk (audio WASM) to any of the PNG forms;
--types writes a .d.ts for the document’s uniforms next to the output.
Related
Section titled “Related”- CLI Reference - Every flag named above
- JavaScript API - Viewer and mini APIs
- Bytecode Format - What each chunk carries