Draw into a canvas

Draw into a canvas

A full-screen @fragment entry and the host that draws it. The host imports fs and calls fs(canvas, { frame }) once a frame, with the time in the uniform: one full-screen triangle, on WebGPU, then WebGL2, then the CPU tier, with no pipeline, buffer or bind group in the host's code (Rule 8.24).

Runs on WebGPUWebGL2CPU tier

The compiler checks this list: a tier is on it exactly when the host's whole run gives the reference with that tier alone. Its journeys gate runs each host on WebGPU and on WebGL2, and its unit suite runs each one on the CPU tier.

"use typeshade";
/* @example
{
"title": "Host: draw into a canvas",
"blurb": "A full-screen `@fragment` entry and the host that draws it. The host imports `fs` and calls `fs(canvas, { frame })` once a frame, with the time in the uniform: one full-screen triangle, on WebGPU, then WebGL2, then the CPU tier, with no pipeline, buffer or bind group in the host's code (Rule 8.24).",
"renderable": false,
"reason": "no entry point"
}
*/
// What the host sets each frame: the time, and the canvas's size in pixels.
class Frame {
time: f32;
size: vec2;
}
declare const frame: uniform<Frame>;
// A full-screen fragment entry reads no builtin but `position`, and writes one colour, so a host
// draws it into a canvas with no vertex entry of its own.
@fragment
export function fs(@builtin("position") p: vec4): vec4 {
const uv = p.xy / frame.size;
const wave = 0.5 + 0.5 * sin(6.2831853 * uv.x + frame.time);
return vec4(uv.x, uv.y, wave, 1.);
}
// Draw a full-screen fragment entry into a canvas (surface §67, Rule 8.24).
//
// The import is the shader module itself: the Vite plugin (`typeshade/vite`) turns `fs` into a
// function that draws one frame. The first draw into a canvas picks its tier: WebGPU, then
// WebGL2, then the CPU tier, which paints the frame pixel by pixel.
import { fs } from './host-draw.shade.ts';
/** Draw one frame at `time` seconds. The promise resolves when the frame is submitted. */
export function draw(canvas: HTMLCanvasElement | OffscreenCanvas, time: number): Promise<void> {
return fs(canvas, { frame: { time, size: [canvas.width, canvas.height] } });
}
/** Animate `canvas`: a new uniform value each frame. A frame loop need not wait for a draw. */
export function animate(canvas: HTMLCanvasElement): void {
const frame = (ms: number): void => {
void draw(canvas, ms / 1000);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
}
/** Draw the frames at `times`, one after another. The canvas then shows the last one. */
export async function run(
canvas: HTMLCanvasElement | OffscreenCanvas,
times: readonly number[] = [0, 0.5, 1.25],
): Promise<void> {
for (const time of times) await draw(canvas, time);
}

Both files are the compiler's own at commit 91c2fe31, from the examples directory.

Module on GitHubHost file on GitHub

Edit this page Report a problem