wgpu_test/
init.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use wgpu::{Adapter, Backends, Device, Features, Instance, Limits, Queue};

use crate::{report::AdapterReport, TestParameters};

/// Initialize the logger for the test runner.
pub fn init_logger() {
    // We don't actually care if it fails
    #[cfg(not(target_arch = "wasm32"))]
    let _ = env_logger::try_init();
    #[cfg(target_arch = "wasm32")]
    let _ = console_log::init_with_level(log::Level::Info);
}

/// Initialize a wgpu instance with the options from the environment.
pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) -> Instance {
    // We ignore `WGPU_BACKEND` for now, merely using test filtering to only run a single backend's tests.
    //
    // We can potentially work support back into the test runner in the future, but as the adapters are matched up
    // based on adapter index, removing some backends messes up the indexes in annoying ways.
    //
    // WORKAROUND for https://github.com/rust-lang/cargo/issues/7160:
    // `--no-default-features` is not passed through correctly to the test runner.
    // We use it whenever we want to explicitly run with webgl instead of webgpu.
    // To "disable" webgpu regardless, we do this by removing the webgpu backend whenever we see
    // the webgl feature.
    let backends = if cfg!(feature = "webgl") {
        backends - wgpu::Backends::BROWSER_WEBGPU
    } else {
        backends
    };
    // Some tests need to be able to force demote to FXC, to specifically test workarounds for FXC
    // behavior.
    let dx12_shader_compiler = if params.force_fxc {
        wgpu::Dx12Compiler::Fxc
    } else {
        wgpu::Dx12Compiler::from_env().unwrap_or(wgpu::Dx12Compiler::StaticDxc)
    };
    // The defaults for debugging, overridden by the environment, overridden by the test parameters.
    let flags = wgpu::InstanceFlags::debugging()
        .with_env()
        .union(params.required_instance_flags);

    Instance::new(&wgpu::InstanceDescriptor {
        backends,
        flags,
        backend_options: wgpu::BackendOptions {
            dx12: wgpu::Dx12BackendOptions {
                shader_compiler: dx12_shader_compiler,
            },
            gl: wgpu::GlBackendOptions {
                fence_behavior: if cfg!(target_family = "wasm") {
                    // On WebGL, you cannot call Poll(Wait) with any timeout. This is because the
                    // browser does not things to block. However all of our tests are written to
                    // expect this behavior. This is the workaround to allow this to work.
                    //
                    // However on native you can wait, so we want to ensure that behavior as well.
                    wgpu::GlFenceBehavior::AutoFinish
                } else {
                    wgpu::GlFenceBehavior::Normal
                },
                ..Default::default()
            }
            .with_env(),
            // TODO(https://github.com/gfx-rs/wgpu/issues/7119): Enable noop backend?
            noop: wgpu::NoopBackendOptions::default(),
        },
    })
}

/// Initialize a wgpu adapter, using the given adapter report to match the adapter.
pub async fn initialize_adapter(
    adapter_report: Option<&AdapterReport>,
    params: &TestParameters,
) -> (Instance, Adapter, Option<SurfaceGuard>) {
    let backends = adapter_report
        .map(|report| Backends::from(report.info.backend))
        .unwrap_or_default();

    let instance = initialize_instance(backends, params);
    #[allow(unused_variables)]
    let surface: Option<wgpu::Surface>;
    let surface_guard: Option<SurfaceGuard>;

    #[allow(unused_assignments)]
    // Create a canvas if we need a WebGL2RenderingContext to have a working device.
    #[cfg(not(all(
        target_arch = "wasm32",
        any(target_os = "emscripten", feature = "webgl")
    )))]
    {
        surface = None;
        surface_guard = None;
    }
    #[cfg(all(
        target_arch = "wasm32",
        any(target_os = "emscripten", feature = "webgl")
    ))]
    {
        // On wasm, append a canvas to the document body for initializing the adapter
        let canvas = initialize_html_canvas();

        surface = Some(
            instance
                .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
                .expect("could not create surface from canvas"),
        );

        surface_guard = Some(SurfaceGuard { canvas });
    }

    cfg_if::cfg_if! {
        if #[cfg(not(target_arch = "wasm32"))] {
            let adapter_iter = instance.enumerate_adapters(backends);
            let adapter = adapter_iter.into_iter()
                // If we have a report, we only want to match the adapter with the same info.
                //
                // If we don't have a report, we just take the first adapter.
                .find(|adapter| if let Some(adapter_report) = adapter_report {
                    adapter.get_info() == adapter_report.info
                } else {
                    true
                });
            let Some(adapter) = adapter else {
                panic!(
                    "Could not find adapter with info {:#?} in {:#?}",
                    adapter_report.map(|r| &r.info),
                    instance.enumerate_adapters(backends).into_iter().map(|a| a.get_info()).collect::<Vec<_>>(),
                );
            };
        } else {
            let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
                compatible_surface: surface.as_ref(),
                ..Default::default()
            }).await.unwrap();
        }
    }

    log::info!("Testing using adapter: {:#?}", adapter.get_info());

    (instance, adapter, surface_guard)
}

/// Initialize a wgpu device from a given adapter.
pub async fn initialize_device(
    adapter: &Adapter,
    features: Features,
    limits: Limits,
) -> (Device, Queue) {
    let bundle = adapter
        .request_device(&wgpu::DeviceDescriptor {
            label: None,
            required_features: features,
            required_limits: limits,
            memory_hints: wgpu::MemoryHints::MemoryUsage,
            trace: wgpu::Trace::Off,
        })
        .await;

    match bundle {
        Ok(b) => b,
        Err(e) => panic!("Failed to initialize device: {e}"),
    }
}

/// Create a canvas for testing.
#[cfg(target_arch = "wasm32")]
pub fn initialize_html_canvas() -> web_sys::HtmlCanvasElement {
    use wasm_bindgen::JsCast;

    web_sys::window()
        .and_then(|win| win.document())
        .and_then(|doc| {
            let canvas = doc.create_element("Canvas").unwrap();
            canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
        })
        .expect("couldn't create canvas")
}

pub struct SurfaceGuard {
    #[cfg(target_arch = "wasm32")]
    #[allow(unused)]
    canvas: web_sys::HtmlCanvasElement,
}

impl SurfaceGuard {
    #[cfg(all(
        target_arch = "wasm32",
        any(target_os = "emscripten", feature = "webgl")
    ))]
    pub(crate) fn check_for_unreported_errors(&self) -> bool {
        use wasm_bindgen::JsCast;

        self.canvas
            .get_context("webgl2")
            .unwrap()
            .unwrap()
            .dyn_into::<web_sys::WebGl2RenderingContext>()
            .unwrap()
            .get_error()
            != web_sys::WebGl2RenderingContext::NO_ERROR
    }
}