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

# substrate

The `substrate` namespace — 21 functions.

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

```lua
substrate.createBuffer(opts: BufferOpts) -> TypedBuffer?
```

Allocate a typed buffer and return its handle.

A `"gpu"` buffer is storage a compute shader binds; `usage` adds
`"vertex"`, `"index"`, `"indirect"` or `"readback"` on top of the storage
it always has. A `"cpu"` buffer lives in the scripting heap and reads back
as a flat array of floats.

The handle's `write` answers whether the words landed: a payload whose end
falls past the end of the buffer is refused whole on both kinds, so the
buffer keeps what it held and the call answers false. `writeU32` and
`writeBytes` answer the same way, against the same extent.

**Parameters**

- `opts` `BufferOpts` — `{ type, len, kind?, usage?, name? }` — `type` is `"f32"`, `"vec3"`,
`"vec4"`, `"quat"` or `"mat4"`; `kind` is `"cpu"` (the default) or `"gpu"`.
`name` is the name a dispatch binds a `"gpu"` buffer by, and the name
`substrate.getBuffer` and `substrate.destroyBuffer` reach it under.

**Returns** `TypedBuffer?` — The buffer handle, or nil when the allocation failed — an unknown `type` or `kind`, a zero length, or a `name` that already holds a GPU buffer of another shape. A `name` holding a buffer of the SAME type and length hands that buffer back, contents and all; `substrate.destroyBuffer` frees a name whose buffer is the wrong shape.

```lua
local pose = substrate.createBuffer({ type = "mat4", len = boneCount })
local field = substrate.createBuffer({ type = "vec3", len = 4096, kind = "gpu" })
local values = pose:read(0, 16):result()
```

## globals/substrate/destroyBuffer {#globals-substrate-destroybuffer}

```lua
substrate.destroyBuffer(name: string) -> boolean
```

Free the GPU buffer `name` denotes, whatever else still holds a handle
to it.

The allocation goes and the name is free to be created again at any type
and length; every handle that pointed at it answers `:alive()` false. This
is what releases a name whose creating handle is gone, so a build that
re-runs at a different size gets its name back.

**Parameters**

- `name` `string` — The name the buffer was created under.

**Returns** `boolean` — True when a GPU buffer under that name was freed.

```lua
substrate.destroyBuffer("env.town.xf")
```

## globals/substrate/getBuffer {#globals-substrate-getbuffer}

```lua
substrate.getBuffer(name: string) -> TypedBuffer?
```

The GPU buffer `name` denotes, as a handle you now hold.

A name is how a dispatch binds a buffer, so the name is what an owner asks
by once the handle it created with has gone out of scope — a `.module`
that hot-reloaded, a build that ran in an earlier `execute`. The handle
carries everything `createBuffer`'s does and releases its reference with
`:destroy()`.

**Parameters**

- `name` `string` — The name the buffer was created under.

**Returns** `TypedBuffer?` — The buffer handle, or nil when no GPU buffer holds that name.

```lua
local xf = substrate.getBuffer("env.town.xf")
local shape = xf and { xf:type(), xf:length() }
```

## globals/substrate/gpuReadback {#globals-substrate-gpureadback}

```lua
substrate.gpuReadback(key: string?) -> Readback?
```

Wrap the key an FFI read handed back as the `Readback` that polls it.
Every GPU→CPU read reaches the caller through this, so a texture's read
and a buffer's read answer with the same thing.

**Parameters**

- `key` `string` _(optional)_ — The key the read returned.

**Returns** `Readback?` — The `Readback`, or nil when the read did not start.

```lua
local pending = substrate.gpuReadback(compute.readTexture3D(handle))
```

## globals/substrate/listBuffers {#globals-substrate-listbuffers}

```lua
substrate.listBuffers() -> { NamedBuffer }
```

Every named GPU buffer the engine holds, in name order.

Each record states `id`, `name`, `type` (`"F32"`, `"Vec3"`, `"Vec4"`,
`"Quat"`, `"Mat4"`), `len` in records, and `refs` — how many holders it
has. This is what states which names are taken and at what shape.

**Returns** `{ NamedBuffer }` — Array of `{ id, name, type, len, refs }`.

```lua
for _, b in ipairs(substrate.listBuffers()) do print(b.name, b.type, b.len) end
```

## modules/substrate/README {#modules-substrate-readme}

```lua
require("@builtin/modules/api/engine/substrate") -- substrate (also available as global 'substrate')
```

Typed data buffers, on the CPU or the GPU — the engine's one buffer primitive. Public Luau surface over the `__substrate` Internal FFI namespace.

