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