Skip to main content

script/dom/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 std::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::jsapi::{HandleObject, Heap, JSObject};
9use js::realm::CurrentRealm;
10use script_bindings::cformat;
11use script_bindings::like::Setlike;
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
13use webgpu_traits::{
14    RequestDeviceError, WebGPU, WebGPUAdapter, WebGPUDeviceResponse, WebGPURequest,
15};
16use wgpu_types::{self, AdapterInfo, ExperimentalFeatures, MemoryHints};
17
18use super::gpusupportedfeatures::GPUSupportedFeatures;
19use super::gpusupportedlimits::set_limit;
20use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
21    GPUAdapterMethods, GPUDeviceDescriptor, GPUDeviceLostReason,
22};
23use crate::dom::bindings::error::Error;
24use crate::dom::bindings::reflector::DomGlobal;
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::bindings::str::DOMString;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::promise::Promise;
29use crate::dom::types::{GPUAdapterInfo, GPUSupportedLimits};
30use crate::dom::webgpu::gpudevice::GPUDevice;
31use crate::dom::webgpu::gpusupportedfeatures::gpu_to_wgt_feature;
32use crate::routed_promise::{RoutedPromiseListener, callback_promise};
33
34#[derive(JSTraceable, MallocSizeOf)]
35struct DroppableGPUAdapter {
36    #[no_trace]
37    channel: WebGPU,
38    #[no_trace]
39    adapter: WebGPUAdapter,
40}
41
42impl Drop for DroppableGPUAdapter {
43    fn drop(&mut self) {
44        if let Err(e) = self
45            .channel
46            .0
47            .send(WebGPURequest::DropAdapter(self.adapter.0))
48        {
49            warn!(
50                "Failed to send WebGPURequest::DropAdapter({:?}) ({})",
51                self.adapter.0, e
52            );
53        };
54    }
55}
56
57#[dom_struct]
58pub(crate) struct GPUAdapter {
59    reflector_: Reflector,
60    name: DOMString,
61    #[ignore_malloc_size_of = "mozjs"]
62    extensions: Heap<*mut JSObject>,
63    features: Dom<GPUSupportedFeatures>,
64    limits: Dom<GPUSupportedLimits>,
65    info: Dom<GPUAdapterInfo>,
66    droppable: DroppableGPUAdapter,
67}
68
69impl GPUAdapter {
70    fn new_inherited(
71        channel: WebGPU,
72        name: DOMString,
73        features: &GPUSupportedFeatures,
74        limits: &GPUSupportedLimits,
75        info: &GPUAdapterInfo,
76        adapter: WebGPUAdapter,
77    ) -> Self {
78        Self {
79            reflector_: Reflector::new(),
80            name,
81            extensions: Heap::default(),
82            features: Dom::from_ref(features),
83            limits: Dom::from_ref(limits),
84            info: Dom::from_ref(info),
85            droppable: DroppableGPUAdapter { channel, adapter },
86        }
87    }
88
89    #[allow(clippy::too_many_arguments)]
90    pub(crate) fn new(
91        cx: &mut js::context::JSContext,
92        global: &GlobalScope,
93        channel: WebGPU,
94        name: DOMString,
95        extensions: HandleObject,
96        features: wgpu_types::Features,
97        limits: wgpu_types::Limits,
98        info: wgpu_types::AdapterInfo,
99        adapter: WebGPUAdapter,
100    ) -> DomRoot<Self> {
101        let features = GPUSupportedFeatures::Constructor(cx, global, None, features).unwrap();
102        let limits = GPUSupportedLimits::new(cx, global, limits);
103        let info = GPUAdapter::create_adapter_info(cx, global, info, &features);
104        let dom_root = reflect_dom_object_with_cx(
105            Box::new(GPUAdapter::new_inherited(
106                channel, name, &features, &limits, &info, adapter,
107            )),
108            global,
109            cx,
110        );
111        dom_root.extensions.set(*extensions);
112        dom_root
113    }
114
115    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-new-adapter-info>
116    fn create_adapter_info(
117        cx: &mut js::context::JSContext,
118        global: &GlobalScope,
119        info: AdapterInfo,
120        features: &GPUSupportedFeatures,
121    ) -> DomRoot<GPUAdapterInfo> {
122        // Step 2. If the vendor is known, set adapterInfo.vendor to the name of adapter’s vendor as
123        // a normalized identifier string. To preserve privacy, the user agent may instead set
124        // adapterInfo.vendor to the empty string or a reasonable approximation of the vendor as a
125        // normalized identifier string.
126        let vendor = if info.vendor != 0 {
127            info.vendor.to_string().into()
128        } else {
129            DOMString::new()
130        };
131
132        // Step 3. If the architecture is known, set adapterInfo.architecture to a normalized
133        // identifier string representing the family or class of adapters to which adapter belongs.
134        // To preserve privacy, the user agent may instead set adapterInfo.architecture to the empty
135        // string or a reasonable approximation of the architecture as a normalized identifier
136        // string.
137        // TODO: AdapterInfo::architecture missing
138        // https://github.com/gfx-rs/wgpu/issues/2170
139        let architecture = DOMString::new();
140
141        // Step 4. If the device is known, set adapterInfo.device to a normalized identifier string
142        // representing a vendor-specific identifier for adapter. To preserve privacy, the user
143        // agent may instead set adapterInfo.device to to the empty string or a reasonable
144        // approximation of a vendor-specific identifier as a normalized identifier string.
145        let device = if info.device != 0 {
146            info.device.to_string().into()
147        } else {
148            DOMString::new()
149        };
150
151        // Step 5. If a description is known, set adapterInfo.description to a description of the
152        // adapter as reported by the driver. To preserve privacy, the user agent may instead set
153        // adapterInfo.description to the empty string or a reasonable approximation of a
154        // description.
155        let description = info.name.clone().into();
156
157        // Step 6. If "subgroups" is supported, set subgroupMinSize to the smallest supported
158        // subgroup size. Otherwise, set this value to 4.
159        // Step 7. If "subgroups" is supported, set subgroupMaxSize to the largest supported
160        // subgroup size. Otherwise, set this value to 128.
161        let (subgroup_min_size, subgroup_max_size) = if features.has("subgroups".into()) {
162            (info.subgroup_min_size, info.subgroup_max_size)
163        } else {
164            (4, 128)
165        };
166
167        // Step 8. Set adapterInfo.isFallbackAdapter to adapter.[[fallback]].
168        let is_fallback_adapter = info.device_type == wgpu_types::DeviceType::Cpu;
169
170        // Step 1. Let adapterInfo be a new GPUAdapterInfo.
171        GPUAdapterInfo::new(
172            cx,
173            global,
174            vendor,
175            architecture,
176            device,
177            description,
178            subgroup_min_size,
179            subgroup_max_size,
180            is_fallback_adapter,
181        )
182    }
183}
184
185impl GPUAdapterMethods<crate::DomTypeHolder> for GPUAdapter {
186    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
187    fn RequestDevice(
188        &self,
189        cx: &mut CurrentRealm<'_>,
190        descriptor: &GPUDeviceDescriptor,
191    ) -> Rc<Promise> {
192        // Step 2
193        let promise = Promise::new_in_realm(cx);
194        let callback = callback_promise(
195            &promise,
196            self,
197            self.global().task_manager().dom_manipulation_task_source(),
198        );
199        let mut required_features = wgpu_types::Features::empty();
200        for &ext in descriptor.requiredFeatures.iter() {
201            if let Some(feature) = gpu_to_wgt_feature(ext) {
202                required_features.insert(feature);
203            } else {
204                promise.reject_error_with_cx(
205                    cx,
206                    Error::Type(cformat!("{} is not supported feature", ext.as_str())),
207                );
208                return promise;
209            }
210        }
211
212        let mut required_limits = wgpu_types::Limits::default();
213        if let Some(limits) = &descriptor.requiredLimits {
214            for (limit, value) in (*limits).iter() {
215                if !set_limit(&mut required_limits, &limit.str(), *value) {
216                    warn!("Unknown GPUDevice limit: {limit}");
217                    promise.reject_error_with_cx(cx, Error::Operation(None));
218                    return promise;
219                }
220            }
221        }
222
223        let desc = wgpu_types::DeviceDescriptor {
224            required_features,
225            required_limits,
226            label: Some(descriptor.parent.label.to_string()),
227            memory_hints: MemoryHints::MemoryUsage,
228            trace: wgpu_types::Trace::Off,
229            experimental_features: ExperimentalFeatures::disabled(),
230        };
231        let device_id = self.global().wgpu_id_hub().create_device_id();
232        let queue_id = self.global().wgpu_id_hub().create_queue_id();
233        let pipeline_id = self.global().pipeline_id();
234        if self
235            .droppable
236            .channel
237            .0
238            .send(WebGPURequest::RequestDevice {
239                sender: callback,
240                adapter_id: self.droppable.adapter,
241                descriptor: desc,
242                device_id,
243                queue_id,
244                pipeline_id,
245            })
246            .is_err()
247        {
248            promise.reject_error_with_cx(cx, Error::Operation(None));
249        }
250        // Step 5
251        promise
252    }
253
254    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
255    fn Features(&self) -> DomRoot<GPUSupportedFeatures> {
256        DomRoot::from_ref(&self.features)
257    }
258
259    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
260    fn Limits(&self) -> DomRoot<GPUSupportedLimits> {
261        DomRoot::from_ref(&self.limits)
262    }
263
264    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-info>
265    fn Info(&self) -> DomRoot<GPUAdapterInfo> {
266        DomRoot::from_ref(&self.info)
267    }
268}
269
270impl RoutedPromiseListener<WebGPUDeviceResponse> for GPUAdapter {
271    /// <https://www.w3.org/TR/webgpu/#dom-gpuadapter-requestdevice>
272    fn handle_response(
273        &self,
274        cx: &mut js::context::JSContext,
275        response: WebGPUDeviceResponse,
276        promise: &Rc<Promise>,
277    ) {
278        match response {
279            // 3.1 Let device be a new device with the capabilities described by descriptor.
280            (device_id, queue_id, Ok(descriptor)) => {
281                let device = GPUDevice::new(
282                    cx,
283                    &self.global(),
284                    self.droppable.channel.clone(),
285                    self,
286                    HandleObject::null(),
287                    descriptor.required_features,
288                    descriptor.required_limits,
289                    device_id,
290                    queue_id,
291                    descriptor.label.unwrap_or_default(),
292                );
293                self.global().add_gpu_device(&device);
294                promise.resolve_native_with_cx(cx, &device);
295            },
296            // 1. If features are not supported reject promise with a TypeError.
297            (_, _, Err(RequestDeviceError::UnsupportedFeature(f))) => promise.reject_error_with_cx(
298                cx,
299                Error::Type(cformat!(
300                    "{}",
301                    wgpu_core::instance::RequestDeviceError::UnsupportedFeature(f)
302                )),
303            ),
304            // 2. If limits are not supported reject promise with an OperationError.
305            (_, _, Err(RequestDeviceError::LimitsExceeded(l))) => {
306                warn!(
307                    "{}",
308                    wgpu_core::instance::RequestDeviceError::LimitsExceeded(l)
309                );
310                promise.reject_error_with_cx(cx, Error::Operation(None))
311            },
312            // 3. user agent otherwise cannot fulfill the request
313            (device_id, queue_id, Err(RequestDeviceError::Other(e))) => {
314                // TODO(sagudev): firefox always says operation error,
315                // meanwhile we create "invalid" device that is not invalid in wgpu
316                // causing crashes when one tries to use it
317                // 1. Let device be a new device.
318                let device = GPUDevice::new(
319                    cx,
320                    &self.global(),
321                    self.droppable.channel.clone(),
322                    self,
323                    HandleObject::null(),
324                    wgpu_types::Features::default(),
325                    wgpu_types::Limits::default(),
326                    device_id,
327                    queue_id,
328                    String::new(),
329                );
330                // 2. Lose the device(device, "unknown").
331                device.lose(GPUDeviceLostReason::Unknown, e);
332                promise.resolve_native_with_cx(cx, &device);
333            },
334        }
335    }
336}