---
title: "compute"
description: "The compute namespace — the engine's Luau API reference for compute."
section: "API Reference"
slug: "api-compute"
canonical: "https://origozero.ai/docs/api-compute"
updated: "2026-09-05T23:13:46.311905337+00:00"
tags: ["api", "reference"]
---

# compute

The `compute` namespace — 176 functions.

## compute/compile {#compute-compile}

```lua
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.

```lua
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
```

## compute/createSampler {#compute-createsampler}

```lua
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}

```lua
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}

```lua
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}

```lua
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}

```lua
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.

```lua
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
```

## compute/destroyShaderEx {#compute-destroyshaderex}

```lua
compute.destroyShaderEx(name)
```

Destroy a registered volume shader.

**Parameters**

- `name` `string`

**Returns** `boolean`

## compute/destroyStorageTexture2D {#compute-destroystoragetexture2d}

```lua
compute.destroyStorageTexture2D(name)
```

Destroy a named volume render target.

**Parameters**

- `name` `string`

**Returns** `boolean`

## compute/destroyTexture3D {#compute-destroytexture3d}

```lua
compute.destroyTexture3D(name)
```

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

**Parameters**

- `name` `string`

**Returns** `boolean`

## compute/destroyTextureHistory {#compute-destroytexturehistory}

```lua
compute.destroyTextureHistory(name)
```

Destroy a named texture-history pair.

**Parameters**

- `name` `string`

**Returns** `boolean`

## compute/dispatchEx {#compute-dispatchex}

```lua
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}

```lua
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}

```lua
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}

```lua
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.

```lua
compute.setParam("my_sim", "scale", 4.0)
```

## compute/textureFormatBytes {#compute-textureformatbytes}

```lua
compute.textureFormatBytes(format)
```

Bytes per voxel for a format name. Returns 0 for unknown formats.

**Parameters**

- `format` `string`

**Returns** `number`

## compute/writeFloatsTexture3D {#compute-writefloatstexture3d}

```lua
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}

```lua
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 {#globals-compute-absentreasons}

```lua
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.

```lua
for _, r in ipairs(compute.absentReasons()) do print(r) end
```

## globals/compute/beginBvh {#globals-compute-beginbvh}

```lua
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.

```lua
local id = compute.beginBvh(gather.instances)
```

## globals/compute/buildBvh {#globals-compute-buildbvh}

```lua
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.

```lua
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
```

## globals/compute/buildBvhSliced {#globals-compute-buildbvhsliced}

```lua
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)`.

```lua
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
```

## globals/compute/bvhBuilds {#globals-compute-bvhbuilds}

```lua
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.

```lua
print(#compute.bvhBuilds(), "hierarchies in flight")
```

## globals/compute/cancelBvh {#globals-compute-cancelbvh}

```lua
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.

```lua
compute.cancelBvh(id)
```

## globals/compute/compile {#globals-compute-compile}

```lua
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.

```lua
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
```

## globals/compute/compileByName {#globals-compute-compilebyname}

```lua
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.

```lua
compute.compileByName("@builtin::shaders.compute_double")
```

## globals/compute/copyBufferToTexture {#globals-compute-copybuffertotexture}

```lua
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.

```lua
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
```

## globals/compute/createBuffer {#globals-compute-createbuffer}

