Skip to main content

script_webgpu/
gpuadapter.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-new-adapter-info>
121    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        // Step 2. If the vendor is known, set adapterInfo.vendor to the name of adapter’s vendor as
128        // a normalized identifier string. To preserve privacy, the user agent may instead set
129        // adapterInfo.vendor to the empty string or a reasonable approximation of the vendor as a
130        // normalized identifier string.
131        let vendor = if info.vendor != 0 {
132            info.vendor.to_string().into()
133        } else {
134            DOMString::new()
135        };
136
137        // Step 3. If the architecture is known, set adapterInfo.architecture to a normalized
138        // identifier string representing the family or class of adapters to which adapter belongs.
139        // To preserve privacy, the user agent may instead set adapterInfo.architecture to the empty
140        // string or a reasonable approximation of the architecture as a normalized identifier
141        // string.
142        // TODO: AdapterInfo::architecture missing
143        // https://github.com/gfx-rs/wgpu/issues/2170
144        let architecture = DOMString::new();
145
146        // Step 4. If the device is known, set adapterInfo.device to a normalized identifier string
147        // representing a vendor-specific identifier for adapter. To preserve privacy, the user
148        // agent may instead set adapterInfo.device to to the empty string or a reasonable
149        // approximation of a vendor-specific identifier as a normalized identifier string.
150        let device = if info.device != 0 {
151            info.device.to_string().into()
152        } else {
153            DOMString::new()
154        };
155
156        // Step 5. If a description is known, set adapterInfo.description to a description of the
157        // adapter as reported by the driver. To preserve privacy, the user agent may instead set
158        // adapterInfo.description to the empty string or a reasonable approximation of a
159        // description.
160        let description = info.name.clone().into();
161
162        // Step 6. If "subgroups" is supported, set subgroupMinSize to the smallest supported
163        // subgroup size. Otherwise, set this value to 4.
164        // Step 7. If "subgroups" is supported, set subgroupMaxSize to the largest supported
165        // subgroup size. Otherwise, set this value to 128.
166        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        // Step 8. Set adapterInfo.isFallbackAdapter to adapter.[[fallback]].
174        let is_fallback_adapter = info.device_type == DeviceType::Cpu;
175
176        // Step 1. Let adapterInfo be a new GPUAdapterInfo.
177        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    /// duplicates GPUAdapter::Info but it has reduced bounds
195    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    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
206    fn RequestDevice(
207        &self,
208        cx: &mut CurrentRealm<'_>,
209        descriptor: &GPUDeviceDescriptor,
210    ) -> <D::Promise as PromiseHelpers<D>>::StackRoot {
211        // Step 2
212        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        // Step 5
279        promise
280    }
281
282    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
283    fn Features(&self) -> DomRoot<GPUSupportedFeatures<D>> {
284        DomRoot::from_ref(&self.features)
285    }
286
287    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
288    fn Limits(&self) -> DomRoot<GPUSupportedLimits<D>> {
289        DomRoot::from_ref(&self.limits)
290    }
291
292    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-info>
293    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    /// <https://www.w3.org/TR/webgpu/#dom-gpuadapter-requestdevice>
304    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            // 3.1 Let device be a new device with the capabilities described by descriptor.
312            (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            // 1. If features are not supported reject promise with a TypeError.
329            (_, _, Err(RequestDeviceError::UnsupportedFeature(f))) => promise.reject_error(
330                cx,
331                Error::Type(cformat!("Unsupported features were requested: {}", f)),
332            ),
333            // 2. If limits are not supported reject promise with an OperationError.
334            (_, _, 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            // 3. user agent otherwise cannot fulfill the request
342            (device_id, queue_id, Err(RequestDeviceError::Other(e))) => {
343                // TODO(sagudev): firefox always says operation error,
344                // meanwhile we create "invalid" device that is not invalid in wgpu
345                // causing crashes when one tries to use it
346                // 1. Let device be a new device.
347                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                // 2. Lose the device(device, "unknown").
360                device.lose(GPUDeviceLostReason::Unknown, e);
361                promise.resolve_native(cx, &device);
362            },
363        }
364    }
365}