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, 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::reflector::{DomGlobal, Reflector, reflect_dom_object};
21use crate::dom::bindings::root::{Dom, DomRoot};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::promise::Promise;
25use crate::dom::types::{GPUAdapterInfo, GPUSupportedLimits};
26use crate::dom::webgpu::gpudevice::GPUDevice;
27use crate::dom::webgpu::gpusupportedfeatures::gpu_to_wgt_feature;
28use crate::realms::InRealm;
29use crate::routed_promise::{RoutedPromiseListener, route_promise};
30use crate::script_runtime::CanGc;
31
32#[dom_struct]
33pub(crate) struct GPUAdapter {
34    reflector_: Reflector,
35    #[ignore_malloc_size_of = "channels are hard"]
36    #[no_trace]
37    channel: WebGPU,
38    name: DOMString,
39    #[ignore_malloc_size_of = "mozjs"]
40    extensions: Heap<*mut JSObject>,
41    features: Dom<GPUSupportedFeatures>,
42    limits: Dom<GPUSupportedLimits>,
43    info: Dom<GPUAdapterInfo>,
44    #[no_trace]
45    adapter: WebGPUAdapter,
46}
47
48impl GPUAdapter {
49    fn new_inherited(
50        channel: WebGPU,
51        name: DOMString,
52        features: &GPUSupportedFeatures,
53        limits: &GPUSupportedLimits,
54        info: &GPUAdapterInfo,
55        adapter: WebGPUAdapter,
56    ) -> Self {
57        Self {
58            reflector_: Reflector::new(),
59            channel,
60            name,
61            extensions: Heap::default(),
62            features: Dom::from_ref(features),
63            limits: Dom::from_ref(limits),
64            info: Dom::from_ref(info),
65            adapter,
66        }
67    }
68
69    #[allow(clippy::too_many_arguments)]
70    pub(crate) fn new(
71        global: &GlobalScope,
72        channel: WebGPU,
73        name: DOMString,
74        extensions: HandleObject,
75        features: wgpu_types::Features,
76        limits: wgpu_types::Limits,
77        info: wgpu_types::AdapterInfo,
78        adapter: WebGPUAdapter,
79        can_gc: CanGc,
80    ) -> DomRoot<Self> {
81        let features = GPUSupportedFeatures::Constructor(global, None, features, can_gc).unwrap();
82        let limits = GPUSupportedLimits::new(global, limits, can_gc);
83        let info = GPUAdapterInfo::new(global, info, can_gc);
84        let dom_root = reflect_dom_object(
85            Box::new(GPUAdapter::new_inherited(
86                channel, name, &features, &limits, &info, adapter,
87            )),
88            global,
89            can_gc,
90        );
91        dom_root.extensions.set(*extensions);
92        dom_root
93    }
94}
95
96impl Drop for GPUAdapter {
97    fn drop(&mut self) {
98        if let Err(e) = self
99            .channel
100            .0
101            .send(WebGPURequest::DropAdapter(self.adapter.0))
102        {
103            warn!(
104                "Failed to send WebGPURequest::DropAdapter({:?}) ({})",
105                self.adapter.0, e
106            );
107        };
108    }
109}
110
111impl GPUAdapterMethods<crate::DomTypeHolder> for GPUAdapter {
112    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
113    fn RequestDevice(
114        &self,
115        descriptor: &GPUDeviceDescriptor,
116        comp: InRealm,
117        can_gc: CanGc,
118    ) -> Rc<Promise> {
119        // Step 2
120        let promise = Promise::new_in_current_realm(comp, can_gc);
121        let sender = route_promise(
122            &promise,
123            self,
124            self.global().task_manager().dom_manipulation_task_source(),
125        );
126        let mut required_features = wgpu_types::Features::empty();
127        for &ext in descriptor.requiredFeatures.iter() {
128            if let Some(feature) = gpu_to_wgt_feature(ext) {
129                required_features.insert(feature);
130            } else {
131                promise.reject_error(
132                    Error::Type(format!("{} is not supported feature", ext.as_str())),
133                    can_gc,
134                );
135                return promise;
136            }
137        }
138
139        let mut required_limits = wgpu_types::Limits::default();
140        if let Some(limits) = &descriptor.requiredLimits {
141            for (limit, value) in (*limits).iter() {
142                if !set_limit(&mut required_limits, limit.as_ref(), *value) {
143                    warn!("Unknown GPUDevice limit: {limit}");
144                    promise.reject_error(Error::Operation, can_gc);
145                    return promise;
146                }
147            }
148        }
149
150        let desc = wgpu_types::DeviceDescriptor {
151            required_features,
152            required_limits,
153            label: Some(descriptor.parent.label.to_string()),
154            memory_hints: MemoryHints::MemoryUsage,
155            trace: wgpu_types::Trace::Off,
156        };
157        let device_id = self.global().wgpu_id_hub().create_device_id();
158        let queue_id = self.global().wgpu_id_hub().create_queue_id();
159        let pipeline_id = self.global().pipeline_id();
160        if self
161            .channel
162            .0
163            .send(WebGPURequest::RequestDevice {
164                sender,
165                adapter_id: self.adapter,
166                descriptor: desc,
167                device_id,
168                queue_id,
169                pipeline_id,
170            })
171            .is_err()
172        {
173            promise.reject_error(Error::Operation, can_gc);
174        }
175        // Step 5
176        promise
177    }
178
179    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-isfallbackadapter>
180    fn IsFallbackAdapter(&self) -> bool {
181        // TODO
182        false
183    }
184
185    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestadapterinfo>
186    fn RequestAdapterInfo(
187        &self,
188        unmask_hints: Vec<DOMString>,
189        comp: InRealm,
190        can_gc: CanGc,
191    ) -> Rc<Promise> {
192        // XXX: Adapter info should be generated here ...
193        // Step 1
194        let promise = Promise::new_in_current_realm(comp, can_gc);
195        // Step 4
196        if !unmask_hints.is_empty() {
197            todo!("unmaskHints on RequestAdapterInfo");
198        }
199        promise.resolve_native(&*self.info, can_gc);
200        // Step 5
201        promise
202    }
203
204    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
205    fn Features(&self) -> DomRoot<GPUSupportedFeatures> {
206        DomRoot::from_ref(&self.features)
207    }
208
209    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
210    fn Limits(&self) -> DomRoot<GPUSupportedLimits> {
211        DomRoot::from_ref(&self.limits)
212    }
213}
214
215impl RoutedPromiseListener<WebGPUDeviceResponse> for GPUAdapter {
216    /// <https://www.w3.org/TR/webgpu/#dom-gpuadapter-requestdevice>
217    fn handle_response(
218        &self,
219        response: WebGPUDeviceResponse,
220        promise: &Rc<Promise>,
221        can_gc: CanGc,
222    ) {
223        match response {
224            // 3.1 Let device be a new device with the capabilities described by descriptor.
225            (device_id, queue_id, Ok(descriptor)) => {
226                let device = GPUDevice::new(
227                    &self.global(),
228                    self.channel.clone(),
229                    self,
230                    HandleObject::null(),
231                    descriptor.required_features,
232                    descriptor.required_limits,
233                    device_id,
234                    queue_id,
235                    descriptor.label.unwrap_or_default(),
236                    can_gc,
237                );
238                self.global().add_gpu_device(&device);
239                promise.resolve_native(&device, can_gc);
240            },
241            // 1. If features are not supported reject promise with a TypeError.
242            (_, _, Err(RequestDeviceError::UnsupportedFeature(f))) => promise.reject_error(
243                Error::Type(
244                    wgpu_core::instance::RequestDeviceError::UnsupportedFeature(f).to_string(),
245                ),
246                can_gc,
247            ),
248            // 2. If limits are not supported reject promise with an OperationError.
249            (_, _, Err(RequestDeviceError::LimitsExceeded(l))) => {
250                warn!(
251                    "{}",
252                    wgpu_core::instance::RequestDeviceError::LimitsExceeded(l)
253                );
254                promise.reject_error(Error::Operation, can_gc)
255            },
256            // 3. user agent otherwise cannot fulfill the request
257            (device_id, queue_id, Err(RequestDeviceError::Other(e))) => {
258                // 1. Let device be a new device.
259                let device = GPUDevice::new(
260                    &self.global(),
261                    self.channel.clone(),
262                    self,
263                    HandleObject::null(),
264                    wgpu_types::Features::default(),
265                    wgpu_types::Limits::default(),
266                    device_id,
267                    queue_id,
268                    String::new(),
269                    can_gc,
270                );
271                // 2. Lose the device(device, "unknown").
272                device.lose(GPUDeviceLostReason::Unknown, e);
273                promise.resolve_native(&device, can_gc);
274            },
275        }
276    }
277}