forked from 1b-agent/fieldcraft-upstream
ci: prove the lavapipe GPU path draws (FIELDCRAFT_REQUIRE_GPU=1)
The drawing tests skipped silently without an adapter. Under FIELDCRAFT_REQUIRE_GPU=1 they fail instead, and the snapshot and MCP screenshot tests require a GPU renderer. CI runs them with FIELDCRAFT_GPU=software on lavapipe. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
b905f1bc89
commit
c6bc4d2ba0
5 files changed
+24
-5
No files matched your search
@@ -133,10 +133,13 @@ jobs:
|
||||
shared-key: ci
|
||||
save-if: ${{ github.ref == 'refs/heads/develop' }}
|
||||
- name: Software GPU (lavapipe), so headless snapshots run the GPU pipeline
|
||||
run: sudo apt-get update -q && sudo apt-get install -y -q --no-install-recommends mesa-vulkan-drivers
|
||||
run: sudo apt-get update -q && sudo apt-get install -y -q --no-install-recommends mesa-vulkan-drivers libvulkan1
|
||||
- name: cargo xtask ci (the affected crates, or everything)
|
||||
env:
|
||||
BASE: ${{ needs.plan.outputs.base }}
|
||||
# The drawing tests run on lavapipe and fail, instead of skipping, without it.
|
||||
FIELDCRAFT_GPU: software
|
||||
FIELDCRAFT_REQUIRE_GPU: "1"
|
||||
run: |
|
||||
if [ -n "$BASE" ]; then cargo xtask ci --affected "$BASE"; else cargo xtask ci; fi
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ mod tests {
|
||||
|
||||
/// The cantilever drawn headless: a PNG of the size asked for, with the model in it (pixels
|
||||
/// of the hex colour) and its element edges (dark pixels). `auto` draws on the GPU when the
|
||||
/// machine has one and on the CPU otherwise (a CI box without a software GPU); `cpu` always
|
||||
/// machine has one and on the CPU otherwise (it must be the GPU under `FIELDCRAFT_REQUIRE_GPU=1`); `cpu` always
|
||||
/// on the CPU, and both pictures show the same model. A section and a field change it.
|
||||
#[test]
|
||||
fn snapshots_the_model_and_its_mesh_headless() {
|
||||
@@ -243,6 +243,8 @@ mod tests {
|
||||
assert!(edges > 100, "{drawn:?}: {edges} edge pixels");
|
||||
if policy == Policy::Cpu {
|
||||
assert_eq!(drawn, Renderer::Cpu { why: None });
|
||||
} else if fieldcraft_gpu::select::gpu_required() {
|
||||
assert!(matches!(drawn, Renderer::Gpu { .. }), "FIELDCRAFT_REQUIRE_GPU=1 but drawn by {drawn:?}");
|
||||
}
|
||||
}
|
||||
// A section along the beam draws half of it.
|
||||
|
||||
@@ -304,12 +304,14 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A picture on whatever adapter the machine has: the right size, the triangle's red and
|
||||
/// the edge's black on the white background. A machine with no adapter (a CI box without
|
||||
/// a software GPU) is reported, not failed: `auto` then draws on the CPU.
|
||||
/// the edge's black on the white background. A machine with no adapter is reported, not
|
||||
/// failed (`auto` then draws on the CPU), unless `FIELDCRAFT_REQUIRE_GPU=1` (CI).
|
||||
#[test]
|
||||
fn draws_offscreen_when_an_adapter_exists() {
|
||||
let cap = match Capturer::open(Policy::Auto) {
|
||||
let cap = match Capturer::open(Policy::from_env().unwrap()) {
|
||||
Ok(Some(c)) => c,
|
||||
Ok(None) if crate::select::gpu_required() => panic!("FIELDCRAFT_REQUIRE_GPU=1 but no GPU adapter"),
|
||||
Err(e) if crate::select::gpu_required() => panic!("FIELDCRAFT_REQUIRE_GPU=1 but no GPU device: {e}"),
|
||||
Ok(None) => return eprintln!("no GPU adapter: skipped"),
|
||||
Err(e) => return eprintln!("no GPU device ({e}): skipped"),
|
||||
};
|
||||
|
||||
@@ -46,6 +46,15 @@ impl Policy {
|
||||
|
||||
pub use crate::Tier;
|
||||
|
||||
/// Set to `1` where a GPU adapter must exist (CI with lavapipe): tests that draw then fail
|
||||
/// instead of skipping when none is found, so the GPU path is proven to draw.
|
||||
pub const REQUIRE_ENV: &str = "FIELDCRAFT_REQUIRE_GPU";
|
||||
|
||||
/// Whether [`REQUIRE_ENV`] is set to `1`.
|
||||
pub fn gpu_required() -> bool {
|
||||
std::env::var(REQUIRE_ENV).is_ok_and(|v| v.trim() == "1")
|
||||
}
|
||||
|
||||
/// An adapter's tier and its rank within it (lower is better).
|
||||
pub fn rank(t: wgpu::DeviceType) -> (Tier, u8) {
|
||||
match t {
|
||||
|
||||
@@ -222,6 +222,9 @@ fn screenshot_takes_a_camera_orientation() {
|
||||
assert_eq!(info["view"], json!({"yaw": 30.0, "pitch": 90.0, "distance": 1e9, "target": {"center_of": "mass"}}), "{info}");
|
||||
// The reply names what drew it: the viewport's pipeline on a GPU, or the CPU rasterizer.
|
||||
assert!(matches!(info["renderer"]["kind"].as_str(), Some("hardware" | "software" | "cpu")), "{info}");
|
||||
if fieldcraft_gpu::select::gpu_required() {
|
||||
assert!(matches!(info["renderer"]["kind"].as_str(), Some("hardware" | "software")), "FIELDCRAFT_REQUIRE_GPU=1: {info}");
|
||||
}
|
||||
let (again, _) = picture(&mut s, json!({"width": 64, "height": 48}));
|
||||
assert_eq!(again, turned);
|
||||
// Aimed at a point, the camera looks at it; a section cuts the mesh.
|
||||
|
||||
Reference in new issue
Block a user