Builtin Library
- ..
- README.md9.4 KB
- init.luau163 KB
- leaves.luau29.7 KB
- operators.luau38.0 KB
README.md
fieldc
A Field is a per-element expression. fieldc holds what a Field is made of and how it runs: a chain of templates over a position leaf, an element number, column reads and numbers is written as one WGSL function and runs as ONE dispatch of kernels/map, wherever it is consumed. Nothing dispatches per operator.
Where a Field is evaluated
local column = fieldc.evaluate(field, domain, opts)
The one entry point a consumer of a Field calls (a kernel's input, an attribute write, an output): device.kernel for a Field bound to a column input, attribute.write for a Field value, and every other node that needs a Column.
field: a Field, a Column, aBufferRef(a device column handed throughcpu.call), a number or a vector record. A Column an op hands a chain as an operand is a leaf of the chain, read where it is held.domain: a number of elements, or a Geometry (itsopts.classelements, points by default), whose attributes the leaves reading the evaluation domain bind.opts:{ class, count, type, clock, where, label }.type = "Int"writes i32 words (a Field tagged Int does so by default).clockis{ time, frame, fps, duration }: animated values are first sampled there.wherenames the caller in a refusal.- Answers a Column of the domain's count. Raises, naming what refused, when the chain cannot be written as a function: a chain whose last field is a
Field.fn(a Luau function of the element) states no expression, and the refusal says so. A constant Field (proc.Field.constant) is the same value at every element, as a number is. - The first evaluation of a chain whose kernel is new waits, in a thread that can yield, for the device to build it (
compute_abi.awaitProgram). A kernel the device refuses raises there, naming the caller and the compiler's message, so no evaluation answers a column no dispatch wrote. A thread that cannot yield does not wait.
A chain holding an animated value or a live run answers a live column. fieldc.liveRuns finds it and fieldc.tick(runs, clock) rewrites it each frame: the animated values are sampled at the clock and written into the numbers the dispatch reads, and the dispatch runs again into the same run. An animated input therefore changes the result every frame without a cook.
Fields the nodes make
| Construct | Module | Answers |
|---|---|---|
fieldc.field(v) | fieldc | any socket value as a Field: Field, Column, BufferRef, number, vector, animated value |
operators.apply(name, ...) | fieldc.operators | math.*: the plain result over numbers and vectors, a Field over a run or a Field |
fieldc.element(name, class?, width?) | fieldc | what g.element stands for: "id", "P", or a column of the domain (g.element.point.N is element("N", "point")) |
leaves.elementId() | fieldc.leaves | the element number, an Int Field (g.element.id) |
fieldc.position | fieldc | the position leaf p (g.element.P, and an unwired field.position()): the domain's point.P where the chain is evaluated over a Geometry, the point fieldc.withPosition names inside a substitution; fieldc.evaluate refuses a chain that reads it over a number of elements with no geometry |
leaves.context(class, name, width?) | fieldc.leaves | an attribute of the evaluation domain |
leaves.rand(seed, scope, id) | fieldc.leaves | the unit draw, equal to hash.unitFloat to the bit |
leaves.call(fn, args) | fieldc.leaves | a function of a prelude shader module of kernels/ |
leaves.gather(column, index) | fieldc.leaves | a run read at computed elements |
leaves.substitute(field, position) | fieldc.leaves | the chain read at another point (fieldc.withPosition) |
leaves.index(domain) | fieldc.leaves | an Int column of 0 to n - 1 |
fieldc.loop(count, init, probes, answers) | fieldc | the state after count passes of a fold written once over fieldc.loopProbes(strides): one loop of the kernel, its count one of the chain's numbers |
An animated value that becomes a Field (fieldc.field) is the live field holding its value at a time: inside a node's cook, the time of the clock the cook runs at (the evaluator runs each cook through fieldc.pcallSampling), so a curve handed to a node asset and read by a field op follows opts.clock of proc.eval; outside any cook, time 0. It stays a live field: a present for a later time rewrites the run it lands in (fieldc.tick), and the node is not cooked again for the time.
fieldc.op(field, wgsl, stride, args) binds a Column or a BufferRef among args as the run it is, the way fieldc.field does, so a field op given a column reads it where it lives on the device and nothing is read back.
leaves.call (the field.call node) calls a function of a module of kernels/ that compiles inside a field chain's kernel. The first call into a module compiles a kernels/map variant that includes it and waits for the device's verdict; a module written for one kernel (walk_core, walk_advance, hash_walk, the bvh_* and intersect_core modules, each naming what its includer binds or declares) does not compile there, and a call of its functions is refused naming the function, the module and the compiler's message.
A Field made by these answers f.x, f.y, f.z, f.w as the Field of that component. Components are Fields, so the Field of a vector is built from them with operators.vec2, operators.vec3 or operators.vec4; Luau's vector.create takes numbers.
How the leaves reach the kernel
A chain's leaves are columns. kernels/map states every leaf's shape in its cols table and reads up to as many leaves as one dispatch has storage buffers for (compute_abi.directLimit: 8 per stage on every target, less the table, the numbers and the output, so 5 for map) straight from the buffer of the leaf's own run, at no cost beyond the binding. A chain of up to 5 leaf columns is the one dispatch of map and nothing else. Leaves past those are copied into the table, one copy dispatch each.
fieldc.bindSpec(spec, { direct }) makes the table; direct is the list of leaf slots read from their own runs (compute_abi.directPlan(columns, limit)), and the variant is made with the same list: program:variant({ specialize = ..., direct = { cols = direct } }). Without direct every leaf is copied into the table.
fieldc.operators folds numbers: every operand a number or vector gives a number or vector. A narrower operand widens to the widest (a number applies to every component, a shorter vector reads 0 where it has none). Division and remainder by 0, the square root and the logarithm of a value at or below 0 answer 0.
The walk step contract (for kernels/walk)
A walk kernel takes its per-walker function as a Field spec, the way kernels/map takes TEMPLATE.
local spec = fieldc.walkSpec(step, { width = 2, count = walkers, done = stop })
-- a state of 8 floats: lanes 0 to 3 in `step`, lanes 4 to 7 in `high`
local wide = fieldc.walkSpec(low, { width = 8, count = walkers, high = high })
local bound = fieldc.bindSpec(spec)
-- specialize = spec.specialize; bind cols = bound.cols:buffer(), u = bound.u
width: floats per state, 1 to 8.stepis the next state's lanes 0 to 3, a Field ofmin(width, 4)components; above 4,opts.highis the Field of thewidth - 4components of lanes 4 to 7.- The state is read through
fieldc.walkState(count, first?):count(1 to 4) lanes from lanefirst(default 0), all inside lanes 0 to 3 or all inside lanes 4 to 7. A state of 8 floats is read as two leaves,walkState(4, 0)andwalkState(4, 4). The nodesfield.walk_state({ count, first })andfield.walk_index()are these leaves in a graph. stepandhighare written over the state leaves,fieldc.walkIndex()(the step number k, from 0),leaves.elementId()(the walker's number), Column reads (one value per walker, or a gather at a computed index) and numbers.opts.done: an optional Field<Float> over the same leaves; the walker stops before step k when it is not 0.spec.specializeis a table of the specialization values the walk kernel takes, ready to hand todevice.kernel/program:variantas itsspecializevalue:WALK_STEP(zero_walk_chain_step(s, s_hi, k, id)),WALK_STEP_HI(zero_walk_chain_step_hi(s, s_hi, k, id), ors_hiwhenwidthis 4 or less),WALK_DONE(zero_walk_chain_done(s, s_hi, k, id), orfalsewhen there is nodone) andWALK_PRELUDE, the module-scope WGSL that defines those functions, preceded by the preludes the chain calls. Lanes 0 towidth - 1ofs(lanes 0 to 3) ands_hi(lanes 4 to 7) are the state.- The functions read its leaves through a
colscolumns binding (cols_f32(slot, i, 0u),cols_vec3(slot, i),cols_i32for Int runs) and its numbers throughu_get(k): the kernel declares those two bindings exactly askernels/mapdoes.fieldc.bindSpec(spec)answers the table (cols) and the numbers buffer (u) to bind;release()lets both go. - A read past a run's end reads its last element.
Other entry points
fieldc.mapSpec(chain, count, domain, { position }) writes a chain as one function for kernels/map and fieldc.runMap(spec) runs it. fieldc.compile / fieldc.column write the same chain as a kernel of its own, for the consumers that bind buffers directly.