@motionscript/renderer 0.1.0-alpha.8 → 0.1.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,162 +1,162 @@
1
- # @motionscript/renderer
2
-
3
- A retained-mode 2D/3D renderer in Rust on wgpu/WebGPU, compiled to
4
- WebAssembly: the renderer `@motionscript/browser` draws every frame with.
5
- Hosts use browser's `createBrowserEngine()`, which builds this package in; it
6
- depends on nothing but `@motionscript/core` (a peer) and mediabunny.
7
- Apache-2.0; the Rust crates compiled into the WASM are listed with their
8
- licences in [THIRD_PARTY_NOTICES.txt](THIRD_PARTY_NOTICES.txt).
9
-
10
- Read [DESIGN.md](DESIGN.md) for the design and how each part was built.
11
-
12
- ## Requirements
13
-
14
- - Rust through rustup: [rust-toolchain.toml](rust-toolchain.toml) pins the
15
- toolchain and the `wasm32-unknown-unknown` target, which rustup installs on
16
- first use. `wasm-bindgen-cli` must be the exact version of the
17
- `wasm-bindgen` crate in `Cargo.lock` (`0.2.130`); the build scripts refuse
18
- any other:
19
- ```bash
20
- cargo install wasm-bindgen-cli --version 0.2.130 --locked
21
- ```
22
- - To regenerate the licence notices, `cargo-about` 0.9.3:
23
- `cargo install cargo-about --version 0.9.3 --features cli --locked`.
24
- - Node 24 and pnpm.
25
- - A browser with WebGPU (Chrome/Edge, recent Safari).
26
-
27
- ## In the workspace pipelines
28
-
29
- `pnpm build` here builds the WASM (`--locked`, with remapped build paths),
30
- then `dist/`, then `dist/browser/` with the engine's
31
- `scripts/build-browser.mjs`; turbo runs it before `@motionscript/browser`'s
32
- build. `prepack` refuses to pack a package without its `dist/`.
33
-
34
- `dist/browser/index.js` is the CDN build: `@motionscript/core` stays an import
35
- (the host's import map names it), the rest is bundled. Its `manifest.json`
36
- lists the WASM under `assets["ms_renderer_bg.wasm"]` (from
37
- `motionscriptBrowser.assets` in `package.json`): `dist/ms_renderer_bg.wasm`.
38
- The module's default `wasmUrl` (beside itself) does not hold there, so a host
39
- loading the CDN build passes that asset's URL as `wasmUrl`.
40
-
41
- The root `pnpm lint` and `pnpm typecheck` include it without Rust: the
42
- `.d.ts` files wasm-bindgen generates (`web/pkg/ms_renderer.d.ts`,
43
- `web/pkg-text/ms_text.d.ts`, `web/pkg-r3d/ms_render3d.d.ts`) are committed,
44
- and a WASM build rewrites them; commit the change when the Rust API moves.
45
-
46
- CI's `renderer` job (`.github/workflows/ci.yml`) installs the pinned
47
- toolchain and runs `cargo test --locked --workspace` (with
48
- `crates/renderer/tests/wgsl_modules.rs`, which runs every WGSL module the
49
- renderer ships through naga's validation), `pnpm notices:check`, the three
50
- WASM builds, a check that the committed `.d.ts` files are current, lint,
51
- typecheck, and the e2e wgpu lane (`packages/e2e`, `pnpm test:wgpu`: scenes
52
- drawn by this renderer on SwiftShader's WebGPU adapter, diffed against
53
- committed frames).
54
-
55
- `pnpm notices` regenerates [THIRD_PARTY_NOTICES.txt](THIRD_PARTY_NOTICES.txt)
56
- with cargo-about ([about.toml](about.toml) lists the accepted licences, so a
57
- crate under any other fails it); run it whenever `Cargo.lock` changes.
58
-
59
- ## The entry
60
-
61
- `src/engine` (in `web/src/engine/`) is what `@motionscript/browser` builds in.
62
- `createWgpuRenderer({ wasmUrl, loadBytes })` loads the WASM and returns a
63
- `WgpuRenderer`: `createStorageAdapter`, `createRenderContext` and
64
- `createMeasureContext` for a session, its exports and stills.
65
- `isWgpuSupported()` says whether the browser has a WebGPU adapter; there is no
66
- other backend to fall back to.
67
-
68
- `wasmUrl` is where `@motionscript/renderer/ms_renderer_bg.wasm` is served. It
69
- defaults to beside the module.
70
-
71
- - `WgpuStorageAdapter` decodes images to `ImageBitmap`s, video frames with
72
- mediabunny, and hands font bytes to ms-text. Bytes come through
73
- `loadBytes` (a plain `fetch` by default), which a platform points at its own
74
- asset store.
75
- - `WgpuSurfaceContext` is the adapter's render context mounted on a canvas,
76
- with the preview knobs a host sets (`pixelRatio`, `view`, `frame`,
77
- `clipToFrame`, `frameBackground`). Mounting is asynchronous (WebGPU device
78
- creation); frames recorded before it finishes are drawn when it does. The
79
- canvas composites premultiplied, so a transparent frame background shows the
80
- page behind it. What the adapter can't draw yet is logged once per kind
81
- (`[wgpu] not drawn yet: …`) and returned by `takeRenderIssues()` as a
82
- `missing` issue per kind and node (a blend mode it lacks, text on a path,
83
- `rasterize()`, nested or projective booleans, an off-centre motion-blur
84
- shutter, an effect with no WGSL port, a 3D feature it doesn't draw), beside
85
- `shader` issues (WGSL that does not compile, a stored SkSL shader) and
86
- `params` issues (a uniform that names no `Params` member, a member given no
87
- value). A session lists them in `buildErrors`; an export hands them to
88
- `onIssue`.
89
- - `measureText` and `layoutTextBlock` are ms-text (`MsText`, exported from
90
- the same WASM), so measurement, carets and drawing agree.
91
-
92
- ### GPU faults
93
-
94
- Every surface on the page shares one device, so a fault is everyone's.
95
- `onWgpuEvent(listener)` hears each one (`web/src/engine/faults.ts`):
96
-
97
- - **`device-lost`** (a driver reset, the GPU process restarting,
98
- `device.destroy()`): the device and its pipelines are forgotten and every
99
- live surface is mounted again on a new one, which rebuilds pipelines, fonts
100
- and user-shader programs and draws the last frame; `device-restored` follows.
101
- Each surface queues a `device-lost` render issue, which fails an export or
102
- still under way with a `RenderBackendError` (`retryable: true`) rather than
103
- encoding frames drawn on the dead device. A frame drawn while a surface
104
- remounts waits for it, as for a compiling shader. Four losses within a
105
- minute stop the recovery (`crashed`).
106
- - **`gpu-error`**: a WebGPU validation, out-of-memory or internal error the
107
- device raised (its `uncapturederror`), also a `gpu` render issue on every
108
- surface. The first 32 per page are reported.
109
- - **`crashed`**: the renderer's WASM panicked (it is built `panic = "abort"`;
110
- unwinding on wasm32 needs a nightly std, and the hook that reports the panic
111
- costs nothing measurable in size). The instance is not called again: every
112
- surface draws nothing more, gets a `crashed` issue, and an export fails
113
- with a `RenderBackendError` that is not retryable. A reload recovers.
114
-
115
- A user shader can't hang the device: its loops share a budget of 16,384
116
- iterations per pixel and a compile that takes over 10 s fails
117
- ([DESIGN.md §6.3](DESIGN.md#63-user-shaders-wgsl-built-2026-11-02)).
118
-
119
- ### User shaders (WGSL)
120
-
121
- Shader fills and shader effects are WGSL, written against the contract in
122
- [DESIGN.md §6.3](DESIGN.md#63-user-shaders-wgsl-built-2026-11-02). A session
123
- validates each new source with naga, compiles its pipelines in the background
124
- and draws it once they are ready (an export, and any settled frame, waits). A
125
- document stored with an SkSL shader (`language: "sksl"`) draws nothing there
126
- and gets a `shader` render issue saying to port it to WGSL. For a shader
127
- editor the package also exports:
128
-
129
- - `validateShader(scope, source)`: naga's diagnostics (line and column in the
130
- author's code) and the reflected `struct Params` members.
131
- - `ShaderPreview`: a fill or an effect drawn on its own canvas over a test
132
- pattern or an image (`frame`, `split`, `backdrop`), with the browser's
133
- `getCompilationInfo()` messages in its status and `snapshot()` for
134
- thumbnails. It redraws by itself when a program finishes compiling.
135
- - `WGSL_PRELUDE`: what the renderer puts in front of the author's code, to show
136
- beside the editor.
137
-
138
- `node scripts/validate-wgsl.mjs fill|effect <files>` runs the same validation
139
- in Node (after `pnpm build:wasm`), and `pnpm dev` serves `web/shaders.html`,
140
- a page that drives the preview (http://localhost:5190/shaders.html).
141
-
142
- ### Built-in effects
143
-
144
- Each built-in effect is a port (`web/src/effects/<type>.ts`, a `definePort`
145
- naming its parameters against core's effect spec) and a pass
146
- (`crates/renderer/src/effects/<type>.wgsl`) that reads them through generated
147
- accessors (`gen/<type>.wgsl`). `pnpm effects:gen` regenerates those after a
148
- port changes; `pnpm test:effects` checks that they are current and that the
149
- spec, the port and the pass agree by name. How to add one:
150
- [docs-effects-porting.md](docs-effects-porting.md).
151
-
152
- ## Text
153
-
154
- `ms-text` (`crates/text`) shapes, lays out and measures text and builds glyph
155
- outlines. `cargo test -p ms-text` checks its layout, measurement and outlines
156
- against the recorded reference numbers in `crates/text/tests/oracle.json`
157
- (needs the e2e fonts, `../e2e/fonts` or `MS_FONTS_DIR`). See
158
- [DESIGN.md §5](DESIGN.md#5-phase-2--text-images-video).
159
-
160
- ## Layout
161
-
162
- See the repo map at the end of [DESIGN.md](DESIGN.md#9-repo-map).
1
+ # @motionscript/renderer
2
+
3
+ A retained-mode 2D/3D renderer in Rust on wgpu/WebGPU, compiled to
4
+ WebAssembly: the renderer `@motionscript/browser` draws every frame with.
5
+ Hosts use browser's `createBrowserEngine()`, which builds this package in; it
6
+ depends on nothing but `@motionscript/core` (a peer) and mediabunny.
7
+ Apache-2.0; the Rust crates compiled into the WASM are listed with their
8
+ licences in [THIRD_PARTY_NOTICES.txt](THIRD_PARTY_NOTICES.txt).
9
+
10
+ Read [DESIGN.md](DESIGN.md) for the design and how each part was built.
11
+
12
+ ## Requirements
13
+
14
+ - Rust through rustup: [rust-toolchain.toml](rust-toolchain.toml) pins the
15
+ toolchain and the `wasm32-unknown-unknown` target, which rustup installs on
16
+ first use. `wasm-bindgen-cli` must be the exact version of the
17
+ `wasm-bindgen` crate in `Cargo.lock` (`0.2.130`); the build scripts refuse
18
+ any other:
19
+ ```bash
20
+ cargo install wasm-bindgen-cli --version 0.2.130 --locked
21
+ ```
22
+ - To regenerate the licence notices, `cargo-about` 0.9.3:
23
+ `cargo install cargo-about --version 0.9.3 --features cli --locked`.
24
+ - Node 24 and pnpm.
25
+ - A browser with WebGPU (Chrome/Edge, recent Safari).
26
+
27
+ ## In the workspace pipelines
28
+
29
+ `pnpm build` here builds the WASM (`--locked`, with remapped build paths),
30
+ then `dist/`, then `dist/browser/` with the engine's
31
+ `scripts/build-browser.mjs`; turbo runs it before `@motionscript/browser`'s
32
+ build. `prepack` refuses to pack a package without its `dist/`.
33
+
34
+ `dist/browser/index.js` is the CDN build: `@motionscript/core` stays an import
35
+ (the host's import map names it), the rest is bundled. Its `manifest.json`
36
+ lists the WASM under `assets["ms_renderer_bg.wasm"]` (from
37
+ `motionscriptBrowser.assets` in `package.json`): `dist/ms_renderer_bg.wasm`.
38
+ The module's default `wasmUrl` (beside itself) does not hold there, so a host
39
+ loading the CDN build passes that asset's URL as `wasmUrl`.
40
+
41
+ The root `pnpm lint` and `pnpm typecheck` include it without Rust: the
42
+ `.d.ts` files wasm-bindgen generates (`web/pkg/ms_renderer.d.ts`,
43
+ `web/pkg-text/ms_text.d.ts`, `web/pkg-r3d/ms_render3d.d.ts`) are committed,
44
+ and a WASM build rewrites them; commit the change when the Rust API moves.
45
+
46
+ CI's `renderer` job (`.github/workflows/ci.yml`) installs the pinned
47
+ toolchain and runs `cargo test --locked --workspace` (with
48
+ `crates/renderer/tests/wgsl_modules.rs`, which runs every WGSL module the
49
+ renderer ships through naga's validation), `pnpm notices:check`, the three
50
+ WASM builds, a check that the committed `.d.ts` files are current, lint,
51
+ typecheck, and the e2e wgpu lane (`packages/e2e`, `pnpm test:wgpu`: scenes
52
+ drawn by this renderer on SwiftShader's WebGPU adapter, diffed against
53
+ committed frames).
54
+
55
+ `pnpm notices` regenerates [THIRD_PARTY_NOTICES.txt](THIRD_PARTY_NOTICES.txt)
56
+ with cargo-about ([about.toml](about.toml) lists the accepted licences, so a
57
+ crate under any other fails it); run it whenever `Cargo.lock` changes.
58
+
59
+ ## The entry
60
+
61
+ `src/engine` (in `web/src/engine/`) is what `@motionscript/browser` builds in.
62
+ `createWgpuRenderer({ wasmUrl, loadBytes })` loads the WASM and returns a
63
+ `WgpuRenderer`: `createStorageAdapter`, `createRenderContext` and
64
+ `createMeasureContext` for a session, its exports and stills.
65
+ `isWgpuSupported()` says whether the browser has a WebGPU adapter; there is no
66
+ other backend to fall back to.
67
+
68
+ `wasmUrl` is where `@motionscript/renderer/ms_renderer_bg.wasm` is served. It
69
+ defaults to beside the module.
70
+
71
+ - `WgpuStorageAdapter` decodes images to `ImageBitmap`s, video frames with
72
+ mediabunny, and hands font bytes to ms-text. Bytes come through
73
+ `loadBytes` (a plain `fetch` by default), which a platform points at its own
74
+ asset store.
75
+ - `WgpuSurfaceContext` is the adapter's render context mounted on a canvas,
76
+ with the preview knobs a host sets (`pixelRatio`, `view`, `frame`,
77
+ `clipToFrame`, `frameBackground`). Mounting is asynchronous (WebGPU device
78
+ creation); frames recorded before it finishes are drawn when it does. The
79
+ canvas composites premultiplied, so a transparent frame background shows the
80
+ page behind it. What the adapter can't draw yet is logged once per kind
81
+ (`[wgpu] not drawn yet: …`) and returned by `takeRenderIssues()` as a
82
+ `missing` issue per kind and node (a blend mode it lacks, text on a path,
83
+ `rasterize()`, nested or projective booleans, an off-centre motion-blur
84
+ shutter, an effect with no WGSL port, a 3D feature it doesn't draw), beside
85
+ `shader` issues (WGSL that does not compile, a stored SkSL shader) and
86
+ `params` issues (a uniform that names no `Params` member, a member given no
87
+ value). A session lists them in `buildErrors`; an export hands them to
88
+ `onIssue`.
89
+ - `measureText` and `layoutTextBlock` are ms-text (`MsText`, exported from
90
+ the same WASM), so measurement, carets and drawing agree.
91
+
92
+ ### GPU faults
93
+
94
+ Every surface on the page shares one device, so a fault is everyone's.
95
+ `onWgpuEvent(listener)` hears each one (`web/src/engine/faults.ts`):
96
+
97
+ - **`device-lost`** (a driver reset, the GPU process restarting,
98
+ `device.destroy()`): the device and its pipelines are forgotten and every
99
+ live surface is mounted again on a new one, which rebuilds pipelines, fonts
100
+ and user-shader programs and draws the last frame; `device-restored` follows.
101
+ Each surface queues a `device-lost` render issue, which fails an export or
102
+ still under way with a `RenderBackendError` (`retryable: true`) rather than
103
+ encoding frames drawn on the dead device. A frame drawn while a surface
104
+ remounts waits for it, as for a compiling shader. Four losses within a
105
+ minute stop the recovery (`crashed`).
106
+ - **`gpu-error`**: a WebGPU validation, out-of-memory or internal error the
107
+ device raised (its `uncapturederror`), also a `gpu` render issue on every
108
+ surface. The first 32 per page are reported.
109
+ - **`crashed`**: the renderer's WASM panicked (it is built `panic = "abort"`;
110
+ unwinding on wasm32 needs a nightly std, and the hook that reports the panic
111
+ costs nothing measurable in size). The instance is not called again: every
112
+ surface draws nothing more, gets a `crashed` issue, and an export fails
113
+ with a `RenderBackendError` that is not retryable. A reload recovers.
114
+
115
+ A user shader can't hang the device: its loops share a budget of 16,384
116
+ iterations per pixel and a compile that takes over 10 s fails
117
+ ([DESIGN.md §6.3](DESIGN.md#63-user-shaders-wgsl-built-2026-11-02)).
118
+
119
+ ### User shaders (WGSL)
120
+
121
+ Shader fills and shader effects are WGSL, written against the contract in
122
+ [DESIGN.md §6.3](DESIGN.md#63-user-shaders-wgsl-built-2026-11-02). A session
123
+ validates each new source with naga, compiles its pipelines in the background
124
+ and draws it once they are ready (an export, and any settled frame, waits). A
125
+ document stored with an SkSL shader (`language: "sksl"`) draws nothing there
126
+ and gets a `shader` render issue saying to port it to WGSL. For a shader
127
+ editor the package also exports:
128
+
129
+ - `validateShader(scope, source)`: naga's diagnostics (line and column in the
130
+ author's code) and the reflected `struct Params` members.
131
+ - `ShaderPreview`: a fill or an effect drawn on its own canvas over a test
132
+ pattern or an image (`frame`, `split`, `backdrop`), with the browser's
133
+ `getCompilationInfo()` messages in its status and `snapshot()` for
134
+ thumbnails. It redraws by itself when a program finishes compiling.
135
+ - `WGSL_PRELUDE`: what the renderer puts in front of the author's code, to show
136
+ beside the editor.
137
+
138
+ `node scripts/validate-wgsl.mjs fill|effect <files>` runs the same validation
139
+ in Node (after `pnpm build:wasm`), and `pnpm dev` serves `web/shaders.html`,
140
+ a page that drives the preview (http://localhost:5190/shaders.html).
141
+
142
+ ### Built-in effects
143
+
144
+ Each built-in effect is a port (`web/src/effects/<type>.ts`, a `definePort`
145
+ naming its parameters against core's effect spec) and a pass
146
+ (`crates/renderer/src/effects/<type>.wgsl`) that reads them through generated
147
+ accessors (`gen/<type>.wgsl`). `pnpm effects:gen` regenerates those after a
148
+ port changes; `pnpm test:effects` checks that they are current and that the
149
+ spec, the port and the pass agree by name. How to add one:
150
+ [docs-effects-porting.md](docs-effects-porting.md).
151
+
152
+ ## Text
153
+
154
+ `ms-text` (`crates/text`) shapes, lays out and measures text and builds glyph
155
+ outlines. `cargo test -p ms-text` checks its layout, measurement and outlines
156
+ against the recorded reference numbers in `crates/text/tests/oracle.json`
157
+ (needs the e2e fonts, `../e2e/fonts` or `MS_FONTS_DIR`). See
158
+ [DESIGN.md §5](DESIGN.md#5-phase-2--text-images-video).
159
+
160
+ ## Layout
161
+
162
+ See the repo map at the end of [DESIGN.md](DESIGN.md#9-repo-map).