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
|
shared-key: ci
|
||||||
save-if: ${{ github.ref == 'refs/heads/develop' }}
|
save-if: ${{ github.ref == 'refs/heads/develop' }}
|
||||||
- name: Software GPU (lavapipe), so headless snapshots run the GPU pipeline
|
- 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)
|
- name: cargo xtask ci (the affected crates, or everything)
|
||||||
env:
|
env:
|
||||||
BASE: ${{ needs.plan.outputs.base }}
|
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: |
|
run: |
|
||||||
if [ -n "$BASE" ]; then cargo xtask ci --affected "$BASE"; else cargo xtask ci; fi
|
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
|
/// 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
|
/// 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.
|
/// on the CPU, and both pictures show the same model. A section and a field change it.
|
||||||
#[test]
|
#[test]
|
||||||
fn snapshots_the_model_and_its_mesh_headless() {
|
fn snapshots_the_model_and_its_mesh_headless() {
|
||||||
@@ -243,6 +243,8 @@ mod tests {
|
|||||||
assert!(edges > 100, "{drawn:?}: {edges} edge pixels");
|
assert!(edges > 100, "{drawn:?}: {edges} edge pixels");
|
||||||
if policy == Policy::Cpu {
|
if policy == Policy::Cpu {
|
||||||
assert_eq!(drawn, Renderer::Cpu { why: None });
|
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.
|
// 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
|
/// 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
|
/// the edge's black on the white background. A machine with no adapter is reported, not
|
||||||
/// a software GPU) is reported, not failed: `auto` then draws on the CPU.
|
/// failed (`auto` then draws on the CPU), unless `FIELDCRAFT_REQUIRE_GPU=1` (CI).
|
||||||
#[test]
|
#[test]
|
||||||
fn draws_offscreen_when_an_adapter_exists() {
|
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(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"),
|
Ok(None) => return eprintln!("no GPU adapter: skipped"),
|
||||||
Err(e) => return eprintln!("no GPU device ({e}): skipped"),
|
Err(e) => return eprintln!("no GPU device ({e}): skipped"),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ impl Policy {
|
|||||||
|
|
||||||
pub use crate::Tier;
|
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).
|
/// An adapter's tier and its rank within it (lower is better).
|
||||||
pub fn rank(t: wgpu::DeviceType) -> (Tier, u8) {
|
pub fn rank(t: wgpu::DeviceType) -> (Tier, u8) {
|
||||||
match t {
|
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}");
|
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.
|
// 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}");
|
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}));
|
let (again, _) = picture(&mut s, json!({"width": 64, "height": 48}));
|
||||||
assert_eq!(again, turned);
|
assert_eq!(again, turned);
|
||||||
// Aimed at a point, the camera looks at it; a section cuts the mesh.
|
// Aimed at a point, the camera looks at it; a section cuts the mesh.
|
||||||
|
|||||||
Reference in new issue
Block a user