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