Usage: local substrate = require("@builtin/modules/api/engine/substrate")
Also available as global: substrate

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

```lua
createBuffer(opts: BufferOpts): TypedBuffer?
```

Allocate a typed buffer and return its handle.

A `"gpu"` buffer is storage a compute shader binds; `usage` adds
`"vertex"`, `"index"`, `"indirect"` or `"readback"` on top of the storage
it always has. A `"cpu"` buffer lives in the scripting heap and reads back
as a flat array of floats.

The handle's `write` answers whether the words landed: a payload whose end
falls past the end of the buffer is refused whole on both kinds, so the
buffer keeps what it held and the call answers false. `writeU32` and
`writeBytes` answer the same way, against the same extent.

**Parameters**

- `opts` `BufferOpts` — `{ type, len, kind?, usage?, name? }` — `type` is `"f32"`, `"vec3"`,
`"vec4"`, `"quat"` or `"mat4"`; `kind` is `"cpu"` (the default) or `"gpu"`.
`name` is the name a dispatch binds a `"gpu"` buffer by, and the name
`substrate.getBuffer` and `substrate.destroyBuffer` reach it under.

```lua
local pose = substrate.createBuffer({ type = "mat4", len = boneCount })
local field = substrate.createBuffer({ type = "vec3", len = 4096, kind = "gpu" })
local values = pose:read(0, 16):result()
```

## modules/substrate/destroyBuffer {#modules-substrate-destroybuffer}

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

Free the GPU buffer `name` denotes, whatever else still holds a handle
to it.

The allocation goes and the name is free to be created again at any type
and length; every handle that pointed at it answers `:alive()` false. This
is what releases a name whose creating handle is gone, so a build that
re-runs at a different size gets its name back.

**Parameters**

- `name` `string` — The name the buffer was created under.

```lua
substrate.destroyBuffer("env.town.xf")
```

## modules/substrate/getBuffer {#modules-substrate-getbuffer}

```lua
getBuffer(name: string): TypedBuffer?
```

The GPU buffer `name` denotes, as a handle you now hold.

A name is how a dispatch binds a buffer, so the name is what an owner asks
by once the handle it created with has gone out of scope — a `.module`
that hot-reloaded, a build that ran in an earlier `execute`. The handle
carries everything `createBuffer`'s does and releases its reference with
`:destroy()`.

**Parameters**

- `name` `string` — The name the buffer was created under.

```lua
local xf = substrate.getBuffer("env.town.xf")
local shape = xf and { xf:type(), xf:length() }
```

## modules/substrate/gpuReadback {#modules-substrate-gpureadback}

```lua
gpuReadback(key: string?): Readback?
```

Wrap the key an FFI read handed back as the `Readback` that polls it.
Every GPU→CPU read reaches the caller through this, so a texture's read
and a buffer's read answer with the same thing.

**Parameters**

- `key` `string?` _(optional)_ — The key the read returned.

```lua
local pending = substrate.gpuReadback(compute.readTexture3D(handle))
```

## modules/substrate/listBuffers {#modules-substrate-listbuffers}

```lua
listBuffers(): { NamedBuffer }
```

Every named GPU buffer the engine holds, in name order.

Each record states `id`, `name`, `type` (`"F32"`, `"Vec3"`, `"Vec4"`,
`"Quat"`, `"Mat4"`), `len` in records, and `refs` — how many holders it
has. This is what states which names are taken and at what shape.

```lua
for _, b in ipairs(substrate.listBuffers()) do print(b.name, b.type, b.len) end
```

## modules/substrate/ready {#modules-substrate-ready}

```lua
ready(self): boolean
```

Whether this read has arrived.

**Parameters**

- `self` `any` _(optional)_

## modules/substrate/result {#modules-substrate-result}

```lua
result(self): { number }?
```

Drain this read as f32 values, nil while it is still on its way.

**Parameters**

- `self` `any` _(optional)_

## modules/substrate/resultBytes {#modules-substrate-resultbytes}

```lua
resultBytes(self): buffer?
```

Drain this read as a Luau `buffer`, nil while it is still on its way.

**Parameters**

- `self` `any` _(optional)_

## modules/substrate/resultU32 {#modules-substrate-resultu32}

```lua
resultU32(self): { number }?
```

Drain this read as u32 values, nil while it is still on its way.

**Parameters**

- `self` `any` _(optional)_

## modules/substrate/state {#modules-substrate-state}

```lua
state(self): string
```

Where this read stands, without draining it and without raising:
`"pending"`, `"ready"`, or `"unknown"` (already drained).

**Parameters**

