compute
Public Luau surface over the `__compute` Internal FFI namespace — GPU compute pipelines.
compute Module
Public Luau surface over the __compute Internal FFI namespace — GPU
compute pipelines.
Purpose
A compute shader is a .computeShader asset, and everything you do with one you
do through a resolved reference to it. This module carries what is left: the
readback poll, and the name-keyed entry points kept working for content written
before the reference surface existed.
Usage
local double = asset.resolve("@builtin::shaders.compute_double", "computeShader")
local scratch = double:createBuffer("scratch", { size = 4096, readback = true })
scratch:write({ 1.0, 2.0, 3.0, 4.0 })
double:dispatch({ buffers = { scratch }, workgroups = { 1 } })
local key = scratch:read()
while not compute.isReadbackReady(key) do task.wait(0) end
local floats = compute.getReadbackResult(key)
asset.resolve records your script's dependency on the shader — which is what
carries the shader along when the world is packed, pulled or published. A name
passed to one of the entry points below records nothing, and the shader is left
behind.
Is it compiled yet?
compute.programState(ref) says where a shader's compiled program stands:
"absent" (nothing held under this key and nothing in flight), "pending"
(a registration or compile was asked for and has not reached the device — a
recompile of a resident program reads pending too), "ready" (compiled and
resident, so a dispatch binds it), or "failed", with the reason as a second
return.
if compute.programState(shaderRef) == "ready" then
shaderRef:dispatch({ buffers = { buf }, workgroups = { 1 } })
end
ref:compile() queues the compile and the pipeline is built on the render side
frames later, so that call cannot answer this. A dispatch issued before the
program is ready is held behind the compile and dropped if it never lands, and
a readback taken then reports the buffer as it was.
Is anything broken?
compute.failing() lists every (shader, target) pair whose most recent
dispatch failed, with the reason. An empty result means every dispatch the
engine has been given is running. For one shader, ref:status() reports each of
its targets, running or not.
for _, d in ipairs(compute.failing()) do
print(d.shader, d.target, d.failures, d.lastError)
end
A dispatch is recorded into a command encoder frames after the call that asked
for it returned, so the call answers that it queued the dispatch and these two
answer what the dispatch did. A zero in any workgroups dimension is one of the
things they report: it is refused, and a count computed from how much data there
is passes through math.max(1, ...) so an empty set dispatches one workgroup
rather than none.
Exports
- Health:
programState,failing. - Readback:
isReadbackReady,readbackState,getReadbackResult,getReadbackResultU32,getReadbackResultBytes. A handle answers a read itself —buf:read(offset, len)gives aReadbackwithready,stateand the three drains on it. - Deprecated, name-keyed:
registerShader,destroyShader,registerShaderEx,destroyShaderEx,compile,compileByName,dispatch,dispatchOnVertices,dispatchEx,setParam. Each has a method on the.computeShaderreference — seetypes/computeShader. - Deprecated, name-keyed buffers:
createBuffer(name, { size, readback? }),destroyBuffer,readBuffer,writeBuffer(name, data, offset?),writeBufferU32,writeBufferBytes. Each reaches the substrate under the name a dispatch binds it by; hold the handle fromsubstrate.createBufferorref:createBufferinstead.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2global compute global-types module compute GPU compute pipelines — compile shaders, dispatch workgroups, read back results. Public Luau surface over the `__compute` Internal FFI namespace. A buffer belongs to the shader that owns it (`shaderRef:createBuffer`) or to the substrate (`substrate.createBuffer`), and reaches a dispatch as a handle. require modules/api/engine/compute
deprecated(what: string, instead: string) → void
| arg | type | description |
|---|---|---|
| what | string | |
| instead | string |
bufferKey(v: any, where: string, slot: string) → any
Resolve a shader reference to the key the engine dispatches by. For a `.computeShader` ASSET (identity string or a resolved handle), we use its stable **guid** — the same collision-safe, rename-proof key every other asset reference uses, and the key the assetType registers the compiled program under. The engine lazily compiles a `.computeShader` on the first dispatch miss (no `compileByName` ceremony needed). A raw `registerShader` name (not an asset) passes through unchanged. Pass a resolved handle to skip the per-call resolve. The key a `buffers` / `resources` entry names. A buffer handle contributes the name it was created under; a string passes through, which is how the engine-owned names (`@vertex`, `@vertex:<guid>`) reach the engine untouched.
| arg | type | description |
|---|---|---|
| v | any | |
| where | string | |
| slot | string |
resolveBuffers(opts: any, where: string) → any
Resolve every buffer a dispatch names, leaving the caller's table untouched.
| arg | type | description |
|---|---|---|
| opts | any | |
| where | string |
declaredBindingCount(source: string) → number
How many group-0 binding slots a WGSL source declares, counted from the declarations themselves: the highest slot the source names, plus one, so a source that leaves a gap still gets a layout wide enough to reach its top slot. Nil when the source names no group-0 binding.
| arg | type | description |
|---|---|---|
| source | string |
note(idx: string) → void
| arg | type | description |
|---|---|---|
| idx | string |
withDeclaredBindings(opts: any, where: string) → any
`bindings` is how many group-0 slots the pipeline layout is built with, and the source already says. Taking the count from the source leaves one place for it to be stated; a count supplied alongside a source that contradicts it is refused here, where the caller can see both numbers.
| arg | type | description |
|---|---|---|
| opts | any | |
| where | string |
checkReadOnlyBindings(opts: any, where: string) → void
Every `readOnlyBindings` entry names a group-0 slot of the layout the registration builds, which runs 0 to `bindings - 1`. An index outside that run marks nothing: the slot the author meant as `var<storage, read>` is built read-write, and the shader either fails the device's access-mode validation or writes through a binding declared read-only. Refused here, with both numbers in hand and the registering call still on the stack.
| arg | type | description |
|---|---|---|
| opts | any | |
| where | string |
checkBufferCount(ref: any, opts: any, where: string) → void
A dispatch binds one buffer per declared binding, so a count that does not match is work that cannot run. Raised against the registration's own count while the dispatching call is still on the stack.
| arg | type | description |
|---|---|---|
| ref | any | |
| opts | any | |
| where | string |
resolveComputeKey(ref: string | { [string]: any } | AssetRef) → any
| arg | type | description |
|---|---|---|
| ref | string | { [string]: any } | AssetRef |
refusedDeclaration(ref: string | { [string]: any } | AssetRef) → string
Why the `bindings.yaml` of the `.computeShader` asset `ref` names was not read, when its compile refuses the file; nil for any other ref. The asset's `getBindings` runs the parse its compile runs, so it raises exactly when the compile refuses the declaration.
| arg | type | description |
|---|---|---|
| ref | string | { [string]: any } | AssetRef |
holdInline(key: string, register: (string, any) → void
| arg | type | description |
|---|---|---|
| key | string | |
| register | (string, any |
programCaller( ) → string
The chunk that called the compute function calling this: the first frame above this module's own that is Luau code, past the native dispatcher a `typed` function's argument check runs that function through. It is the same frame at every optimization level the module compiles at.
suiteOwned(source: string) → boolean
Whether the chunk `source` names is a test suite's own, or user code: a compiled program that goes back with the world.
| arg | type | description |
|---|---|---|
| source | string |
registerShader(nameOrHandle: string | { [string]: any } | AssetRef, opts: ShaderOpts?) → boolean
Register a compute shader. Accepts an asset handle from `asset.load()`, or `(name, opts)` with inline WGSL source. `@group(0) @binding(n)` declarations when omitted; supplying a count that disagrees with them raises. Every `readOnlyBindings` entry names one of those declared bindings, as a whole number from 0 to `bindings - 1`; an entry outside that run raises. deprecated A compute shader is a `.computeShader` asset. Author one and dispatch it through its ref — `asset.resolve(name, "computeShader"):dispatch(opts)`. Raw WGSL registered under a bare name has no asset identity, so nothing records a dependency on it and it does not travel with the content that uses it. hidden
| arg | type | description |
|---|---|---|
| nameOrHandle | string | { [string]: any } | AssetRef | Shader name or asset handle. |
| opts | ShaderOpts? | Shader options. `bindings` comes from the source's own |
examples
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })destroyShader(name: string) → boolean
Destroy a named compute shader pipeline. deprecated A `.computeShader` asset's compiled program is owned by the asset and released with it. hidden
| arg | type | description |
|---|---|---|
| name | string | Shader name. |
buildBvh(instances: { any }, opts: { [string]: any }?) →
Build a bounding-volume hierarchy over the world-space triangles of a set of mesh instances and upload it as two named compute buffers — geometry never passes through the scripting heap. Each instance is `{ guid, transform, attributes? }`: `guid` names a mesh resident in the meshcpu store (materialise with `ref:load()` / `meshcpu.load`), `transform` is 16 numbers, row-major, translation in slots 4/8/12, and `attributes` is up to 40 floats stamped onto every triangle of that instance (surface colors, material ids, physics tags — whatever the consuming shader wants per-surface). Triangles pack 18 vec4 each (v0/v1/v2, n0/n1/n2, uvs, then 10 attribute vec4s — float slots 32.. carry the instance attributes, zero when absent) in BVH leaf order; nodes 2 vec4 each (min + first-or-left, max + leaf-tagged count-or-right). Consumers: GI baking, ray-traced passes, GPU picking, navmesh and SDF generation. handles the caller owns, passed to a dispatch like any other and destroyed when the hierarchy is done with. Or `(nil, err)` naming any non-resident guid.
| arg | type | description |
|---|---|---|
| instances | { any } | Array of `{ guid, transform, attributes? }` mesh instances. |
| opts | { [string]: any }? | Optional `{ maxLeaf? }` — max triangles per leaf. |
examples
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })beginBvh(instances: { any }, opts: { [string]: any }?) →
Start the build `compute.buildBvh` runs, without running any of it. Takes the same instances and options and reports the same non-resident guids, and returns an id `compute.stepBvh` advances a bounded slice at a time and `compute.finishBvh` collects. Each mesh the instances name is copied as this is called — once per guid however many instances share it — so the CPU mesh may be unloaded on the next line and the build still finishes on the copy it holds. `compute.buildBvhSliced` is the whole loop as one call.
| arg | type | description |
|---|---|---|
| instances | { any } | Array of `{ guid, transform, attributes? }` mesh instances. |
| opts | { [string]: any }? | Optional `{ maxLeaf? }` — max triangles per leaf. |
examples
local id = compute.beginBvh(gather.instances)
stepBvh(id: number, budgetMs: number?) →
Advance a build by as many work units as `budgetMs` buys, and report whether it has finished: `"pending"` means there is work left, `"ready"` means `compute.finishBvh` will hand over the buffers. The slices carry the hierarchy onto the GPU as well as building it, so a build that reads `"ready"` has already uploaded every byte of itself. A slice always runs at least one unit, so a budget of 0 advances the build by exactly one and the largest single unit sets the floor under a slice.
| arg | type | description |
|---|---|---|
| id | number | Build id from `compute.beginBvh`. |
| budgetMs | number? | Wall time this slice may spend, in milliseconds (default 4). |
examples
while compute.stepBvh(id, 4) == "pending" do task.wait() end
finishBvh(id: number) →
Hand over a finished build's hierarchy as the same two buffers `compute.buildBvh` returns, and release the build. The slices put every byte of it on the GPU as they ran, so this costs the frame it is called in the handover and nothing of the scene. names no build or the build still has work left.
| arg | type | description |
|---|---|---|
| id | number | Build id from `compute.beginBvh`, stepped until `"ready"`. |
examples
local built = compute.finishBvh(id)
cancelBvh(id: number) → boolean
Drop a build along with the triangles it has gathered.
| arg | type | description |
|---|---|---|
| id | number | Build id from `compute.beginBvh`. |
examples
compute.cancelBvh(id)
bvhBuilds( ) →
What the builds started by `compute.beginBvh` and not yet finished are costing, oldest id first. Each row is `{ id, phase, triangles, units, slices, cpuMs, uploadedBytes }`: `phase` is `"gather"`, `"build"`, `"serialize"`, `"upload"` or `"ready"`, `triangles` how many have been gathered, `units` the work units run, `slices` the `compute.stepBvh` calls they ran in, `cpuMs` the wall time spent inside those calls, and `uploadedBytes` how much of the hierarchy has reached the GPU.
examples
print(#compute.bvhBuilds(), "hierarchies in flight")
buildBvhSliced(instances: { any }, opts: { [string]: any }?) →
The hierarchy `compute.buildBvh` builds, spread over as many frames as it takes: a slice of the build per frame, so a scene's triangle count costs the frame loop `budgetMs` at a time instead of the whole build at once. Yields, so it is called from a task. The result is the same pair of buffers and the same counts `compute.buildBvh` returns. the wall time one frame may spend on the build (default 4 ms).
| arg | type | description |
|---|---|---|
| instances | { any } | Array of `{ guid, transform, attributes? }` mesh instances. |
| opts | { [string]: any }? | Optional `{ maxLeaf?, budgetMs? }` — max triangles per leaf, and |
examples
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })dispatch(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: DispatchOpts) → boolean
Dispatch a compute shader with bound buffers. Accepts a shader name string or an asset handle from `asset.load()`. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.failing()`. deprecated Use `asset.resolve(name, "computeShader"):dispatch(opts)`. A name passed here records no dependency on the shader, so a packed or pulled world leaves it behind. hidden
| arg | type | description |
|---|---|---|
| shaderNameOrHandle | string | { [string]: any } | AssetRef | Shader name or asset handle. |
| opts | DispatchOpts | `{ buffers, workgroups }`. |
examples
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })dispatchOnVertices(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: DispatchOnVerticesOpts) → boolean
Dispatch a compute shader with a model's vertex buffer bound at binding 0 (read_write). Use to mutate vertex positions directly. Asset-backed `.computeShader`s resolve to their stable guid (collision-safe, lazily compiled on first dispatch); raw `registerShader` names pass through. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.failing()`. deprecated Use `asset.resolve(name, "computeShader"):dispatchOnVertices(opts)`. A name passed here records no dependency on the shader, so a packed or pulled world leaves it behind. hidden
| arg | type | description |
|---|---|---|
| shaderNameOrHandle | string | { [string]: any } | AssetRef | Shader name or asset handle. |
| opts | DispatchOnVerticesOpts | `{ model, buffers?, workgroups }`. |
examples
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })assertBindingKinds(what: string, opts: { [string]: any }?) → void
| arg | type | description |
|---|---|---|
| what | string | |
| opts | { [string]: any }? |
compile(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?) →
Compile a compute shader from inline WGSL + a declarative binding schema — the same codegen a `.computeShader` asset uses. The engine generates the `@group/@binding` declarations from `bindings`/`params`, so the source writes only `@compute fn main`. Symmetric with `registerShader`, but with zero-scaffolding bindings (incl. textures, samplers, storage textures and a params uniform). For asset-backed shaders prefer authoring a `.computeShader` (compiled automatically); use this for dynamic/generated compute shaders. ordered list of `{ name, kind, access?, element?, format?, array?, sample? }`. when the schema or the module is one the device would refuse. deprecated Author a `.computeShader` asset — the assetType compiles it from `shader.wgsl` + `bindings.yaml`. Use `asset.resolve(name, "computeShader"):compile()` to force one early. hidden
| arg | type | description |
|---|---|---|
| nameOrHandle | string | { [string]: any } | AssetRef | Shader identity to register under (string or asset handle). |
| opts | { [string]: any }? | `{ source, entryPoint?, bindings, params? }` — `bindings` is an |
examples
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })setParam(nameOrHandle: string | { [string]: any } | AssetRef, prop: string, value: number) → boolean
Set a named scalar parameter on a `.computeShader` (a `params:` entry in its `bindings.yaml`). Updates the shader's params uniform in place; the next dispatch sees the new value. No effect on raw `registerShader` shaders, which have no params block. handle `asset.load` / `asset.resolve` returns — the same forms `dispatch` takes. deprecated Use `asset.resolve(name, "computeShader"):setParam(prop, value)`. hidden
| arg | type | description |
|---|---|---|
| nameOrHandle | string | { [string]: any } | AssetRef | Shader identity (the `.computeShader` asset name), or the |
| prop | string | Parameter name as declared in `bindings.yaml`. |
| value | number | New scalar value (numbers only). |
examples
compute.setParam("my_sim", "scale", 4.0)compileByName(ref: string | { [string]: any } | AssetRef) → void
Optional explicit pre-warm for a `.computeShader` asset (idempotent — fingerprint-guarded). NORMALLY UNNECESSARY: `compute.dispatch` / `dispatchEx` auto-compile a `.computeShader` on first use. Reach for this only to avoid the one-frame first-dispatch warm-up in a latency-critical spot. Accepts an identity/guid string or a resolved asset handle (its `.identity` is used). deprecated Use `asset.resolve(name, "computeShader"):compile()`. hidden
| arg | type | description |
|---|---|---|
| ref | string | { [string]: any } | AssetRef | A `.computeShader` identity/guid string, or a resolved asset handle. |
examples
compute.compileByName("@builtin::shaders.compute_double")isReadbackReady(resultKey: string) → boolean
Check if a readback result is available without consuming it. Raises for a key this engine never issued, or whose result was already drained — `nil`/`false` already means "still in flight", so a mistyped key reports itself instead of polling forever. Use `readbackState()` to test that case without raising.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
readbackState(resultKey: string) → string
Where a readback key stands, without consuming it and without raising: `"pending"` (issued, GPU has not delivered), `"ready"` (delivered, waiting to be drained), `"failed"` (given up on, with the reason as a second return), or `"unknown"` (never issued by `readBuffer()`, or already drained — a result is delivered once). Every read leaves `"pending"`: one the GPU does not answer inside the bound the engine holds it to reaches `"failed"` naming the buffer. A wait on `readbackState(key) ~= "pending"` therefore ends on what the read did, which a count of engine frames cannot say.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
examples
if compute.readbackState(key) == "ready" then ... end
readbackPromise(resultKey: string) → any
The promise that settles when a read-back reaches a terminal state, delivered or given up on. `task.await` it to wait for the read without polling: where the engine can wait on the device the coroutine resumes in the frame the device answered, so a task that reads, uses the answer and reads again does all of it in one frame; elsewhere it resumes on the next frame the answer reaches the engine. Answers nil when the read-back is not in flight (already delivered, failed, drained or never issued), and `readbackState()` then says which.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
examples
local promise = compute.readbackPromise(key); if promise then task.await(promise) end
getReadbackResult(resultKey: string) →
Poll for a completed read-back and return its bytes as a 1-indexed array of f32 values, nil if pending. The f32 reinterpretation applies to whatever the buffer holds: bytes written as u32 `1, 2, 3, 4` read back here as `1.4e-45, 2.8e-45, 4.2e-45, 5.6e-45` — use `getReadbackResultU32()` for those, or `getReadbackResultBytes()` for a `buffer` the rest of the buffer surface accepts. Result is consumed on retrieval, and polling a key that was never issued raises rather than reading as forever-pending.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
examples
local floats = compute.getReadbackResult(key)
getReadbackResultU32(resultKey: string) →
Poll for a completed read-back interpreting bytes as u32. Returns array of integer values if ready, nil if pending.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
copyBufferToTexture(bufferName: string, textureKey: string, width: number, height: number, format: string?, rowBytes: number?) → boolean
Copy a compute buffer into a cached GPU texture under `textureKey`, staying on the GPU. The path for an image a compute pass produced: the buffer holds tightly-packed rows in the format's texel layout, and the result is an ordinary cached texture — sample it from a material, or pack it into the shared feature-texture array. The copy reads rows 256-byte aligned: rows packed tight must be a multiple of 256 bytes (at `rgba16f`, any width that is a multiple of 32), and any other width is laid out with its rows padded to that alignment and `rowBytes` saying how far apart they are. past `width` texels — a multiple of 256 at least a row's texels long.
| arg | type | description |
|---|---|---|
| bufferName | string | Source compute buffer. |
| textureKey | string | Cache key to register the texture under. |
| width | number | Texture width in texels. |
| height | number | Texture height in texels. |
| format | string? | Texel format: `"rgba16f"` (default), `"rgba32f"`, `"rgba8"`. |
| rowBytes | number? | Bytes from one row's start to the next when rows are padded |
examples
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)compute.copyBufferToTexture("heights_packed", "heights", 300, 300, "rgba16f", 2560)destroyTexture(textureKey: string) → boolean
Release the cached GPU texture `copyBufferToTexture` registered under `textureKey`, freeing its memory. Call it once the image is no longer sampled. Writing the same key again replaces the texture, so a key you keep re-using holds one allocation.
| arg | type | description |
|---|---|---|
| textureKey | string | Cache key the texture was registered under. |
examples
compute.destroyTexture("lm_wall")getReadbackResultBytes(resultKey: string) → buffer
Poll for a completed read-back and get its raw bytes as a `buffer`, copied once. The read counterpart of `writeBufferBytes`: read values out with `buffer.readf32` / `buffer.readu32`, or hand the buffer straight to `writeBuffer` — a payload that stays packed never becomes a table. Result is consumed on retrieval.
| arg | type | description |
|---|---|---|
| resultKey | string | Key returned by `readBuffer()`. |
examples
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
createTexture3D(name: string, opts: { [string]: any }) → boolean
Create a 3D texture volume. opts: `{ width, height, depth, format?, storage? }`.
| arg | type | description |
|---|---|---|
| name | string | Unique volume name. |
| opts | { [string]: any } | Dimensions + format (`r8`/`r16f`/`r32f`/`rgba8`/`rgba16f`/`rgba32f`). |
writeTexture3D(name: string, data: buffer | string | { number }) → boolean
Upload raw bytes (u8) into a named 3D volume. A `buffer` or a binary string holds the volume's byte layout verbatim and crosses in one copy — the shape a file's voxel payload arrives in; an array carries one byte value (0..255) per entry.
| arg | type | description |
|---|---|---|
| name | string | Volume name. |
| data | buffer | string | { number } | Voxel bytes as a `buffer`, a binary string, or an array of bytes. |
writeFloatsTexture3D(name: string, floats: { number }, formatOrOpts: (string | { [string]: any }) → void
Upload float values into a named 3D volume, packed via the given format (default rgba16f).
| arg | type | description |
|---|---|---|
| name | string | |
| floats | { number } | |
| formatOrOpts | (string | { [string]: any } |
readTexture3D(name: string) → string
Start a GPU→CPU readback of a named 3D volume. Returns a result key to poll.
| arg | type | description |
|---|---|---|
| name | string |
destroyTexture3D(name: string) → boolean
Destroy a named 3D volume and free its GPU memory.
| arg | type | description |
|---|---|---|
| name | string |
textureForPlugin(name: string) → any
The texture this script created under `name` as a handle a script passes a plugin in a call. The handle holds the texture by an id of its own, never by the name: once the texture is destroyed the handle refuses, even when the name is made again. Only a texture made by `compute.createTexture3D` or `compute.createStorageTexture2D` can be handed over. hidden
| arg | type | description |
|---|---|---|
| name | string | The texture's name. |
createStorageTexture2D(name: string, opts: { [string]: any }) → boolean
Create a 2D storage texture (compute-writable render target). opts: `{ width, height, format? }`.
| arg | type | description |
|---|---|---|
| name | string | |
| opts | { [string]: any } |
destroyStorageTexture2D(name: string) → boolean
Destroy a named 2D storage texture.
| arg | type | description |
|---|---|---|
| name | string |
createTextureHistory(name: string, opts: { [string]: any }) → boolean
Create a temporal history buffer (ping-pong textures) for a target. opts: `{ width, height, format? }`.
| arg | type | description |
|---|---|---|
| name | string | |
| opts | { [string]: any } |
destroyTextureHistory(name: string) → boolean
Destroy a named texture-history buffer.
| arg | type | description |
|---|---|---|
| name | string |
createSampler(name: string, opts: { [string]: any }?) → boolean
Create a named GPU sampler. opts: filter/wrap settings.
| arg | type | description |
|---|---|---|
| name | string | |
| opts | { [string]: any }? |
destroySampler(name: string) → boolean
Release a named sampler created by `compute.createSampler` and free it. The counterpart to that call, alongside `destroyBuffer`, `destroyTexture`, `destroyTexture3D`, `destroyStorageTexture2D` and `destroyTextureHistory`. The manager's own defaults (`linear_clamp`, `linear_repeat`, `nearest_clamp`) are kept for the session, since a compute pass binds them by name.
| arg | type | description |
|---|---|---|
| name | string | Sampler name. |
examples
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")registerShaderEx(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?) → boolean
Register a compute shader with extended texture/sampler bindings. Accepts a name + opts or an asset handle. deprecated A compute shader is a `.computeShader` asset. Author one and dispatch it through its ref — `asset.resolve(name, "computeShader"):dispatchEx(opts)`. Raw WGSL registered under a bare name has no asset identity, so nothing records a dependency on it and it does not travel with the content that uses it. hidden
| arg | type | description |
|---|---|---|
| nameOrHandle | string | { [string]: any } | AssetRef | |
| opts | { [string]: any }? |
destroyShaderEx(name: string) → boolean
Destroy a shader registered via `registerShaderEx`. deprecated A `.computeShader` asset's compiled program is owned by the asset and released with it. hidden
| arg | type | description |
|---|---|---|
| name | string |
assertResourceKinds(what: string, opts: { [string]: any }?) → void
| arg | type | description |
|---|---|---|
| what | string | |
| opts | { [string]: any }? |
dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }) → boolean
Dispatch a compute shader with extended texture/storage/sampler bindings. Asset-backed `.computeShader`s resolve to their stable guid (collision-safe, lazily compiled on first dispatch); raw `registerShaderEx` names pass through. `resources` covers the bindings the shader DECLARES. A `params:` block's uniform is engine-owned — the compile creates and packs it, `setParam` writes it, and the dispatch binds it — so it takes no entry here. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.failing()`. deprecated Use `asset.resolve(name, "computeShader"):dispatchEx(opts)`. A name passed here records no dependency on the shader, so a packed or pulled world leaves it behind. hidden
| arg | type | description |
|---|---|---|
| shaderNameOrHandle | string | { [string]: any } | AssetRef | Shader name or asset handle. |
| opts | { [string]: any } | `{ resources, workgroups }` — each resource is `{ kind, name }`. |
textureFormatBytes(format: string) → number
Bytes-per-voxel for a texture format string (`rgba16f`, `r8`, ...).
| arg | type | description |
|---|---|---|
| format | string |
createBuffer(name: string, opts: { [string]: any }) → boolean
Allocate a buffer under `name`, sized in bytes. deprecated Hold the handle: `substrate.createBuffer({ type = "f32", len = size // 4, kind = "gpu" })`, or `shaderRef:createBuffer(name, opts)` for a buffer the shader owns.
| arg | type | description |
|---|---|---|
| name | string | The name a dispatch binds it by. |
| opts | { [string]: any } | `{ size, readback? }` — `size` in bytes. |
destroyBuffer(name: string) → boolean
Release the buffer allocated under `name`. deprecated Hold the handle and call `buf:destroy()`.
| arg | type | description |
|---|---|---|
| name | string | The name it was created under. |
readBuffer(name: string) → string
Start a GPU→CPU read of the buffer under `name`. not start answers with the empty key, which every drain reports as unknown — the same shape a caller already handles. deprecated Hold the handle: `buf:read(offset, len)` returns a Readback you ask directly.
| arg | type | description |
|---|---|---|
| name | string | The name it was created under. |
writeBuffer(name: string, values: { number } | buffer | string, offset: number?) → boolean
Write words into the buffer under `name`. deprecated Hold the handle and call `buf:write(values, offset)`.
| arg | type | description |
|---|---|---|
| name | string | The name it was created under. |
| values | { number } | buffer | string | The floats to write, or a `buffer` / binary string already holding them. |
| offset | number? | 32-bit word offset to write at. |
writeBufferU32(name: string, values: { number } | buffer | string, offsetBytes: number?) → boolean
Write 32-bit words into the buffer under `name`. deprecated Hold the handle and call `buf:writeU32(values, offsetBytes)`.
| arg | type | description |
|---|---|---|
| name | string | The name it was created under. |
| values | { number } | buffer | string | The words to write. |
| offsetBytes | number? | Byte offset to write at. |
writeBufferBytes(name: string, bytes: buffer | string, offsetBytes: number?) → boolean
Write packed bytes into the buffer under `name`. deprecated Hold the handle and call `buf:writeBytes(bytes, offsetBytes)`.
| arg | type | description |
|---|---|---|
| name | string | The name it was created under. |
| bytes | buffer | string | The payload. |
| offsetBytes | number? | Byte offset to write at. |
failing( ) →
Every compute dispatch whose most recent run FAILED, one record per `(shader, target)` pair. A dispatch is recorded into a command encoder frames after the call that asked for it returned, so a pass that stops running reports here rather than through that call's return value: each record carries the shader key, the target it writes (a mesh guid for a dispatch over vertices, the buffers it bound for one that writes only those), how many dispatches and failures it has had, and `lastError`. An empty result means every dispatch the engine has been given is running. lastFailedFrame?, lastError? }`.
examples
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
programState(ref: string | { [string]: any } | AssetRef) →
Where a shader's compiled program stands. A registration is queued from script and the pipeline is built on the render side frames later, so the call that asked for the compile cannot say whether it produced a program: `"absent"` (the engine holds nothing under this key and nothing is in flight — never asked for, or released), `"pending"` (asked for, not on the device yet — a recompile of a resident program reads pending too, because what it produces is a different program from the one bound now), `"ready"` (compiled and resident, so a dispatch binds it), or `"failed"` (the most recent registration produced no program), returned with the reason as a second value. A `.computeShader` whose `bindings.yaml` its compile refuses reads `"failed"`, with the reason naming the file and the line, because no program is compiled from the file as it stands. Wait for `"ready"` before a dispatch whose result is read back, rather than for a count of frames. or the name a raw registration chose. when `"failed"`.
| arg | type | description |
|---|---|---|
| ref | string | { [string]: any } | AssetRef | A `.computeShader` identity/guid string, a resolved asset handle, |
examples
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
listEntry(key: string, opts: { [string]: any }) → void
A dispatch as a compute list entry: its program, the names of the buffers it binds, its workgroups as three counts, and a copy of its params.
| arg | type | description |
|---|---|---|
| key | string | |
| opts | { [string]: any } |
compile(key: string, spec: { [string]: any }) →
Register a compiled program under `key` from WGSL plus a declared binding schema. The engine generates the `@group`/`@binding` declarations from the schema, expands `#include`s, naga-validates, and registers the result. schema. A buffer binding stating `range = true` (or every array buffer binding, with `ranges = true`) takes a range of a buffer: a dispatch binds it with `zero_base_NAME` / `zero_len_NAME` in its params, and the body's `NAME[i]` and `arrayLength(&NAME)` address that range. One that states no range binds the whole buffer. when the schema or the module is one the device would refuse. hidden
| arg | type | description |
|---|---|---|
| key | string | The key to register under. |
| spec | { [string]: any } | `{ source, entryPoint?, bindings, params, ranges? }` — the parsed |
destroy(key: string) → boolean
Release the program registered under `key`. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
forPlugin(key: string, bindings: number | { string }) → any
The program under `key` as a handle a script passes a plugin in a call, for it to dispatch over the GPU buffers and textures it was handed. `bindings` is how many storage buffers the shader binds, or the kind of each binding in declaration order as `bindings.yaml` spells them (`buffer`, `texture3d`, `storage3d`, `storage2d`). hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
| bindings | number | { string } | A count of buffers, or each binding's kind. |
dispatch(key: string, opts: { [string]: any }) → boolean
Dispatch the program under `key` with one buffer per declared storage binding, in declaration order. program's `params:` uniform, by name, to this dispatch alone: two dispatches of one program in a frame each read their own, and a value left out reads what the program holds. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.program.status(key)`. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
| opts | { [string]: any } | `{ buffers, workgroups, params? }`. `params` gives values of the |
record(fn: ({ { [string]: any } }) →
Run `fn` and answer every `compute.program.dispatch` it made, in order, as the entries of a compute list: `{ program, buffers, workgroups, params }` each. The dispatches are queued as they always are; the answer is what `compute.program.list` takes to replay them. `fn` is handed the entries as they are made, so it can add `slots` to one it just dispatched. hidden
| arg | type | description |
|---|---|---|
| fn | ({ { [string]: any } } | The work to run, given the entries recorded so far. |
dispatchOnVertices(key: string, opts: { [string]: any }) → boolean
Dispatch the program under `key` over a mesh's vertices. The mesh `opts.model` names fills the shader's `vertices` binding, and `opts.buffers` fills the remaining storage bindings. the program's `params:` uniform to this dispatch alone. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.program.status(key)`. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
| opts | { [string]: any } | `{ model, buffers?, workgroups, params? }`. `params` gives values of |
dispatchEx(key: string, opts: { [string]: any }) → boolean
Dispatch the program under `key` with explicit resources — one `{ kind, name }` per declared binding, in declaration order. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with `math.max(1, math.ceil(n / 64))`. in `compute.program.status(key)`. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
| opts | { [string]: any } | `{ resources, workgroups }`. |
list(key: string, entries: { { [string]: any } }) → boolean
Register the compute list under `key`, replacing one already there: an ordered run of dispatches and buffer writes that `runList` queues in one call, each value a run supplies named as a slot. `{ program, buffers, workgroups, params?, slots?, when? }`, `slots` mapping a param name to the slot that gives it its value. A write is `{ write = bufferName, offset?, values, unsigned?, slots?, when? }`: `offset` in 32-bit words, `values` written as f32 (u32 when `unsigned`), `slots` mapping a value's place, from 1, to the slot that gives it. `when` names a slot: a run giving it 0 leaves the entry out. hidden
| arg | type | description |
|---|---|---|
| key | string | The key to register the list under. |
| entries | { { [string]: any } } | The dispatches and writes, in the order they run. A dispatch is |
runList(key: string, slots: { [string]: number }?) → boolean
Queue the compute list under `key`: every entry in order, each value an entry names a slot for taken from `slots`, every other value as the list holds it. hidden
| arg | type | description |
|---|---|---|
| key | string | The list's key. |
| slots | { [string]: number }? | The value of each slot for this run, by name. |
dropList(key: string) → boolean
Forget the compute list under `key`. hidden
| arg | type | description |
|---|---|---|
| key | string | The list's key. |
setParam(key: string, prop: string, value: number) → boolean
Write one scalar of the program's `params:` uniform. A value set before the program's first compile is the value it starts with. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
| prop | string | Parameter name as declared. |
| value | number | New scalar value. |
status(key: string) →
What the engine did with the dispatches of the program under `key`, one record per target. hidden
| arg | type | description |
|---|---|---|
| key | string | The program's key. |
observe( ) →
Every GPU resource the compute subsystem is holding right now — its storage and uniform buffers, its 3D textures, its 2D storage targets, its history pairs and its samplers — with what each one costs and which shader asked for it. This is the call to reach for when compute is holding memory and you do not know what, or when a key out of a dispatch failure needs matching against what exists. `resources` carries `key`, `kind` (`buffer` / `uniformBuffer` / `texture3d` / `storageTexture2d` / `textureHistory` / `sampler`), `owner` (`{ shader, name, serial }`, read off the key), `bytes`, `format`, `width`, `height`, `depth`, `usage` (the bits it was created with — `storage`, `copySrc`, `copyDst`, `vertex`, `index`, `indirect`, `uniform`, `sampled`, `sampler`) and `createdFrame`. `totals` is `{ count, bytes, byKind }`, what the rows sum to — and `totals.bytes` is the `compute` figure of `renderer.gpuMemory()`, read off the same registries. `published` is false when no renderer has published a reading yet, which is the engine saying it cannot answer rather than answering with nothing. The reading is the one the renderer published, republished on a frame where a registry gained or lost an entry: a resource created earlier in this same script is in the next reading, so wait a frame before asking about it, and `generation` moves when it arrives.
examples
local r = compute.observe()
print(r.totals.count, r.totals.bytes)
for _, res in ipairs(r.resources) do print(res.key, res.kind, res.bytes) end
resources(owner: any?) →
The resource rows on their own, optionally narrowed to what one shader owns. every resource compute holds. A value carrying no shader raises, so a narrowing that cannot be done reads as an error rather than as the whole inventory. A guid stands for itself, so resources outlive the asset that made them and stay reachable by their owner. Empty when the owner holds nothing.
| arg | type | description |
|---|---|---|
| owner | any? | A `.computeShader` ref, its guid, or its asset identity. Omit for |
examples
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
diagnose(key: string) →
Whether a resource is filed under `key` right now, and when none is, which state the inventory says the key is in. A key out of a dispatch failure resolves here; a mistyped one reports why it does not. when one is filed under the key. `reason` is one of `compute.absentReasons()`. `current` names the live key when the owner holds a resource under the same name at a different serial.
| arg | type | description |
|---|---|---|
| key | string | The resource key, verbatim. |
examples
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
absentReasons( ) →
Every reason `compute.diagnose` reports, sorted. `resident` is the one that means the resource is there.
examples
for _, r in ipairs(compute.absentReasons()) do print(r) end
computeMark(generation: number?) → void
Internal: the mark `generation` names, with its index, or the newest.
| arg | type | description |
|---|---|---|
| generation | number? |
markBaseline( ) → number
Record which compute resources are resident right now, and make that the point `compute.resetToBaseline` returns to. A caller that runs work in someone else's engine — a sweep of test suites sharing one, an editor tool driving a session — allocates as it goes: a pass builds the grid its quality asks for and keeps it, a shader files the uniform it was compiled with. Kept is right for a frame that draws again and wrong for the next caller, which then reads an inventory it did not make. This is how a caller says where its own allocations start. pass The resident keys are recorded as a mark above the marks before it. `compute.resetToBaseline` takes to restore to this mark.
examples
local mark = compute.markBaseline() -- then run, then compute.resetToBaseline(mark)
resetToBaseline(generation: number?) → number
Release every compute resource allocated since `compute.markBaseline`, leaving the ones it recorded where they are. Each kind this closes is one a caller allocated under a name it chose, so the next caller finds that name free. A storage buffer is such a resource and is not closed here: every one a caller can make is a substrate buffer, and `substrate.resetToBaseline` is what puts those back. A compiled shader's params uniform is not, and is left where it is: it is filed under its shader's guid, which no other caller can ask for, and it belongs to the `.computeShader` asset that compiled it, released when that is. pass Every texture, buffer texture, history and sampler allocated since the mark is closed; nothing the mark recorded is touched. it; the newest mark when omitted. The marks taken after it go with the reset.
| arg | type | description |
|---|---|---|
| generation | number? | The mark to restore to, as `compute.markBaseline` returned |
examples
local released = compute.resetToBaseline(mark)
releaseBaseline(generation: number) → boolean
Drop the mark `generation` names, and every mark taken after it, once the caller that took it is done resetting to it. It touches the marks alone.
| arg | type | description |
|---|---|---|
| generation | number | The mark to drop, as `compute.markBaseline` returned it. |
examples
compute.releaseBaseline(mark) -- the run is over
ShaderOpts = {DispatchOpts = {DispatchOnVerticesOpts = {Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.