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::traits::{Equivalence, 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: Equivalence,
69{
70    fn new_inherited(
71        channel: WebGPU,
72        name: DOMString,
73        features: &GPUSupportedFeatures<D>,
74        limits: &GPUSupportedLimits<D>,
75        info: &GPUAdapterInfo<D>,
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 fn new(
91        cx: &mut js::context::JSContext,
92        global: &D::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    ) -> DomRoot<Self> {
101        let features = GPUSupportedFeatures::Constructor(cx, global, None, features).unwrap();
102        let limits = GPUSupportedLimits::new(cx, global, limits);
103        let info = GPUAdapter::create_adapter_info(cx, global, info, &features);
104        let dom_root = reflect_dom_object_with_wrap::<D, _, _>(
105            Box::new(GPUAdapter::new_inherited(
106                channel, name, &features, &limits, &info, adapter,
107            )),
108            global,
109            cx,
110            GPUAdapterWrap::<D>,
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        cx: &mut js::context::JSContext,
119        global: &D::GlobalScope,
120        info: AdapterInfo,
121        features: &GPUSupportedFeatures<D>,
122    ) -> DomRoot<GPUAdapterInfo<D>> {
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(cx, "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            cx,
174            global,
175            vendor,
176            architecture,
177            device,
178            description,
179            subgroup_min_size,
180            subgroup_max_size,
181            is_fallback_adapter,
182        )
183    }
184
185    pub fn channel(&self) -> WebGPU {
186        self.droppable.channel.clone()
187    }
188
189    fn global(&self) -> DomRoot<D::GlobalScope> {
190        <Self as DomGlobalGeneric<D>>::global_from_reflector(self)
191    }
192}
193
194impl<D> GPUAdapterMethods<D> for GPUAdapter<D>
195where
196    D: Equivalence,
197    D::Promise: WebGPUPromiseTrait<D> + PromiseHelpers<D>,
198    D::GlobalScope: WebGPUGlobalTrait + GlobalScopeHelpers<D>,
199{
200    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice>
201    fn RequestDevice(
202        &self,
203        cx: &mut CurrentRealm<'_>,
204        descriptor: &GPUDeviceDescriptor,
205    ) -> Rc<D::Promise> {
206        // Step 2
207        let promise = D::Promise::new_in_realm(cx);
208
209        let callback = WebGPUPromiseTrait::<D>::callback_promise_adapter(&promise, self);
210        let mut required_features = wgpu_types::Features::empty();
211        for &ext in descriptor.requiredFeatures.iter() {
212            if let Some(feature) = gpu_to_wgt_feature(ext) {
213                required_features.insert(feature);
214            } else {
215                promise.reject_error(
216                    cx,
217                    Error::Type(cformat!("{} is not supported feature", ext.as_str())),
218                );
219                return promise;
220            }
221        }
222
223        let mut required_limits = wgpu_types::Limits::default();
224        if let Some(limits) = &descriptor.requiredLimits {
225            for (limit, value) in (*limits).iter() {
226                if !set_limit(&mut required_limits, &limit.str(), *value) {
227                    warn!("Unknown GPUDevice limit: {limit}");
228                    promise.reject_error(
229                        cx,
230                        Error::Operation(Some(format!("Unknown GPUDevice limit: {limit}"))),
231                    );
232                    return promise;
233                }
234            }
235        }
236
237        let desc = wgpu_types::DeviceDescriptor {
238            required_features,
239            required_limits,
240            label: Some(descriptor.parent.label.to_string()),
241            memory_hints: MemoryHints::MemoryUsage,
242            trace: wgpu_types::Trace::Off,
243            experimental_features: ExperimentalFeatures::disabled(),
244        };
245        let device_id = self.global().global_wgpu_id_hub().create_device_id();
246        let queue_id = self.global().global_wgpu_id_hub().create_queue_id();
247        let pipeline_id = self.global().pipeline_id();
248        if self
249            .droppable
250            .channel
251            .0
252            .send(WebGPURequest::RequestDevice {
253                sender: callback,
254                adapter_id: self.droppable.adapter,
255                descriptor: desc,
256                device_id,
257                queue_id,
258                pipeline_id,
259            })
260            .is_err()
261        {
262            promise.reject_error(
263                cx,
264                Error::Operation(Some("Could not Request GPU Device".to_string())),
265            );
266        }
267        // Step 5
268        promise
269    }
270
271    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features>
272    fn Features(&self) -> DomRoot<GPUSupportedFeatures<D>> {
273        DomRoot::from_ref(&self.features)
274    }
275
276    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits>
277    fn Limits(&self) -> DomRoot<GPUSupportedLimits<D>> {
278        DomRoot::from_ref(&self.limits)
279    }
280
281    /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-info>
282    fn Info(&self) -> DomRoot<GPUAdapterInfo<D>> {
283        DomRoot::from_ref(&self.info)
284    }
285}