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