```lua
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 {#globals-compute-createsampler}

```lua
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 {#globals-compute-createstoragetexture2d}

```lua
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 {#globals-compute-createtexture3d}

```lua
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 {#globals-compute-createtexturehistory}

```lua
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 {#globals-compute-destroybuffer}

```lua
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 {#globals-compute-destroysampler}

```lua
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.

```lua
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
```

## globals/compute/destroyShader {#globals-compute-destroyshader}

```lua
compute.destroyShader(name: string) -> boolean
```

Destroy a named compute shader pipeline.

**Parameters**

- `name` `string` — Shader name.

**Returns** `boolean` — True on success.

## globals/compute/destroyShaderEx {#globals-compute-destroyshaderex}

```lua
compute.destroyShaderEx(name: string) -> boolean
```

Destroy a shader registered via `registerShaderEx`.

**Parameters**

- `name` `string`

**Returns** `boolean`

## globals/compute/destroyStorageTexture2D {#globals-compute-destroystoragetexture2d}

```lua
compute.destroyStorageTexture2D(name: string) -> boolean
```

Destroy a named 2D storage texture.

**Parameters**

- `name` `string`

**Returns** `boolean`

## globals/compute/destroyTexture {#globals-compute-destroytexture}

```lua
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.

```lua
compute.destroyTexture("lm_wall")
```

## globals/compute/destroyTexture3D {#globals-compute-destroytexture3d}

```lua
compute.destroyTexture3D(name: string) -> boolean
```

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

**Parameters**

- `name` `string`

**Returns** `boolean`

## globals/compute/destroyTextureHistory {#globals-compute-destroytexturehistory}

```lua
compute.destroyTextureHistory(name: string) -> boolean
```

Destroy a named texture-history buffer.

**Parameters**

- `name` `string`

**Returns** `boolean`

## globals/compute/diagnose {#globals-compute-diagnose}

```lua
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.

```lua
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
```

## globals/compute/dispatch {#globals-compute-dispatch}

```lua
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()`.

```lua
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
```

## globals/compute/dispatchEx {#globals-compute-dispatchex}

```lua
compute.dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }) -> boolean
```

Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed `.computeShader`s resolve to their stable guid (collision-safe,
lazily compiled on first dispatch); raw `registerShaderEx` names pass through.
`resources` covers the bindings the shader DECLARES. A `params:` block's
uniform is engine-owned — the compile creates and packs it, `setParam`
writes it, and the dispatch binds it — so it takes no entry here.

**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 {#globals-compute-dispatchonvertices}

```lua
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 `.computeShader`s 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()`.

```lua
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
```

## globals/compute/failing {#globals-compute-failing}

```lua
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? }`.

```lua
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
```

## globals/compute/finishBvh {#globals-compute-finishbvh}

```lua
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.

```lua
local built = compute.finishBvh(id)
```

## globals/compute/getReadbackResult {#globals-compute-getreadbackresult}

```lua
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.

```lua
local floats = compute.getReadbackResult(key)
```

## globals/compute/getReadbackResultBytes {#globals-compute-getreadbackresultbytes}

```lua
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.

```lua
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
```

## globals/compute/getReadbackResultU32 {#globals-compute-getreadbackresultu32}

