1use super::{Api, BackendEntry, BackendFactory, BackendKind, MemKind, Reason, Source, Status};
5
6pub struct WgpuFactory;
8
9impl BackendFactory for WgpuFactory {
10 fn kind(&self) -> BackendKind {
11 BackendKind::Wgpu
12 }
13
14 fn discover(&self) -> Vec<BackendEntry> {
15 let adapters = crate::backends::gpu::runtime::block_on(async {
16 let instance = crate::backends::gpu::shared_instance();
17 instance
18 .enumerate_adapters(crate::backends::gpu::gpu_backends())
19 .iter()
20 .map(|a| (a.get_info(), a.limits()))
21 .collect::<Vec<_>>()
22 });
23 if adapters.is_empty() {
24 return vec![BackendEntry::unavailable(
25 BackendKind::Wgpu,
26 Api::Wgpu,
27 Source::CompiledIn,
28 Reason::NoDevice,
29 )];
30 }
31 adapters.iter().enumerate().map(|(i, (info, limits))| entry(i, info, limits)).collect()
32 }
33}
34
35fn vendor_name(vendor_id: u32) -> &'static str {
37 match vendor_id {
38 0x10de => "NVIDIA",
39 0x1002 => "AMD",
40 0x8086 => "Intel",
41 0x106b => "Apple",
42 _ => "unknown",
43 }
44}
45
46fn transport_name(backend: wgpu::Backend) -> &'static str {
48 match backend {
49 wgpu::Backend::Vulkan => "vulkan",
50 wgpu::Backend::Metal => "metal",
51 wgpu::Backend::Dx12 => "dx12",
52 wgpu::Backend::Gl => "gl",
53 wgpu::Backend::BrowserWebGpu => "webgpu",
54 wgpu::Backend::Noop => "noop",
55 }
56}
57
58fn classify(device_type: wgpu::DeviceType, name: &str) -> (&'static str, Status) {
61 match device_type {
62 wgpu::DeviceType::DiscreteGpu => ("discrete-gpu", Status::Ready),
63 wgpu::DeviceType::IntegratedGpu => ("integrated-gpu", Status::Ready),
64 wgpu::DeviceType::VirtualGpu => ("virtual-gpu", Status::Ready),
65 wgpu::DeviceType::Cpu => (
66 "software",
67 Status::Unavailable(Reason::NoBackend {
68 vendor: format!("software rasteriser ({name}) is not a GPU"),
69 }),
70 ),
71 wgpu::DeviceType::Other => ("other", Status::Ready),
72 }
73}
74
75fn mem_kind(
79 backend: wgpu::Backend,
80 device_type: wgpu::DeviceType,
81 limits: &wgpu::Limits,
82) -> MemKind {
83 let unified = matches!(backend, wgpu::Backend::Metal)
84 || matches!(device_type, wgpu::DeviceType::IntegratedGpu);
85 if unified {
86 MemKind::Unified { working_set_limit: Some(limits.max_buffer_size) }
87 } else {
88 MemKind::Discrete
89 }
90}
91
92#[allow(clippy::cast_possible_truncation)]
93fn entry(i: usize, info: &wgpu::AdapterInfo, limits: &wgpu::Limits) -> BackendEntry {
94 let vendor = vendor_name(info.vendor);
95 let (device_type, status) = classify(info.device_type, &info.name);
96 BackendEntry {
97 kind: BackendKind::Wgpu,
98 api: Api::Wgpu,
99 device_index: Some(i as u32),
100 device_uid: Some(super::device_uid(vendor, &info.name)),
101 device_name: info.name.clone(),
102 vendor: vendor.to_string(),
103 vendor_id: if vendor == "unknown" { None } else { Some(info.vendor) },
104 device_type: device_type.to_string(),
105 mem_total: None,
106 mem_free: None,
107 mem_kind: mem_kind(info.backend, info.device_type, limits),
108 compute_class: None,
109 caps: vec![format!("max_buffer_size={}", limits.max_buffer_size)],
110 source: Source::CompiledIn,
111 status,
112 transport: Some(transport_name(info.backend).to_string()),
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn vendor_table_names_the_four_and_unknown_for_the_rest() {
122 assert_eq!(vendor_name(0x10de), "NVIDIA");
123 assert_eq!(vendor_name(0x1002), "AMD");
124 assert_eq!(vendor_name(0x8086), "Intel");
125 assert_eq!(vendor_name(0x106b), "Apple");
126 assert_eq!(vendor_name(0x0000), "unknown");
127 assert_eq!(vendor_name(0xffff), "unknown");
128 }
129
130 #[test]
131 fn transport_table_is_total_over_wgpu_backends() {
132 assert_eq!(transport_name(wgpu::Backend::Vulkan), "vulkan");
133 assert_eq!(transport_name(wgpu::Backend::Metal), "metal");
134 assert_eq!(transport_name(wgpu::Backend::Dx12), "dx12");
135 assert_eq!(transport_name(wgpu::Backend::Gl), "gl");
136 assert_eq!(transport_name(wgpu::Backend::BrowserWebGpu), "webgpu");
137 assert_eq!(transport_name(wgpu::Backend::Noop), "noop");
138 }
139
140 #[test]
141 fn software_rasteriser_is_listed_but_never_ready() {
142 let (ty, status) = classify(wgpu::DeviceType::Cpu, "llvmpipe");
143 assert_eq!(ty, "software");
144 match status {
145 Status::Unavailable(Reason::NoBackend { vendor }) => {
146 assert!(vendor.contains("llvmpipe"), "names the adapter: {vendor}");
147 }
148 other => panic!("software rasteriser must be Unavailable(NoBackend), got {other:?}"),
149 }
150 for (dt, want) in [
151 (wgpu::DeviceType::DiscreteGpu, "discrete-gpu"),
152 (wgpu::DeviceType::IntegratedGpu, "integrated-gpu"),
153 (wgpu::DeviceType::VirtualGpu, "virtual-gpu"),
154 (wgpu::DeviceType::Other, "other"),
155 ] {
156 let (ty, status) = classify(dt, "x");
157 assert_eq!(ty, want);
158 assert!(matches!(status, Status::Ready), "{want} is Ready");
159 }
160 }
161
162 #[test]
163 fn unified_memory_is_metal_or_integrated_and_carries_the_buffer_cap() {
164 let mut limits = wgpu::Limits::default();
165 limits.max_buffer_size = 4096;
166 for (b, dt) in [
167 (wgpu::Backend::Metal, wgpu::DeviceType::DiscreteGpu),
168 (wgpu::Backend::Vulkan, wgpu::DeviceType::IntegratedGpu),
169 ] {
170 match mem_kind(b, dt, &limits) {
171 MemKind::Unified { working_set_limit } => assert_eq!(working_set_limit, Some(4096)),
172 other => panic!("{b:?}/{dt:?} must be Unified, got {other:?}"),
173 }
174 }
175 assert!(matches!(
176 mem_kind(wgpu::Backend::Vulkan, wgpu::DeviceType::DiscreteGpu, &limits),
177 MemKind::Discrete
178 ));
179 }
180}