instancing
stableManage a GPU instance buffer holding per-instance position, scale, and RGBA color data for instanced rendering.
use plugin instancing::{InstanceBuffer.new, add_instance, count, …} Functions (13)
- InstanceBuffer.new Create a new empty instance buffer
- add_instance Append one instance with transform and color
- count Get the number of instances
- get_instance Read one instance's data by index
- set_transform Update position of an existing instance
- set_scale Update scale of an existing instance
- set_color Update color of an existing instance
- remove Remove an instance by index (order-preserving)
- swap_remove Remove an instance by index (O(1), swaps last)
- clear Remove all instances
- batch_update Replace all instances from a table
- to_bytes Serialize all instances to packed bytes
- 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"]}")