henad-compute 0.3.0

CPU and GPU engines that run Henad models, with their sim threads and parallel primitives.
Documentation
//! Headless device for this crate's GPU tests.

use crate::fault::FaultSink;
use crate::gpu::GpuContext;

/// Set in CI to turn "no GPU here" from a skip into a failure.
const REQUIRE_GPU: &str = "HENAD_REQUIRE_GPU";

/// Returns whether `HENAD_REQUIRE_GPU` is set. Empty counts as unset, so a workflow matrix can blank it out on
/// runners without a GPU.
pub fn gpu_required() -> bool {
    std::env::var_os(REQUIRE_GPU).is_some_and(|v| !v.is_empty() && v != "0")
}

/// Returns a headless device, or `None` when this machine cannot provide one.
///
/// # Panics
///
/// Panics if [`REQUIRE_GPU`] is set and no adapter or device could be acquired. Missing
/// `required_features` still returns `None`, since a software rasteriser owes us nothing
/// optional.
pub fn headless_context(label: &str, required_features: wgpu::Features) -> Option<GpuContext> {
    let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());

    let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default()));
    if let Err(err) = &adapter {
        assert!(
            !gpu_required(),
            "{REQUIRE_GPU} is set but no wgpu adapter is available: {err}"
        );
    }
    let adapter = adapter.ok()?;

    if !adapter.features().contains(required_features) {
        return None;
    }

    let device = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
        label: Some(label),
        required_features,
        // No binding count. henad-compute cannot see the models, and its own tests bind few.
        required_limits: crate::gpu::limits::raise(&adapter, &wgpu::Limits::default(), crate::gpu::GpuNeeds::default()),
        ..Default::default()
    }));
    if let Err(err) = &device {
        assert!(
            !gpu_required(),
            "{REQUIRE_GPU} is set but adapter '{}' gave no device: {err}",
            adapter.get_info().name
        );
    }
    let (device, queue) = device.ok()?;

    Some(GpuContext::new(
        device,
        queue,
        wgpu::TextureFormat::Rgba8Unorm,
        FaultSink::new(),
    ))
}