Log inGet started

compute

Updated 5 September 2026

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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity to register under (string or asset handle).
  • opts { [string]: any } (optional){ source, entryPoint?, bindings, params? }bindings is 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

  • name string
  • opts { [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

  • name string
  • opts { [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

  • name string — 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

  • name string
  • opts { [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

  • name string — 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

  • name string

Returns boolean

compute/destroyStorageTexture2D

compute.destroyStorageTexture2D(name)

Destroy a named volume render target.

Parameters

  • name string

Returns boolean

compute/destroyTexture3D

compute.destroyTexture3D(name)

Destroy a named 3D volume and free its GPU memory.

Parameters

  • name string

Returns boolean

compute/destroyTextureHistory

compute.destroyTextureHistory(name)

Destroy a named texture-history pair.

Parameters

  • name string

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

  • shaderNameOrHandle string | { [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 with math.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

  • name string

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

  • nameOrHandle string | { [string]: any } | AssetRef
  • opts { [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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity (the .computeShader asset name), or the handle asset.load / asset.resolve returns — the same forms dispatch takes.
  • prop string — Parameter name as declared in bindings.yaml.
  • value number — 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

  • format string

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

  • name string
  • floats { 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

  • name string — Volume name.
  • data buffer | string | { number } — Voxel bytes as a buffer, 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

  • id number — Build id from compute.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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity to register under (string or asset handle).
  • opts { [string]: any } (optional){ source, entryPoint?, bindings, params? }bindings is 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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • bufferName string — Source compute buffer.
  • textureKey string — Cache key to register the texture under.
  • width number — Texture width in texels.
  • height number — Texture height in texels.
  • format string (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

  • name string — The name a dispatch binds it by.
  • opts { [string]: any }{ size, readback? }size in 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

  • name string
  • opts { [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

  • name string
  • opts { [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

  • name string — 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

  • name string
  • opts { [string]: any }

Returns boolean

globals/compute/destroyBuffer

compute.destroyBuffer(name: string) -> boolean

Release the buffer allocated under name.

Parameters

  • name string — 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

  • name string — 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

  • name string — Shader name.

Returns boolean — True on success.

globals/compute/destroyShaderEx

compute.destroyShaderEx(name: string) -> boolean

Destroy a shader registered via registerShaderEx.

Parameters

  • name string

Returns boolean

globals/compute/destroyStorageTexture2D

compute.destroyStorageTexture2D(name: string) -> boolean

Destroy a named 2D storage texture.

Parameters

  • name string

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

  • textureKey string — 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

  • name string

Returns boolean

globals/compute/destroyTextureHistory

compute.destroyTextureHistory(name: string) -> boolean

Destroy a named texture-history buffer.

Parameters

  • name string

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

  • key string — 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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOpts{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).

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

  • shaderNameOrHandle string | { [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 with math.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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOnVerticesOpts{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).

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

  • id number — Build id from compute.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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • key string — 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

  • key string — 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

  • key string — 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 with math.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

  • key string — 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 with math.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

  • key string — 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 with math.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

  • key string — The program's key.
  • prop string — Parameter name as declared.
  • value number — 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

  • key string — 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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • name string — 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

  • name string

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

  • resultKey string — Key returned by readBuffer().

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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts ShaderOpts (optional) — Shader options. bindings comes from the source's own @group(0) @binding(n) declarations when omitted; supplying a count that disagrees with them raises. Every readOnlyBindings entry names one of those declared bindings, as a whole number from 0 to bindings - 1; an entry outside that run raises.

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

  • nameOrHandle string | { [string]: any } | AssetRef
  • opts { [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

  • owner any (optional) — A .computeShader ref, 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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity (the .computeShader asset name), or the handle asset.load / asset.resolve returns — the same forms dispatch takes.
  • prop string — Parameter name as declared in bindings.yaml.
  • value number — 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

  • id number — Build id from compute.beginBvh.
  • budgetMs number (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

  • format string

Returns number

globals/compute/writeBuffer

compute.writeBuffer(name: string, values: { number } | buffer | string, offset: number?) -> boolean

Write words into the buffer under name.

Parameters

  • name string — The name it was created under.
  • values { number } | buffer | string — The floats to write, or a buffer / binary string already holding them.
  • offset number (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

  • name string — The name it was created under.
  • bytes buffer | string — The payload.
  • offsetBytes number (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

  • name string — The name it was created under.
  • values { number } | buffer | string — The words to write.
  • offsetBytes number (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

  • name string
  • floats { 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

  • name string — Volume name.
  • data buffer | string | { number } — Voxel bytes as a buffer, 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

  • id number — Build id from compute.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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity to register under (string or asset handle).
  • opts { [string]: any }? (optional){ source, entryPoint?, bindings, params? }bindings is 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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • bufferName string — Source compute buffer.
  • textureKey string — Cache key to register the texture under.
  • width number — Texture width in texels.
  • height number — Texture height in texels.
  • format string? (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

  • name string — The name a dispatch binds it by.
  • opts { [string]: any }{ size, readback? }size in bytes.

modules/compute/createSampler

createSampler(name: string, opts: { [string]: any }?): boolean

Create a named GPU sampler. opts: filter/wrap settings.

Parameters

  • name string
  • opts { [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

  • name string
  • opts { [string]: any }

modules/compute/createTexture3D

createTexture3D(name: string, opts: { [string]: any }): boolean

Create a 3D texture volume. opts: { width, height, depth, format?, storage? }.

Parameters

  • name string — 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

  • name string
  • opts { [string]: any }

modules/compute/destroyBuffer

destroyBuffer(name: string): boolean

Release the buffer allocated under name.

Parameters

  • name string — 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

  • name string — Sampler name.
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")

modules/compute/destroyShader

destroyShader(name: string): boolean

Destroy a named compute shader pipeline.

Parameters

  • name string — Shader name.

modules/compute/destroyShaderEx

destroyShaderEx(name: string): boolean

Destroy a shader registered via registerShaderEx.

Parameters

  • name string

modules/compute/destroyStorageTexture2D

destroyStorageTexture2D(name: string): boolean

Destroy a named 2D storage texture.

Parameters

  • name string

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

  • textureKey string — 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

  • name string

modules/compute/destroyTextureHistory

destroyTextureHistory(name: string): boolean

Destroy a named texture-history buffer.

Parameters

  • name string

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

  • key string — 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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOpts{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).
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

  • shaderNameOrHandle string | { [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 with math.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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOnVerticesOpts{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).
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

  • id number — Build id from compute.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

  • resultKey string — Key returned by readBuffer().
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

  • resultKey string — Key returned by readBuffer().
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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • key string — 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

  • key string — 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

  • key string — 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 with math.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

  • key string — 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 with math.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

  • key string — 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 with math.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

  • key string — The program's key.
  • prop string — Parameter name as declared.
  • value number — 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

  • key string — 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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • name string — 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

  • name string

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

  • resultKey string — Key returned by readBuffer().
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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts ShaderOpts? (optional) — Shader options. bindings comes from the source's own @group(0) @binding(n) declarations when omitted; supplying a count that disagrees with them raises. Every readOnlyBindings entry names one of those declared bindings, as a whole number from 0 to bindings - 1; an entry outside that run raises.
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

  • nameOrHandle string | { [string]: any } | AssetRef
  • opts { [string]: any }? (optional)

modules/compute/resources

resources(owner: any?): { any }

The resource rows on their own, optionally narrowed to what one shader owns.

Parameters

  • owner any? (optional) — A .computeShader ref, 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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity (the .computeShader asset name), or the handle asset.load / asset.resolve returns — the same forms dispatch takes.
  • prop string — Parameter name as declared in bindings.yaml.
  • value number — 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

  • id number — Build id from compute.beginBvh.
  • budgetMs number? (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

  • format string

modules/compute/writeBuffer

writeBuffer(name: string, values: { number } | buffer | string, offset: number?): boolean

Write words into the buffer under name.

Parameters

  • name string — The name it was created under.
  • values { number } | buffer | string — The floats to write, or a buffer / binary string already holding them.
  • offset number? (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

  • name string — The name it was created under.
  • bytes buffer | string — The payload.
  • offsetBytes number? (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

  • name string — The name it was created under.
  • values { number } | buffer | string — The words to write.
  • offsetBytes number? (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

  • name string
  • floats { 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

  • name string — Volume name.
  • data buffer | string | { number } — Voxel bytes as a buffer, 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

  • id number — Build id from compute.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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity to register under (string or asset handle).
  • opts { [string]: any } (optional){ source, entryPoint?, bindings, params? }bindings is 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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • bufferName string — Source compute buffer.
  • textureKey string — Cache key to register the texture under.
  • width number — Texture width in texels.
  • height number — Texture height in texels.
  • format string (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

  • name string — The name a dispatch binds it by.
  • opts { [string]: any }{ size, readback? }size in 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

  • name string
  • opts { [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

  • name string
  • opts { [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

  • name string — 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

  • name string
  • opts { [string]: any }

Returns boolean

typed/builtin//modules/api/engine/compute/compute/destroyBuffer

compute.destroyBuffer(name: string) -> boolean

Release the buffer allocated under name.

Parameters

  • name string — 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

  • name string — 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

  • name string — 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

  • name string

Returns boolean

typed/builtin//modules/api/engine/compute/compute/destroyStorageTexture2D

compute.destroyStorageTexture2D(name: string) -> boolean

Destroy a named 2D storage texture.

Parameters

  • name string

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

  • textureKey string — 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

  • name string

Returns boolean

typed/builtin//modules/api/engine/compute/compute/destroyTextureHistory

compute.destroyTextureHistory(name: string) -> boolean

Destroy a named texture-history buffer.

Parameters

  • name string

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

  • key string — 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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOpts{ buffers, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).

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

  • shaderNameOrHandle string | { [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 with math.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

  • shaderNameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts DispatchOnVerticesOpts{ model, buffers?, workgroups }. A zero in any workgroup dimension is refused and recorded as a dispatch failure — clamp a computed count with math.max(1, math.ceil(n / 64)).

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

  • id number — Build id from compute.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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • resultKey string — Key returned by readBuffer().

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

  • ref string | { [string]: any } | AssetRef — A .computeShader identity/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

  • name string — 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

  • name string

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

  • resultKey string — Key returned by readBuffer().

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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader name or asset handle.
  • opts ShaderOpts (optional) — Shader options. bindings comes from the source's own @group(0) @binding(n) declarations when omitted; supplying a count that disagrees with them raises. Every readOnlyBindings entry names one of those declared bindings, as a whole number from 0 to bindings - 1; an entry outside that run raises.

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

  • nameOrHandle string | { [string]: any } | AssetRef
  • opts { [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

  • owner any (optional) — A .computeShader ref, 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

  • nameOrHandle string | { [string]: any } | AssetRef — Shader identity (the .computeShader asset name), or the handle asset.load / asset.resolve returns — the same forms dispatch takes.
  • prop string — Parameter name as declared in bindings.yaml.
  • value number — 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

  • id number — Build id from compute.beginBvh.
  • budgetMs number (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

  • format string

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

  • name string — The name it was created under.
  • values { number } | buffer | string — The floats to write, or a buffer / binary string already holding them.
  • offset number (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

  • name string — The name it was created under.
  • bytes buffer | string — The payload.
  • offsetBytes number (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

  • name string — The name it was created under.
  • values { number } | buffer | string — The words to write.
  • offsetBytes number (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

  • name string
  • floats { 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

  • name string — Volume name.
  • data buffer | string | { number } — Voxel bytes as a buffer, a binary string, or an array of bytes.

Returns boolean — True on success (mutation queued).

  • api
  • reference