ironaccelerator_webgpu/
backend.rs1use ironaccelerator_core::{
4 Backend, BackendKind, Capability, CapabilityFlags, ComputeTier, DeviceDescriptor, DeviceId,
5 Error, Result, Vendor,
6};
7
8use crate::drv::AdapterInfo;
9
10pub struct WebGpuBackend;
11pub static WEBGPU_BACKEND: WebGpuBackend = WebGpuBackend;
12
13impl Backend for WebGpuBackend {
14 fn kind(&self) -> BackendKind {
15 BackendKind::WebGpu
16 }
17
18 fn is_available(&self) -> bool {
19 crate::drv::is_available()
20 }
21
22 fn enumerate(&self) -> Result<Vec<DeviceDescriptor>> {
23 Ok(crate::drv::enumerate()
24 .into_iter()
25 .enumerate()
26 .map(|(i, info)| describe(i as u32, info))
27 .collect())
28 }
29
30 fn capabilities(&self, device: u32) -> Result<CapabilityFlags> {
31 if device != 0 {
32 return Err(Error::InvalidArgument(
33 "webgpu exposes a single bound adapter at ordinal 0",
34 ));
35 }
36 crate::drv::bound_adapter()
37 .map(|a| flags_for(&a))
38 .ok_or(Error::BackendUnavailable("webgpu: no adapter bound"))
39 }
40}
41
42fn flags_for(a: &AdapterInfo) -> CapabilityFlags {
49 let mut flags = CapabilityFlags::FP32;
50 if a.shader_f16 {
51 flags |= CapabilityFlags::FP16;
52 }
53 flags
54}
55
56fn describe(ordinal: u32, a: AdapterInfo) -> DeviceDescriptor {
57 let vendor = match a.vendor.as_str() {
59 "nvidia" => Vendor::Nvidia,
60 "amd" => Vendor::Amd,
61 "intel" => Vendor::Intel,
62 "apple" => Vendor::Apple,
63 "qualcomm" => Vendor::Qualcomm,
64 _ => Vendor::Other,
65 };
66
67 let tier = ComputeTier::Baseline;
70
71 let name = if !a.description.is_empty() {
72 a.description.clone()
73 } else if !a.device.is_empty() {
74 a.device.clone()
75 } else if !a.vendor.is_empty() {
76 a.vendor.clone()
77 } else {
78 "webgpu adapter".to_string()
79 };
80
81 let arch = if a.architecture.is_empty() {
82 "webgpu".to_string()
83 } else {
84 format!("webgpu-{}", a.architecture)
85 };
86
87 DeviceDescriptor {
88 id: DeviceId {
89 backend: BackendKind::WebGpu,
90 ordinal,
91 },
92 vendor,
93 name,
94 arch,
95 total_memory_bytes: a.max_buffer_size,
99 multiprocessor_count: 0,
100 clock_khz: 0,
101 capability: Capability {
102 flags: flags_for(&a),
103 tier,
104 fp16_tflops: None,
105 fp8_tflops: None,
106 mem_bandwidth_gbs: None,
107 },
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::drv::{bind_adapter, unbind_adapter};
115 use std::sync::Mutex;
116
117 static GUARD: Mutex<()> = Mutex::new(());
118
119 #[test]
120 fn unbound_backend_reports_unavailable() {
121 let _g = GUARD.lock().unwrap_or_else(|e| e.into_inner());
122 unbind_adapter();
123 assert!(!WEBGPU_BACKEND.is_available());
124 assert!(WEBGPU_BACKEND.enumerate().unwrap().is_empty());
125 assert!(WEBGPU_BACKEND.capabilities(0).is_err());
126 }
127
128 #[test]
129 fn bound_adapter_is_described_and_capable() {
130 let _g = GUARD.lock().unwrap_or_else(|e| e.into_inner());
131 bind_adapter(AdapterInfo {
132 vendor: "amd".into(),
133 architecture: "rdna-3".into(),
134 shader_f16: true,
135 max_buffer_size: 4096,
136 ..Default::default()
137 });
138
139 let devices = WEBGPU_BACKEND.enumerate().unwrap();
140 assert_eq!(devices.len(), 1);
141 assert_eq!(devices[0].vendor, Vendor::Amd);
142 assert_eq!(devices[0].arch, "webgpu-rdna-3");
143 assert_eq!(devices[0].name, "amd");
144 assert_eq!(devices[0].total_memory_bytes, 4096);
145
146 let flags = WEBGPU_BACKEND.capabilities(0).unwrap();
147 assert_eq!(flags, CapabilityFlags::FP32 | CapabilityFlags::FP16);
148 assert_eq!(flags, devices[0].capability.flags);
149
150 assert!(
151 WEBGPU_BACKEND.capabilities(1).is_err(),
152 "only ordinal 0 exists"
153 );
154 unbind_adapter();
155 }
156
157 #[test]
158 fn without_shader_f16_only_fp32() {
159 let _g = GUARD.lock().unwrap_or_else(|e| e.into_inner());
160 bind_adapter(AdapterInfo {
161 vendor: "intel".into(),
162 ..Default::default()
163 });
164 assert_eq!(
165 WEBGPU_BACKEND.capabilities(0).unwrap(),
166 CapabilityFlags::FP32
167 );
168 unbind_adapter();
169 }
170}