Skip to main content

unlit_wgpu_test_util/
gpu_info.rs

1//! The device a test run got, described in wgpu's own terms.
2//!
3//! A snapshot mismatch is usually a difference between one driver and another,
4//! so the first thing to establish about a failing run is which driver it ran on:
5//! the backend, the adapter, the features and the limits, all of which change what
6//! a frame records. The same description is what a passing run leaves behind,
7//! because a comparison against a stored frame is only meaningful against the
8//! device the stored frame came from.
9//!
10//! The types are wgpu's and its `serde` feature describes them, so nothing here
11//! formats a value by hand: the description is the adapter, features and limits
12//! serialized as wgpu already spells them, which is what keeps a printed run and
13//! a `wgpu-info` reading comparable.
14
15use serde::Serialize;
16
17/// The device a test run draws with, as a serializable record.
18///
19/// [`Self::read`] is the whole of it; the structure of the JSON is the field
20/// list below.
21#[derive(Debug, Serialize)]
22pub struct GpuInfo {
23    /// The tier the device was requested at: `webgpu`, `webgl2` or `native`.
24    ///
25    /// A tier is what the run *asked* for, so it is not visible in the device:
26    /// a WebGL2-shaped device on a desktop adapter reports the adapter's
27    /// features. The limits and the capabilities below are what tell the two
28    /// apart.
29    pub tier: String,
30    /// Whether an indexed draw may use a non-zero `base_vertex`, which is what
31    /// WebGL2 lacks.
32    ///
33    /// wgpu keeps this apart from the device, so it is carried here rather
34    /// than derived; see [`DeviceCapabilities`](crate::DeviceCapabilities).
35    pub base_vertex: bool,
36    /// The adapter: backend, name, vendor, driver and device type.
37    pub adapter: wgpu::AdapterInfo,
38    /// The features the device was created with.
39    pub features: wgpu::Features,
40    /// The limits the device was created with.
41    pub limits: wgpu::Limits,
42}
43
44impl GpuInfo {
45    /// Read the device a context holds.
46    ///
47    /// The tier comes from the context rather than from the device: a device
48    /// cannot report which limits were asked for, only what it was given.
49    pub fn read(ctx: &crate::Ctx) -> Self {
50        Self {
51            tier: ctx.tier.to_string(),
52            base_vertex: ctx.capabilities.base_vertex(),
53            adapter: ctx.device.adapter_info(),
54            features: ctx.device.features(),
55            limits: ctx.device.limits(),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    /// A record with values that need no GPU.
65    fn sample() -> GpuInfo {
66        GpuInfo {
67            tier: "webgpu".to_owned(),
68            base_vertex: true,
69            adapter: wgpu::AdapterInfo::new(wgpu::DeviceType::Cpu, wgpu::Backend::Vulkan),
70            features: wgpu::Features::empty(),
71            limits: wgpu::Limits::defaults(),
72        }
73    }
74
75    #[test]
76    fn the_description_is_wgpus_own_spelling() {
77        let value = serde_json::to_value(sample()).expect("a serializable description");
78        let object = value.as_object().expect("an object at the top level");
79        let keys: Vec<&String> = object.keys().collect();
80        assert_eq!(
81            keys,
82            ["adapter", "base_vertex", "features", "limits", "tier"],
83            "the record's own fields come first, alphabetically by serde_json's map"
84        );
85
86        // The limits are wgpu's, whose names are the fields it declares rather
87        // than anything this crate chose: every one of them is carried, so a
88        // reader gets the same table a `wgpu-info` run prints.
89        let limits = object["limits"].as_object().expect("an object of limits");
90        let declared =
91            serde_json::to_value(wgpu::Limits::defaults()).expect("wgpu's own serialization");
92        assert_eq!(limits, declared.as_object().expect("an object of limits"));
93        assert!(limits.contains_key("maxTextureDimension2D"));
94
95        // `AdapterInfo`'s fields arrive under their own names too, not under a
96        // summary this crate would have had to invent.
97        let adapter = object["adapter"].as_object().expect("an object");
98        assert!(adapter.contains_key("device_type"));
99        assert!(adapter.contains_key("backend"));
100    }
101
102    #[test]
103    fn an_empty_feature_set_is_an_empty_string() {
104        // `Features` is a bitflags set, which serializes as `"A | B"`: the
105        // empty set has nothing to join, so it is the empty string rather than
106        // an empty list. A reader diffing two runs sees the names themselves.
107        let value = serde_json::to_value(wgpu::Features::empty()).expect("serializable");
108        assert_eq!(value, serde_json::Value::String(String::new()));
109    }
110
111    #[test]
112    fn the_tier_is_its_name() {
113        for tier in crate::DeviceTier::ALL {
114            let info = GpuInfo {
115                tier: tier.to_string(),
116                ..sample()
117            };
118            let value = serde_json::to_value(info).expect("serializable");
119            assert_eq!(value["tier"], serde_json::Value::String(tier.to_string()));
120        }
121    }
122}