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