---
title: "frameStream"
description: "Carries an image the GPU drew out to another program: the view a camera renders…"
section: "API Reference"
slug: "api-framestream"
canonical: "https://origozero.ai/docs/api-framestream"
updated: "2026-10-09T00:35:23.093364879+00:00"
tags: ["api", "reference"]
---

# frameStream

Carries an image the GPU drew out to another program — the view a camera renders, read back off its render target and written into a byte stream frame after frame, so pixels that sit in GPU memory reach a process outside the engine while the world runs.

```lua
require("@builtin::modules.api.engine.frameStream") -- frameStream (also available as global 'frameStream')
```

The `frameStream` namespace: 12 functions.

## globals/frameStream/attach {#globals-framestream-attach}

```lua
frameStream.attach(texture: string, stream: string, opts: AttachOpts?) -> (string?, string?)
```

Carry an image the GPU drew out to an open byte stream, frame
after frame. `texture` is the guid of the render target it was
drawn into — `renderer.texture.create({ width = W, height = H })`
makes one, and a Camera component draws into it as its
`textureHandle`; the session reads that target back when a frame
comes due, so what the camera drew last reaches the far end.
`stream` is a handle from `stream.open`. What reaches the stream
is one frame's pixels then the next frame's, with nothing between
them: a frame is `width * height * bytesPerPixel` bytes of tight
rows, written in a single call so a consumer reads a whole frame
or none of it. Each frame is read back off the render thread, so
the stream never holds the renderer up. `fps` caps how often a
frame is taken and defaults to one per rendered frame; `format`
accepts `"rgb24"` (3 bytes per pixel, the default) or `"rgba8"`
(4) — a call with a format outside those two raises, naming both;
`flipY` writes the last texture row first. Returns the session
handle, or nil and the reason an empty texture, a handle naming no
open stream, a stream another session already carries, or a
non-positive fps was refused with.

**Parameters**

