// Instanced drawing with per-instance attributes: one 3-vertex triangle in
// the vertex buffer, drawn hundreds of times, each copy reading its own
// record from an instance buffer (instanceAttributes on the pipeline +
// instanceBuffer on the target, vertex divisor 1 underneath). Here each
// record is one petal of a phyllotaxis spiral - its angle, radius, size, and
// tint, computed once in JS - so the vertex stage just reads state instead
// of re-deriving it from gl_InstanceID every frame, and the geometry buffer
// stays 6 floats no matter the population.
//
// The draw range is data: setDraw merges partial updates like params, so the
// per-frame write below changes only instanceCount while firstVertex and
// vertexCount keep their values (the whole buffer). With an instance buffer
// bound, an omitted instanceCount would default to one instance per record
// (the whole population); the explicit 1 here starts the bloom closed, and
// the count is bounds-checked against the records either way.
import { render, onFrame, createSignal } from "@solidrt/core"
import { createBuffer, createPipelineTexture, glsl, setDraw } from "@solidrt/core/gpu"
let MAX_PETALS = 324
let VERTEX = glsl`
in vec2 aPos;
in float iAngle;
in float iRadius;
in float iScale;
in vec3 iTint;
out vec3 vTint;
uniform float uTime;
void main() {
// The record places the petal; time only spins the whole flower.
float angle = iAngle + uTime * 0.3;
float c = cos(angle), s = sin(angle);
vec2 p = iRadius * vec2(c, s) + mat2(c, s, -s, c) * (aPos * iScale);
gl_Position = vec4(p, 0.0, 1.0);
vTint = iTint;
}
`
let FRAGMENT = glsl`
in vec3 vTint;
void main() {
fragColor = vec4(vTint, 1.0);
}
`
// One record per petal (matching instanceAttributes: 6 floats): radius grows
// with sqrt(index), the angle steps by the golden angle (~2.39996 rad) - the
// sunflower layout - petals shrink toward the rim, and the tint walks a
// cosine palette.
function petalRecords() {
let records = new Float32Array(MAX_PETALS * 6)
for (let i = 0; i < MAX_PETALS; i++) {
let at = i * 6
let t = Math.sqrt(i) / 18
records[at] = i * 2.39996
records[at + 1] = 0.045 * Math.sqrt(i)
records[at + 2] = 0.05 * (1 - t) + 0.016 * t
for (let k = 0; k < 3; k++) {
records[at + 3 + k] = 0.5 + 0.5 * Math.cos(6.2832 * (i / 96) + k * 2.1)
}
}
return records
}
function App() {
// The whole mesh: one triangle, reused by every instance.
let bufferId = createBuffer(new Float32Array([0, 1.3, -1, -0.75, 1, -0.75]), { label: "petal-tri" })
let instanceId = createBuffer(petalRecords(), { label: "petal-records" })
let id = createPipelineTexture(VERTEX, FRAGMENT, 512, 512, { uTime: 0 }, {
label: "petals",
attributes: [{ name: "aPos", format: "vec2" }],
buffer: bufferId,
instanceAttributes: [
{ name: "iAngle", format: "f32" },
{ name: "iRadius", format: "f32" },
{ name: "iScale", format: "f32" },
{ name: "iTint", format: "vec3" },
],
instanceBuffer: instanceId,
instanceCount: 1,
clearColor: [0.03, 0.03, 0.06, 1],
})
let [time, setTime] = createSignal(0)
onFrame((tick) => {
let t = tick / 1000
setTime(t)
setDraw(id, { instanceCount: Math.round(MAX_PETALS / 2 + (MAX_PETALS / 2 - 1) * Math.sin(t * 0.7)) })
})
return (
)
}
render(() => )