Skip to content

(canvas …)

Configures how the canvas composites with the page. An optional singleton: declare it at most once, with no :name.

(canvas :alpha-mode opaque)
Key Type Required Default Description
:alpha-mode symbol No opaque opaque or premultiplied
Value Behaviour
opaque The alpha channel is ignored and the canvas composites as opaque. Cheaper, and it avoids fringing on a coloured page background. This is the WebGPU spec default.
premultiplied Colour values are premultiplied by alpha and blended with the page, so a shader can render genuinely transparent regions.

With the form absent the canvas is opaque, exactly as GPUCanvasConfiguration.alphaMode specifies. Only the deviation costs anything: write the form when you want the page to show through.

opaque is the cheaper composite and removes any chance of edge fringing on a coloured page. It is also what a document with no (canvas …) gets, so saying nothing and saying opaque compile to the same bytes:

(canvas :alpha-mode opaque)

premultiplied blends the canvas with the page, so a clear value with alpha 0 and a fragment that writes alpha below 1 both let the page through:

(canvas :alpha-mode premultiplied)
(shader-module :name code :code """
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
return vec4f(f32(i) - 1.0, f32(i & 1u) * 2.0 - 1.0, 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(0.9, 0.2, 0.1, 0.5); // half transparent
}
""")
(render-pipeline :name pipe :layout auto
(vertex :module code :entry vs)
(fragment :module code :entry fs
(target :format preferred-canvas-format)))
(render-pass :name draw
(color-attachment :view context-current-texture
:clear-value [0 0 0 0] :load-op clear :store-op store)
:pipeline pipe
(draw :vertex-count 3))
(frame :name main :perform [draw])

examples/pngine_background.sjon in the engine repository is the worked case.

The value rides a header flag of PNGB (PNGine’s compact bytecode) rather than the opcode stream, and the flag is clear for opaque. The JS runtime consumes it at context.configure(), before the executor (the small WASM interpreter that plays the bytecode) runs. The --flat export, a pre-decoded command stream for the small main-thread player pngine/mini, mirrors it in its own flags byte, and --html emits a second configure() only when the flag is set.

Native --frame rendering is offscreen and has no page to composite with, so it ignores the form entirely; a (canvas …) never changes a CLI-rendered PNG.

Rule Error
Declare at most one (canvas …) per document duplicate (canvas …) form, declare canvas configuration at most once
:alpha-mode must be opaque or premultiplied not_member
(canvas …) takes no :name unknown_key
context.configure({
device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: 'premultiplied', // from :alpha-mode; 'opaque' when the form is absent
});