Skip to main content

script_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 jstraceable_derive::JSTraceable;
11use log::warn;
12use malloc_size_of_derive::MallocSizeOf;
13use script_bindings::codegen::GenericBindings::WebGPUBinding::{
14    GPUAdapterMethods, GPUAdapterWrap, GPUDeviceDescriptor,
15};
16use script_bindings::interfaces::{GlobalScopeHelpers, PromiseHelpers};
17use script_bindings::like::Setlike;
18use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
19use script_bindings::{DomTypes, cformat};
20use webgpu_traits::{WebGPU, WebGPUAdapter, WebGPURequest};
21use wgpu_types::{AdapterInfo, ExperimentalFeatures, MemoryHints};
22
23use crate::dom::bindings::error::Error;
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::bindings::str::DOMString;
26use crate::gpuadapterinfo::GPUAdapterInfo;
27use crate::gpusupportedfeatures::{GPUSupportedFeatures, gpu_to_wgt_feature};
28use crate::gpusupportedlimits::{GPUSupportedLimits, set_limit};
29use crate::promise::{WebGPUGlobalTrait, WebGPUPromiseTrait};
30
31#[derive(JSTraceable, MallocSizeOf)]
32struct DroppableGPUAdapter {
33    #[no_trace]
34    channel: WebGPU,
35    #[no_trace]
36    adapter: WebGPUAdapter,
37}
38
39impl Drop for DroppableGPUAdapter {
40    fn drop(&mut self) {
41        if let Err(e) = self
42            .channel
43            .0
44            .send(WebGPURequest::DropAdapter(self.adapter.0))
45        {
46            warn!(
47                "Failed to send WebGPURequest::DropAdapter({:?}) ({})",
48                self.adapter.0, e
49            );
50        };
51    }
52}
53
54#[dom_struct]
55pub struct GPUAdapter<D: DomTypes> {
56    reflector_: Reflector,
57    name: DOMString,
58    #[ignore_malloc_size_of = "mozjs"]
59    extensions: Heap<*mut JSObject>,
60    features: Dom<GPUSupportedFeatures<D>>,
61    limits: Dom<GPUSupportedLimits<D>>,
62    info: Dom<GPUAdapterInfo<D>>,
63    droppable: DroppableGPUAdapter,
64}
65
66impl<D> GPUAdapter<D>
67where
68    D: DomTypes<
69            GPUAdapter = GPUAdapter<D>,
70            GPUAdapterInfo = GPUAdapterInfo<D>,
71            GPUSupportedFeatures = GPUSupportedFeatures<D>,
72            GPUSupportedLimits = GPUSupportedLimits<D>,
73        >,
74{
75    fn new_inherited(
76        channel: WebGPU,
77        name: DOMString,
78        features: &GPUSupportedFeatures<D>,
79        limits: &GPUSupportedLimits<D>,
80        info: &GPUAdapterInfo<D>,
81        adapter: WebGPUAdapter,
82    ) -> Self {
83        Self {
84            reflector_: Reflector::new(),
85            name,
86            extensions: Heap::default(),
87            features: Dom::from_ref(features),
88            limits: Dom::from_ref(limits),
89            info: Dom::from_ref(info),
90            droppable: DroppableGPUAdapter { channel, adapter },
91        }
92    }
93
94    #[allow(clippy::too_many_arguments)]
95    pub fn new(
96        cx: &mut js::context::JSContext,
97        global: &D::GlobalScope,
98        channel: WebGPU,
99        name: DOMString,
100        extensions: HandleObject,
101        features: wgpu_types::Features,
102        limits: wgpu_types::Limits,
103        info: wgpu_types::AdapterInfo,
104        adapter: WebGPUAdapter,
105    ) -> DomRoot<Self> {
106        let features = GPUSupportedFeatures::Constructor(cx, global, None, features).unwrap();
107        let limits = GPUSupportedLimits::new(cx, global, limits);
108        let info = GPUAdapter::create_adapter_info(cx, global, info, &features);
109        let dom_root = reflect_dom_object_with_wrap::<D, _, _>(
110            Box::new(GPUAdapter::new_inherited(
111                channel, name, &features, &limits, &info, adapter,
112            )),
113            global,
114            cx,
115            GPUAdapterWrap::<D>,
116        );
117        dom_root.extensions.set(*extensions);
118        dom_root
119    }
120
121    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-new-adapter-info>
122    fn create_adapter_info(
123        cx: &mut js::context::JSContext,
124        global: &D::GlobalScope,
125        info: AdapterInfo,
126        features: &GPUSupportedFeatures<D>,
127    ) -> DomRoot<GPUAdapterInfo<D>> {
128        // Step 2. If the vendor is known, set adapterInfo.vendor to the name of adapter’s vendor as
129        // a normalized identifier string. To preserve privacy, the user agent may instead set
130        // adapterInfo.vendor to the empty string or a reasonable approximation of the vendor as a
131        // normalized identifier string.
132        let vendor = if info.vendor != 0 {
133            info.vendor.to_string().into()
134        } else {
135            DOMString::new()
136        };
137
138        // Step 3. If the architecture is known, set adapterInfo.architecture to a normalized
139        // identifier string representing the family or class of adapters to which adapter belongs.
140        // To preserve privacy, the user agent may instead set adapterInfo.architecture to the empty
141        // string or a reasonable approximation of the architecture as a normalized identifier
142        // string.
143        // TODO: AdapterInfo::architecture missing
144        // https://github.com/gfx-rs/wgpu/issues/2170
145        let architecture = DOMString::new();
146
147        // Step 4. If the device is known, set adapterInfo.device to a normalized identifier string
148        // representing a vendor-specific identifier for adapter. To preserve privacy, the user
149        // agent may instead set adapterInfo.device to to the empty string or a reasonable
150        // approximation of a vendor-specific identifier as a normalized identifier string.
151        let device = if info.device != 0 {
152            info.device.to_string().into()
153        } else {
154            DOMString::new()
155        };
156
157        // Step 5. If a description is known, set adapterInfo.description to a description of the
158        // adapter as reported by the driver. To preserve privacy, the user agent may instead set
159        // adapterInfo.description to the empty string or a reasonable approximation of a
160        // description.
161        let description = info.name.clone().into();
162
163        // Step 6. If "subgroups" is supported, set subgroupMinSize to the smallest supported
164        // subgroup size. Otherwise, set this value to 4.
165        // Step 7. If "subgroups" is supported, set subgroupMaxSize to the largest supported
166        // subgroup size. Otherwise, set this value to 128.
167        let (subgroup_min_size, subgroup_max_size) = if features.has(cx, "subgroups".into()) {
168            (info.subgroup_min_size, info.subgroup_max_size)
169        } else {
170            (4, 128)
171        };
172
173        // Step 8. Set adapterInfo.isFallbackAdapter to adapter.[[fallback]].
174        let is_fallback_adapter = info.device_type == wgpu_types::DeviceType::Cpu;
175
176        // Step 1. Let adapterInfo be a new GPUAdapterInfo.
177        GPUAdapterInfo::new(
178            cx,
179            global,
180            vendor,
181            architecture,
182            device,
183            description,
184            subgroup_min_size,
185            subgroup_max_size,
186            is_fallback_adapter,
187        )
188    }
189
190    pub fn channel(&self) -> WebGPU {
191        self.droppable.channel.clone()
192    }
193
194    fn global(&self) -> DomRoot<D::GlobalScope> {
195        <Self as DomGlobalGeneric<D>>::global_from_reflector(self)
196    }
197}
198
199impl<D> GPUAdapterMethods<D> for GPUAdapter<D>
200where
201    D: DomTypes<
202            GPUAdapter = GPUAdapter<D>,
203            GPUAdapterInfo = GPUAdapterInfo<D>,
204            GPUSupportedFeatures = GPUSupportedFeatures<D>,
205            GPUSupportedLimits = GPUSupportedLimits<D>,
206        >,
207    D::Promise: WebGPUPromiseTrait<D> + PromiseHelpers<D>,
208    D::GlobalScope: WebGPUGlobalTrait + GlobalScopeHelpers<D>,
209{
210    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
211    fn RequestDevice(
212        &self,
213        cx: &mut CurrentRealm<'_>,
214        descriptor: &GPUDeviceDescriptor,
215    ) -> Rc<D::Promise> {
216        // Step 2
217        let promise = D::Promise::new_in_realm(cx);
218
219        let callback = WebGPUPromiseTrait::<D>::callback_promise(&promise, self);
220        let mut required_features = wgpu_types::Features::empty();
221        for &ext in descriptor.requiredFeatures.iter() {
222            if let Some(feature) = gpu_to_wgt_feature(ext) {
223                required_features.insert(feature);
224            } else {
225                promise.reject_error(
226                    cx,
227                    Error::Type(cformat!("{} is not supported feature", ext.as_str())),
228                );
229                return promise;
230            }
231        }
232
233        let mut required_limits = wgpu_types::Limits::default();
234        if let Some(limits) = &descriptor.requiredLimits {
235            for (limit, value) in (*limits).iter() {
236                if !set_limit(&mut required_limits, &limit.str(), *value) {
237                    warn!("Unknown GPUDevice limit: {limit}");
238                    promise.reject_error(
239                        cx,
240                        Error::Operation(Some(format!("Unknown GPUDevice limit: {limit}"))),
241                    );
242                    return promise;
243                }
244            }
245        }
246
247        let desc = wgpu_types::DeviceDescriptor {
248            required_features,
249            required_limits,
250            label: Some(descriptor.parent.label.to_string()),
251            memory_hints: MemoryHints::MemoryUsage,
252            trace: wgpu_types::Trace::Off,
253            experimental_features: ExperimentalFeatures::disabled(),
254        };
255        let device_id = self.global().global_wgpu_id_hub().create_device_id();
256        let queue_id = self.global().global_wgpu_id_hub().create_queue_id();
257        let pipeline_id = self.global().pipeline_id();
258        if self
259            .droppable
260            .channel
261            .0
262            .send(WebGPURequest::RequestDevice {
263                sender: callback,
264                adapter_id: self.droppable.adapter,
265                descriptor: desc,
266                device_id,
267                queue_id,
268                pipeline_id,
269            })
270            .is_err()
271        {
272            promise.reject_error(
273                cx,
274                Error::Operation(Some("Could not Request GPU Device".to_string())),
275            );
276        }
277        // Step 5
278        promise
279    }
280
281    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
282    fn Features(&self) -> DomRoot<GPUSupportedFeatures<D>> {
283        DomRoot::from_ref(&self.features)
284    }
285
286    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
287    fn Limits(&self) -> DomRoot<GPUSupportedLimits<D>> {
288        DomRoot::from_ref(&self.limits)
289    }
290
291    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-info>
292    fn Info(&self) -> DomRoot<GPUAdapterInfo<D>> {
293        DomRoot::from_ref(&self.info)
294    }
295}