- `self` `any` _(optional)_

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

```lua
substrate.createBuffer(opts: BufferOpts) -> TypedBuffer?
```

Allocate a typed buffer and return its handle.

A `"gpu"` buffer is storage a compute shader binds; `usage` adds
`"vertex"`, `"index"`, `"indirect"` or `"readback"` on top of the storage
it always has. A `"cpu"` buffer lives in the scripting heap and reads back
as a flat array of floats.

The handle's `write` answers whether the words landed: a payload whose end
falls past the end of the buffer is refused whole on both kinds, so the
buffer keeps what it held and the call answers false. `writeU32` and
`writeBytes` answer the same way, against the same extent.

**Parameters**

- `opts` `BufferOpts` — `{ type, len, kind?, usage?, name? }` — `type` is `"f32"`, `"vec3"`,
`"vec4"`, `"quat"` or `"mat4"`; `kind` is `"cpu"` (the default) or `"gpu"`.
`name` is the name a dispatch binds a `"gpu"` buffer by, and the name
`substrate.getBuffer` and `substrate.destroyBuffer` reach it under.

**Returns** `TypedBuffer?` — The buffer handle, or nil when the allocation failed — an unknown `type` or `kind`, a zero length, or a `name` that already holds a GPU buffer of another shape. A `name` holding a buffer of the SAME type and length hands that buffer back, contents and all; `substrate.destroyBuffer` frees a name whose buffer is the wrong shape.

```lua
local pose = substrate.createBuffer({ type = "mat4", len = boneCount })
local field = substrate.createBuffer({ type = "vec3", len = 4096, kind = "gpu" })
local values = pose:read(0, 16):result()
```

## typed/builtin//modules/api/engine/substrate/substrate/destroyBuffer {#typed-builtin-modules-api-engine-substrate-substrate-destroybuffer}

```lua
substrate.destroyBuffer(name: string) -> boolean
```

Free the GPU buffer `name` denotes, whatever else still holds a handle
to it.

The allocation goes and the name is free to be created again at any type
and length; every handle that pointed at it answers `:alive()` false. This
is what releases a name whose creating handle is gone, so a build that
re-runs at a different size gets its name back.

**Parameters**

- `name` `string` — The name the buffer was created under.

**Returns** `boolean` — True when a GPU buffer under that name was freed.

```lua
substrate.destroyBuffer("env.town.xf")
```

## typed/builtin//modules/api/engine/substrate/substrate/getBuffer {#typed-builtin-modules-api-engine-substrate-substrate-getbuffer}

```lua
substrate.getBuffer(name: string) -> TypedBuffer?
```

The GPU buffer `name` denotes, as a handle you now hold.

A name is how a dispatch binds a buffer, so the name is what an owner asks
by once the handle it created with has gone out of scope — a `.module`
that hot-reloaded, a build that ran in an earlier `execute`. The handle
carries everything `createBuffer`'s does and releases its reference with
`:destroy()`.

**Parameters**

- `name` `string` — The name the buffer was created under.

**Returns** `TypedBuffer?` — The buffer handle, or nil when no GPU buffer holds that name.

```lua
local xf = substrate.getBuffer("env.town.xf")
local shape = xf and { xf:type(), xf:length() }
```

## typed/builtin//modules/api/engine/substrate/substrate/gpuReadback {#typed-builtin-modules-api-engine-substrate-substrate-gpureadback}

```lua
substrate.gpuReadback(key: string?) -> Readback?
```

Wrap the key an FFI read handed back as the `Readback` that polls it.
Every GPU→CPU read reaches the caller through this, so a texture's read
and a buffer's read answer with the same thing.

**Parameters**

- `key` `string` _(optional)_ — The key the read returned.

**Returns** `Readback?` — The `Readback`, or nil when the read did not start.

```lua
local pending = substrate.gpuReadback(compute.readTexture3D(handle))
```

## typed/builtin//modules/api/engine/substrate/substrate/listBuffers {#typed-builtin-modules-api-engine-substrate-substrate-listbuffers}

```lua
substrate.listBuffers() -> { NamedBuffer }
```

Every named GPU buffer the engine holds, in name order.

Each record states `id`, `name`, `type` (`"F32"`, `"Vec3"`, `"Vec4"`,
`"Quat"`, `"Mat4"`), `len` in records, and `refs` — how many holders it
has. This is what states which names are taken and at what shape.

**Returns** `{ NamedBuffer }` — Array of `{ id, name, type, len, refs }`.

```lua
for _, b in ipairs(substrate.listBuffers()) do print(b.name, b.type, b.len) end
```
