wgpu_test/
init.rs

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