(wasm-call …)
Calls a function from a WebAssembly (WASM) module every frame and makes its
result available to a (queue …) write. Use it for values the CPU computes per
frame, a camera matrix being the usual one.
Syntax
Section titled “Syntax”(wasm-call :name myCall :file "path/to/module.wasm" :func functionName :args [time-total canvas-width] :returns "f32")| Key | Type | Required | Default | Description |
|---|---|---|---|---|
:name |
symbol | Yes | - | Unique name, referenced by a (write-buffer … :data …) |
:file |
string | Yes | - | WASM file path, relative to the source file |
:func |
symbol | Yes | - | Exported function called each frame |
:returns |
string | Yes | - | Result type, which fixes how many bytes are written |
:args |
array | No | [] |
Per-frame arguments (at most 32) |
Type: string
The WASM file is read at compile time, relative to the source file, and its
bytes are embedded in PNGB (PNGine’s compiled bytecode). Two (wasm-call …)
forms naming the same path share one module:
:file "math.wasm"Type: symbol
Exported function name to call:
:func computeMatrixType: array
Arguments to pass to the function. Each is either a built-in symbol the runtime fills every frame, or a numeric literal:
| Built-in arg | Description |
|---|---|
canvas-width |
Canvas width in pixels |
canvas-height |
Canvas height in pixels |
time-total |
Elapsed seconds since start |
time-delta |
Seconds since last frame |
:args [time-total canvas-width canvas-height]:args [45.0 1.777 0.1 100.0]An integer literal is passed as a u32, anything else as an f32. The call
opcode carries at most 32 arguments; a longer list is rejected at compile time
rather than truncated.
:returns
Section titled “:returns”Type: string
The result type, which is how the compiler knows how many bytes to copy out of the module’s memory. The accepted spellings are exactly these:
| Type | Bytes |
|---|---|
"f32", "i32", "u32" |
4 |
"vec2" |
8 |
"vec3" |
12 |
"vec4" |
16 |
"mat3x3" |
36 |
"mat4x4" |
64 |
Anything else is a compile error: (wasm-call …) declares :returns ‘vec4f’,
which is not a return type the wire can carry. The longer array<f32, N>
spelling belongs to (wasm-data …), the once-at-creation form, not here.
Examples
Section titled “Examples”Perspective Matrix
Section titled “Perspective Matrix”(wasm-call :name perspective :file "matrices.wasm" :func perspective :args [1.0472 1.777 0.1 100.0] :returns "mat4x4")
(buffer :name mvpBuffer :size 64 :usage [uniform copy-dst])
(queue :name writeMVP (write-buffer :buffer mvpBuffer :offset 0 :data perspective))A (write-buffer … :data <name>) naming the call writes its result into the
buffer each frame. That one key covers all three things the source can be, a
runtime built-in such as pngine-inputs, a (data …) entry, or a
(wasm-call …); which one it is follows from what the name was declared as.
Model-View-Projection
Section titled “Model-View-Projection”(wasm-call :name modelMatrix :file "transform.wasm" :func rotateY :args [time-total] :returns "mat4x4")
(wasm-call :name viewMatrix :file "transform.wasm" :func lookAt :args [0 0 5 0 0 0 0 1 0] :returns "mat4x4")
(wasm-call :name projMatrix :file "transform.wasm" :func perspective :args [45 1.0 0.1 100] :returns "mat4x4")Generated Data, Once
Section titled “Generated Data, Once”A table the shader reads but nothing animates does not belong here: a
(wasm-data …) inside a (data …) runs once at buffer-create time instead, and
its :returns may be an array.
(data :name noiseTable (wasm-data :file "noise.wasm" :func generateNoise :returns "array<f32,1024>"))
(buffer :name noiseBuffer :usage [storage] :data noiseTable)Runtime Arguments
Section titled “Runtime Arguments”(wasm-call :name rotatedMVP :file "matrices.wasm" :func rotatingMVP :args [time-total canvas-width canvas-height] :returns "mat4x4")WASM Module Requirements
Section titled “WASM Module Requirements”The module either exports its memory as memory or imports one as
env.memory (the player supplies a one-page memory under that name and reads
the result from whichever the module uses; an exported memory wins). The
called function returns a pointer: a byte offset into that memory where
the result already sits. The
runtime copies :returns-many bytes from there into the target buffer, so the
function writes its output wherever it likes and hands back the address.
// Example WASM source (C)static float out[16];
__attribute__((export_name("perspective")))float *perspective(float fov, float aspect, float near, float far) { // fill out[0..15] return out; // the runtime reads 64 bytes from here}Or in Rust:
static mut OUT: [f32; 16] = [0.0; 16];
#[unsafe(no_mangle)]pub extern "C" fn perspective(fov: f32, aspect: f32, near: f32, far: f32) -> *const f32 { // fill OUT unsafe { OUT.as_ptr() }}examples/assets/mvp.wasm in the engine repository is a working module of this
shape: buildMVPMatrix(f32, f32, f32) -> i32, exporting memory.
Related forms
Section titled “Related forms”(wasm-call …) runs every frame. Two neighbours run at other times:
(wasm-data …), a positional child of(data …), runs once at buffer-create time and fills the buffer throughmappedAtCreation. It takes the same:file/:func/:returnskeys, and its:returnsalso acceptsarray<f32, N>.(buffer … :file "mod.wasm")takes both the size and the initial bytes from the module’s ownl/s/genexports, so no:sizeis authored.
Native --frame rendering stubs the WASM tiers, so a document that depends on
one of these renders in the browser rather than through the CLI.
Data Flow
Section titled “Data Flow”(wasm-call …) calls :func with :args, once per frame │ ▼returned pointer into the module's memory │ ▼(write-buffer :buffer B :offset O :data <call name>) copies :returns bytes │ ▼GPU buffer, readable by the shaders bound to itValidation Rules
Section titled “Validation Rules”| Rule | Error |
|---|---|
Two (wasm-call …) forms may not share a name, and a (data …) may not share it either: they are one reference group |
duplicate_cross_ref_target |
:file, :func and :returns are required |
missing_required_key |
| The file must exist, relative to the source | cannot read 'math.wasm': FileNotFound |
:returns must be one of the spellings above |
(wasm-call …) declares :returns ‘X’, which is not a return type the wire can carry |
:args takes at most 32 arguments |
wasm-call ‘:args’ lists 40 arguments, but the call opcode carries at most 32 |
An :args symbol must be one of the four runtime built-ins; a (define …) name is not accepted here, write it as arithmetic, (* SCALE 1) |
union_no_branch_matched |
Related
Section titled “Related”(data …)- Alternative for static/generated data(queue …)- Write WASM results to buffers(buffer …)- GPU buffers for results