use rusting_engine::rendering::benchmark::{
gpu_busy_percent, repeat_spread, run_render_benchmark_with,
RenderBenchmarkReport,
};
use rusting_engine::rendering::init_vulkan_headless;
use rusting_engine::runtime::{
QualityProfile, RenderBenchmarkExtras, RENDER_BENCHMARK_FRAMES,
};
fn main() {
let mut extras = RenderBenchmarkExtras {
screen_size: [320, 180],
..RenderBenchmarkExtras::default()
};
let mut extent = [1920, 1080];
let mut repeats = 1;
let mut positional = Vec::new();
let mut arguments = std::env::args().skip(1);
while let Some(argument) = arguments.next() {
let mut value = || {
arguments
.next()
.unwrap_or_else(|| panic!("{argument} needs a value"))
};
let size = |text: String| -> [u32; 2] {
let (w, h) = text.split_once('x').expect("size is WxH");
[w.parse().expect("width"), h.parse().expect("height")]
};
match argument.as_str() {
"--screens" => extras.screens = value().parse().expect("count"),
"--screen-size" => extras.screen_size = size(value()),
"--bears" => extras.bears = value().parse().expect("count"),
"--bear-updates" => {
extras.bear_updates = value().parse().expect("count");
}
"--screen-every" => {
extras.screen_every = value().parse().expect("frames");
}
"--no-bodies" => extras.without_bodies = true,
"--lights" => extras.lights = value().parse().expect("count"),
"--city" => extras.city = value().parse().expect("count"),
"--effects" => extras.effects = true,
"--probes" => extras.probes = true,
"--rain" => extras.rain = true,
"--extent" => extent = size(value()),
"--repeat" => repeats = value().parse().expect("count"),
_ => positional.push(argument),
}
}
let mut arguments = positional.into_iter();
let quality = match arguments.next().as_deref().unwrap_or("auto") {
"auto" => QualityProfile::Auto,
"eco" => QualityProfile::Eco,
"balanced" => QualityProfile::Balanced,
"high" => QualityProfile::High,
other => {
panic!("unknown quality `{other}`: auto, eco, balanced or high")
}
};
let baseline = arguments.next();
let base = init_vulkan_headless();
let mut runs: Vec<RenderBenchmarkReport> = (0..repeats.max(1))
.map(|_| {
let busy = gpu_busy_percent();
let mut report = run_render_benchmark_with(
base.queue.clone(),
extent,
quality,
RENDER_BENCHMARK_FRAMES,
extras,
)
.unwrap_or_else(|error| panic!("benchmark failed: {error}"));
report.gpu_busy_percent =
busy.zip(gpu_busy_percent()).map(Into::into);
if let Some(before) = busy.filter(|busy| *busy > 10) {
eprintln!("warning: the GPU was {before}% busy before the run");
}
report
})
.collect();
let means: Vec<f64> =
runs.iter().map(|run| run.frame_time.mean_ms).collect();
let (min, median, spread) = repeat_spread(&means);
let mut report = runs
.swap_remove(means.iter().position(|mean| *mean == median).unwrap());
if means.len() > 1 {
eprintln!(
"{} runs: mean frame min {min:.2} ms, median {median:.2} ms, spread {:.0}%",
means.len(),
spread * 100.0
);
if spread > 0.2 {
eprintln!("warning: the runs spread over 20%; the GPU or CPU was not steady");
}
report.repeat_frame_means_ms = means;
}
let json = serde_json::to_string_pretty(&report).unwrap();
println!("{json}");
let Some(path) = baseline else { return };
match std::fs::read_to_string(&path) {
Ok(stored) => {
let stored: RenderBenchmarkReport = serde_json::from_str(&stored)
.unwrap_or_else(|error| panic!("{path}: {error}"));
if !report.same_machine(&stored) {
eprintln!(
"times not compared: {} {} against baseline {} {}",
report.device, report.driver, stored.device, stored.driver
);
for drift in report.time_growth(&stored) {
eprintln!("warning: {drift}");
}
}
let regressions = report.regressions(&stored);
if regressions.is_empty() {
eprintln!("no regressions against {path}");
} else {
for regression in ®ressions {
eprintln!("regression: {regression}");
}
std::process::exit(1);
}
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
std::fs::write(&path, json + "\n")
.unwrap_or_else(|error| panic!("{path}: {error}"));
eprintln!("wrote baseline {path}");
}
Err(error) => panic!("{path}: {error}"),
}
}