Skip to main content

script_webgpu/
gpu.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::marker::PhantomData;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::jsapi::HandleObject;
10use js::realm::CurrentRealm;
11use jstraceable_derive::JSTraceable;
12use log::warn;
13use malloc_size_of_derive::MallocSizeOf;
14use script_bindings::DomTypes;
15use script_bindings::codegen::GenericBindings::WebGPUBinding::{
16    GPUMethods, GPUPowerPreference, GPURequestAdapterOptions, GPUTextureFormat, GPUWrap,
17};
18use script_bindings::dom::MutNullableDom;
19use script_bindings::interfaces::{GlobalScopeHelpers, PromiseHelpers};
20use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
21use script_bindings::root::DomRoot;
22use script_bindings::routed_promise::RoutedPromiseListener;
23use script_bindings::str::DOMString;
24use servo_constellation_traits::ScriptToConstellationMessage;
25use webgpu_traits::{PowerPreference, RequestAdapterOptions, WebGPUAdapterResponse};
26
27use super::wgsllanguagefeatures::WGSLLanguageFeatures;
28use crate::dom::bindings::error::Error;
29use crate::gpuadapter::GPUAdapter;
30use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise, WebGPUPromiseCallbackTrait};
31
32#[dom_struct]
33pub struct GPU<D: DomTypes> {
34    reflector_: Reflector,
35    /// Same object for <https://www.w3.org/TR/webgpu/#dom-gpu-wgsllanguagefeatures>
36    wgsl_language_features: MutNullableDom<WGSLLanguageFeatures<D>>,
37    #[no_trace = "PhantomData does not exist"]
38    phantom: PhantomData<D>,
39}
40
41impl<D: Equivalence> GPU<D> {
42    pub(crate) fn new_inherited() -> GPU<D> {
43        GPU {
44            reflector_: Reflector::new(),
45            wgsl_language_features: MutNullableDom::default(),
46            phantom: PhantomData,
47        }
48    }
49
50    pub fn new(cx: &mut JSContext, global: &D::GlobalScope) -> DomRoot<GPU<D>> {
51        reflect_dom_object_with_wrap::<D, _, _>(
52            cx,
53            Box::new(GPU::new_inherited()),
54            global,
55            GPUWrap::<D>,
56        )
57    }
58}
59
60impl<D> GPUMethods<D> for GPU<D>
61where
62    D: Equivalence,
63    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
64    Self: DomGlobalGeneric<D>,
65{
66    /// <https://gpuweb.github.io/gpuweb/#dom-gpu-requestadapter>
67    fn RequestAdapter(
68        &self,
69        cx: &mut CurrentRealm,
70        options: &GPURequestAdapterOptions,
71    ) -> <D::Promise as PromiseHelpers<D>>::StackRoot {
72        let global = self.global_from_reflector();
73        // 1. Let promise be a new promise.
74        let promise = D::Promise::new_in_realm(cx);
75        let callback = promise.callback_promise_dom_manipulation_task_source(self);
76
77        let power_preference = match options.powerPreference {
78            Some(GPUPowerPreference::Low_power) => PowerPreference::LowPower,
79            Some(GPUPowerPreference::High_performance) => PowerPreference::HighPerformance,
80            None => PowerPreference::default(),
81        };
82        let ids = global.global_wgpu_id_hub().create_adapter_id();
83
84        // 3. Issue the initialization steps on the Device timeline of this
85
86        /*
87        We do some steps here to avoid IPC round-trips
88        1. options.featureLevel must be a feature level string.
89        If any are unmet
90            Let adapter be null, issue the resolution steps on contentTimeline, and return.
91        If adapter is null:
92            Resolve promise with null.
93        */
94        match &*options.featureLevel.str() {
95            "core" => {},
96            "compatibility" => {
97                // Set options.featureLevel to "compatibility" if the user agent chooses to support it, or "core" if not.
98                // and wgpu does not support "compatibility" yet so we return core for now
99            },
100            _ => {
101                promise.resolve_native(cx, &None::<GPUAdapter<D>>);
102                return promise;
103            },
104        }
105        let script_to_constellation_chan = global.script_to_constellation_chan();
106        if script_to_constellation_chan
107            .send(ScriptToConstellationMessage::RequestAdapter(
108                callback,
109                RequestAdapterOptions {
110                    power_preference,
111                    compatible_surface: None,
112                    force_fallback_adapter: options.forceFallbackAdapter,
113                    apply_limit_buckets: false,
114                },
115                ids,
116            ))
117            .is_err()
118        {
119            promise.reject_error(
120                cx,
121                Error::Operation(Some(
122                    "Could not send `requestAdapter` request from script thread to constellation thread".into(),
123                )),
124            );
125        }
126        // 4. Return promise
127        promise
128    }
129
130    /// <https://gpuweb.github.io/gpuweb/#dom-gpu-getpreferredcanvasformat>
131    fn GetPreferredCanvasFormat(&self) -> GPUTextureFormat {
132        // From https://github.com/mozilla-firefox/firefox/blob/24d49101ce17b78c3ba1217d00297fe2891be6b3/dom/webgpu/Instance.h#L68
133        if cfg!(target_os = "android") {
134            GPUTextureFormat::Rgba8unorm
135        } else {
136            GPUTextureFormat::Bgra8unorm
137        }
138    }
139
140    /// <https://www.w3.org/TR/webgpu/#dom-gpu-wgsllanguagefeatures>
141    fn WgslLanguageFeatures(
142        &self,
143        cx: &mut js::context::JSContext,
144    ) -> DomRoot<WGSLLanguageFeatures<D>> {
145        self.wgsl_language_features
146            .or_init(|| WGSLLanguageFeatures::new(cx, &*self.global_from_reflector(), None))
147    }
148}
149
150impl<D: Equivalence> RoutedPromiseListener<D, WebGPUAdapterResponse> for GPU<D>
151where
152    Self: DomGlobalGeneric<D>,
153{
154    fn handle_response(
155        &self,
156        cx: &mut js::context::JSContext,
157        response: WebGPUAdapterResponse,
158        promise: &<D::Promise as PromiseHelpers<D>>::StackRoot,
159    ) {
160        match response {
161            Some(Ok(adapter)) => {
162                let adapter = GPUAdapter::<D>::new(
163                    cx,
164                    &self.global_from_reflector(),
165                    adapter.channel,
166                    DOMString::from(format!(
167                        "{} ({:?})",
168                        adapter.adapter_info.name, adapter.adapter_id.0
169                    )),
170                    HandleObject::null(),
171                    adapter.features,
172                    adapter.limits,
173                    adapter.adapter_info,
174                    adapter.adapter_id,
175                );
176                promise.resolve_native(cx, &adapter);
177            },
178            Some(Err(e)) => {
179                warn!("Could not get GPUAdapter ({:?})", e);
180                promise.resolve_native(cx, &None::<GPUAdapter<D>>);
181            },
182            None => {
183                warn!("Couldn't get a response, because WebGPU is disabled");
184                promise.resolve_native(cx, &None::<GPUAdapter<D>>);
185            },
186        }
187    }
188}