backends.wgsl.compile_scene_with_uniforms
backends.wgsl.compile_scene_with_uniforms(
geometry,
example_point=None,
*,
group=DEFAULT_PARAMETER_GROUP,
binding=DEFAULT_PARAMETER_BINDING,
culling=True,
scope='free',
)
Compile a scene to WGSL whose parameters live in a uniform buffer.
:func:compile_scene_to_wgsl folds every design parameter into the shader as a float literal, so moving one slider by 0.05 rewrites three lines of a 139 kB module and forces the browser to recompile all of it. Here the same parameters are function arguments read out of a uniform block instead: the source is byte-identical for every value of every parameter, and an edit is a buffer write and a redraw.
The buffer is an array of vec4<f32> — one 16-byte slot per parameter, which is the only element type a WGSL uniform array carries without per-field alignment rules — declared as::
struct SdfParameters { values: array<vec4<f32>, N> };
@group(G) @binding(B) var<storage, read> sdf_parameters: SdfParameters;
Parameters a pattern consumes structurally (a count) keep their slot but are never read: the instance count decides how much shader gets emitted, so it cannot be edited without recompiling.
Only the free parameters get a slot, and that is the whole design. A value the shader reads from a buffer is a value the GPU’s compiler cannot fold, and folding is not a minor optimisation here — it is what makes the generated field affordable at all. Measured on this machine’s Metal adapter, scenes/end_cap.py at 1200x800:
====================================== ========== =========== form preview path sample ====================================== ========== =========== every parameter in the buffer (330) 106.2 ms 185.7 ms every parameter a literal 4.0 ms 41.5 ms only the 11 free parameters buffered 3.3 ms 36.1 ms ====================================== ========== ===========
All three draw the same image to the last pixel. The cost is not the loads — hoisting all 4 560 of them to 671 changed nothing — and it is not the shape of the emitted code: substituting the literals back into the all-uniform module, structure untouched, recovers the full speed. It is that scenes/end_cap.py is 1.7 MB of WGSL because it is mostly constant-foldable: 21 pattern instances, each unrolled with its own transform algebra, which collapses to a handful of instructions per instance when the transform is known and runs in full when it is not.
The free parameters are the ones a handle drags and an optimizer moves, they are two orders of magnitude fewer (11 against 319 here), and the subtrees they unfold are the ones being edited anyway. A fixed parameter — a node attribute typed into the inspector — still costs a recompile. That is the trade, and it is the right way round.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| geometry | Root cadjoint SDF node with optional per-primitive materials. | required | |
| example_point | jnp.ndarray | None | Example input for shape inference. Defaults to zeros. | None |
| group | int | Bind group index for the uniform. | DEFAULT_PARAMETER_GROUP |
| binding | int | Binding index within that group. | DEFAULT_PARAMETER_BINDING |
| culling | bool | Skip far-away operands with a real branch; see :func:compile_scene_to_wgsl. |
True |
| scope | str | Which parameters get a slot — "free" (the default, and what the viewer ships) or "all". "all" is kept because it is what the measurement above compares against and what the emitter’s argument-binding pass is stressed by; it is not a configuration anything should choose. |
'free' |
Returns
| Name | Type | Description |
|---|---|---|
| A | ShaderProgram | class:ShaderProgram carrying the source, the buffer layout and the parameters’ current values. |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If scope is not one of :data:PARAMETER_SCOPES. |