Skip to content

instancing

stable

Manage a GPU instance buffer holding per-instance position, scale, and RGBA color data for instanced rendering.

use plugin instancing::{InstanceBuffer.new, add_instance, count, …}
13 functions Graphics
/ filter jk navigate Esc clear
Functions (13)
  1. InstanceBuffer.new Create a new empty instance buffer
  2. add_instance Append one instance with transform and color
  3. count Get the number of instances
  4. get_instance Read one instance's data by index
  5. set_transform Update position of an existing instance
  6. set_scale Update scale of an existing instance
  7. set_color Update color of an existing instance
  8. remove Remove an instance by index (order-preserving)
  9. swap_remove Remove an instance by index (O(1), swaps last)
  10. clear Remove all instances
  11. batch_update Replace all instances from a table
  12. to_bytes Serialize all instances to packed bytes
  13. draw_info Get stride and offset metadata for GPU binding

Overview

instancing provides a single stateful InstanceBuffer class for GPU instanced rendering — the technique of drawing many copies of the same mesh in one draw call, each with its own transform and color. Every instance stores a position (x, y, z), a per-axis scale (sx, sy, sz), and an RGBA color (r, g, b, a), all as 32-bit floats. The buffer is reached through a handle returned by InstanceBuffer.new(), and you mutate it in place with the add_instance, set_*, remove, and clear methods.

The core workflow is: build up the per-instance data on the CPU side, then call to_bytes() to get a tightly packed little-endian f32 byte buffer (40 bytes per instance) that you upload straight to a GPU vertex/instance buffer. Pair it with draw_info() to read back the stride and the byte offsets of the position, scale, and color attributes so you can wire up the vertex layout. Use it whenever you render forests, particles, crowds, debris, or any large field of repeated geometry that differs only in transform and tint.

Common patterns

Populate a buffer, then serialize it for a GPU upload:

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 0.0, 0.0, 1.0)
buf.add_instance(2.0, 0.0, 0.0,  1.0, 1.0, 1.0,  0.0, 1.0, 0.0, 1.0)

let info = buf.draw_info()
let bytes = buf.to_bytes()
print("{buf.count()} instances, {info["stride"]}-byte stride, {bytes.len()} bytes total")

Replace the whole buffer in one shot with a table of instances:

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.batch_update([
  #{"x": 0.0, "y": 0.0, "z": 0.0, "r": 1.0, "g": 0.0, "b": 0.0, "a": 1.0},
  #{"x": 2.0, "y": 0.0, "z": 0.0, "r": 0.0, "g": 1.0, "b": 0.0, "a": 1.0},
  #{"x": 4.0, "y": 0.0, "z": 0.0, "r": 0.0, "g": 0.0, "b": 1.0, "a": 1.0}
])
print("loaded {buf.count()} instances")

Animate existing instances by reading and rewriting their transforms:

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)

let i = 0
while i < buf.count() {
  let inst = buf.get_instance(i)
  buf.set_transform(i, inst["x"] + 1.0, inst["y"], inst["z"])
  i = i + 1
}
print("moved {buf.count()} instances")

Create a new empty instance buffer

Creates a new empty instance buffer. Each instance stores position (x, y, z), scale (sx, sy, sz), and color (r, g, b, a) as 32-bit floats.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
print(buf.count())

Append one instance with transform and color

Appends a new instance. Position components are world-space floats. Scale defaults to 1.0 per axis. Color channels are 0.0–1.0 floats.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,   1.0, 1.0, 1.0,   1.0, 0.0, 0.0, 1.0)
buf.add_instance(2.0, 0.0, 0.0,   1.0, 1.0, 1.0,   0.0, 1.0, 0.0, 1.0)
print(buf.count())

Get the number of instances

Returns the number of instances currently in the buffer.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
print(buf.count())

Read one instance's data by index

Returns the data for instance at the given 0-based index as {x, y, z, sx, sy, sz, r, g, b, a}.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(1.0, 2.0, 3.0,  1.0, 1.0, 1.0,  0.5, 0.5, 0.5, 1.0)
let inst = buf.get_instance(0)
print(inst["x"])
print(inst["r"])

