Skip to content

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 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.

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:

Terminal window
pngine shader.sjon --flat -o art.png
import { 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 --flat PNG plays only in the mini player; the viewer needs pNGb. 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 pNGf exported before clear values became one f32 per channel encodes its pass commands differently, so an old flat PNG needs re-exporting rather than a fallback path.

--html emits one self-contained HTML file: generated WebGPU JavaScript plus the payload, no npm dependency at all.

Terminal window
pngine shader.sjon --html -o art.html

Measured (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

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.

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:

vite.config.js
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.

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.