Skip to main content
Version: 0.2

Rendering pipeline

EXEPERT draws three things: neurons (points), axons (line segments), and signals (a pooled particle system). All three bind their geometry to WASM memory; the difference is how often that memory changes.

The render loop

src/run.ts owns the requestAnimationFrame loop. Because scene.ts sets renderer.autoClear = false (so the background color stays configurable through the GUI), the loop clears manually each frame:

src/run.ts
function frame(): void {
requestAnimationFrame(frame)
clearColor.set(sceneSettings.bgColor)
renderer.setClearColor(clearColor, 1)
renderer.clear()
update() // brain.update(dt) when not paused
renderer.render(scene, camera) // synchronous: classic WebGLRenderer
stats.update()
tickFrameCount()
updateStatusFps()
updateBrainStatusBar()
}

The render call is synchronous because the renderer is a classic WebGLRenderer. (Earlier WebGPU drafts needed an awaited render; that is no longer the case.)

Neurons: Points

Built in Brain.buildNeuronGeometry(). The geometry has three attributes:

  • position: a Float32Array view into WASM memory (static after build).
  • color: per-neuron RGB, written from the configured neuron color.
  • size: a per-neuron random size in [0.75, 3.0).

Neurons render with the custom neuron ShaderMaterial (src/materials/neuron.ts): additive blending, a sprite texture, and a distance-attenuated point size. See Materials.

Axons: LineSegments

Built in Brain.buildAxonGeometry(). The Rust core samples each axon's cubic Bezier curve into 9 vertices (8 subdivisions) and exposes three buffers:

  • position: sampled curve vertices.
  • index: line-segment connectivity (setIndex).
  • opacity: a per-vertex opacity used by the axon shader.

These buffers are uploaded once. Axons render with the axon ShaderMaterial (src/materials/axon.ts): per-vertex opacity times a global multiplier, with additive blending.

Signals: particle pool

The ParticlePoolBridge (inside src/brain.ts) manages a Points cloud whose position attribute views the WASM-owned particle buffer. This is the buffer that changes every frame:

src/brain.ts
update(deltaTime: number): void {
this.refreshIfMemoryGrew()
this.world.step_cpu(deltaTime) // writes particle positions into WASM memory
this.particlePool.update() // flags needsUpdate for the GL upload
}

world.step_cpu(dt) advances each live signal along its axon, samples the Bezier curve, and writes the resulting position into WASM memory. The bridge then flags the attribute needsUpdate so Three.js re-uploads it to the GPU.

Why memory growth matters here

Of the three buffers, the particle buffer is the one most affected by WASM memory growth, so ParticlePoolBridge re-checks wasm.memory.buffer every frame and rewraps its view if the buffer changed. The neuron view is rebuilt by Brain.refreshIfMemoryGrew(); axon attributes are static. See Overview for the mechanism.