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}