Update position of an existing instance

Updates the world-space position of the instance at the given 0-based index.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
buf.set_transform(0, 5.0, 0.0, 0.0)
let inst = buf.get_instance(0)
print(inst["x"])

Update scale of an existing instance

Updates the scale of the instance at the given 0-based index.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
buf.set_scale(0, 2.0, 2.0, 2.0)

Update color of an existing instance

Updates the RGBA color of the instance at the given 0-based index. Channels are 0.0–1.0 floats.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
buf.set_color(0, 1.0, 0.0, 0.0, 1.0)

Remove an instance by index (order-preserving)

Removes the instance at the given 0-based index, preserving the order of remaining instances.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 0.0, 0.0, 1.0)
buf.add_instance(1.0, 0.0, 0.0,  1.0, 1.0, 1.0,  0.0, 1.0, 0.0, 1.0)
buf.remove(0)
print(buf.count())

Remove an instance by index (O(1), swaps last)

Removes the instance at the given index by swapping it with the last element and popping. O(1) but does not preserve order.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 0.0, 0.0, 1.0)
buf.add_instance(1.0, 0.0, 0.0,  1.0, 1.0, 1.0,  0.0, 1.0, 0.0, 1.0)
buf.add_instance(2.0, 0.0, 0.0,  1.0, 1.0, 1.0,  0.0, 0.0, 1.0, 1.0)
buf.swap_remove(0)
print(buf.count())

Remove all instances

Removes all instances from the buffer.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
buf.clear()
print(buf.count())

Replace all instances from a table

Replaces all instances in the buffer with data from a table of tables. Each sub-table may have keys x, y, z, sx, sy, sz, r, g, b, a with floats; missing keys default to 0.0 for position and 1.0 for scale and color.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.batch_update([
  #{"x": 0.0, "y": 0.0, "z": 0.0, "sx": 1.0, "sy": 1.0, "sz": 1.0, "r": 1.0, "g": 0.0, "b": 0.0, "a": 1.0},
  #{"x": 2.0, "y": 0.0, "z": 0.0, "sx": 1.0, "sy": 1.0, "sz": 1.0, "r": 0.0, "g": 1.0, "b": 0.0, "a": 1.0}
])
print(buf.count())

Serialize all instances to packed bytes

Serializes all instances to a packed byte buffer of little-endian f32 values. Each instance is 40 bytes: x, y, z, sx, sy, sz, r, g, b, a. Pass this directly to a GPU vertex/instance buffer upload.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(1.0, 2.0, 3.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
let bytes = buf.to_bytes()
print("Buffer size: {bytes.len()} bytes")

The byte count always equals count() * 40, which lets you cross-check the serialized size against draw_info:

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 0.0, 0.0, 1.0)
buf.add_instance(2.0, 0.0, 0.0,  1.0, 1.0, 1.0,  0.0, 1.0, 0.0, 1.0)

let bytes = buf.to_bytes()
let info = buf.draw_info()
print("{bytes.len()} bytes == {info["total_bytes"]} reported")

Get stride and offset metadata for GPU binding

Returns a table with GPU binding metadata: {stride, count, total_bytes, position_offset, scale_offset, color_offset}. Use this to configure vertex attribute pointers.

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
let info = buf.draw_info()
print("stride={info["stride"]} count={info["count"]}")
print("color_offset={info["color_offset"]}")

The offsets are fixed: position starts at byte 0, scale at 12, and color at 24, so you can use them directly when describing vertex attributes:

use plugin instancing::{InstanceBuffer}

let buf = InstanceBuffer.new()
buf.add_instance(0.0, 0.0, 0.0,  1.0, 1.0, 1.0,  1.0, 1.0, 1.0, 1.0)
let info = buf.draw_info()
print("position @ {info["position_offset"]}")
print("scale    @ {info["scale_offset"]}")
print("color    @ {info["color_offset"]}")
enespt-br