Conversation
Skinned, tet, displaced and grass meshes have always deformed inside the frame command buffer. A PLAIN mesh whose attributes are rewritten every frame — Flock's merged bird mesh, a CPU trail, a host-driven soft body — fell to the occasional-edit path instead and paid, per dirty frame, one vkDeviceWaitIdle plus two one-shot submit+vkQueueWaitIdle pairs. Three full device drains to move 1.7k vertices, and the drains cost roughly a GPU frame each because that is what they wait for. The particle billboards' explicit "never flag them geomDirty, it would fire a per-frame vkDeviceWaitIdle" exclusion was a patch over the same hole. A BlasRecord dirty kDynamicGraduationStreak (3) frames in a row now graduates to perFrameDynamic for the rest of its life and takes recordDynamicGeomRefits instead: positions and normals are packed into a per-frames-in-flight staging ring at RECORD time — past that slot's fence, which is what makes the host write provably not race the GPU copy a still-in-flight frame issued from the same slot — then the vertex->prevVertex snapshot, the staging->vertex/normal copies and the batched BLAS refit are recorded into the frame cb with barriers. Zero submits, zero waits. Graduation is one-way; a topology change destroys the record and its replacement starts cold. refreshGeomBlasBatch and its drain remain for genuinely occasional edits, and only run when a non-graduated op needs them. No cross-frame WAR fence before the copies, on purpose. The prior frame may still be reading vertex/normal when they execute, but a barrier wide enough to cover every reader (deferred_shade's ray-query fetches are COMPUTE) also fences the previous frame's whole post chain: measured +6 ms/frame, handing back everything the drain removal bought. Every existing deformer already writes its BLAS buffers under exactly this exposure; this path matches their contract rather than inventing a stricter one it cannot afford. Auto-LOD stops churning on deforming geometry. A chain enqueued while a mesh is being edited is stale on arrival — drainLodResults drops it on the geomVersion mismatch, the dirty pass resets lodState, selection re-enqueues, forever — so a full attribute snapshot plus a background simplification were burned every frame and nothing was ever selectable. Selection now requires a quiet window (kLodDirtyQuietFrames, 8) since the last edit and skips graduated records outright. Measured on flock_demo --vulkan, 24 birds, RTX 4070, headless with presents suppressed, slopes taken at 600/1200/2400 frames so the teardown constant falls out: 5.11 -> 4.53 ms/frame, against a 4.40 ms GPU floor the new path now sits on (frame.0_fenceWait absorbs the rest, which is what healthy GPU-bound pipelining looks like). The win scales with GPU frame time, since that is what a drain waits for. Naive wall-clock A/B says the opposite and is wrong three times over: an occluded window's present paces the whole pipelined loop, a per-frame device drain accidentally bypasses that pacing, and a pipelined exit carries a ~4.3 s teardown constant an empty scene pays too. THREEPP_VULKAN_SUPPRESS_PRESENT only engages on a headless canvas. Verified: editor --selftest ALL PASS on GL and Vulkan; flock_demo --vulkan strict validation clean (exit 0); GL and Vulkan captures of the same deterministic frame agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flock.hpp states three properties in prose and nothing enforced any of them. Flock_test does, cheaply enough (0.3 s) to sit in every run. Determinism is the headline claim and gets an EXACT float comparison, because "bit-identical for the same binary, the same seed and the same dt sequence" is what the banner promises and an epsilon would test a weaker promise. The replay drives a three-period dt cycle so dtSmoothed never settles and a mid-run startle so the disturbance path is covered, then a second flock on a different seed must DIVERGE — without that control the identity check would also pass for a flock pinned motionless at home. The perch bake's blocking and amortised forms must produce byte-equal tables, which is the whole reason PerchIndex budgets by work-unit count rather than milliseconds: a time budget would make a bird's trajectory depend on machine speed. PerchSpot's defaulted operator== takes that claim literally. The soak asserts a landing actually FIRES, which is the bug class --selftest was built around: a state machine that compiles, runs, produces no NaN and never once perches. One fixture lesson worth stating, because it cost an hour and is not a library bug: authored perches packed closer than about twice separationDistance, with half the flock committed at once, turn the final-metre capture into a shoving match no approach survives — zero landings in 40 s. The first rail put 8 spots 2 m apart in a line; the same 8 on a 14 m circle land immediately. Baked scenes scatter spots and never hit it; a designer authoring one railing through addPerch can. Flock also opts out of renderer auto-LOD. The whole mesh is rebaked in place every update(), so a chain would simplify a pose one frame from gone. The renderer now refuses to build one for a per-frame deformer anyway; this is the feature saying so itself, the way terrain tiles do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add > Flock creates a Group carrying userData["flock"], and the node's POSITION is the territory's home — the transform gizmo is the authoring tool for where the birds live, the way a particle field's node is its emitter frame. The birds are never document nodes: FlockPlaySession builds one Flock per authored node at Play, adds it at the scene root (the simulation is world-space and the mesh wants an identity parent, which Flock.hpp states in capitals), starts an AMORTISED perch bake so a heavy scene costs a few frames of birds-not-landing-yet instead of a hitch on the button, and Stop restores a document that never saw them. A saved scene therefore carries eleven scalars, not 2,256 vertices of bird, and save/load/prefabs/.tpz work for free because the config rides the same userData channel Tree and Granular already round-trip. THE PERCH BAKE MUST NOT SEE THE EDITOR'S OWN CHROME, and this is the bug the wiring was written around. Gizmo handles, light markers and waypoint pucks are real meshes in the graph the bake traverses; heightAt reports the highest sampled surface in a column, so a marker hovering at altitude becomes the ground under it and the floor clamp launches the whole flock onto a phantom floor. Measured over the flat template scene: centroid y=12 -> y=430 within one second of the bake completing, which on Vulkan reads as the birds vanishing upward and on GL as them behaving strangely. FlockPlaySession::setMeshFilter carries the exclusion to Flock::setPerchFilter and EditorApp wires isEditorOnly. Any future session that bakes against the editor scene needs the same filter, so the selftest keeps a trap: after four seconds of play the birds must be under y=60 and above y=-5, bounds no honest cruise band reaches and only a poisoned bake crosses. Selecting a flock draws its extents through the existing spawn-box helper, which now serves three configs: the roam edge and the 0.75x ring where the bounds force starts to bite, the same circle again at expected-ground level with a drop line and tick (a tick off the real floor is the one spatial fact a misplaced flock node gets wrong), the altitude band at +/- cruiseAltitude*altitudeSpread, and the wind arrow. altitudeSpread is surfaced in the inspector for that reason: it was in Flock::Params, it is the thickness of the band the helper draws, and 0 flies a plane that reads as a formation rather than a flock. The new node spawns at y=14, the default cruise altitude, so a fresh flock over a ground-at-origin scene reads as placed right. Verified: editor --selftest ALL PASS on GL and Vulkan (factory, config round trip, undo, helper, and the phantom-floor trap), EditorFlockConfig_test 15 assertions, Flock_test 8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds a bird flock to threepp that can be used to populate scenes.