Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
//! Benchmark for the Stage 5 schedule/module overhead.
|
||||
//!
|
||||
//! Stage 5 criterion: module/system scheduling overhead must be negligible
|
||||
//! compared to the Stage-4 hardcoded loop. There is no per-frame work here — the
|
||||
//! benchmark measures the *frame overhead itself*: advancing timing, walking the
|
||||
//! phase lists, and the fixed-timestep accumulator, with a realistic handful of
|
||||
//! empty systems registered. Check that `app_empty_update` is in the low
|
||||
//! nanoseconds (i.e. lost in the noise next to any real system's work).
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use oxide_engine::app::{App, Schedule};
|
||||
|
||||
fn empty_update(c: &mut Criterion) {
|
||||
let mut app = App::new();
|
||||
// A few no-op systems spread across phases, as a trivial game might have.
|
||||
for _ in 0..4 {
|
||||
app.add_system(Schedule::Update, |_| {});
|
||||
}
|
||||
app.add_system(Schedule::FixedUpdate, |_| {});
|
||||
app.add_system(Schedule::Render, |_| {});
|
||||
|
||||
c.bench_function("app_empty_update", |bencher| {
|
||||
bencher.iter(|| {
|
||||
app.update(black_box(1.0 / 60.0));
|
||||
black_box(app.time.frame)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, empty_update);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,58 @@
|
||||
//! Benchmark for scene world-transform resolution.
|
||||
//!
|
||||
//! Stage 3 test criterion: a 10,000-entity scene with a 5-level-deep hierarchy
|
||||
//! must resolve all world transforms in under 1ms. The `world_transforms_10k`
|
||||
//! benchmark builds exactly that scene and measures a full bulk resolve; check
|
||||
//! its reported time against the 1ms budget.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use oxide_engine::math::{Transform, Vec3};
|
||||
use oxide_engine::scene::{Entity, Scene};
|
||||
|
||||
/// Builds a scene of `total` entities arranged as a `depth`-level hierarchy.
|
||||
///
|
||||
/// Level 0 holds the roots; each subsequent level's entities are distributed as
|
||||
/// children of the previous level, so the tree is `depth` levels deep and the
|
||||
/// node count is exactly `total`.
|
||||
fn build_scene(total: usize, depth: usize) -> Scene {
|
||||
let mut scene = Scene::new();
|
||||
let per_level = total / depth;
|
||||
let mut previous: Vec<Entity> = Vec::new();
|
||||
|
||||
for level in 0..depth {
|
||||
// The last level absorbs any remainder so the count is exact.
|
||||
let count = if level == depth - 1 {
|
||||
total - per_level * (depth - 1)
|
||||
} else {
|
||||
per_level
|
||||
};
|
||||
let mut current = Vec::with_capacity(count);
|
||||
for i in 0..count {
|
||||
let t = Transform::from_translation(Vec3::new(0.01 * i as f32, 0.02, 0.03));
|
||||
let entity = if previous.is_empty() {
|
||||
scene.spawn("n", t)
|
||||
} else {
|
||||
// Spread children across the previous level round-robin.
|
||||
scene.spawn_child(previous[i % previous.len()], "n", t)
|
||||
};
|
||||
current.push(entity);
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
scene
|
||||
}
|
||||
|
||||
fn world_transforms_10k(c: &mut Criterion) {
|
||||
let scene = build_scene(10_000, 5);
|
||||
assert_eq!(scene.len(), 10_000);
|
||||
|
||||
c.bench_function("world_transforms_10k_depth5", |bencher| {
|
||||
bencher.iter(|| {
|
||||
let resolved = scene.world_transforms();
|
||||
black_box(resolved.len())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, world_transforms_10k);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Benchmark for transform composition.
|
||||
//!
|
||||
//! Stage 1 test criterion: 1M transform multiplications must complete under
|
||||
//! 10ms. The `compose_1m` benchmark below measures exactly that workload; check
|
||||
//! its reported time against the 10ms budget.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use oxide_engine::math::{Quat, Transform, Vec3};
|
||||
|
||||
fn compose_1m(c: &mut Criterion) {
|
||||
// A representative non-trivial transform (uniform scale → exact fast path).
|
||||
let a = Transform::from_trs(
|
||||
Vec3::new(1.0, 2.0, 3.0),
|
||||
Quat::from_euler(glam::EulerRot::XYZ, 0.3, 0.5, 0.7),
|
||||
Vec3::splat(1.5),
|
||||
);
|
||||
let b = Transform::from_trs(
|
||||
Vec3::new(-2.0, 0.5, 4.0),
|
||||
Quat::from_rotation_y(0.9),
|
||||
Vec3::splat(0.8),
|
||||
);
|
||||
|
||||
c.bench_function("compose_1m", |bencher| {
|
||||
bencher.iter(|| {
|
||||
// Compose 1M times. Inputs are re-fetched through `black_box` each
|
||||
// iteration so the optimizer can neither hoist the call nor let the
|
||||
// accumulated values blow up to infinity; the product is consumed.
|
||||
let mut acc = Vec3::ZERO;
|
||||
for _ in 0..1_000_000 {
|
||||
let product = black_box(a).mul_transform(&black_box(b));
|
||||
acc += product.translation;
|
||||
}
|
||||
black_box(acc)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn point_transform_1m(c: &mut Criterion) {
|
||||
let t = Transform::from_trs(
|
||||
Vec3::new(1.0, 2.0, 3.0),
|
||||
Quat::from_rotation_z(0.6),
|
||||
Vec3::splat(2.0),
|
||||
);
|
||||
c.bench_function("transform_point_1m", |bencher| {
|
||||
bencher.iter(|| {
|
||||
let mut acc = Vec3::ZERO;
|
||||
for i in 0..1_000_000u32 {
|
||||
let p = Vec3::splat(i as f32 * 1e-6);
|
||||
acc += t.transform_point(black_box(p));
|
||||
}
|
||||
black_box(acc)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, compose_1m, point_transform_1m);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user