// The zero-copy per-frame geometry path: instanced quads whose records are // rewritten every frame through a buffer write lease. beginBufferWrite hands // back a Float32Array over runtime-owned memory (contents unspecified - fill // everything you publish), the frame callback writes every live record into // it, and endBufferWrite publishes by MOVING the block to the raster thread: // no data copy anywhere on the CPU path, and no per-sprite property writes. // Compare gpu-instancing.tsx, where the records are static and only the draw // range changes; here the records themselves are the animation. // // The buffer is created from a byte LENGTH (zeroed storage) - the natural // create when every byte arrives through the lease. Publishing a prefix and // setDraw({ instanceCount }) keep buffer capacity and live population // independent: reserve the max up front, publish what exists this frame. import { render, onFrame } from "@solidrt/core" import { beginBufferWrite, createBuffer, createPipelineTexture, endBufferWrite, glsl, setDraw } from "@solidrt/core/gpu" const MAX_SPRITES = 2000 // floats per record: center vec2, half-size f32, tint vec3. const RECORD = 6 let VERTEX = glsl` in vec2 aPos; in vec2 iCenter; in float iSize; in vec3 iTint; out vec3 vTint; void main() { gl_Position = vec4(iCenter + aPos * iSize, 0.0, 1.0); vTint = iTint; } ` let FRAGMENT = glsl` in vec3 vTint; void main() { fragColor = vec4(vTint, 1.0); } ` // Simulation state lives in plain arrays; the instance buffer holds only // this frame's published snapshot of it. let x = new Float32Array(MAX_SPRITES) let y = new Float32Array(MAX_SPRITES) let vx = new Float32Array(MAX_SPRITES) let vy = new Float32Array(MAX_SPRITES) for (let i = 0; i < MAX_SPRITES; i++) { x[i] = Math.random() * 1.9 - 0.95 y[i] = Math.random() * 1.9 - 0.95 vx[i] = (Math.random() * 2 - 1) * 0.01 vy[i] = (Math.random() * 2 - 1) * 0.01 } function App() { // One unit quad (triangle strip), reused by every instance. let quad = createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]), { label: "sprite-quad" }) let records = createBuffer(MAX_SPRITES * RECORD * 4, { label: "sprite-records" }) let id = createPipelineTexture(VERTEX, FRAGMENT, 720, 720, null, { label: "sprites", topology: "triangle-strip", vertexCount: 4, attributes: [{ name: "aPos", format: "vec2" }], buffer: quad, instanceAttributes: [ { name: "iCenter", format: "vec2" }, { name: "iSize", format: "f32" }, { name: "iTint", format: "vec3" }, ], instanceBuffer: records, instanceCount: MAX_SPRITES, clearColor: [0.03, 0.03, 0.06, 1], }) onFrame(() => { let out = beginBufferWrite(records) for (let i = 0; i < MAX_SPRITES; i++) { let nx = x[i]! + vx[i]! let ny = y[i]! + vy[i]! if (nx < -0.95 || nx > 0.95) vx[i] = -vx[i]! else x[i] = nx if (ny < -0.95 || ny > 0.95) vy[i] = -vy[i]! else y[i] = ny let at = i * RECORD out[at] = x[i]! out[at + 1] = y[i]! out[at + 2] = 0.01 + 0.008 * (i % 5) out[at + 3] = 0.5 + 0.5 * Math.cos(i * 0.11) out[at + 4] = 0.5 + 0.5 * Math.cos(i * 0.13 + 2.1) out[at + 5] = 0.5 + 0.5 * Math.cos(i * 0.17 + 4.2) } endBufferWrite(records) }) return ( ) } render(() => )