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
use wgpu::{Adapter, Device, Instance, Queue};
use wgt::{Backends, Features, Limits};

use crate::report::AdapterReport;

/// 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, force_fxc: bool) -> 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 force_fxc {
        wgpu::Dx12Compiler::Fxc
    } else {
        wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default()
    };
    let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default();
    Instance::new(wgpu::InstanceDescriptor {
        backends,
        flags: wgpu::InstanceFlags::debugging().with_env(),
        dx12_shader_compiler,
        gles_minor_version,
    })
}

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

    let instance = initialize_instance(backends, force_fxc);
    #[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,
            },
            None,
        )
        .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
    }
}