feat: bees and frogs

This commit is contained in:
Dan Finch 2026-08-07 12:49:13 +02:00
parent 46e7070b6b
commit 16f205babe
13 changed files with 540 additions and 31 deletions

View file

@ -2,7 +2,7 @@ import { Framebuffer } from "../engine/render/Framebuffer"
import type { RenderConfig } from "../engine/render/RenderConfig"
import type { Mat4 } from "../engine/math/Mat4"
import type { Camera } from "../engine/scene/Camera"
import { renderBand, type Scene } from "./renderScene"
import { renderBand, MOB_FLOATS, type MobDraw, type Scene } from "./renderScene"
/** Sky is drawn at 1/SKY_STEP resolution; band splits align to it. */
const SKY_STEP = 2
@ -21,6 +21,7 @@ const MAX_WORKERS = 3
const FRAME = 0 // bumped by main to dispatch a frame
const DONE = 1 // workers add 1 when their band is finished
const VIS = 2 // number of visible chunk indices this frame
const MOBVIS = 3 // number of visible mobs this frame
/**
* Render driver. When the page is cross-origin-isolated it runs a pool of worker
@ -38,7 +39,7 @@ export type Renderer = {
readonly parallel: boolean
reconfigure: (config: RenderConfig) => void
/** Start rendering one frame (non-blocking in the worker path). */
dispatch: (camera: Camera, viewProj: Mat4, visible: number[], time: number) => void
dispatch: (camera: Camera, viewProj: Mat4, visible: number[], time: number, mobDraws: MobDraw[]) => void
/** Has the dispatched frame finished? (always true single-threaded.) */
done: () => boolean
/** Critical-path render time of the last frame, ms (max band time / inline time). */
@ -49,6 +50,7 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
const hw = (globalThis.navigator as Navigator | undefined)?.hardwareConcurrency ?? 4
const workerCount = Math.max(1, Math.min(MAX_WORKERS, hw - 1))
const maxVis = Math.max(1, scene.chunks.length)
const maxMobs = Math.max(1, scene.mobCount)
let config = initial
const want = forceWorkers ?? ENABLE_WORKERS
let parallel = want && canShare()
@ -59,6 +61,7 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
let cam: Float64Array<ArrayBufferLike> = new Float64Array(0) // pos x/y/z, yaw, pitch, fov, time
let vp: Float32Array<ArrayBufferLike> = new Float32Array(0) // the view-projection matrix
let vis: Int32Array<ArrayBufferLike> = new Int32Array(0) // visible chunk indices
let mob: Float32Array<ArrayBufferLike> = new Float32Array(0) // visible mob transforms (MOB_FLOATS each)
let times: Float64Array<ArrayBufferLike> = new Float64Array(0) // per-worker band render ms
let lastWork = 0
@ -77,6 +80,7 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
cam = new Float64Array(new SharedArrayBuffer(7 * 8))
vp = new Float32Array(new SharedArrayBuffer(16 * 4))
vis = new Int32Array(new SharedArrayBuffer(maxVis * 4))
mob = new Float32Array(new SharedArrayBuffer(maxMobs * MOB_FLOATS * 4))
times = new Float64Array(new SharedArrayBuffer(bands.length * 8))
try {
bands.forEach((band, index) => {
@ -97,6 +101,7 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
camSAB: cam.buffer,
vpSAB: vp.buffer,
visSAB: vis.buffer,
mobSAB: mob.buffer,
timesSAB: times.buffer,
index,
})
@ -127,7 +132,7 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
config = next
setup()
},
dispatch(camera, viewProj, visible, time) {
dispatch(camera, viewProj, visible, time, mobDraws) {
if (parallel && workers.length > 0) {
cam[0] = camera.position.x
cam[1] = camera.position.y
@ -141,14 +146,26 @@ export function createRenderer(scene: Scene, initial: RenderConfig, forceWorkers
for (let i = 0; i < count; i++) {
vis[i] = visible[i]
}
const mobCount = Math.min(mobDraws.length, maxMobs)
for (let i = 0; i < mobCount; i++) {
const d = mobDraws[i]
const o = i * MOB_FLOATS
mob[o] = d.kind === "bee" ? 1 : 0
mob[o + 1] = d.x
mob[o + 2] = d.y
mob[o + 3] = d.z
mob[o + 4] = d.heading
mob[o + 5] = d.scale
}
Atomics.store(ctrl, VIS, count)
Atomics.store(ctrl, MOBVIS, mobCount)
Atomics.store(ctrl, DONE, 0)
Atomics.add(ctrl, FRAME, 1)
Atomics.notify(ctrl, FRAME, workers.length)
return
}
const t0 = performance.now()
renderBand(fb, scene, camera, viewProj, visible, config, SKY_STEP, time, 0, fb.height)
renderBand(fb, scene, camera, viewProj, visible, mobDraws, config, SKY_STEP, time, 0, fb.height)
lastWork = performance.now() - t0
},
done() {