Seed-deterministic, baked at spawn, emitted straight into the 24-byte packed vertex format. Two devices given the same seed produce byte-identical geometry — which is what lets a forest replicate as (preset, seed, position) tuples instead of mesh data. - L-system plants: expansion + 3D turtle + skeleton, 8 species (oak, pine, palm, bush, fern, cactus, dead, grass) - Surface nets (not marching cubes — fewer, better-shaped triangles at low-poly) for rocks, boulders, mushrooms, clouds, blobs - Spline tracks with width, banking, curbs and rails, returning centreline frames that carry lap distance — so spawn points and checkpoints derive from the track instead of a second hand-written list - Poisson-disk scatter with flatness/height rules - Texture generator with CPU mip chains (backends never generate them) - LRU cache keyed by FNV-1a over the full recipe with floats hashed by exact bits; -0.0 and 0.0 normalise together (identical geometry), 4.0 and 4.000001 stay distinct. Meshes hand out as Rc so eviction cannot pull geometry out from under a frame mid-draw - DrawGameFoliage: growth and wind as an OPT-IN shader variant, a sibling of DrawGameSkinned rather than a flag inside the shared shaders — wind costs ~20 vertex ALU and the cube shader draws most of the world. Growth and flex weights share one packed nibble pair, so both animations cost zero extra vertex bytes A realistic forest — 150x150 m, 582 trees, 3 species, 6 seeds — generates in 1.70 ms with 470 KB resident: 6 generations and 576 cache hits. Three bugs the unit tests had passed, found by writing an ASCII silhouette probe because captures were out of scope: every species came out ~4x its requested height (the test compared two sizes RELATIVELY, so a uniform overshoot sailed through — now the finished skeleton is measured and rescaled, and the test asserts absolute height for all 8 species at three sizes); palm emitted no foliage at all because its L-system contained no leaf symbol; and cactus sprawled sideways like a shrub. Pine also dropped from 4 iterations to 3 — 15032 -> 2504 triangles, 728 -> 68 us — because 15k triangles for one background tree is indefensible. Honest caveat: that is a silhouette judgement, not a rendered one. Shading, leaf-card orientation and the wind/growth animation are visually unverified. Script verbs are not wired yet — the crate is a library only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
136 lines
3.8 KiB
Rust
136 lines
3.8 KiB
Rust
//! Generation cost and triangle counts. `cargo run --release -p makepad-game-gen --example costs`
|
|
//!
|
|
//! Release numbers only — debug is 10-30x slower and would mislead anyone
|
|
//! sizing a Quest budget from this table.
|
|
|
|
use makepad_game_gen::*;
|
|
use std::time::Instant;
|
|
|
|
fn bench(label: &str, tris: usize, bytes: usize, f: impl Fn()) {
|
|
// Warm once so the first allocation isn't counted as generation cost.
|
|
f();
|
|
let n = 20;
|
|
let t = Instant::now();
|
|
for _ in 0..n {
|
|
f();
|
|
}
|
|
let us = t.elapsed().as_secs_f64() * 1.0e6 / n as f64;
|
|
println!("{label:<26} {us:>9.1} us {tris:>7} tris {:>8} B", bytes);
|
|
}
|
|
|
|
fn main() {
|
|
println!("--- trees (height 4, Medium LOD) ---");
|
|
for name in SPECIES {
|
|
let p = TreeParams {
|
|
seed: 1,
|
|
..Default::default()
|
|
};
|
|
let m = tree(name, p);
|
|
bench(name, m.triangle_count(), m.gpu_bytes(), || {
|
|
let _ = tree(name, p);
|
|
});
|
|
}
|
|
|
|
println!("\n--- tree LOD (oak) ---");
|
|
for (label, lod) in [("oak Low", Lod::Low), ("oak Medium", Lod::Medium), ("oak High", Lod::High)]
|
|
{
|
|
let p = TreeParams {
|
|
seed: 1,
|
|
lod,
|
|
..Default::default()
|
|
};
|
|
let m = tree("oak", p);
|
|
bench(label, m.triangle_count(), m.gpu_bytes(), || {
|
|
let _ = tree("oak", p);
|
|
});
|
|
}
|
|
|
|
println!("\n--- blobs (surface nets, resolution 16) ---");
|
|
for kind in BLOBS {
|
|
let p = BlobParams {
|
|
seed: 1,
|
|
..Default::default()
|
|
};
|
|
let m = blob(kind, p);
|
|
bench(kind, m.triangle_count(), m.gpu_bytes(), || {
|
|
let _ = blob(kind, p);
|
|
});
|
|
}
|
|
|
|
println!("\n--- track (closed oval, 10 control points) ---");
|
|
for res in [4usize, 8, 16] {
|
|
let s = oval(120.0, 80.0, 10);
|
|
let p = TrackParams {
|
|
resolution: res,
|
|
rail_height: 1.0,
|
|
..Default::default()
|
|
};
|
|
let t = track(&s, p);
|
|
bench(
|
|
&format!("track res={res}"),
|
|
t.mesh.triangle_count(),
|
|
t.mesh.gpu_bytes(),
|
|
|| {
|
|
let _ = track(&s, p);
|
|
},
|
|
);
|
|
}
|
|
|
|
println!("\n--- textures (256px + full mip chain) ---");
|
|
for name in MATERIALS {
|
|
let chain = material_mipped(name, 256, 1);
|
|
bench(name, 0, chain.total_bytes(), || {
|
|
let _ = material_mipped(name, 256, 1);
|
|
});
|
|
}
|
|
|
|
println!("\n--- scatter ---");
|
|
for (label, spacing, extent) in [
|
|
("scatter 60x60 s=4", 4.0f32, 60.0f32),
|
|
("scatter 120x120 s=4", 4.0, 120.0),
|
|
("scatter 200x200 s=6", 6.0, 200.0),
|
|
] {
|
|
let p = ScatterParams {
|
|
seed: 1,
|
|
spacing,
|
|
extent: makepad_math::vec2f(extent, extent),
|
|
max_count: 20_000,
|
|
..Default::default()
|
|
};
|
|
let n = scatter(&p).len();
|
|
bench(&format!("{label} ({n})"), 0, 0, || {
|
|
let _ = scatter(&p);
|
|
});
|
|
}
|
|
|
|
println!("\n--- a realistic forest ---");
|
|
let t = Instant::now();
|
|
let mut cache = GenCache::default();
|
|
let placements = scatter(&ScatterParams {
|
|
seed: 7,
|
|
spacing: 5.0,
|
|
extent: makepad_math::vec2f(150.0, 150.0),
|
|
variants: 6,
|
|
..Default::default()
|
|
});
|
|
let species = ["oak", "pine", "bush"];
|
|
for p in &placements {
|
|
let sp = species[(p.variant as usize) % species.len()];
|
|
cache.tree(
|
|
sp,
|
|
TreeParams {
|
|
seed: p.variant as u64,
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
let ms = t.elapsed().as_secs_f64() * 1000.0;
|
|
println!(
|
|
"{} trees placed, {} distinct meshes generated ({} hits), {:.2} ms total, {} KB resident",
|
|
placements.len(),
|
|
cache.misses(),
|
|
cache.hits(),
|
|
ms,
|
|
cache.bytes() / 1024
|
|
);
|
|
}
|