SJON Syntax
PNGine source is SJON: an S-expression language in which every WebGPU
resource is a form, validated against the WebGPU schema in
schema/pngine.sjon,
the single source of truth for what a .sjon document may contain. SJON owns the
syntax, validation, cross-reference resolution, expressions, and sugar lowering
(the expansion of shorthand forms into ordinary ones); PNGine owns the schema,
the pngine/* lowering hooks, and bytecode emission.
This page is a tour of what a .sjon PNGine document looks like. For the
language underneath (its reader syntax, value kinds, and how a host defines a
schema), see the SJON documentation. For
complete documentation of each PNGine form, see the
SJON Reference.
Basic Structure
Section titled “Basic Structure”A .sjon file is a sequence of S-expression forms. Each form is a head symbol
followed by keyword keys (:key value) and positional sub-forms:
(form-name :key value :flag true (sub-form positional :key value) [array of items])
; comments start with a semicolonValue Types
Section titled “Value Types”| Type | Example | Description |
|---|---|---|
| String | "WGSL code" or """multi-line""" |
Quoted / triple-quoted text |
| Number | 123, 0.5 |
Decimal or float |
| Boolean | true, false |
Boolean values |
| Symbol | myName, triangle-list |
Bare identifier (enum member or cross-ref) |
| Array | [a b c] |
Space-separated list |
| Sub-form | (vertex :module code :entry vs) |
Positional nested form |
| Expression | (* NUM 16), (ceil (/ NUM 64)) |
Compile-time arithmetic |
Quick Reference
Section titled “Quick Reference”Shaders
| Form | Purpose | Details |
|---|---|---|
(shader-module …) |
Named WGSL module | Full docs |
Resources
| Form | Purpose | Details |
|---|---|---|
(buffer …) |
GPU buffer (vertex, uniform, storage, index) | Full docs |
(texture …) |
GPU texture | Full docs |
(texture-view …) |
Explicit view over a texture | Full docs |
(sampler …) |
Texture sampler | Full docs |
(image-bitmap …) |
Decoded image → texture | Full docs |
(data …) |
Embedded data and shape generators | Full docs |
(query-set …) |
Occlusion / timestamp queries | Full docs |
Pipelines
| Form | Purpose | Details |
|---|---|---|
(render-pipeline …) |
Render pipeline | Full docs |
(compute-pipeline …) |
Compute pipeline | Full docs |
(bind-group …) |
Resource bindings | Full docs |
(bind-group-layout …) |
Explicit bind group layout | Full docs |
(pipeline-layout …) |
Explicit pipeline layout | Full docs |
Execution
| Form | Purpose | Details |
|---|---|---|
(render-pass …) |
Render pass with draw commands | Full docs |
(compute-pass …) |
Compute pass with dispatch | Full docs |
(render-bundle …) |
Pre-recorded, replayable draws | Full docs |
(queue …) |
Buffer writes, copies, query resolves | Full docs |
(frame …) |
Frame execution order | Full docs |
Advanced
| Form | Purpose | Details |
|---|---|---|
(init …) |
One-shot compute init (sugar) | Full docs |
(pass-graph …) |
Fullscreen shader-art sugar | Full docs |
(wasm-call …) |
WASM function called every frame | Full docs |
(define …) |
Compile-time constants | Full docs |
(limits …) |
Raise WebGPU device limits | Full docs |
(canvas …) |
Canvas alpha mode | Full docs |
Minimal Example
Section titled “Minimal Example”(shader-module :name code :code """ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { var pos = array<vec2f, 3>( vec2f(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5) ); return vec4f(pos[i], 0.0, 1.0); }
@fragment fn fs() -> @location(0) vec4f { return vec4f(1.0, 0.0, 0.0, 1.0); }""")
(render-pipeline :name pipeline :layout auto (vertex :module code :entry vs) (fragment :module code :entry fs (target :format preferred-canvas-format)))
(render-pass :name pass (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :pipeline pipeline (draw :vertex-count 3))
(frame :name main :perform [pass])References
Section titled “References”Resources reference each other using bare identifiers. SJON’s validator resolves
them document-wide against the declared forms (a dangling name is a
not_cross_ref diagnostic):
:pipeline myPipeline ; resolves to a (render-pipeline …) or (compute-pipeline …):module shader ; resolves to a (shader-module …):buffer vertices ; resolves to a (buffer …)Every :name lives in one namespace across every form kind: a buffer and a
texture cannot both be called shared, and the compiler reports the second
declaration. The built-in spellings (canvas, context-current-texture,
preferred-canvas-format, pngine-inputs) are reserved and cannot be declared.
See References for details.
Expressions
Section titled “Expressions”Every numeric slot accepts a literal, a bounded compile-time expression over
(define …) constants, or a bare constant name:
(define :name NUM_PARTICLES :value 2048)
(buffer :name particles :size (* NUM_PARTICLES 16) :usage [storage])
(compute-pass :name step :pipeline simPipeline (dispatch :workgroups [(ceil (/ NUM_PARTICLES 64))]))The usual functions are +, -, *, /, ceil, floor and fract; the
full set (mod, pow, min, max, clamp, sqrt, abs, round, the
trigonometric functions, comparisons and if) is on the expressions page.
A bare name works wherever a number does, so :size NUM_PARTICLES and
:instance-count NUM_PARTICLES are both values, not cross-references to another
form. A misspelt one is a not_cross_ref diagnostic on that slot.
See Expressions for details.
Shape Generators
Section titled “Shape Generators”Generate vertex data at compile time with a positional shape sub-form inside
(data …):
(data :name cubeVertices (cube :format [position4 color4 uv2]))
(data :name planeVertices (plane :format [position3 uv2]))Format specifiers:
position3,position4- Vertex position (vec3f or vec4f)normal3- Surface normal (vec3f)color3,color4- Vertex color (vec3f or vec4f)uv2- Texture coordinates (vec2f)
See (data …) for details.
Built-in Uniforms
Section titled “Built-in Uniforms”Write runtime data (time, canvas size) to a uniform buffer using the
pngine-inputs source in a write-buffer:
(buffer :name uniforms :size 16 :usage [uniform copy-dst])
(queue :name writeTime (write-buffer :buffer uniforms :offset 0 :data pngine-inputs))pngine-inputs provides 16 bytes. Two more built-in sources exist:
scene-time-inputs (12 bytes: time, width, height) and pointer-inputs
(48 bytes of pointer state).
pngine-inputs fields:
| Field | Type | Description |
|---|---|---|
time |
f32 | Elapsed seconds |
width |
f32 | Canvas width |
height |
f32 | Canvas height |
aspect |
f32 | width / height |
See (queue …) for details.
Initialization Passes
Section titled “Initialization Passes”Run a pass once per loaded payload (not once per frame) using :init:
(frame :name main :init [setupCompute] ; runs once per loaded payload :perform [updateCompute renderPass]) ; runs every frameEach :init entry names a compute pass: usually an (init …) form, the sugar
that expands into a compute pipeline, a bind group and a compute pass, but any
(compute-pass …) works there. Loading the same PNG again re-arms it;
seeking, pausing or restarting the animation does not.
See (frame …) for details.
Ping-Pong Buffers
Section titled “Ping-Pong Buffers”For compute simulations requiring double-buffering:
(buffer :name particles :size 32768 :usage [vertex storage] :pool 2)With :pool 2, the runtime creates two buffer instances and alternates between
them each frame. See (buffer …) for details.
Related
Section titled “Related”- SJON Reference - Complete form documentation
- Getting Started - First program tutorial
- CLI Reference - Command-line tools