Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commitdd348ac6c3

README.md

shader

An author's own compute shaders as graph nodes. A .computeShader written for anything else runs inside a graph unchanged: its bindings are the geometry's columns, its params: are the node's knobs, and what it writes comes out as columns every op after it reads.

procgen.searchNodes("", { pack = "shader" }) lists the family and procgen.describeNode("op.<id>") describes any one of them.

Ops

  • shader.compute — run a .computeShader over a geometry's elements, one invocation per element. A buffer binding is the column of its own name, or the one bind names: access: read reads it, access: read_write writes it and makes it when the domain has none. The shader's params: take their values from uniforms, where each can be animated or wired, and count, time and frame are filled in when the shader declares them. shader is a reference, written asset.resolve("<identity>", "computeShader"); a name alone is refused. The same shader placed as two nodes reads each node's own columns and values.
  • sim.shader — a solver whose step is a .computeShader: the domain's columns are its state, the shader runs once per element at every fixed step writing its read_write bindings in place, and dt, time and frame are the solver's own.

Writing a shader for a graph

The element index is global_invocation_id.x. A column's lanes are packed f32s: a Float column binds as array<f32>, a Vec3 column as array<f32> read three to an element, and a Vec2 or Vec4 column as array<vec2<f32>> / array<vec4<f32>>.

bindings:
  - { name: P, access: read_write, element: f32 }
  - { name: weight, access: read, element: f32 }
params:
  - { name: lift, type: float, default: 1.0 }
  - { name: count, type: int, default: 0 }
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
    let i = id.x;
    if (i >= u32(params.count)) { return; }
    P[i * 3u + 1u] = P[i * 3u + 1u] + params.lift * weight[i];
}
local pts = g.op.attribute.set({ domain = g.points.grid({ countX = 4, countZ = 4 }), name = "weight", value = 1 })
local raised = g.op.shader.compute({ domain = pts, shader = asset.resolve("lift", "computeShader"),
    uniforms = { lift = 2 } })

Finding the points near a point

A shader that looks at its neighbours — separation in a flock, the car ahead in traffic, a density — declares the three read bindings of a spatial grid and includes the grid shader module; the node gets a neighborRadius, builds a grid of the domain's points with cells that wide before it runs (and again at every step of a sim.shader), and binds it. The shader then walks the 27 cells around a point instead of every point.

bindings:
  - { name: P, access: read, element: f32 }
  - { name: nb, access: read_write, element: f32 }
  - { name: gcfg, access: read, element: f32 }
  - { name: grange, access: read, element: u32 }
  - { name: gorder, access: read, element: u32 }
#include "@builtin::systems.procgen.grid"

let c = zero_grid_cell(p);
for (var dz = -1; dz <= 1; dz = dz + 1) {
    for (var dy = -1; dy <= 1; dy = dy + 1) {
        for (var dx = -1; dx <= 1; dx = dx + 1) {
            let cc = c + vec3<i32>(dx, dy, dz);
            let span = zero_grid_range(cc);
            for (var s = span.x; s < span.y; s = s + 1u) {
                let j = gorder[s];
                // j == i is the point itself; a j whose own cell is not cc
                // shares a bucket with it and belongs to another cell
            }
        }
    }
}
local birds = g.op.points.sphere({ count = 64, radius = 2 })
local flock = g.op.sim.shader({ domain = birds, shader = asset.resolve("flock_step", "computeShader"),
    neighborRadius = 1.5 })

A node whose shader declares the grid's bindings and is given no neighborRadius is refused, naming the input.

A step that reads its neighbours' state while every element writes its own would read some of them half-updated. A read binding named prev_<column> (prev_P, prev_v) is that column as it stood before the dispatch — a copy sim.shader takes before every step — so each element reads the same before-state and writes only its own:

bindings:
  - { name: P, access: read_write, element: f32 }
  - { name: prev_P, access: read, element: f32 }

points.neighbors answers the common questions — how many neighbours, the nearest, their centroid and their mean of an attribute — without a shader.