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 webgpu_traits::{
10    RequestDeviceError, WebGPU, WebGPUAdapter, WebGPUDeviceResponse, WebGPURequest,
11};
12use wgpu_types::{self, AdapterInfo, MemoryHints};
13
14use super::gpusupportedfeatures::GPUSupportedFeatures;
15use super::gpusupportedlimits::set_limit;
16use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
17    GPUAdapterMethods, GPUDeviceDescriptor, GPUDeviceLostReason,
18};
19use crate::dom::bindings::error::Error;
20use crate::dom::bindings::like::Setlike;
21use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
22use crate::dom::bindings::root::{Dom, DomRoot};
23use crate::dom::bindings::str::DOMString;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::promise::Promise;
26use crate::dom::types::{GPUAdapterInfo, GPUSupportedLimits};
27use crate::dom::webgpu::gpudevice::GPUDevice;
28use crate::dom::webgpu::gpusupportedfeatures::gpu_to_wgt_feature;
29use crate::realms::InRealm;
30use crate::routed_promise::{RoutedPromiseListener, callback_promise};
31use crate::script_runtime::CanGc;
32
33#[derive(JSTraceable, MallocSizeOf)]
34struct DroppableGPUAdapter {
35    #[ignore_malloc_size_of = "channels are hard"]
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        global: &GlobalScope,
92        channel: WebGPU,
93        name: DOMString,
94        extensions: HandleObject,
95        features: wgpu_types::Features,
96        limits: wgpu_types::Limits,
97        info: wgpu_types::AdapterInfo,
98        adapter: WebGPUAdapter,
99        can_gc: CanGc,
100    ) -> DomRoot<Self> {
101        let features = GPUSupportedFeatures::Constructor(global, None, features, can_gc).unwrap();
102        let limits = GPUSupportedLimits::new(global, limits, can_gc);
103        let info = GPUAdapter::create_adapter_info(global, info, &features, &limits, can_gc);
104        let dom_root = reflect_dom_object(
105            Box::new(GPUAdapter::new_inherited(
106                channel, name, &features, &limits, &info, adapter,
107            )),
108            global,
109            can_gc,
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        global: &GlobalScope,
118        info: AdapterInfo,
119        features: &GPUSupportedFeatures,
120        limits: &GPUSupportedLimits,
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            DOMString::from_string(info.vendor.to_string())
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            DOMString::from_string(info.device.to_string())
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 = DOMString::from_string(info.name.clone());
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            (
164                limits.wgpu_limits().min_subgroup_size,
165                limits.wgpu_limits().max_subgroup_size,
166            )
167        } else {
168            (4, 128)
169        };
170
171        // Step 8. Set adapterInfo.isFallbackAdapter to adapter.[[fallback]].
172        let is_fallback_adapter = info.device_type == wgpu_types::DeviceType::Cpu;
173
174        // Step 1. Let adapterInfo be a new GPUAdapterInfo.
175        GPUAdapterInfo::new(
176            global,
177            vendor,
178            architecture,
179            device,
180            description,
181            subgroup_min_size,
182            subgroup_max_size,
183            is_fallback_adapter,
184            can_gc,
185        )
186    }
187}
188
189impl GPUAdapterMethods<crate::DomTypeHolder> for GPUAdapter {
190    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
191    fn RequestDevice(
192        &self,
193        descriptor: &GPUDeviceDescriptor,
194        comp: InRealm,
195        can_gc: CanGc,
196    ) -> Rc<Promise> {
197        // Step 2
198        let promise = Promise::new_in_current_realm(comp, can_gc);
199        let callback = callback_promise(
200            &promise,
201            self,
202            self.global().task_manager().dom_manipulation_task_source(),
203        );
204        let mut required_features = wgpu_types::Features::empty();
205        for &ext in descriptor.requiredFeatures.iter() {
206            if let Some(feature) = gpu_to_wgt_feature(ext) {
207                required_features.insert(feature);
208            } else {
209                promise.reject_error(
210                    Error::Type(format!("{} is not supported feature", ext.as_str())),
211                    can_gc,
212                );
213                return promise;
214            }
215        }
216
217        let mut required_limits = wgpu_types::Limits::default();
218        if let Some(limits) = &descriptor.requiredLimits {
219            for (limit, value) in (*limits).iter() {
220                if !set_limit(&mut required_limits, &limit.str(), *value) {
221                    warn!("Unknown GPUDevice limit: {limit}");
222                    promise.reject_error(Error::Operation(None), can_gc);
223                    return promise;
224                }
225            }
226        }
227
228        let desc = wgpu_types::DeviceDescriptor {
229            required_features,
230            required_limits,
231            label: Some(descriptor.parent.label.to_string()),
232            memory_hints: MemoryHints::MemoryUsage,
233            trace: wgpu_types::Trace::Off,
234        };
235        let device_id = self.global().wgpu_id_hub().create_device_id();
236        let queue_id = self.global().wgpu_id_hub().create_queue_id();
237        let pipeline_id = self.global().pipeline_id();
238        if self
239            .droppable
240            .channel
241            .0
242            .send(WebGPURequest::RequestDevice {
243                sender: callback,
244                adapter_id: self.droppable.adapter,
245                descriptor: desc,
246                device_id,
247                queue_id,
248                pipeline_id,
249            })
250            .is_err()
251        {
252            promise.reject_error(Error::Operation(None), can_gc);
253        }
254        // Step 5
255        promise
256    }
257
258    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
259    fn Features(&self) -> DomRoot<GPUSupportedFeatures> {
260        DomRoot::from_ref(&self.features)
261    }
262
263    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
264    fn Limits(&self) -> DomRoot<GPUSupportedLimits> {
265        DomRoot::from_ref(&self.limits)
266    }
267
268    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-info>
269    fn Info(&self) -> DomRoot<GPUAdapterInfo> {
270        DomRoot::from_ref(&self.info)
271    }
272}
273
274impl RoutedPromiseListener<WebGPUDeviceResponse> for GPUAdapter {
275    /// <https://www.w3.org/TR/webgpu/#dom-gpuadapter-requestdevice>
276    fn handle_response(
277        &self,
278        response: WebGPUDeviceResponse,
279        promise: &Rc<Promise>,
280        can_gc: CanGc,
281    ) {
282        match response {
283            // 3.1 Let device be a new device with the capabilities described by descriptor.
284            (device_id, queue_id, Ok(descriptor)) => {
285                let device = GPUDevice::new(
286                    &self.global(),
287                    self.droppable.channel.clone(),
288                    self,
289                    HandleObject::null(),
290                    descriptor.required_features,
291                    descriptor.required_limits,
292                    device_id,
293                    queue_id,
294                    descriptor.label.unwrap_or_default(),
295                    can_gc,
296                );
297                self.global().add_gpu_device(&device);
298                promise.resolve_native(&device, can_gc);
299            },
300            // 1. If features are not supported reject promise with a TypeError.
301            (_, _, Err(RequestDeviceError::UnsupportedFeature(f))) => promise.reject_error(
302                Error::Type(
303                    wgpu_core::instance::RequestDeviceError::UnsupportedFeature(f).to_string(),
304                ),
305                can_gc,
306            ),
307            // 2. If limits are not supported reject promise with an OperationError.
308            (_, _, Err(RequestDeviceError::LimitsExceeded(l))) => {
309                warn!(
310                    "{}",
311                    wgpu_core::instance::RequestDeviceError::LimitsExceeded(l)
312                );
313                promise.reject_error(Error::Operation(None), can_gc)
314            },
315            // 3. user agent otherwise cannot fulfill the request
316            (device_id, queue_id, Err(RequestDeviceError::Other(e))) => {
317                // 1. Let device be a new device.
318                let device = GPUDevice::new(
319                    &self.global(),
320                    self.droppable.channel.clone(),
321                    self,
322                    HandleObject::null(),
323                    wgpu_types::Features::default(),
324                    wgpu_types::Limits::default(),
325                    device_id,
326                    queue_id,
327                    String::new(),
328                    can_gc,
329                );
330                // 2. Lose the device(device, "unknown").
331                device.lose(GPUDeviceLostReason::Unknown, e);
332                promise.resolve_native(&device, can_gc);
333            },
334        }
335    }
336}