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.
108///
109/// Returns `None` if the adapter from the report is not returned by `enumerate_adapters` due to `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` being set.
110pub async fn initialize_adapter(
111    adapter_report: Option<&AdapterReport>,
112    params: &TestParameters,
113) -> Option<(Instance, Adapter, Option<SurfaceGuard>)> {
114    let backends = adapter_report
115        .map(|report| Backends::from(report.info.backend))
116        .unwrap_or_default();
117
118    let instance = initialize_instance(backends, params);
119    #[allow(unused_variables)]
120    let surface: Option<wgpu::Surface>;
121    let surface_guard: Option<SurfaceGuard>;
122
123    #[allow(unused_assignments)]
124    // Create a canvas if we need a WebGL2RenderingContext to have a working device.
125    #[cfg(not(all(
126        target_arch = "wasm32",
127        any(target_os = "emscripten", feature = "webgl")
128    )))]
129    {
130        surface = None;
131        surface_guard = None;
132    }
133    #[cfg(all(
134        target_arch = "wasm32",
135        any(target_os = "emscripten", feature = "webgl")
136    ))]
137    {
138        // On wasm, append a canvas to the document body for initializing the adapter
139        let canvas = initialize_html_canvas();
140
141        surface = Some(
142            instance
143                .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
144                .expect("could not create surface from canvas"),
145        );
146
147        surface_guard = Some(SurfaceGuard { canvas });
148    }
149
150    cfg_if::cfg_if! {
151        if #[cfg(not(target_arch = "wasm32"))] {
152            let adapter_iter = instance.enumerate_adapters(backends).await;
153            let adapter = adapter_iter.into_iter()
154                // If we have a report, we only want to match the adapter with the same info.
155                //
156                // If we don't have a report, we just take the first adapter.
157                .find(|adapter| if let Some(adapter_report) = adapter_report {
158                    adapter.get_info() == adapter_report.info
159                } else {
160                    true
161                });
162        } else {
163            let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
164                compatible_surface: surface.as_ref(),
165                ..Default::default()
166            }).await.ok();
167        }
168    }
169
170    let Some(adapter) = adapter else {
171        if params
172            .required_instance_flags
173            .contains(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE)
174        {
175            return None;
176        } else {
177            panic!(
178                "Could not find adapter with info {:#?} in {:#?}",
179                adapter_report.map(|r| &r.info),
180                instance
181                    .enumerate_adapters(backends)
182                    .await
183                    .into_iter()
184                    .map(|a| a.get_info())
185                    .collect::<Vec<_>>(),
186            );
187        }
188    };
189
190    log::info!("Testing using adapter: {:#?}", adapter.get_info());
191    Some((instance, adapter, surface_guard))
192}
193
194/// Initialize a wgpu device from a given adapter.
195pub async fn initialize_device(
196    adapter: &Adapter,
197    features: Features,
198    limits: Limits,
199) -> (Device, Queue) {
200    let bundle = adapter
201        .request_device(&wgpu::DeviceDescriptor {
202            label: None,
203            required_features: features,
204            required_limits: limits,
205            experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
206            memory_hints: wgpu::MemoryHints::MemoryUsage,
207            trace: wgpu::Trace::Off,
208        })
209        .await;
210
211    let (device, queue) = match bundle {
212        Ok((device, queue)) => (device, queue),
213        Err(e) => panic!("Failed to initialize device: {e}"),
214    };
215
216    device.set_device_lost_callback(default_device_lost_callback);
217
218    (device, queue)
219}
220
221/// Create a canvas for testing.
222#[cfg(target_arch = "wasm32")]
223pub fn initialize_html_canvas() -> web_sys::HtmlCanvasElement {
224    use wasm_bindgen::JsCast;
225
226    web_sys::window()
227        .and_then(|win| win.document())
228        .and_then(|doc| {
229            let canvas = doc.create_element("Canvas").unwrap();
230            canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
231        })
232        .expect("couldn't create canvas")
233}
234
235pub struct SurfaceGuard {
236    #[cfg(target_arch = "wasm32")]
237    #[allow(unused)]
238    canvas: web_sys::HtmlCanvasElement,
239}
240
241impl SurfaceGuard {
242    #[cfg(all(
243        target_arch = "wasm32",
244        any(target_os = "emscripten", feature = "webgl")
245    ))]
246    pub(crate) fn check_for_unreported_errors(&self) -> bool {
247        use wasm_bindgen::JsCast;
248
249        self.canvas
250            .get_context("webgl2")
251            .unwrap()
252            .unwrap()
253            .dyn_into::<web_sys::WebGl2RenderingContext>()
254            .unwrap()
255            .get_error()
256            != web_sys::WebGl2RenderingContext::NO_ERROR
257    }
258}
259
260/// [`raw_window_handle::HasDisplayHandle`] implementation for Web that's [`Send`]+[`Sync`]
261/// because it doesn't own any pointers
262#[cfg(all(
263    target_arch = "wasm32",
264    any(target_os = "emscripten", feature = "webgl")
265))]
266#[derive(Debug)]
267struct WebDisplayHandle;
268
269#[cfg(all(
270    target_arch = "wasm32",
271    any(target_os = "emscripten", feature = "webgl")
272))]
273impl raw_window_handle::HasDisplayHandle for WebDisplayHandle {
274    fn display_handle(
275        &self,
276    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
277        Ok(raw_window_handle::DisplayHandle::web())
278    }
279}