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