CLI, along with several examples, plus model downloading

This commit is contained in:
connor committed 2026-10-14 18:01:49 -04:00
1 parent 4f9e73a418
commit 51ace681a7
22 files changed
+1804 -18

No files matched your search

+130
View File
@@ -0,0 +1,130 @@
//! Runs the `resolved` binary on the example scenes.
use std::path::{Path, PathBuf};
use std::process::Command;
fn repo() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn out_dir() -> PathBuf {
let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("cli");
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// Runs the binary from the repository root with `ASTROKIT_KERNELS` cleared, so the
/// kernel search falls back the way it does for someone who has not set it.
fn resolved(args: &[&str]) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_resolved"))
.args(args)
.current_dir(repo())
.env_remove("ASTROKIT_KERNELS")
.output()
.unwrap()
}
/// Renders `scene` on `adapter` to a 16-bit PNG and returns its pixels as DN (the
/// file holds 12-bit DN shifted up by 4).
fn render(scene: &str, adapter: &str) -> image::ImageBuffer<image::Rgb<u16>, Vec<u16>> {
let out = out_dir().join(format!(
"{}-{adapter}.png",
Path::new(scene).file_stem().unwrap().to_str().unwrap()
));
let preview = out.with_extension("preview.png");
let result = resolved(&[
"render",
"--scene",
scene,
"--adapter",
adapter,
"--out",
out.to_str().unwrap(),
"--preview",
preview.to_str().unwrap(),
]);
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
assert!(preview.is_file());
let mut pixels = image::open(&out).unwrap().into_rgb16();
for p in pixels.pixels_mut() {
assert!(p.0.iter().all(|v| v & 0xf == 0), "low 4 bits are padding");
p.0 = p.0.map(|v| v >> 4);
}
pixels
}
#[test]
fn example_scene_on_gpu_and_cpu() {
let gpu = render("examples/scenes/boxwing.toml", "gpu");
let cpu = render("examples/scenes/boxwing.toml", "cpu");
assert_eq!(gpu.dimensions(), (1920, 1080));
// The image center is on the sunlit gold bus: brighter in red than in blue.
let [r, _, b] = gpu.get_pixel(960, 540).0;
assert!(r > 500 && r > 2 * b, "{r} {b}");
// Above the limb is empty space, which reads the 64 DN bias; below it, Earth's
// dayside.
assert_eq!(gpu.get_pixel(0, 0).0, [64; 3]);
assert!(gpu.get_pixel(0, 1079).0.iter().all(|&d| d > 300));
let differing = gpu
.pixels()
.zip(cpu.pixels())
.filter(|(a, b)| a.0.iter().zip(b.0).any(|(x, y)| x.abs_diff(y) > 1))
.count();
// Only pixels on silhouette edges may land differently.
assert!(
differing < 500,
"{differing} pixels differ between GPU and CPU"
);
}
#[cfg(feature = "astrokit")]
#[test]
fn epoch_scene_renders() {
let frame = render("examples/scenes/boxwing-epoch.toml", "gpu");
// The same view as boxwing.toml, rotated to the real Sun: the sunlit bus at the
// center, space above the limb.
assert!(frame.get_pixel(960, 540).0[0] > 500);
assert_eq!(frame.get_pixel(0, 0).0, [64; 3]);
}
#[test]
fn errors_are_reported() {
let missing = resolved(&["render", "--scene", "no/such.toml", "--preview", "x.png"]);
assert!(!missing.status.success());
assert!(String::from_utf8_lossy(&missing.stderr).contains("no/such.toml"));
let idle = resolved(&["render", "--scene", "examples/scenes/boxwing.toml"]);
assert!(!idle.status.success());
assert!(String::from_utf8_lossy(&idle.stderr).contains("nothing to do"));
}
#[cfg(feature = "astrokit")]
#[test]
fn missing_kernels_say_where_they_looked() {
let result = resolved(&[
"render",
"--scene",
"examples/scenes/boxwing-epoch.toml",
"--kernels",
"no/such/dir",
"--preview",
"x.png",
]);
assert!(!result.status.success());
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
stderr.contains("--kernels") && stderr.contains("no/such/dir"),
"{stderr}"
);
}
#[test]
fn adapters_lists_something() {
let result = resolved(&["adapters"]);
assert!(result.status.success());
assert!(!result.stdout.is_empty());
}