- `texture` `string` — Guid of the render target the image was drawn into (a Camera's textureHandle).
- `stream` `string` — Stream handle from stream.open.
- `opts` `AttachOpts` _(optional)_ — Rate, pixel layout and row order (optional).

**Returns** `(string?, string?)` — Session handle, or nil and the refusal reason.

```lua
local session = frameStream.attach(rt.guid, handle, { fps = 30 })
```

## globals/frameStream/detach {#globals-framestream-detach}

```lua
frameStream.detach(handle: string) -> boolean
```

End the session and free the staging buffers it read frames
back through. The stream stays open — whoever opened it closes it.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

**Returns** `boolean` — True if a session was ended, false if handle already named none.

```lua
frameStream.detach(session)
```

## globals/frameStream/list {#globals-framestream-list}

```lua
frameStream.list() -> { string }
```

Every live session handle, in a stable order.

**Returns** `{ string }` — Array of session handles.

```lua
for _, h in frameStream.list() do frameStream.detach(h) end
```

## globals/frameStream/status {#globals-framestream-status}

```lua
frameStream.status(handle: string) -> FrameStreamStatus?
```

Report what the session has carried and lost. `frames` counts
the frames the stream accepted and `bytes` the bytes they
carried. `dropped` counts the frames it refused, of which
`droppedBackpressure` is the part refused because the consumer was
behind; `stalledReadbacks` counts the frames that came due while
every staging buffer still held a copy on its way from the GPU.
`achievedFps` is the rate the accepted frames arrived at, across
the span from the first to the most recent, and reads 0 until two
have been accepted — compare it against `requestedFps` to see a
display running slower than it was asked to. `lastCopyFrame` is
the engine frame, in `time.frameCount()` numbering, the copy of the
most recent accepted frame was taken on, and is nil until one is
accepted: every copy one engine frame takes of a texture holds the
same image, so two sessions on one texture whose `lastCopyFrame`
agree hold the same image as the newest frame of their streams.
`lastOutcome` names what became of the most recent frame offered.
nil when handle names no live session.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

**Returns** `FrameStreamStatus?` — Session status, or nil when handle names no live session.

```lua
local s = frameStream.status(session); print(s.frames, s.dropped, s.achievedFps)
```

## modules/frameStream/attach {#modules-framestream-attach}

```lua
attach(texture: string, stream: string, opts: AttachOpts?): (string?, string?)
```

Carry an image the GPU drew out to an open byte stream, frame
after frame. `texture` is the guid of the render target it was
drawn into — `renderer.texture.create({ width = W, height = H })`
makes one, and a Camera component draws into it as its
`textureHandle`; the session reads that target back when a frame
comes due, so what the camera drew last reaches the far end.
`stream` is a handle from `stream.open`. What reaches the stream
is one frame's pixels then the next frame's, with nothing between
them: a frame is `width * height * bytesPerPixel` bytes of tight
rows, written in a single call so a consumer reads a whole frame
or none of it. Each frame is read back off the render thread, so
the stream never holds the renderer up. `fps` caps how often a
frame is taken and defaults to one per rendered frame; `format`
accepts `"rgb24"` (3 bytes per pixel, the default) or `"rgba8"`
(4) — a call with a format outside those two raises, naming both;
`flipY` writes the last texture row first. Returns the session
handle, or nil and the reason an empty texture, a handle naming no
open stream, a stream another session already carries, or a
non-positive fps was refused with.

**Parameters**

- `texture` `string` — Guid of the render target the image was drawn into (a Camera's textureHandle).
- `stream` `string` — Stream handle from stream.open.
- `opts` `AttachOpts?` _(optional)_ — Rate, pixel layout and row order (optional).

```lua
local session = frameStream.attach(rt.guid, handle, { fps = 30 })
```

## modules/frameStream/detach {#modules-framestream-detach}

```lua
detach(handle: string): boolean
```

End the session and free the staging buffers it read frames
back through. The stream stays open — whoever opened it closes it.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

```lua
frameStream.detach(session)
```

## modules/frameStream/list {#modules-framestream-list}

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

Every live session handle, in a stable order.

```lua
for _, h in frameStream.list() do frameStream.detach(h) end
```

## modules/frameStream/status {#modules-framestream-status}

```lua
status(handle: string): FrameStreamStatus?
```

Report what the session has carried and lost. `frames` counts
the frames the stream accepted and `bytes` the bytes they
carried. `dropped` counts the frames it refused, of which
`droppedBackpressure` is the part refused because the consumer was
behind; `stalledReadbacks` counts the frames that came due while
every staging buffer still held a copy on its way from the GPU.
`achievedFps` is the rate the accepted frames arrived at, across
the span from the first to the most recent, and reads 0 until two
have been accepted — compare it against `requestedFps` to see a
display running slower than it was asked to. `lastCopyFrame` is
the engine frame, in `time.frameCount()` numbering, the copy of the
most recent accepted frame was taken on, and is nil until one is
accepted: every copy one engine frame takes of a texture holds the
same image, so two sessions on one texture whose `lastCopyFrame`
agree hold the same image as the newest frame of their streams.
`lastOutcome` names what became of the most recent frame offered.
nil when handle names no live session.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

```lua
local s = frameStream.status(session); print(s.frames, s.dropped, s.achievedFps)
```

## typed/builtin//modules/api/engine/frameStream/frameStream/attach {#typed-builtin-modules-api-engine-framestream-framestream-attach}

```lua
frameStream.attach(texture: string, stream: string, opts: AttachOpts?) -> (string?, string?)
```

Carry an image the GPU drew out to an open byte stream, frame
after frame. `texture` is the guid of the render target it was
drawn into — `renderer.texture.create({ width = W, height = H })`
makes one, and a Camera component draws into it as its
`textureHandle`; the session reads that target back when a frame
comes due, so what the camera drew last reaches the far end.
`stream` is a handle from `stream.open`. What reaches the stream
is one frame's pixels then the next frame's, with nothing between
them: a frame is `width * height * bytesPerPixel` bytes of tight
rows, written in a single call so a consumer reads a whole frame
or none of it. Each frame is read back off the render thread, so
the stream never holds the renderer up. `fps` caps how often a
frame is taken and defaults to one per rendered frame; `format`
accepts `"rgb24"` (3 bytes per pixel, the default) or `"rgba8"`
(4) — a call with a format outside those two raises, naming both;
`flipY` writes the last texture row first. Returns the session
handle, or nil and the reason an empty texture, a handle naming no
open stream, a stream another session already carries, or a
non-positive fps was refused with.

**Parameters**

- `texture` `string` — Guid of the render target the image was drawn into (a Camera's textureHandle).
- `stream` `string` — Stream handle from stream.open.
- `opts` `AttachOpts` _(optional)_ — Rate, pixel layout and row order (optional).

**Returns** `(string?, string?)` — Session handle, or nil and the refusal reason.

```lua
local session = frameStream.attach(rt.guid, handle, { fps = 30 })
```

## typed/builtin//modules/api/engine/frameStream/frameStream/detach {#typed-builtin-modules-api-engine-framestream-framestream-detach}

```lua
frameStream.detach(handle: string) -> boolean
```

End the session and free the staging buffers it read frames
back through. The stream stays open — whoever opened it closes it.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

**Returns** `boolean` — True if a session was ended, false if handle already named none.

```lua
frameStream.detach(session)
```

## typed/builtin//modules/api/engine/frameStream/frameStream/list {#typed-builtin-modules-api-engine-framestream-framestream-list}

```lua
frameStream.list() -> { string }
```

Every live session handle, in a stable order.

**Returns** `{ string }` — Array of session handles.

```lua
for _, h in frameStream.list() do frameStream.detach(h) end
```

## typed/builtin//modules/api/engine/frameStream/frameStream/status {#typed-builtin-modules-api-engine-framestream-framestream-status}

```lua
frameStream.status(handle: string) -> FrameStreamStatus?
```

Report what the session has carried and lost. `frames` counts
the frames the stream accepted and `bytes` the bytes they
carried. `dropped` counts the frames it refused, of which
`droppedBackpressure` is the part refused because the consumer was
behind; `stalledReadbacks` counts the frames that came due while
every staging buffer still held a copy on its way from the GPU.
`achievedFps` is the rate the accepted frames arrived at, across
the span from the first to the most recent, and reads 0 until two
have been accepted — compare it against `requestedFps` to see a
display running slower than it was asked to. `lastCopyFrame` is
the engine frame, in `time.frameCount()` numbering, the copy of the
most recent accepted frame was taken on, and is nil until one is
accepted: every copy one engine frame takes of a texture holds the
same image, so two sessions on one texture whose `lastCopyFrame`
agree hold the same image as the newest frame of their streams.
`lastOutcome` names what became of the most recent frame offered.
nil when handle names no live session.

**Parameters**

- `handle` `string` — Session handle from frameStream.attach.

**Returns** `FrameStreamStatus?` — Session status, or nil when handle names no live session.

```lua
local s = frameStream.status(session); print(s.frames, s.dropped, s.achievedFps)
```
