compute
The compute namespace — 176 functions.
compute/compile
compute.compile(nameOrHandle, opts)
Compile + register a zero-scaffolding .computeShader. The author writes ONLY @compute fn main (no @group/@binding); the engine generates the entire group(0) interface from the declared schema and registers it via the mixed-binding Ex path. opts: { source, entryPoint = "main", bindings = { { name, kind, access?, element?, format? }, ... }, params = { { name, type, default }, ... }, label }. kind ∈ buffer|texture3d|texture2d|storage3d|storage2d|sampler. label is what profiler.gpuFrame() calls each dispatch of this shader — pass the asset's identity, since the first argument is the guid a dispatch resolves by; omitted, the profiler reports that key. Internal — driven by the .computeShader assetType.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity to register under (string or asset handle).opts{ [string]: any }(optional) —{ source, entryPoint?, bindings, params? }—bindingsis an ordered list of{ name, kind, access?, element?, format?, array? }.
Returns boolean — True on success.
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
compute/createSampler
compute.createSampler(name, opts?)
Create a named sampler. opts: { filter = true, clamp = true }. The volume manager always provides 'linear_clamp', 'linear_repeat', 'nearest_clamp' by default.
Parameters
namestringopts{ [string]: any }(optional)
Returns boolean
compute/createStorageTexture2D
compute.createStorageTexture2D(name, opts)
Create a 2D storage texture used as a volume-shader output (raymarch target). opts: { width, height, format = "rgba16f" }.
Parameters
namestringopts{ [string]: any }
Returns boolean
compute/createTexture3D
compute.createTexture3D(name, opts)
Create a 3D texture. opts: { width, height, depth, format = "rgba16f", storage = true }. format is one of r8/r16f/r32f/rgba8/rgba16f/rgba32f. storage = true (default) lets compute shaders write to it.
Parameters
namestring— Unique volume name.opts{ [string]: any }— Dimensions + format (r8/r16f/r32f/rgba8/rgba16f/rgba32f).
Returns boolean — True on success (mutation queued).
compute/createTextureHistory
compute.createTextureHistory(name, opts)
Create a double-buffered 2D storage texture pair for temporal accumulation. Bind kind = "history_prev" to read the previous frame, kind = "history_curr" to write the current frame; the renderer flips them once per frame. opts: { width, height, format = "rgba16f" }.
Parameters
namestringopts{ [string]: any }
Returns boolean
compute/destroySampler
compute.destroySampler(name)
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.
Parameters
namestring— Sampler name.
Returns boolean — True when the release was queued.
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
compute/destroyShaderEx
compute.destroyShaderEx(name)
Destroy a registered volume shader.
Parameters
namestring
Returns boolean
compute/destroyStorageTexture2D
compute.destroyStorageTexture2D(name)
Destroy a named volume render target.
Parameters
namestring
Returns boolean
compute/destroyTexture3D
compute.destroyTexture3D(name)
Destroy a named 3D volume and free its GPU memory.
Parameters
namestring
Returns boolean
compute/destroyTextureHistory
compute.destroyTextureHistory(name)
Destroy a named texture-history pair.
Parameters
namestring
Returns boolean
compute/dispatchEx
compute.dispatchEx(shaderNameOrHandle, opts)
Dispatch a volume shader. Accepts a shader name string or an asset handle from asset.resolve(...). opts: { resources = { {kind, name}, ... }, workgroups = {x,y,z} }. Resource kinds: 'texture_2d', 'texture_3d', 'storage_2d', 'history_prev', 'history_curr', 'sampler', 'buffer', 'scene_depth' — any other kind raises, naming the accepted set. A 3D storage texture binds as 'texture_3d': the shader's declared layout decides storage versus sampled, so there is no 'storage_3d' resource kind even though 'storage3d' is a binding kind on the shader-declaration side. 'scene_depth' needs no name: it binds the engine's per-frame scene-depth blit (R32Float, depth in .r) to a texture_2d slot — read it with textureLoad to depth-clamp a raymarch against opaque geometry. The range is reversed: 1.0 is the near plane, 0.0 is the far plane and what empty sky clears to, and a nearer surface reads GREATER — so 'd > 0.0' is the test for 'something was drawn here'. Unproject a sample with a matrix built over the same reversed range (volumetricSky's invViewProjFromCam), or convert it to metres with zero_linear_depth in a post-process shader.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.opts{ [string]: any }—{ resources, workgroups }— each resource is{ kind, name }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
compute/readTexture3D
compute.readTexture3D(name)
Start a GPU→CPU readback of a named 3D volume. Returns a result key — poll with compute.getReadbackResult() (the readback channel is shared).
Parameters
namestring
Returns string
compute/registerShaderEx
compute.registerShaderEx(nameOrHandle, opts?)
Register a volume compute shader. Accepts an asset handle from asset.resolve(...) (preferred — the engine reuses the cached source) or (name, opts) with inline WGSL. opts: { source?, entry = "main", bindings = {...} }. Each binding is { kind = "texture_3d|texture_2d|storage_3d|storage_2d|sampler|storage_buffer|uniform_buffer", format = "rgba16f"?, readOnly = false? }.
Parameters
nameOrHandlestring | { [string]: any } | AssetRefopts{ [string]: any }(optional)
Returns boolean
compute/setParam
compute.setParam(name, prop, value)
Write one scalar field of a compiled .computeShader's params uniform. No-op (logged warning) if the shader or param is unknown.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity (the.computeShaderasset name), or the handleasset.load/asset.resolvereturns — the same formsdispatchtakes.propstring— Parameter name as declared inbindings.yaml.valuenumber— New scalar value (numbers only).
Returns boolean — True on success.
compute.setParam("my_sim", "scale", 4.0)
compute/textureFormatBytes
compute.textureFormatBytes(format)
Bytes per voxel for a format name. Returns 0 for unknown formats.
Parameters
formatstring
Returns number
compute/writeFloatsTexture3D
compute.writeFloatsTexture3D(name, floats, formatOrOpts?)
Upload float values into a named 3D volume. Values are packed into bytes using the supplied format string (or opts.format), defaulting to rgba16f. Pass the same format the volume was created with.
Parameters
namestringfloats{ number }formatOrOpts(string | { [string]: any })(optional)
Returns boolean
compute/writeTexture3D
compute.writeTexture3D(name, data)
Upload raw bytes (interpreted as u8) into a named 3D volume. data is a buffer or a binary string holding the volume's byte layout verbatim, or an array of byte values (0..255). Byte count must match the volume's dimensions * format bytes-per-voxel.
Parameters
namestring— Volume name.databuffer | string | { number }— Voxel bytes as abuffer, a binary string, or an array of bytes.
Returns boolean — True on success (mutation queued).
globals/compute/absentReasons
compute.absentReasons() -> { string }
Every reason compute.diagnose reports, sorted. resident is the one
that means the resource is there.
Returns { string } — The closed set, as strings.
for _, r in ipairs(compute.absentReasons()) do print(r) end
globals/compute/beginBvh
compute.beginBvh(instances: { any }, opts: { [string]: any }?) -> (number?, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf? }— max triangles per leaf.
Returns (number?, string?) — The build id, or (nil, err) naming any non-resident guid.
local id = compute.beginBvh(gather.instances)
globals/compute/buildBvh
compute.buildBvh(instances: { any }, opts: { [string]: any }?) -> (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf? }— max triangles per leaf.
Returns (any, string?) — { nodes, tris, nodeCount, triCount } — nodes and tris are buffer 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.
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
globals/compute/buildBvhSliced
compute.buildBvhSliced(instances: { any }, opts: { [string]: any }?) -> (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf?, budgetMs? }— max triangles per leaf, and the wall time one frame may spend on the build (default 4 ms).
Returns (any, string?) — { nodes, tris, nodeCount, triCount }, or (nil, err).
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
globals/compute/bvhBuilds
compute.bvhBuilds() -> { any }
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.
Returns { any } — Array of build rows.
print(#compute.bvhBuilds(), "hierarchies in flight")
globals/compute/cancelBvh
compute.cancelBvh(id: number) -> boolean
Drop a build along with the triangles it has gathered.
Parameters
idnumber— Build id fromcompute.beginBvh.
Returns boolean — True when the id named a build.
compute.cancelBvh(id)
globals/compute/compile
compute.compile(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?) -> boolean
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity to register under (string or asset handle).opts{ [string]: any }(optional) —{ source, entryPoint?, bindings, params? }—bindingsis an ordered list of{ name, kind, access?, element?, format?, array? }.
Returns boolean — True on success.
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
globals/compute/compileByName
compute.compileByName(ref: string | { [string]: any } | AssetRef)
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).
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, or a resolved asset handle.
compute.compileByName("@builtin::shaders.compute_double")
globals/compute/copyBufferToTexture
compute.copyBufferToTexture(bufferName: string, textureKey: string, width: number, height: number, format: string?) -> 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.
Rows must be a multiple of 256 bytes (at rgba16f, any width from 32
up in powers of two).
Parameters
bufferNamestring— Source compute buffer.textureKeystring— Cache key to register the texture under.widthnumber— Texture width in texels.heightnumber— Texture height in texels.formatstring(optional) — Texel format:"rgba16f"(default),"rgba32f","rgba8".
Returns boolean — True when the copy was queued.
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
globals/compute/createBuffer
compute.createBuffer(name: string, opts: { [string]: any }) -> boolean
Allocate a buffer under name, sized in bytes.
Parameters
namestring— The name a dispatch binds it by.opts{ [string]: any }—{ size, readback? }—sizein bytes.
Returns boolean — True once allocated.
globals/compute/createSampler
compute.createSampler(name: string, opts: { [string]: any }?) -> boolean
Create a named GPU sampler. opts: filter/wrap settings.
Parameters
namestringopts{ [string]: any }(optional)
Returns boolean
globals/compute/createStorageTexture2D
compute.createStorageTexture2D(name: string, opts: { [string]: any }) -> boolean
Create a 2D storage texture (compute-writable render target). opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
Returns boolean
globals/compute/createTexture3D
compute.createTexture3D(name: string, opts: { [string]: any }) -> boolean
Create a 3D texture volume. opts: { width, height, depth, format?, storage? }.
Parameters
namestring— Unique volume name.opts{ [string]: any }— Dimensions + format (r8/r16f/r32f/rgba8/rgba16f/rgba32f).
Returns boolean — True on success (mutation queued).
globals/compute/createTextureHistory
compute.createTextureHistory(name: string, opts: { [string]: any }) -> boolean
Create a temporal history buffer (ping-pong textures) for a target. opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
Returns boolean
globals/compute/destroyBuffer
compute.destroyBuffer(name: string) -> boolean
Release the buffer allocated under name.
Parameters
namestring— The name it was created under.
Returns boolean — True if a buffer under that name was released.
globals/compute/destroySampler
compute.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.
Parameters
namestring— Sampler name.
Returns boolean — True when the release was queued.
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
globals/compute/destroyShader
compute.destroyShader(name: string) -> boolean
Destroy a named compute shader pipeline.
Parameters
namestring— Shader name.
Returns boolean — True on success.
globals/compute/destroyShaderEx
compute.destroyShaderEx(name: string) -> boolean
Destroy a shader registered via registerShaderEx.
Parameters
namestring
Returns boolean
globals/compute/destroyStorageTexture2D
compute.destroyStorageTexture2D(name: string) -> boolean
Destroy a named 2D storage texture.
Parameters
namestring
Returns boolean
globals/compute/destroyTexture
compute.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.
Parameters
textureKeystring— Cache key the texture was registered under.
Returns boolean — True when the release was queued.
compute.destroyTexture("lm_wall")
globals/compute/destroyTexture3D
compute.destroyTexture3D(name: string) -> boolean
Destroy a named 3D volume and free its GPU memory.
Parameters
namestring
Returns boolean
globals/compute/destroyTextureHistory
compute.destroyTextureHistory(name: string) -> boolean
Destroy a named texture-history buffer.
Parameters
namestring
Returns boolean
globals/compute/diagnose
compute.diagnose(key: string) -> { [string]: any }
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.
Parameters
keystring— The resource key, verbatim.
Returns { [string]: any } — { key, exists, reason, resource?, current? }. resource is the row 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.
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
globals/compute/dispatch
compute.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().
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOpts—{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
globals/compute/dispatchEx
compute.dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }) -> boolean
Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed .computeShaders 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.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.opts{ [string]: any }—{ resources, workgroups }— each resource is{ kind, name }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
globals/compute/dispatchOnVertices
compute.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 .computeShaders resolve to their stable guid
(collision-safe, lazily compiled on first dispatch); raw
registerShader names pass through.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOnVerticesOpts—{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
globals/compute/failing
compute.failing() -> { { [string]: any } }
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.
Returns { { [string]: any } } — Array of { shader, target, dispatches, failures, ok, lastFrame, lastFailedFrame?, lastError? }.
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
globals/compute/finishBvh
compute.finishBvh(id: number) -> (any, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh, stepped until"ready".
Returns (any, string?) — { nodes, tris, nodeCount, triCount }, or (nil, err) when the id names no build or the build still has work left.
local built = compute.finishBvh(id)
globals/compute/getReadbackResult
compute.getReadbackResult(resultKey: string) -> { number }?
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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns { number }? — 1-indexed array of f32 values, or nil if not ready.
local floats = compute.getReadbackResult(key)
globals/compute/getReadbackResultBytes
compute.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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns buffer? — The read-back's bytes, or nil if not ready.
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
globals/compute/getReadbackResultU32
compute.getReadbackResultU32(resultKey: string) -> { number }?
Poll for a completed read-back interpreting bytes as u32. Returns array of integer values if ready, nil if pending.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns { number }? — Array of u32 values, or nil if not ready.
globals/compute/isReadbackReady
compute.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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns boolean — True if the result is ready.
globals/compute/observe
compute.observe() -> { [string]: any }
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.
Returns { [string]: any } — { published, generation, resources, totals }. Each row of 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.
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
globals/compute/program/compile
compute.program.compile(key: string, spec: { [string]: any }) -> boolean
Register a compiled program under key from WGSL plus a declared
binding schema. The engine generates the @group/@binding declarations
from the schema, expands #includes, naga-validates, and registers the
result.
Parameters
keystring— The key to register under.spec{ [string]: any }—{ source, entryPoint?, bindings, params }— the parsed schema.
Returns boolean — True on success.
globals/compute/program/destroy
compute.program.destroy(key: string) -> boolean
Release the program registered under key.
Parameters
keystring— The program's key.
Returns boolean — True on success.
globals/compute/program/dispatch
compute.program.dispatch(key: string, opts: { [string]: any }) -> boolean
Dispatch the program under key with one buffer per declared storage
binding, in declaration order.
Parameters
keystring— The program's key.opts{ [string]: any }—{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.program.status(key).
globals/compute/program/dispatchEx
compute.program.dispatchEx(key: string, opts: { [string]: any }) -> boolean
Dispatch the program under key with explicit resources — one
{ kind, name } per declared binding, in declaration order.
Parameters
keystring— The program's key.opts{ [string]: any }—{ resources, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.program.status(key).
globals/compute/program/dispatchOnVertices
compute.program.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.
Parameters
keystring— The program's key.opts{ [string]: any }—{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.program.status(key).
globals/compute/program/setParam
compute.program.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.
Parameters
keystring— The program's key.propstring— Parameter name as declared.valuenumber— New scalar value.
Returns boolean — True on success.
globals/compute/program/status
compute.program.status(key: string) -> { { [string]: any } }
What the engine did with the dispatches of the program under key,
one record per target.
Parameters
keystring— The program's key.
Returns { { [string]: any } } — Array of dispatch records, most recently dispatched first.
globals/compute/programState
compute.programState(ref: string | { [string]: any } | AssetRef) -> (string, string?)
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. Wait for "ready" before a dispatch whose
result is read back, rather than for a count of frames.
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, a resolved asset handle, or the name a raw registration chose.
Returns (string, string?) — "absent", "pending", "ready" or "failed", and the reason when "failed".
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
globals/compute/readBuffer
compute.readBuffer(name: string) -> string
Start a GPU→CPU read of the buffer under name.
Parameters
namestring— The name it was created under.
Returns string — The result key to poll with getReadbackResult*. A read that could not start answers with the empty key, which every drain reports as unknown — the same shape a caller already handles.
globals/compute/readTexture3D
compute.readTexture3D(name: string) -> string
Start a GPU→CPU readback of a named 3D volume. Returns a result key to poll.
Parameters
namestring
Returns string
globals/compute/readbackState
compute.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), or "unknown" (never issued by
readBuffer(), or already drained — a result is delivered once).
Parameters
resultKeystring— Key returned byreadBuffer().
Returns string — "pending", "ready", or "unknown".
if compute.readbackState(key) == "ready" then ... end
globals/compute/registerShader
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsShaderOpts(optional) — Shader options.bindingscomes from the source's own@group(0) @binding(n)declarations when omitted; supplying a count that disagrees with them raises. EveryreadOnlyBindingsentry names one of those declared bindings, as a whole number from 0 tobindings - 1; an entry outside that run raises.
Returns boolean — True on success.
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
globals/compute/registerShaderEx
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRefopts{ [string]: any }(optional)
Returns boolean
globals/compute/resources
compute.resources(owner: any?) -> { any }
The resource rows on their own, optionally narrowed to what one shader owns.
Parameters
ownerany(optional) — A.computeShaderref, its guid, or its asset identity. Omit for 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.
Returns { any } — An array of rows in the shape compute.observe().resources carries. Empty when the owner holds nothing.
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
globals/compute/setParam
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity (the.computeShaderasset name), or the handleasset.load/asset.resolvereturns — the same formsdispatchtakes.propstring— Parameter name as declared inbindings.yaml.valuenumber— New scalar value (numbers only).
Returns boolean — True on success.
compute.setParam("my_sim", "scale", 4.0)
globals/compute/stepBvh
compute.stepBvh(id: number, budgetMs: number?) -> (string?, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh.budgetMsnumber(optional) — Wall time this slice may spend, in milliseconds (default 4).
Returns (string?, string?) — "pending" or "ready", or (nil, err) when the id names no build.
while compute.stepBvh(id, 4) == "pending" do task.wait() end
globals/compute/textureFormatBytes
compute.textureFormatBytes(format: string) -> number
Bytes-per-voxel for a texture format string (rgba16f, r8, ...).
Parameters
formatstring
Returns number
globals/compute/writeBuffer
compute.writeBuffer(name: string, values: { number } | buffer | string, offset: number?) -> boolean
Write words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The floats to write, or abuffer/ binary string already holding them.offsetnumber(optional) — 32-bit word offset to write at.
Returns boolean — True on success.
globals/compute/writeBufferBytes
compute.writeBufferBytes(name: string, bytes: buffer | string, offsetBytes: number?) -> boolean
Write packed bytes into the buffer under name.
Parameters
namestring— The name it was created under.bytesbuffer | string— The payload.offsetBytesnumber(optional) — Byte offset to write at.
Returns boolean — True on success.
globals/compute/writeBufferU32
compute.writeBufferU32(name: string, values: { number } | buffer | string, offsetBytes: number?) -> boolean
Write 32-bit words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The words to write.offsetBytesnumber(optional) — Byte offset to write at.
Returns boolean — True on success.
globals/compute/writeFloatsTexture3D
compute.writeFloatsTexture3D(name: string, floats: { number }, formatOrOpts: (string | { [string]: any })?) -> boolean
Upload float values into a named 3D volume, packed via the given format (default rgba16f).
Parameters
namestringfloats{ number }formatOrOpts(string | { [string]: any })(optional)
Returns boolean
globals/compute/writeTexture3D
compute.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.
Parameters
namestring— Volume name.databuffer | string | { number }— Voxel bytes as abuffer, a binary string, or an array of bytes.
Returns boolean — True on success (mutation queued).
modules/compute/README
require("@builtin/modules/api/engine/compute") -- compute (also available as global '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.
Usage: local compute = require("@builtin/modules/api/engine/compute") Also available as global: compute
modules/compute/absentReasons
absentReasons(): { string }
Every reason compute.diagnose reports, sorted. resident is the one
that means the resource is there.
for _, r in ipairs(compute.absentReasons()) do print(r) end
modules/compute/beginBvh
beginBvh(instances: { any }, opts: { [string]: any }?): (number?, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }?(optional) — Optional{ maxLeaf? }— max triangles per leaf.
local id = compute.beginBvh(gather.instances)
modules/compute/buildBvh
buildBvh(instances: { any }, opts: { [string]: any }?): (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }?(optional) — Optional{ maxLeaf? }— max triangles per leaf.
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
modules/compute/buildBvhSliced
buildBvhSliced(instances: { any }, opts: { [string]: any }?): (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }?(optional) — Optional{ maxLeaf?, budgetMs? }— max triangles per leaf, and the wall time one frame may spend on the build (default 4 ms).
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
modules/compute/bvhBuilds
bvhBuilds(): { any }
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.
print(#compute.bvhBuilds(), "hierarchies in flight")
modules/compute/cancelBvh
cancelBvh(id: number): boolean
Drop a build along with the triangles it has gathered.
Parameters
idnumber— Build id fromcompute.beginBvh.
compute.cancelBvh(id)
modules/compute/compile
compile(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?): boolean
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity to register under (string or asset handle).opts{ [string]: any }?(optional) —{ source, entryPoint?, bindings, params? }—bindingsis an ordered list of{ name, kind, access?, element?, format?, array? }.
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
modules/compute/compileByName
compileByName(ref: string | { [string]: any } | AssetRef)
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).
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, or a resolved asset handle.
compute.compileByName("@builtin::shaders.compute_double")
modules/compute/copyBufferToTexture
copyBufferToTexture(bufferName: string, textureKey: string, width: number, height: number, format: string?): 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.
Rows must be a multiple of 256 bytes (at rgba16f, any width from 32
up in powers of two).
Parameters
bufferNamestring— Source compute buffer.textureKeystring— Cache key to register the texture under.widthnumber— Texture width in texels.heightnumber— Texture height in texels.formatstring?(optional) — Texel format:"rgba16f"(default),"rgba32f","rgba8".
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
modules/compute/createBuffer
createBuffer(name: string, opts: { [string]: any }): boolean
Allocate a buffer under name, sized in bytes.
Parameters
namestring— The name a dispatch binds it by.opts{ [string]: any }—{ size, readback? }—sizein bytes.
modules/compute/createSampler
createSampler(name: string, opts: { [string]: any }?): boolean
Create a named GPU sampler. opts: filter/wrap settings.
Parameters
namestringopts{ [string]: any }?(optional)
modules/compute/createStorageTexture2D
createStorageTexture2D(name: string, opts: { [string]: any }): boolean
Create a 2D storage texture (compute-writable render target). opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
modules/compute/createTexture3D
createTexture3D(name: string, opts: { [string]: any }): boolean
Create a 3D texture volume. opts: { width, height, depth, format?, storage? }.
Parameters
namestring— Unique volume name.opts{ [string]: any }— Dimensions + format (r8/r16f/r32f/rgba8/rgba16f/rgba32f).
modules/compute/createTextureHistory
createTextureHistory(name: string, opts: { [string]: any }): boolean
Create a temporal history buffer (ping-pong textures) for a target. opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
modules/compute/destroyBuffer
destroyBuffer(name: string): boolean
Release the buffer allocated under name.
Parameters
namestring— The name it was created under.
modules/compute/destroySampler
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.
Parameters
namestring— Sampler name.
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
modules/compute/destroyShader
destroyShader(name: string): boolean
Destroy a named compute shader pipeline.
Parameters
namestring— Shader name.
modules/compute/destroyShaderEx
destroyShaderEx(name: string): boolean
Destroy a shader registered via registerShaderEx.
Parameters
namestring
modules/compute/destroyStorageTexture2D
destroyStorageTexture2D(name: string): boolean
Destroy a named 2D storage texture.
Parameters
namestring
modules/compute/destroyTexture
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.
Parameters
textureKeystring— Cache key the texture was registered under.
compute.destroyTexture("lm_wall")
modules/compute/destroyTexture3D
destroyTexture3D(name: string): boolean
Destroy a named 3D volume and free its GPU memory.
Parameters
namestring
modules/compute/destroyTextureHistory
destroyTextureHistory(name: string): boolean
Destroy a named texture-history buffer.
Parameters
namestring
modules/compute/diagnose
diagnose(key: string): { [string]: any }
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.
Parameters
keystring— The resource key, verbatim.
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
modules/compute/dispatch
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().
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOpts—{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
modules/compute/dispatchEx
dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }): boolean
Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed .computeShaders 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.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.opts{ [string]: any }—{ resources, workgroups }— each resource is{ kind, name }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
modules/compute/dispatchOnVertices
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 .computeShaders resolve to their stable guid
(collision-safe, lazily compiled on first dispatch); raw
registerShader names pass through.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOnVerticesOpts—{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
modules/compute/failing
failing(): { { [string]: any } }
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.
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
modules/compute/finishBvh
finishBvh(id: number): (any, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh, stepped until"ready".
local built = compute.finishBvh(id)
modules/compute/getReadbackResult
getReadbackResult(resultKey: string): { number }?
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.
Parameters
resultKeystring— Key returned byreadBuffer().
local floats = compute.getReadbackResult(key)
modules/compute/getReadbackResultBytes
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.
Parameters
resultKeystring— Key returned byreadBuffer().
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
modules/compute/getReadbackResultU32
getReadbackResultU32(resultKey: string): { number }?
Poll for a completed read-back interpreting bytes as u32. Returns array of integer values if ready, nil if pending.
Parameters
resultKeystring— Key returned byreadBuffer().
modules/compute/isReadbackReady
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.
Parameters
resultKeystring— Key returned byreadBuffer().
modules/compute/observe
observe(): { [string]: any }
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.
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
modules/compute/program.compile
program.compile(key: string, spec: { [string]: any }): boolean
Register a compiled program under key from WGSL plus a declared
binding schema. The engine generates the @group/@binding declarations
from the schema, expands #includes, naga-validates, and registers the
result.
Parameters
keystring— The key to register under.spec{ [string]: any }—{ source, entryPoint?, bindings, params }— the parsed schema.
modules/compute/program.destroy
program.destroy(key: string): boolean
Release the program registered under key.
Parameters
keystring— The program's key.
modules/compute/program.dispatch
program.dispatch(key: string, opts: { [string]: any }): boolean
Dispatch the program under key with one buffer per declared storage
binding, in declaration order.
Parameters
keystring— The program's key.opts{ [string]: any }—{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
modules/compute/program.dispatchEx
program.dispatchEx(key: string, opts: { [string]: any }): boolean
Dispatch the program under key with explicit resources — one
{ kind, name } per declared binding, in declaration order.
Parameters
keystring— The program's key.opts{ [string]: any }—{ resources, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
modules/compute/program.dispatchOnVertices
program.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.
Parameters
keystring— The program's key.opts{ [string]: any }—{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
modules/compute/program.setParam
program.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.
Parameters
keystring— The program's key.propstring— Parameter name as declared.valuenumber— New scalar value.
modules/compute/program.status
program.status(key: string): { { [string]: any } }
What the engine did with the dispatches of the program under key,
one record per target.
Parameters
keystring— The program's key.
modules/compute/programState
programState(ref: string | { [string]: any } | AssetRef): (string, string?)
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. Wait for "ready" before a dispatch whose
result is read back, rather than for a count of frames.
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, a resolved asset handle, or the name a raw registration chose.
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
modules/compute/readBuffer
readBuffer(name: string): string
Start a GPU→CPU read of the buffer under name.
Parameters
namestring— The name it was created under.
modules/compute/readTexture3D
readTexture3D(name: string): string
Start a GPU→CPU readback of a named 3D volume. Returns a result key to poll.
Parameters
namestring
modules/compute/readbackState
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), or "unknown" (never issued by
readBuffer(), or already drained — a result is delivered once).
Parameters
resultKeystring— Key returned byreadBuffer().
if compute.readbackState(key) == "ready" then ... end
modules/compute/registerShader
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsShaderOpts?(optional) — Shader options.bindingscomes from the source's own@group(0) @binding(n)declarations when omitted; supplying a count that disagrees with them raises. EveryreadOnlyBindingsentry names one of those declared bindings, as a whole number from 0 tobindings - 1; an entry outside that run raises.
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
modules/compute/registerShaderEx
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRefopts{ [string]: any }?(optional)
modules/compute/resources
resources(owner: any?): { any }
The resource rows on their own, optionally narrowed to what one shader owns.
Parameters
ownerany?(optional) — A.computeShaderref, its guid, or its asset identity. Omit for 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.
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
modules/compute/setParam
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity (the.computeShaderasset name), or the handleasset.load/asset.resolvereturns — the same formsdispatchtakes.propstring— Parameter name as declared inbindings.yaml.valuenumber— New scalar value (numbers only).
compute.setParam("my_sim", "scale", 4.0)
modules/compute/stepBvh
stepBvh(id: number, budgetMs: number?): (string?, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh.budgetMsnumber?(optional) — Wall time this slice may spend, in milliseconds (default 4).
while compute.stepBvh(id, 4) == "pending" do task.wait() end
modules/compute/textureFormatBytes
textureFormatBytes(format: string): number
Bytes-per-voxel for a texture format string (rgba16f, r8, ...).
Parameters
formatstring
modules/compute/writeBuffer
writeBuffer(name: string, values: { number } | buffer | string, offset: number?): boolean
Write words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The floats to write, or abuffer/ binary string already holding them.offsetnumber?(optional) — 32-bit word offset to write at.
modules/compute/writeBufferBytes
writeBufferBytes(name: string, bytes: buffer | string, offsetBytes: number?): boolean
Write packed bytes into the buffer under name.
Parameters
namestring— The name it was created under.bytesbuffer | string— The payload.offsetBytesnumber?(optional) — Byte offset to write at.
modules/compute/writeBufferU32
writeBufferU32(name: string, values: { number } | buffer | string, offsetBytes: number?): boolean
Write 32-bit words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The words to write.offsetBytesnumber?(optional) — Byte offset to write at.
modules/compute/writeFloatsTexture3D
writeFloatsTexture3D(name: string, floats: { number }, formatOrOpts: (string | { [string]: any })?): boolean
Upload float values into a named 3D volume, packed via the given format (default rgba16f).
Parameters
namestringfloats{ number }formatOrOpts(string | { [string]: any })?(optional)
modules/compute/writeTexture3D
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.
Parameters
namestring— Volume name.databuffer | string | { number }— Voxel bytes as abuffer, a binary string, or an array of bytes.
typed/builtin//modules/api/engine/compute/compute/absentReasons
compute.absentReasons() -> { string }
Every reason compute.diagnose reports, sorted. resident is the one
that means the resource is there.
Returns { string } — The closed set, as strings.
for _, r in ipairs(compute.absentReasons()) do print(r) end
typed/builtin//modules/api/engine/compute/compute/beginBvh
compute.beginBvh(instances: { any }, opts: { [string]: any }?) -> (number?, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf? }— max triangles per leaf.
Returns (number?, string?) — The build id, or (nil, err) naming any non-resident guid.
local id = compute.beginBvh(gather.instances)
typed/builtin//modules/api/engine/compute/compute/buildBvh
compute.buildBvh(instances: { any }, opts: { [string]: any }?) -> (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf? }— max triangles per leaf.
Returns (any, string?) — { nodes, tris, nodeCount, triCount } — nodes and tris are buffer 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.
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
typed/builtin//modules/api/engine/compute/compute/buildBvhSliced
compute.buildBvhSliced(instances: { any }, opts: { [string]: any }?) -> (any, string?)
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.
Parameters
instances{ any }— Array of{ guid, transform, attributes? }mesh instances.opts{ [string]: any }(optional) — Optional{ maxLeaf?, budgetMs? }— max triangles per leaf, and the wall time one frame may spend on the build (default 4 ms).
Returns (any, string?) — { nodes, tris, nodeCount, triCount }, or (nil, err).
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
typed/builtin//modules/api/engine/compute/compute/bvhBuilds
compute.bvhBuilds() -> { any }
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.
Returns { any } — Array of build rows.
print(#compute.bvhBuilds(), "hierarchies in flight")
typed/builtin//modules/api/engine/compute/compute/cancelBvh
compute.cancelBvh(id: number) -> boolean
Drop a build along with the triangles it has gathered.
Parameters
idnumber— Build id fromcompute.beginBvh.
Returns boolean — True when the id named a build.
compute.cancelBvh(id)
typed/builtin//modules/api/engine/compute/compute/compile
compute.compile(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?) -> boolean
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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity to register under (string or asset handle).opts{ [string]: any }(optional) —{ source, entryPoint?, bindings, params? }—bindingsis an ordered list of{ name, kind, access?, element?, format?, array? }.
Returns boolean — True on success.
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
typed/builtin//modules/api/engine/compute/compute/compileByName
compute.compileByName(ref: string | { [string]: any } | AssetRef)
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).
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, or a resolved asset handle.
compute.compileByName("@builtin::shaders.compute_double")
typed/builtin//modules/api/engine/compute/compute/copyBufferToTexture
compute.copyBufferToTexture(bufferName: string, textureKey: string, width: number, height: number, format: string?) -> 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.
Rows must be a multiple of 256 bytes (at rgba16f, any width from 32
up in powers of two).
Parameters
bufferNamestring— Source compute buffer.textureKeystring— Cache key to register the texture under.widthnumber— Texture width in texels.heightnumber— Texture height in texels.formatstring(optional) — Texel format:"rgba16f"(default),"rgba32f","rgba8".
Returns boolean — True when the copy was queued.
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
typed/builtin//modules/api/engine/compute/compute/createBuffer
compute.createBuffer(name: string, opts: { [string]: any }) -> boolean
Allocate a buffer under name, sized in bytes.
Parameters
namestring— The name a dispatch binds it by.opts{ [string]: any }—{ size, readback? }—sizein bytes.
Returns boolean — True once allocated.
typed/builtin//modules/api/engine/compute/compute/createSampler
compute.createSampler(name: string, opts: { [string]: any }?) -> boolean
Create a named GPU sampler. opts: filter/wrap settings.
Parameters
namestringopts{ [string]: any }(optional)
Returns boolean
typed/builtin//modules/api/engine/compute/compute/createStorageTexture2D
compute.createStorageTexture2D(name: string, opts: { [string]: any }) -> boolean
Create a 2D storage texture (compute-writable render target). opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
Returns boolean
typed/builtin//modules/api/engine/compute/compute/createTexture3D
compute.createTexture3D(name: string, opts: { [string]: any }) -> boolean
Create a 3D texture volume. opts: { width, height, depth, format?, storage? }.
Parameters
namestring— Unique volume name.opts{ [string]: any }— Dimensions + format (r8/r16f/r32f/rgba8/rgba16f/rgba32f).
Returns boolean — True on success (mutation queued).
typed/builtin//modules/api/engine/compute/compute/createTextureHistory
compute.createTextureHistory(name: string, opts: { [string]: any }) -> boolean
Create a temporal history buffer (ping-pong textures) for a target. opts: { width, height, format? }.
Parameters
namestringopts{ [string]: any }
Returns boolean
typed/builtin//modules/api/engine/compute/compute/destroyBuffer
compute.destroyBuffer(name: string) -> boolean
Release the buffer allocated under name.
Parameters
namestring— The name it was created under.
Returns boolean — True if a buffer under that name was released.
typed/builtin//modules/api/engine/compute/compute/destroySampler
compute.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.
Parameters
namestring— Sampler name.
Returns boolean — True when the release was queued.
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
typed/builtin//modules/api/engine/compute/compute/destroyShader
compute.destroyShader(name: string) -> boolean
Destroy a named compute shader pipeline.
Parameters
namestring— Shader name.
Returns boolean — True on success.
typed/builtin//modules/api/engine/compute/compute/destroyShaderEx
compute.destroyShaderEx(name: string) -> boolean
Destroy a shader registered via registerShaderEx.
Parameters
namestring
Returns boolean
typed/builtin//modules/api/engine/compute/compute/destroyStorageTexture2D
compute.destroyStorageTexture2D(name: string) -> boolean
Destroy a named 2D storage texture.
Parameters
namestring
Returns boolean
typed/builtin//modules/api/engine/compute/compute/destroyTexture
compute.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.
Parameters
textureKeystring— Cache key the texture was registered under.
Returns boolean — True when the release was queued.
compute.destroyTexture("lm_wall")
typed/builtin//modules/api/engine/compute/compute/destroyTexture3D
compute.destroyTexture3D(name: string) -> boolean
Destroy a named 3D volume and free its GPU memory.
Parameters
namestring
Returns boolean
typed/builtin//modules/api/engine/compute/compute/destroyTextureHistory
compute.destroyTextureHistory(name: string) -> boolean
Destroy a named texture-history buffer.
Parameters
namestring
Returns boolean
typed/builtin//modules/api/engine/compute/compute/diagnose
compute.diagnose(key: string) -> { [string]: any }
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.
Parameters
keystring— The resource key, verbatim.
Returns { [string]: any } — { key, exists, reason, resource?, current? }. resource is the row 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.
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
typed/builtin//modules/api/engine/compute/compute/dispatch
compute.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().
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOpts—{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
typed/builtin//modules/api/engine/compute/compute/dispatchEx
compute.dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }) -> boolean
Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed .computeShaders 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.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.opts{ [string]: any }—{ resources, workgroups }— each resource is{ kind, name }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
typed/builtin//modules/api/engine/compute/compute/dispatchOnVertices
compute.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 .computeShaders resolve to their stable guid
(collision-safe, lazily compiled on first dispatch); raw
registerShader names pass through.
Parameters
shaderNameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsDispatchOnVerticesOpts—{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count withmath.max(1, math.ceil(n / 64)).
Returns boolean — True once the dispatch is queued. What the engine then did with it is in compute.failing().
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
typed/builtin//modules/api/engine/compute/compute/failing
compute.failing() -> { { [string]: any } }
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.
Returns { { [string]: any } } — Array of { shader, target, dispatches, failures, ok, lastFrame, lastFailedFrame?, lastError? }.
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
typed/builtin//modules/api/engine/compute/compute/finishBvh
compute.finishBvh(id: number) -> (any, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh, stepped until"ready".
Returns (any, string?) — { nodes, tris, nodeCount, triCount }, or (nil, err) when the id names no build or the build still has work left.
local built = compute.finishBvh(id)
typed/builtin//modules/api/engine/compute/compute/getReadbackResult
compute.getReadbackResult(resultKey: string) -> { number }?
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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns { number }? — 1-indexed array of f32 values, or nil if not ready.
local floats = compute.getReadbackResult(key)
typed/builtin//modules/api/engine/compute/compute/getReadbackResultBytes
compute.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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns buffer? — The read-back's bytes, or nil if not ready.
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
typed/builtin//modules/api/engine/compute/compute/getReadbackResultU32
compute.getReadbackResultU32(resultKey: string) -> { number }?
Poll for a completed read-back interpreting bytes as u32. Returns array of integer values if ready, nil if pending.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns { number }? — Array of u32 values, or nil if not ready.
typed/builtin//modules/api/engine/compute/compute/isReadbackReady
compute.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.
Parameters
resultKeystring— Key returned byreadBuffer().
Returns boolean — True if the result is ready.
typed/builtin//modules/api/engine/compute/compute/observe
compute.observe() -> { [string]: any }
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.
Returns { [string]: any } — { published, generation, resources, totals }. Each row of 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.
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
typed/builtin//modules/api/engine/compute/compute/programState
compute.programState(ref: string | { [string]: any } | AssetRef) -> (string, string?)
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. Wait for "ready" before a dispatch whose
result is read back, rather than for a count of frames.
Parameters
refstring | { [string]: any } | AssetRef— A.computeShaderidentity/guid string, a resolved asset handle, or the name a raw registration chose.
Returns (string, string?) — "absent", "pending", "ready" or "failed", and the reason when "failed".
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
typed/builtin//modules/api/engine/compute/compute/readBuffer
compute.readBuffer(name: string) -> string
Start a GPU→CPU read of the buffer under name.
Parameters
namestring— The name it was created under.
Returns string — The result key to poll with getReadbackResult*. A read that could not start answers with the empty key, which every drain reports as unknown — the same shape a caller already handles.
typed/builtin//modules/api/engine/compute/compute/readTexture3D
compute.readTexture3D(name: string) -> string
Start a GPU→CPU readback of a named 3D volume. Returns a result key to poll.
Parameters
namestring
Returns string
typed/builtin//modules/api/engine/compute/compute/readbackState
compute.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), or "unknown" (never issued by
readBuffer(), or already drained — a result is delivered once).
Parameters
resultKeystring— Key returned byreadBuffer().
Returns string — "pending", "ready", or "unknown".
if compute.readbackState(key) == "ready" then ... end
typed/builtin//modules/api/engine/compute/compute/registerShader
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader name or asset handle.optsShaderOpts(optional) — Shader options.bindingscomes from the source's own@group(0) @binding(n)declarations when omitted; supplying a count that disagrees with them raises. EveryreadOnlyBindingsentry names one of those declared bindings, as a whole number from 0 tobindings - 1; an entry outside that run raises.
Returns boolean — True on success.
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
typed/builtin//modules/api/engine/compute/compute/registerShaderEx
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRefopts{ [string]: any }(optional)
Returns boolean
typed/builtin//modules/api/engine/compute/compute/resources
compute.resources(owner: any?) -> { any }
The resource rows on their own, optionally narrowed to what one shader owns.
Parameters
ownerany(optional) — A.computeShaderref, its guid, or its asset identity. Omit for 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.
Returns { any } — An array of rows in the shape compute.observe().resources carries. Empty when the owner holds nothing.
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
typed/builtin//modules/api/engine/compute/compute/setParam
compute.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.
Parameters
nameOrHandlestring | { [string]: any } | AssetRef— Shader identity (the.computeShaderasset name), or the handleasset.load/asset.resolvereturns — the same formsdispatchtakes.propstring— Parameter name as declared inbindings.yaml.valuenumber— New scalar value (numbers only).
Returns boolean — True on success.
compute.setParam("my_sim", "scale", 4.0)
typed/builtin//modules/api/engine/compute/compute/stepBvh
compute.stepBvh(id: number, budgetMs: number?) -> (string?, string?)
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.
Parameters
idnumber— Build id fromcompute.beginBvh.budgetMsnumber(optional) — Wall time this slice may spend, in milliseconds (default 4).
Returns (string?, string?) — "pending" or "ready", or (nil, err) when the id names no build.
while compute.stepBvh(id, 4) == "pending" do task.wait() end
typed/builtin//modules/api/engine/compute/compute/textureFormatBytes
compute.textureFormatBytes(format: string) -> number
Bytes-per-voxel for a texture format string (rgba16f, r8, ...).
Parameters
formatstring
Returns number
typed/builtin//modules/api/engine/compute/compute/writeBuffer
compute.writeBuffer(name: string, values: { number } | buffer | string, offset: number?) -> boolean
Write words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The floats to write, or abuffer/ binary string already holding them.offsetnumber(optional) — 32-bit word offset to write at.
Returns boolean — True on success.
typed/builtin//modules/api/engine/compute/compute/writeBufferBytes
compute.writeBufferBytes(name: string, bytes: buffer | string, offsetBytes: number?) -> boolean
Write packed bytes into the buffer under name.
Parameters
namestring— The name it was created under.bytesbuffer | string— The payload.offsetBytesnumber(optional) — Byte offset to write at.
Returns boolean — True on success.
typed/builtin//modules/api/engine/compute/compute/writeBufferU32
compute.writeBufferU32(name: string, values: { number } | buffer | string, offsetBytes: number?) -> boolean
Write 32-bit words into the buffer under name.
Parameters
namestring— The name it was created under.values{ number } | buffer | string— The words to write.offsetBytesnumber(optional) — Byte offset to write at.
Returns boolean — True on success.
typed/builtin//modules/api/engine/compute/compute/writeFloatsTexture3D
compute.writeFloatsTexture3D(name: string, floats: { number }, formatOrOpts: (string | { [string]: any })?) -> boolean
Upload float values into a named 3D volume, packed via the given format (default rgba16f).
Parameters
namestringfloats{ number }formatOrOpts(string | { [string]: any })(optional)
Returns boolean
typed/builtin//modules/api/engine/compute/compute/writeTexture3D
compute.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.
Parameters
namestring— Volume name.databuffer | string | { number }— Voxel bytes as abuffer, a binary string, or an array of bytes.
Returns boolean — True on success (mutation queued).