wgpu_test/
init.rs

1use wgpu::{Adapter, Backends, Device, Features, Instance, Limits, Queue};
2
3use crate::{report::AdapterReport, TestParameters};
4
5/// Default device-lost callback installed by [`initialize_device`]. Panics on
6/// any non-[`wgpu::DeviceLostReason::Destroyed`] device loss, which will
7/// cause the test to be treated as a failure.
8///
9/// Tests intentionally provoking device loss should install their own callback
10/// with [`wgpu::Device::set_device_lost_callback`].
11fn default_device_lost_callback(reason: wgpu::DeviceLostReason, message: String) {
12    if reason != wgpu::DeviceLostReason::Destroyed {
13        panic!("Device lost: {message}");
14    }
15}
16
17/// Initialize the logger for the test runner.
18pub fn init_logger() {
19    // We don't actually care if it fails
20    #[cfg(not(target_arch = "wasm32"))]
21    let _ = env_logger::try_init();
22    #[cfg(target_arch = "wasm32")]
23    let _ = console_log::init_with_level(log::Level::Info);
24}
25
26/// Initialize a wgpu instance with the options from the environment.
27pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) -> Instance {
28    // We ignore `WGPU_BACKEND` for now, merely using test filtering to only run a single backend's tests.
29    //
30    // We can potentially work support back into the test runner in the future, but as the adapters are matched up
31    // based on adapter index, removing some backends messes up the indexes in annoying ways.
32    //
33    // WORKAROUND for https://github.com/rust-lang/cargo/issues/7160:
34    // `--no-default-features` is not passed through correctly to the test runner.
35    // We use it whenever we want to explicitly run with webgl instead of webgpu.
36    // To "disable" webgpu regardless, we do this by removing the webgpu backend whenever we see
37    // the webgl feature.
38    let backends = if cfg!(feature = "webgl") {
39        backends - wgpu::Backends::BROWSER_WEBGPU
40    } else {
41        backends
42    };
43    // Some tests need to be able to force demote to FXC, to specifically test workarounds for FXC
44    // behavior.
45    let dx12_shader_compiler = if params.force_fxc {
46        wgpu::Dx12Compiler::Fxc
47    } else {
48        wgpu::Dx12Compiler::from_env().unwrap_or(wgpu::Dx12Compiler::StaticDxc)
49    };
50    // The defaults for debugging, overridden by the environment, overridden by the test parameters.
51    let flags = wgpu::InstanceFlags::debugging()
52        .with_env()
53        .union(params.required_instance_flags);
54
55    Instance::new(wgpu::InstanceDescriptor {
56        backends,
57        flags,
58        memory_budget_thresholds: wgpu::MemoryBudgetThresholds {
59            for_resource_creation: Some(99),
60            for_device_loss: None,
61        },
62        backend_options: wgpu::BackendOptions {
63            dx12: wgpu::Dx12BackendOptions {
64                shader_compiler: dx12_shader_compiler,
65                ..Default::default()
66            },
67            gl: wgpu::GlBackendOptions {
68                fence_behavior: if cfg!(target_family = "wasm") {
69                    // On WebGL, you cannot call Poll(Wait) with any timeout. This is because the
70                    // browser does not things to block. However all of our tests are written to
71                    // expect this behavior. This is the workaround to allow this to work.
72                    //
73                    // However on native you can wait, so we want to ensure that behavior as well.
74                    wgpu::GlFenceBehavior::AutoFinish
75                } else {
76                    wgpu::GlFenceBehavior::Normal
77                },
78                ..Default::default()
79            },
80            // Allow the noop backend to be used in tests. This will not be used unless
81            // WGPU_GPU_TESTS_USE_NOOP_BACKEND env var is set, because wgpu-info will not
82            // enumerate the noop backend.
83            //
84            // However, we use wasm_bindgen_test to run tests on wasm, and wgpu
85            // will chose the noop on wasm32 for some reason.
86            noop: wgpu::NoopBackendOptions {
87                enable: !cfg!(target_arch = "wasm32"),
88                ..Default::default()
89            },
90        }
91        .with_env(),
92        #[cfg(not(all(
93            target_arch = "wasm32",
94            any(target_os = "emscripten", feature = "webgl")
95        )))]
96        display: None,
97        // Wasm requires a canvas surface below, and create_surface() requires
98        // the `display` to be set even if it's "empty" on Web:
99        #[cfg(all(
100            target_arch = "wasm32",
101            any(target_os = "emscripten", feature = "webgl")
102        ))]
103        display: Some(Box::new(WebDisplayHandle)),
104    })
105}
106
107/// Initialize a wgpu adapter, using the given adapter report to match the adapter.
108pub async fn initialize_adapter(
109    adapter_report: Option<&AdapterReport>,
110    params: &TestParameters,
111) -> (Instance, Adapter, Option<SurfaceGuard>) {
112    let backends = adapter_report
113        .map(|report| Backends::from(report.info.backend))
114        .unwrap_or_default();
115
116    let instance = initialize_instance(backends, params);
117    #[allow(unused_variables)]
118    let surface: Option<wgpu::Surface>;
119    let surface_guard: Option<SurfaceGuard>;
120
121    #[allow(unused_assignments)]
122    // Create a canvas if we need a WebGL2RenderingContext to have a working device.
123    #[cfg(not(all(
124        target_arch = "wasm32",
125        any(target_os = "emscripten", feature = "webgl")
126    )))]
127    {
128        surface = None;
129        surface_guard = None;
130    }
131    #[cfg(all(
132        target_arch = "wasm32",
133        any(target_os = "emscripten", feature = "webgl")
134    ))]
135    {
136        // On wasm, append a canvas to the document body for initializing the adapter
137        let canvas = initialize_html_canvas();
138
139        surface = Some(
140            instance
141                .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
142                .expect("could not create surface from canvas"),
143        );
144
145        surface_guard = Some(SurfaceGuard { canvas });
146    }
147
148    cfg_if::cfg_if! {
149        if #[cfg(not(target_arch = "wasm32"))] {
150            let adapter_iter = instance.enumerate_adapters(backends).await;
151            let adapter = adapter_iter.into_iter()
152                // If we have a report, we only want to match the adapter with the same info.
153                //
154                // If we don't have a report, we just take the first adapter.
155                .find(|adapter| if let Some(adapter_report) = adapter_report {
156                    adapter.get_info() == adapter_report.info
157                } else {
158                    true
159                });
160            let Some(adapter) = adapter else {
161                panic!(
162                    "Could not find adapter with info {:#?} in {:#?}",
163                    adapter_report.map(|r| &r.info),
164                    instance.enumerate_adapters(backends).await.into_iter().map(|a| a.get_info()).collect::<Vec<_>>(),
165                );
166            };
167        } else {
168            let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
169                compatible_surface: surface.as_ref(),
170                ..Default::default()
171            }).await.unwrap();
172        }
173    }
174
175    log::info!("Testing using adapter: {:#?}", adapter.get_info());
176
177    (instance, adapter, surface_guard)
178}
179
180/// Initialize a wgpu device from a given adapter.
181pub async fn initialize_device(
182    adapter: &Adapter,
183    features: Features,
184    limits: Limits,
185) -> (Device, Queue) {
186    let bundle = adapter
187        .request_device(&wgpu::DeviceDescriptor {
188            label: None,
189            required_features: features,
190            required_limits: limits,
191            experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
192            memory_hints: wgpu::MemoryHints::MemoryUsage,
193            trace: wgpu::Trace::Off,
194        })
195        .await;
196
197    let (device, queue) = match bundle {
198        Ok((device, queue)) => (device, queue),
199        Err(e) => panic!("Failed to initialize device: {e}"),
200    };
201
202    device.set_device_lost_callback(default_device_lost_callback);
203
204    (device, queue)
205}
206
207/// Create a canvas for testing.
208#[cfg(target_arch = "wasm32")]
209pub fn initialize_html_canvas() -> web_sys::HtmlCanvasElement {
210    use wasm_bindgen::JsCast;
211
212    web_sys::window()
213        .and_then(|win| win.document())
214        .and_then(|doc| {
215            let canvas = doc.create_element("Canvas").unwrap();
216            canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
217        })
218        .expect("couldn't create canvas")
219}
220
221pub struct SurfaceGuard {
222    #[cfg(target_arch = "wasm32")]
223    #[allow(unused)]
224    canvas: web_sys::HtmlCanvasElement,
225}
226
227impl SurfaceGuard {
228    #[cfg(all(
229        target_arch = "wasm32",
230        any(target_os = "emscripten", feature = "webgl")
231    ))]
232    pub(crate) fn check_for_unreported_errors(&self) -> bool {
233        use wasm_bindgen::JsCast;
234
235        self.canvas
236            .get_context("webgl2")
237            .unwrap()
238            .unwrap()
239            .dyn_into::<web_sys::WebGl2RenderingContext>()
240            .unwrap()
241            .get_error()
242            != web_sys::WebGl2RenderingContext::NO_ERROR
243    }
244}
245
246/// [`raw_window_handle::HasDisplayHandle`] implementation for Web that's [`Send`]+[`Sync`]
247/// because it doesn't own any pointers
248#[cfg(all(
249    target_arch = "wasm32",
250    any(target_os = "emscripten", feature = "webgl")
251))]
252#[derive(Debug)]
253struct WebDisplayHandle;
254
255#[cfg(all(
256    target_arch = "wasm32",
257    any(target_os = "emscripten", feature = "webgl")
258))]
259impl raw_window_handle::HasDisplayHandle for WebDisplayHandle {
260    fn display_handle(
261        &self,
262    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
263        Ok(raw_window_handle::DisplayHandle::web())
264    }
265}