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.