1use dom_struct::dom_struct;
6use js::jsapi::{HandleObject, Heap, JSObject};
7use js::realm::CurrentRealm;
8use jstraceable_derive::JSTraceable;
9use log::warn;
10use malloc_size_of_derive::MallocSizeOf;
11use script_bindings::callback::CallbackContainer;
12use script_bindings::codegen::GenericBindings::EventHandlerBinding::EventHandlerNonNull;
13use script_bindings::codegen::GenericBindings::WebGPUBinding::{
14 GPUAdapterMethods, GPUAdapterWrap, GPUDeviceDescriptor, GPUDeviceLostReason,
15};
16use script_bindings::domstring::TracedDOMString;
17use script_bindings::interfaces::{GlobalScopeHelpers, PromiseHelpers};
18use script_bindings::like::Setlike;
19use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
20use script_bindings::routed_promise::RoutedPromiseListener;
21use script_bindings::{DomTypes, cformat};
22use webgpu_traits::{
23 AdapterInfo, DeviceDescriptor, DeviceType, ExperimentalFeatures, Features, Limits, MemoryHints,
24 RequestDeviceError, Trace, WebGPU, WebGPUAdapter, WebGPUDeviceResponse, WebGPURequest,
25};
26
27use crate::dom::bindings::error::Error;
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::bindings::str::DOMString;
30use crate::gpuadapterinfo::GPUAdapterInfo;
31use crate::gpudevice::GPUDevice;
32use crate::gpusupportedfeatures::{GPUSupportedFeatures, gpu_to_wgt_feature};
33use crate::gpusupportedlimits::{GPUSupportedLimits, set_limit};
34use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise, WebGPUPromiseCallbackTrait};
35
36#[derive(JSTraceable, MallocSizeOf)]
37struct DroppableGPUAdapter {
38 #[no_trace]
39 channel: WebGPU,
40 #[no_trace]
41 adapter: WebGPUAdapter,
42}
43
44impl Drop for DroppableGPUAdapter {
45 fn drop(&mut self) {
46 if let Err(e) = self
47 .channel
48 .0
49 .send(WebGPURequest::DropAdapter(self.adapter.0))
50 {
51 warn!(
52 "Failed to send WebGPURequest::DropAdapter({:?}) ({})",
53 self.adapter.0, e
54 );
55 };
56 }
57}
58
59#[dom_struct]
60pub struct GPUAdapter<D: DomTypes> {
61 reflector_: Reflector,
62 name: TracedDOMString,
63 #[ignore_malloc_size_of = "mozjs"]
64 extensions: Heap<*mut JSObject>,
65 features: Dom<GPUSupportedFeatures<D>>,
66 limits: Dom<GPUSupportedLimits<D>>,
67 info: Dom<GPUAdapterInfo<D>>,
68 droppable: DroppableGPUAdapter,
69}
70
71impl<D> GPUAdapter<D>
72where
73 D: Equivalence,
74{
75 fn new_inherited(
76 channel: WebGPU,
77 name: DOMString,
78 features: &GPUSupportedFeatures<D>,
79 limits: &GPUSupportedLimits<D>,
80 info: &GPUAdapterInfo<D>,
81 adapter: WebGPUAdapter,
82 ) -> Self {
83 Self {
84 reflector_: Reflector::new(),
85 name: name.traced(),
86 extensions: Heap::default(),
87 features: Dom::from_ref(features),
88 limits: Dom::from_ref(limits),
89 info: Dom::from_ref(info),
90 droppable: DroppableGPUAdapter { channel, adapter },
91 }
92 }
93
94 #[allow(clippy::too_many_arguments)]
95 pub fn new(
96 cx: &mut js::context::JSContext,
97 global: &D::GlobalScope,
98 channel: WebGPU,
99 name: DOMString,
100 extensions: HandleObject,
101 features: Features,
102 limits: Limits,
103 info: AdapterInfo,
104 adapter: WebGPUAdapter,
105 ) -> DomRoot<Self> {
106 let features = GPUSupportedFeatures::Constructor(cx, global, None, features).unwrap();
107 let limits = GPUSupportedLimits::new(cx, global, limits);
108 let info = GPUAdapter::create_adapter_info(cx, global, info, &features);
109 let dom_root = reflect_dom_object_with_wrap::<D, _, _>(
110 cx,
111 Box::new(GPUAdapter::new_inherited(
112 channel, name, &features, &limits, &info, adapter,
113 )),
114 global,
115 GPUAdapterWrap::<D>,
116 );
117 dom_root.extensions.set(*extensions);
118 dom_root
119 }
120
121 fn create_adapter_info(
123 cx: &mut js::context::JSContext,
124 global: &D::GlobalScope,
125 info: AdapterInfo,
126 features: &GPUSupportedFeatures<D>,
127 ) -> DomRoot<GPUAdapterInfo<D>> {
128 let vendor = if info.vendor != 0 {
133 info.vendor.to_string().into()
134 } else {
135 DOMString::new()
136 };
137
138 let architecture = DOMString::new();
146
147 let device = if info.device != 0 {
152 info.device.to_string().into()
153 } else {
154 DOMString::new()
155 };
156
157 let description = info.name.clone().into();
162
163 let (subgroup_min_size, subgroup_max_size) =
168 if features.has(cx, DOMString::from_static("subgroups")) {
169 (info.subgroup_min_size, info.subgroup_max_size)
170 } else {
171 (4, 128)
172 };
173
174 let is_fallback_adapter = info.device_type == DeviceType::Cpu;
176
177 GPUAdapterInfo::new(
179 cx,
180 global,
181 vendor,
182 architecture,
183 device,
184 description,
185 subgroup_min_size,
186 subgroup_max_size,
187 is_fallback_adapter,
188 )
189 }
190
191 pub fn channel(&self) -> WebGPU {
192 self.droppable.channel.clone()
193 }
194
195 pub fn info(&self) -> DomRoot<GPUAdapterInfo<D>> {
197 DomRoot::from_ref(&self.info)
198 }
199}
200
201impl<D> GPUAdapterMethods<D> for GPUAdapter<D>
202where
203 D: Equivalence,
204 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
205{
206 fn RequestDevice(
208 &self,
209 cx: &mut CurrentRealm<'_>,
210 descriptor: &GPUDeviceDescriptor,
211 ) -> <D::Promise as PromiseHelpers<D>>::StackRoot {
212 let promise = D::Promise::new_in_realm(cx);
214
215 let callback = promise.callback_promise_dom_manipulation_task_source(self);
216 let mut required_features = Features::empty();
217 for &ext in descriptor.requiredFeatures.iter() {
218 if let Some(feature) = gpu_to_wgt_feature(ext) {
219 required_features.insert(feature);
220 } else {
221 promise.reject_error(
222 cx,
223 Error::Type(cformat!("{} is not supported feature", ext.as_str())),
224 );
225 return promise;
226 }
227 }
228
229 let mut required_limits = Limits::default();
230 if let Some(limits) = &descriptor.requiredLimits {
231 for (limit, value) in (*limits).iter() {
232 if !set_limit(&mut required_limits, &limit.str(), *value) {
233 warn!("Unknown GPUDevice limit: {limit}");
234 promise.reject_error(
235 cx,
236 Error::Operation(Some(format!("Unknown GPUDevice limit: {limit}"))),
237 );
238 return promise;
239 }
240 }
241 }
242
243 let desc = DeviceDescriptor {
244 required_features,
245 required_limits,
246 label: Some(descriptor.parent.label.to_string()),
247 memory_hints: MemoryHints::MemoryUsage,
248 trace: Trace::Off,
249 experimental_features: ExperimentalFeatures::disabled(),
250 };
251 let device_id = self
252 .global_from_reflector()
253 .global_wgpu_id_hub()
254 .create_device_id();
255 let queue_id = self
256 .global_from_reflector()
257 .global_wgpu_id_hub()
258 .create_queue_id();
259 let pipeline_id = self.global_from_reflector().pipeline_id();
260 if self
261 .droppable
262 .channel
263 .0
264 .send(WebGPURequest::RequestDevice {
265 sender: callback,
266 adapter_id: self.droppable.adapter,
267 descriptor: desc,
268 device_id,
269 queue_id,
270 pipeline_id,
271 })
272 .is_err()
273 {
274 promise.reject_error(
275 cx,
276 Error::Operation(Some("Could not Request GPU Device".to_string())),
277 );
278 }
279 promise
281 }
282
283 fn Features(&self) -> DomRoot<GPUSupportedFeatures<D>> {
285 DomRoot::from_ref(&self.features)
286 }
287
288 fn Limits(&self) -> DomRoot<GPUSupportedLimits<D>> {
290 DomRoot::from_ref(&self.limits)
291 }
292
293 fn Info(&self) -> DomRoot<GPUAdapterInfo<D>> {
295 DomRoot::from_ref(&self.info)
296 }
297}
298
299impl<D: Equivalence> RoutedPromiseListener<D, WebGPUDeviceResponse> for GPUAdapter<D>
300where
301 Self: DomGlobalGeneric<D>,
302 EventHandlerNonNull<D>: CallbackContainer,
303{
304 fn handle_response(
306 &self,
307 cx: &mut js::context::JSContext,
308 response: WebGPUDeviceResponse,
309 promise: &<D::Promise as PromiseHelpers<D>>::StackRoot,
310 ) {
311 match response {
312 (device_id, queue_id, Ok(descriptor)) => {
314 let device = GPUDevice::<D>::new(
315 cx,
316 &self.global_from_reflector(),
317 self.channel(),
318 self,
319 HandleObject::null(),
320 descriptor.required_features,
321 descriptor.required_limits,
322 device_id,
323 queue_id,
324 descriptor.label.unwrap_or_default(),
325 );
326 self.global_from_reflector().add_webgpu_device(&device);
327 promise.resolve_native(cx, &device);
328 },
329 (_, _, Err(RequestDeviceError::UnsupportedFeature(f))) => promise.reject_error(
331 cx,
332 Error::Type(cformat!("Unsupported features were requested: {}", f)),
333 ),
334 (_, _, Err(RequestDeviceError::LimitsExceeded(l))) => {
336 warn!("{}", l);
337 promise.reject_error(
338 cx,
339 Error::Operation(Some("WebGPU Device Limit exceeded".to_string())),
340 )
341 },
342 (device_id, queue_id, Err(RequestDeviceError::Other(e))) => {
344 let device = GPUDevice::<D>::new(
349 cx,
350 &self.global_from_reflector(),
351 self.channel(),
352 self,
353 HandleObject::null(),
354 Features::default(),
355 Limits::default(),
356 device_id,
357 queue_id,
358 String::new(),
359 );
360 device.lose(GPUDeviceLostReason::Unknown, e);
362 promise.resolve_native(cx, &device);
363 },
364 }
365 }
366}