// A draw target: one render target holding an ordered, mutable LIST of // draws - two orbiting triangles from two different programs sharing one // depth buffer (they occlude each other correctly as they cross), plus a // third entry added and removed on a timer. One render of the target is one // GPU pass no matter how many entries it holds, and per-entry params // (setDrawParams) are the per-object channel: each triangle carries its own // angle uniform, the model-matrix pattern at toy scale. // // The depth split to notice: the TARGET owns the storage (depth: true on // createDrawTarget - what makes cross-entry occlusion work), each PIPELINE // owns the behavior (depth: true on createRenderPipeline - this draw tests // and writes). A depth-testing pipeline added to a depthless draw target // throws at addDraw. import { render, onFrame } from "@solidrt/core" import { addDraw, compileShader, createBuffer, createDrawTarget, createRenderPipeline, glsl, linkProgram, removeDraw, setDrawParams, } from "@solidrt/core/gpu" import type { DrawId } from "@solidrt/core/gpu" let VERTEX = glsl` in vec2 aPos; uniform float uAngle; void main() { // Orbit in x, swing through depth in z: the two entries cross each // other, and the shared depth buffer decides who is in front. float c = cos(uAngle), s = sin(uAngle); vec2 p = aPos * 0.55 + vec2(0.55 * c, 0.0); gl_Position = vec4(p, 0.5 * s, 1.0); } ` let FRAGMENT_WARM = glsl` void main() { fragColor = vec4(0.95, 0.45, 0.2, 1.0); } ` let FRAGMENT_COOL = glsl` void main() { fragColor = vec4(0.25, 0.55, 0.95, 1.0); } ` let FRAGMENT_PULSE = glsl` uniform float uPhase; void main() { fragColor = vec4(vec3(0.6 + 0.4 * sin(uPhase)), 1.0); } ` function App() { let triangle = createBuffer(new Float32Array([0, 0.6, -0.5, -0.4, 0.5, -0.4]), { label: "tri" }) let vs = compileShader("vertex", VERTEX, { header: true }) let attrs = [{ name: "aPos", format: "vec2" as const }] let warm = createRenderPipeline(linkProgram(vs, compileShader("fragment", FRAGMENT_WARM, { header: true })), { attributes: attrs, depth: true, }) let cool = createRenderPipeline(linkProgram(vs, compileShader("fragment", FRAGMENT_COOL, { header: true })), { attributes: attrs, depth: true, }) let pulse = createRenderPipeline(linkProgram(vs, compileShader("fragment", FRAGMENT_PULSE, { header: true })), { attributes: attrs, }) let target = createDrawTarget(512, 512, null, { depth: true, clearColor: [0.04, 0.04, 0.08, 1], label: "orbits" }) let warmDraw = addDraw(target, warm, { uAngle: 0 }, { buffer: triangle }) let coolDraw = addDraw(target, cool, { uAngle: Math.PI }, { buffer: triangle }) // A third entry blinks in and out every second: add/remove are ordinary // per-frame-affordable writes, and a removed DrawId simply retires. Its // pipeline declares no depth, so it neither tests nor writes: list order // alone places it, and inserting it BEFORE the warm triangle // (before: warmDraw) keeps the flash beneath both orbits - appended // instead, it would cover them. setDrawOrder is the wholesale form when a // scene sorts its whole list. let pulseDraw: DrawId | null = null onFrame((tick) => { let t = tick / 1000 setDrawParams(target, warmDraw, { uAngle: t }) setDrawParams(target, coolDraw, { uAngle: t + Math.PI }) let wantPulse = Math.floor(t) % 2 === 0 if (wantPulse && pulseDraw === null) { pulseDraw = addDraw(target, pulse, { uAngle: t * 0.3, uPhase: t * 5 }, { buffer: triangle, before: warmDraw }) } else if (!wantPulse && pulseDraw !== null) { removeDraw(target, pulseDraw) pulseDraw = null } else if (pulseDraw !== null) { setDrawParams(target, pulseDraw, { uAngle: t * 0.3, uPhase: t * 5 }) } }) return ( ) } render(() => )