```lua
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 {#globals-compute-isreadbackready}

```lua
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 {#globals-compute-observe}

```lua
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.

```lua
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 {#globals-compute-program-compile}

```lua
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 `#include`s, 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 {#globals-compute-program-destroy}

```lua
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 {#globals-compute-program-dispatch}

```lua
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 {#globals-compute-program-dispatchex}

```lua
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 {#globals-compute-program-dispatchonvertices}

```lua
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 {#globals-compute-program-setparam}

```lua
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 {#globals-compute-program-status}

```lua
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 {#globals-compute-programstate}

```lua
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"`.

```lua
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
```

## globals/compute/readBuffer {#globals-compute-readbuffer}

```lua
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 {#globals-compute-readtexture3d}

```lua
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 {#globals-compute-readbackstate}

```lua
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"`.

```lua
if compute.readbackState(key) == "ready" then ... end
```

## globals/compute/registerShader {#globals-compute-registershader}

```lua
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.

```lua
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
```

## globals/compute/registerShaderEx {#globals-compute-registershaderex}

```lua
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 {#globals-compute-resources}

```lua
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.

```lua
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
```

## globals/compute/setParam {#globals-compute-setparam}

```lua
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.

```lua
compute.setParam("my_sim", "scale", 4.0)
```

## globals/compute/stepBvh {#globals-compute-stepbvh}

```lua
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.

```lua
while compute.stepBvh(id, 4) == "pending" do task.wait() end
```

## globals/compute/textureFormatBytes {#globals-compute-textureformatbytes}

```lua
compute.textureFormatBytes(format: string) -> number
```

Bytes-per-voxel for a texture format string (`rgba16f`, `r8`, ...).

**Parameters**

- `format` `string`

**Returns** `number`

## globals/compute/writeBuffer {#globals-compute-writebuffer}

```lua
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 {#globals-compute-writebufferbytes}

```lua
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 {#globals-compute-writebufferu32}

```lua
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 {#globals-compute-writefloatstexture3d}

```lua
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 {#globals-compute-writetexture3d}

```lua
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 {#modules-compute-readme}

```lua
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 {#modules-compute-absentreasons}

```lua
absentReasons(): { string }
```

Every reason `compute.diagnose` reports, sorted. `resident` is the one
that means the resource is there.

```lua
for _, r in ipairs(compute.absentReasons()) do print(r) end
```

## modules/compute/beginBvh {#modules-compute-beginbvh}

```lua
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.

```lua
local id = compute.beginBvh(gather.instances)
```

## modules/compute/buildBvh {#modules-compute-buildbvh}

```lua
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.

```lua
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
```

## modules/compute/buildBvhSliced {#modules-compute-buildbvhsliced}

```lua
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).

```lua
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
```

## modules/compute/bvhBuilds {#modules-compute-bvhbuilds}

```lua
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.

```lua
print(#compute.bvhBuilds(), "hierarchies in flight")
```

## modules/compute/cancelBvh {#modules-compute-cancelbvh}

```lua
cancelBvh(id: number): boolean
```

Drop a build along with the triangles it has gathered.

**Parameters**

- `id` `number` — Build id from `compute.beginBvh`.

```lua
compute.cancelBvh(id)
```

## modules/compute/compile {#modules-compute-compile}

```lua
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? }`.

```lua
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
```

## modules/compute/compileByName {#modules-compute-compilebyname}

```lua
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.

```lua
compute.compileByName("@builtin::shaders.compute_double")
```

## modules/compute/copyBufferToTexture {#modules-compute-copybuffertotexture}

```lua
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"`.

```lua
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
```

## modules/compute/createBuffer {#modules-compute-createbuffer}

```lua
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 {#modules-compute-createsampler}

```lua
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 {#modules-compute-createstoragetexture2d}

```lua
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 {#modules-compute-createtexture3d}

```lua
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 {#modules-compute-createtexturehistory}

```lua
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 {#modules-compute-destroybuffer}

```lua
destroyBuffer(name: string): boolean
```

Release the buffer allocated under `name`.

**Parameters**

- `name` `string` — The name it was created under.

## modules/compute/destroySampler {#modules-compute-destroysampler}

```lua
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.

```lua
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
```

## modules/compute/destroyShader {#modules-compute-destroyshader}

```lua
destroyShader(name: string): boolean
```

Destroy a named compute shader pipeline.

**Parameters**

- `name` `string` — Shader name.

## modules/compute/destroyShaderEx {#modules-compute-destroyshaderex}

```lua
destroyShaderEx(name: string): boolean
```

Destroy a shader registered via `registerShaderEx`.

**Parameters**

- `name` `string`

## modules/compute/destroyStorageTexture2D {#modules-compute-destroystoragetexture2d}

```lua
destroyStorageTexture2D(name: string): boolean
```

Destroy a named 2D storage texture.

**Parameters**

- `name` `string`

## modules/compute/destroyTexture {#modules-compute-destroytexture}

```lua
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.

```lua
compute.destroyTexture("lm_wall")
```

## modules/compute/destroyTexture3D {#modules-compute-destroytexture3d}

```lua
destroyTexture3D(name: string): boolean
```

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

**Parameters**

- `name` `string`

## modules/compute/destroyTextureHistory {#modules-compute-destroytexturehistory}

```lua
destroyTextureHistory(name: string): boolean
```

Destroy a named texture-history buffer.

**Parameters**

- `name` `string`

## modules/compute/diagnose {#modules-compute-diagnose}

```lua
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.

```lua
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
```

## modules/compute/dispatch {#modules-compute-dispatch}

```lua
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))`.

```lua
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
```

## modules/compute/dispatchEx {#modules-compute-dispatchex}

```lua
dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }): boolean
```

Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed `.computeShader`s resolve to their stable guid (collision-safe,
lazily compiled on first dispatch); raw `registerShaderEx` names pass through.
`resources` covers the bindings the shader DECLARES. A `params:` block's
uniform is engine-owned — the compile creates and packs it, `setParam`
writes it, and the dispatch binds it — so it takes no entry here.

**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 {#modules-compute-dispatchonvertices}

```lua
dispatchOnVertices(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: DispatchOnVerticesOpts): boolean
```

Dispatch a compute shader with a model's vertex buffer bound
at binding 0 (read_write). Use to mutate vertex positions
directly. Asset-backed `.computeShader`s resolve to their stable guid
(collision-safe, lazily compiled on first dispatch); raw
`registerShader` names pass through.

**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))`.

```lua
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
```

## modules/compute/failing {#modules-compute-failing}

```lua
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.

```lua
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
```

## modules/compute/finishBvh {#modules-compute-finishbvh}

```lua
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"`.

```lua
local built = compute.finishBvh(id)
```

## modules/compute/getReadbackResult {#modules-compute-getreadbackresult}

```lua
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()`.

```lua
local floats = compute.getReadbackResult(key)
```

## modules/compute/getReadbackResultBytes {#modules-compute-getreadbackresultbytes}

```lua
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()`.

```lua
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
```

## modules/compute/getReadbackResultU32 {#modules-compute-getreadbackresultu32}

```lua
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 {#modules-compute-isreadbackready}

```lua
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 {#modules-compute-observe}

```lua
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.

```lua
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 {#compile}

```lua
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 `#include`s, 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 {#destroy}

```lua
program.destroy(key: string): boolean
```

Release the program registered under `key`.

**Parameters**

- `key` `string` — The program's key.

## modules/compute/program.dispatch {#dispatch}

```lua
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 {#dispatchex}

```lua
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 {#dispatchonvertices}

```lua
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 {#setparam}

```lua
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 {#status}

```lua
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 {#modules-compute-programstate}

```lua
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.

```lua
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
```

## modules/compute/readBuffer {#modules-compute-readbuffer}

```lua
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 {#modules-compute-readtexture3d}

```lua
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 {#modules-compute-readbackstate}

```lua
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()`.

```lua
if compute.readbackState(key) == "ready" then ... end
```

## modules/compute/registerShader {#modules-compute-registershader}

```lua
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.

```lua
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
```

## modules/compute/registerShaderEx {#modules-compute-registershaderex}

```lua
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 {#modules-compute-resources}

```lua
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.

```lua
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
```

## modules/compute/setParam {#modules-compute-setparam}

```lua
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).

```lua
compute.setParam("my_sim", "scale", 4.0)
```

## modules/compute/stepBvh {#modules-compute-stepbvh}

```lua
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).

```lua
while compute.stepBvh(id, 4) == "pending" do task.wait() end
```

## modules/compute/textureFormatBytes {#modules-compute-textureformatbytes}

```lua
textureFormatBytes(format: string): number
```

Bytes-per-voxel for a texture format string (`rgba16f`, `r8`, ...).

**Parameters**

- `format` `string`

## modules/compute/writeBuffer {#modules-compute-writebuffer}

```lua
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 {#modules-compute-writebufferbytes}

```lua
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 {#modules-compute-writebufferu32}

```lua
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 {#modules-compute-writefloatstexture3d}

```lua
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 {#modules-compute-writetexture3d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-absentreasons}

```lua
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.

```lua
for _, r in ipairs(compute.absentReasons()) do print(r) end
```

## typed/builtin//modules/api/engine/compute/compute/beginBvh {#typed-builtin-modules-api-engine-compute-compute-beginbvh}

```lua
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.

```lua
local id = compute.beginBvh(gather.instances)
```

## typed/builtin//modules/api/engine/compute/compute/buildBvh {#typed-builtin-modules-api-engine-compute-compute-buildbvh}

```lua
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.

```lua
local bvh = compute.buildBvh(instances)
shader:dispatch({ buffers = { bvh.nodes, bvh.tris }, workgroups = { 64, 1, 1 } })
```

## typed/builtin//modules/api/engine/compute/compute/buildBvhSliced {#typed-builtin-modules-api-engine-compute-compute-buildbvhsliced}

```lua
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)`.

```lua
local built, err = compute.buildBvhSliced(gather.instances, { budgetMs = 4 })
```

## typed/builtin//modules/api/engine/compute/compute/bvhBuilds {#typed-builtin-modules-api-engine-compute-compute-bvhbuilds}

```lua
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.

```lua
print(#compute.bvhBuilds(), "hierarchies in flight")
```

## typed/builtin//modules/api/engine/compute/compute/cancelBvh {#typed-builtin-modules-api-engine-compute-compute-cancelbvh}

```lua
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.

```lua
compute.cancelBvh(id)
```

## typed/builtin//modules/api/engine/compute/compute/compile {#typed-builtin-modules-api-engine-compute-compute-compile}

```lua
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.

```lua
compute.compile("my_sim", { source = WGSL, bindings = { { name = "data", kind = "buffer", access = "read_write", element = "f32" } } })
```

## typed/builtin//modules/api/engine/compute/compute/compileByName {#typed-builtin-modules-api-engine-compute-compute-compilebyname}

```lua
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.

```lua
compute.compileByName("@builtin::shaders.compute_double")
```

## typed/builtin//modules/api/engine/compute/compute/copyBufferToTexture {#typed-builtin-modules-api-engine-compute-compute-copybuffertotexture}

```lua
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.

```lua
compute.copyBufferToTexture("gi_resolved", "lm_wall", 128, 128)
```

## typed/builtin//modules/api/engine/compute/compute/createBuffer {#typed-builtin-modules-api-engine-compute-compute-createbuffer}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-createsampler}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-createstoragetexture2d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-createtexture3d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-createtexturehistory}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-destroybuffer}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-destroysampler}

```lua
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.

```lua
compute.createSampler("soft", { filter = true }); compute.destroySampler("soft")
```

## typed/builtin//modules/api/engine/compute/compute/destroyShader {#typed-builtin-modules-api-engine-compute-compute-destroyshader}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-destroyshaderex}

```lua
compute.destroyShaderEx(name: string) -> boolean
```

Destroy a shader registered via `registerShaderEx`.

**Parameters**

- `name` `string`

**Returns** `boolean`

## typed/builtin//modules/api/engine/compute/compute/destroyStorageTexture2D {#typed-builtin-modules-api-engine-compute-compute-destroystoragetexture2d}

```lua
compute.destroyStorageTexture2D(name: string) -> boolean
```

Destroy a named 2D storage texture.

**Parameters**

- `name` `string`

**Returns** `boolean`

## typed/builtin//modules/api/engine/compute/compute/destroyTexture {#typed-builtin-modules-api-engine-compute-compute-destroytexture}

```lua
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.

```lua
compute.destroyTexture("lm_wall")
```

## typed/builtin//modules/api/engine/compute/compute/destroyTexture3D {#typed-builtin-modules-api-engine-compute-compute-destroytexture3d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-destroytexturehistory}

```lua
compute.destroyTextureHistory(name: string) -> boolean
```

Destroy a named texture-history buffer.

**Parameters**

- `name` `string`

**Returns** `boolean`

## typed/builtin//modules/api/engine/compute/compute/diagnose {#typed-builtin-modules-api-engine-compute-compute-diagnose}

```lua
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.

```lua
local d = compute.diagnose(key)
if not d.exists then print(d.reason, d.current) end
```

## typed/builtin//modules/api/engine/compute/compute/dispatch {#typed-builtin-modules-api-engine-compute-compute-dispatch}

```lua
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()`.

```lua
compute.dispatch("blur", { buffers = { "src", "dst" }, workgroups = { 8, 8 } })
```

## typed/builtin//modules/api/engine/compute/compute/dispatchEx {#typed-builtin-modules-api-engine-compute-compute-dispatchex}

```lua
compute.dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }) -> boolean
```

Dispatch a compute shader with extended texture/storage/sampler bindings.
Asset-backed `.computeShader`s resolve to their stable guid (collision-safe,
lazily compiled on first dispatch); raw `registerShaderEx` names pass through.
`resources` covers the bindings the shader DECLARES. A `params:` block's
uniform is engine-owned — the compile creates and packs it, `setParam`
writes it, and the dispatch binds it — so it takes no entry here.

**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 {#typed-builtin-modules-api-engine-compute-compute-dispatchonvertices}

```lua
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 `.computeShader`s 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()`.

```lua
compute.dispatchOnVertices("flatten", { model = "@user/mesh", workgroups = { 64 } })
```

## typed/builtin//modules/api/engine/compute/compute/failing {#typed-builtin-modules-api-engine-compute-compute-failing}

```lua
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? }`.

```lua
for _, d in ipairs(compute.failing()) do print(d.shader, d.lastError) end
```

## typed/builtin//modules/api/engine/compute/compute/finishBvh {#typed-builtin-modules-api-engine-compute-compute-finishbvh}

```lua
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.

```lua
local built = compute.finishBvh(id)
```

## typed/builtin//modules/api/engine/compute/compute/getReadbackResult {#typed-builtin-modules-api-engine-compute-compute-getreadbackresult}

```lua
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.

```lua
local floats = compute.getReadbackResult(key)
```

## typed/builtin//modules/api/engine/compute/compute/getReadbackResultBytes {#typed-builtin-modules-api-engine-compute-compute-getreadbackresultbytes}

```lua
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.

```lua
local bytes = compute.getReadbackResultBytes(key)
local firstTexel = buffer.readf32(bytes, 0)
```

## typed/builtin//modules/api/engine/compute/compute/getReadbackResultU32 {#typed-builtin-modules-api-engine-compute-compute-getreadbackresultu32}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-isreadbackready}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-observe}

```lua
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.

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-programstate}

```lua
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"`.

```lua
if compute.programState(carve) == "ready" then carve:dispatch(opts) end
```

## typed/builtin//modules/api/engine/compute/compute/readBuffer {#typed-builtin-modules-api-engine-compute-compute-readbuffer}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-readtexture3d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-readbackstate}

```lua
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"`.

```lua
if compute.readbackState(key) == "ready" then ... end
```

## typed/builtin//modules/api/engine/compute/compute/registerShader {#typed-builtin-modules-api-engine-compute-compute-registershader}

```lua
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.

```lua
compute.registerShader("blur", { source = WGSL, entryPoint = "main" })
```

## typed/builtin//modules/api/engine/compute/compute/registerShaderEx {#typed-builtin-modules-api-engine-compute-compute-registershaderex}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-resources}

```lua
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.

```lua
for _, r in ipairs(compute.resources(shaderRef)) do print(r.key, r.bytes) end
```

## typed/builtin//modules/api/engine/compute/compute/setParam {#typed-builtin-modules-api-engine-compute-compute-setparam}

```lua
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.

```lua
compute.setParam("my_sim", "scale", 4.0)
```

## typed/builtin//modules/api/engine/compute/compute/stepBvh {#typed-builtin-modules-api-engine-compute-compute-stepbvh}

```lua
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.

```lua
while compute.stepBvh(id, 4) == "pending" do task.wait() end
```

## typed/builtin//modules/api/engine/compute/compute/textureFormatBytes {#typed-builtin-modules-api-engine-compute-compute-textureformatbytes}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-writebuffer}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-writebufferbytes}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-writebufferu32}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-writefloatstexture3d}

```lua
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 {#typed-builtin-modules-api-engine-compute-compute-writetexture3d}

```lua
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).
