Skip to content

Commit f0f700c

Browse files
Test setup for WebAssembly+WebGL (#3238)
Co-authored-by: Connor Fitzgerald <[email protected]>
1 parent 85fda2d commit f0f700c

23 files changed

+211
-38
lines changed

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,32 @@ jobs:
172172
cargo doc --target ${{ matrix.target }} -p wgpu -p wgpu-info -p player --all-features --no-deps
173173
cargo doc --target ${{ matrix.target }} -p wgpu-core --no-deps --features="portable_features"
174174
175+
wasm-test:
176+
name: Test WebAssembly
177+
runs-on: ubuntu-latest
178+
steps:
179+
- name: checkout repo
180+
uses: actions/checkout@v3
181+
182+
- name: install rust stable
183+
uses: actions-rs/toolchain@v1
184+
with:
185+
toolchain: stable
186+
profile: minimal
187+
override: true
188+
189+
- name: install wasm-pack # install from fork until this is merged: https://github.com/rustwasm/wasm-pack/pull/1185
190+
run: |
191+
# replace with "install wasm-pack action", which doesn't work for this project because of https://github.com/rustwasm/wasm-pack/issues/1180
192+
# - name: install wasm-pack
193+
# uses: jetli/[email protected]
194+
cargo install --git https://github.com/haraldreingruber/wasm-pack wasm-pack
195+
196+
- name: execute tests
197+
run: |
198+
cd wgpu
199+
wasm-pack test --headless --chrome --features webgl
200+
175201
gpu-test:
176202
strategy:
177203
fail-fast: false

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non
192192
- Update the `minimum supported rust version` to 1.64
193193
- Use cargo 1.64 workspace inheritance feature. By @jinleili in [#3107](https://github.com/gfx-rs/wgpu/pull/3107)
194194
- Move `ResourceMetadata` into its own module. By @jimblandy in [#3213](https://github.com/gfx-rs/wgpu/pull/3213)
195+
- Add WebAssembly testing infrastructure. By @haraldreingruber in [#3238](https://github.com/gfx-rs/wgpu/pull/3238)
195196

196197
#### Vulkan
197198

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ console_log = "0.2"
112112
js-sys = "0.3.60"
113113
wasm-bindgen = "0.2.83"
114114
wasm-bindgen-futures = "0.4.33"
115+
wasm-bindgen-test = "0.3"
115116
web-sys = "0.3.60"
116117

117118
# deno dependencies

wgpu/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ obj.workspace = true
163163
pollster.workspace = true
164164
png.workspace = true
165165
nanorand = { workspace = true, features = ["wyrand"] }
166-
winit.workspace = true # for "halmark" example # for "halmark" example
166+
wasm-bindgen-test.workspace = true
167+
winit.workspace = true # for "halmark" example
167168

168169
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
169170
async-executor.workspace = true
@@ -320,12 +321,13 @@ parking_lot.workspace = true
320321
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
321322
console_error_panic_hook.workspace = true
322323
console_log.workspace = true
323-
# We need these features in the framework examples
324+
# We need these features in the framework examples and tests
324325
web-sys = { workspace = true, features = [
325326
"Location",
326327
"Blob",
327328
"RequestInit",
328329
"RequestMode",
329330
"Request",
330-
"Response"
331+
"Response",
332+
"WebGl2RenderingContext"
331333
] }

wgpu/tests/buffer_usages.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Tests for buffer usages validation.
22
33
use crate::common::{fail_if, initialize_test, TestParameters};
4+
use wasm_bindgen_test::*;
45
use wgt::BufferAddress;
56

67
const BUFFER_SIZE: BufferAddress = 1234;
78

89
#[test]
10+
#[wasm_bindgen_test]
911
fn buffer_usage() {
1012
fn try_create(enable_mappable_primary_buffers: bool, usages: &[(bool, &[wgpu::BufferUsages])]) {
1113
let mut parameters = TestParameters::default();

wgpu/tests/clear_texture.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::common::{initialize_test, TestParameters, TestingContext};
2+
use wasm_bindgen_test::*;
23
use wgpu::util::align_to;
34

45
static TEXTURE_FORMATS_UNCOMPRESSED: &[wgpu::TextureFormat] = &[
@@ -202,9 +203,11 @@ fn single_texture_clear_test(
202203
size: wgpu::Extent3d,
203204
dimension: wgpu::TextureDimension,
204205
) {
205-
println!(
206+
log::info!(
206207
"clearing texture with {:?}, dimension {:?}, size {:?}",
207-
format, dimension, size
208+
format,
209+
dimension,
210+
size
208211
);
209212

210213
let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
@@ -315,6 +318,7 @@ fn clear_texture_2d_uncompressed() {
315318
}
316319

317320
#[test]
321+
#[wasm_bindgen_test]
318322
fn clear_texture_d32_s8() {
319323
initialize_test(
320324
TestParameters::default()

wgpu/tests/common/mod.rs

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
use std::panic::{catch_unwind, AssertUnwindSafe};
55

6+
use wgpu::{Adapter, Device, DownlevelFlags, Instance, Queue, Surface};
67
use wgt::{Backends, DeviceDescriptor, DownlevelCapabilities, Features, Limits};
78

8-
use wgpu::{util, Adapter, Device, DownlevelFlags, Instance, Queue};
9-
109
pub mod image;
1110

11+
const CANVAS_ID: &str = "test-canvas";
12+
1213
async fn initialize_device(
1314
adapter: &Adapter,
1415
features: Features,
@@ -168,16 +169,12 @@ impl TestParameters {
168169
}
169170
pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(TestingContext)) {
170171
// We don't actually care if it fails
172+
#[cfg(not(target_arch = "wasm32"))]
171173
let _ = env_logger::try_init();
174+
#[cfg(target_arch = "wasm32")]
175+
let _ = console_log::init_with_level(log::Level::Info);
172176

173-
let backend_bits = util::backend_bits_from_env().unwrap_or_else(Backends::all);
174-
let instance = Instance::new(backend_bits);
175-
let adapter = pollster::block_on(util::initialize_adapter_from_env_or_default(
176-
&instance,
177-
backend_bits,
178-
None,
179-
))
180-
.expect("could not find suitable adapter on the system");
177+
let (adapter, _) = initialize_adapter();
181178

182179
let adapter_info = adapter.get_info();
183180
let adapter_lowercase_name = adapter_info.name.to_lowercase();
@@ -187,19 +184,19 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
187184

188185
let missing_features = parameters.required_features - adapter_features;
189186
if !missing_features.is_empty() {
190-
println!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features);
187+
log::info!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features);
191188
return;
192189
}
193190

194191
if !parameters.required_limits.check_limits(&adapter_limits) {
195-
println!("TEST SKIPPED: LIMIT TOO LOW");
192+
log::info!("TEST SKIPPED: LIMIT TOO LOW");
196193
return;
197194
}
198195

199196
let missing_downlevel_flags =
200197
parameters.required_downlevel_properties.flags - adapter_downlevel_capabilities.flags;
201198
if !missing_downlevel_flags.is_empty() {
202-
println!(
199+
log::info!(
203200
"TEST SKIPPED: MISSING DOWNLEVEL FLAGS {:?}",
204201
missing_downlevel_flags
205202
);
@@ -209,7 +206,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
209206
if adapter_downlevel_capabilities.shader_model
210207
< parameters.required_downlevel_properties.shader_model
211208
{
212-
println!(
209+
log::info!(
213210
"TEST SKIPPED: LOW SHADER MODEL {:?}",
214211
adapter_downlevel_capabilities.shader_model
215212
);
@@ -273,7 +270,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
273270
});
274271

275272
if let Some((reason, true)) = expected_failure_reason {
276-
println!("EXPECTED TEST FAILURE SKIPPED: {:?}", reason);
273+
log::info!("EXPECTED TEST FAILURE SKIPPED: {:?}", reason);
277274
return;
278275
}
279276

@@ -301,9 +298,10 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
301298
// We got the conditions we expected
302299
if let Some((expected_reason, _)) = expected_failure_reason {
303300
// Print out reason for the failure
304-
println!(
301+
log::info!(
305302
"GOT EXPECTED TEST FAILURE DUE TO {}: {:?}",
306-
failure_cause, expected_reason
303+
failure_cause,
304+
expected_reason
307305
);
308306
}
309307
} else if let Some((reason, _)) = expected_failure_reason {
@@ -314,6 +312,72 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
314312
}
315313
}
316314

315+
fn initialize_adapter() -> (Adapter, SurfaceGuard) {
316+
let backend_bits = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all);
317+
let instance = Instance::new(backend_bits);
318+
let compatible_surface;
319+
320+
#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))]
321+
{
322+
compatible_surface = None;
323+
}
324+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
325+
{
326+
// On wasm, append a canvas to the document body for initializing the adapter
327+
let canvas = create_html_canvas();
328+
329+
let surface = instance
330+
.create_surface_from_canvas(&canvas)
331+
.expect("could not create surface from canvas");
332+
333+
compatible_surface = Some(surface);
334+
}
335+
336+
let compatible_surface: Option<&Surface> = compatible_surface.as_ref();
337+
let adapter = pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default(
338+
&instance,
339+
backend_bits,
340+
compatible_surface,
341+
))
342+
.expect("could not find suitable adapter on the system");
343+
344+
(adapter, SurfaceGuard)
345+
}
346+
347+
struct SurfaceGuard;
348+
349+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
350+
impl Drop for SurfaceGuard {
351+
fn drop(&mut self) {
352+
delete_html_canvas();
353+
}
354+
}
355+
356+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
357+
fn create_html_canvas() -> web_sys::HtmlCanvasElement {
358+
use wasm_bindgen::JsCast;
359+
360+
web_sys::window()
361+
.and_then(|win| win.document())
362+
.and_then(|doc| {
363+
let body = doc.body().unwrap();
364+
let canvas = doc.create_element("Canvas").unwrap();
365+
canvas.set_id(CANVAS_ID);
366+
body.append_child(&canvas).unwrap();
367+
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
368+
})
369+
.expect("couldn't append canvas to document body")
370+
}
371+
372+
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
373+
fn delete_html_canvas() {
374+
if let Some(document) = web_sys::window().and_then(|win| win.document()) {
375+
if let Some(element) = document.get_element_by_id(CANVAS_ID) {
376+
element.remove();
377+
}
378+
};
379+
}
380+
317381
// Run some code in an error scope and assert that validation fails.
318382
pub fn fail<T>(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T {
319383
device.push_error_scope(wgpu::ErrorFilter::Validation);

wgpu/tests/device.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use wasm_bindgen_test::*;
2+
13
use crate::common::{initialize_test, TestParameters};
24

35
#[test]
6+
#[wasm_bindgen_test]
47
fn device_initialization() {
58
initialize_test(TestParameters::default(), |_ctx| {
69
// intentionally empty

wgpu/tests/encoder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::common::{initialize_test, TestParameters};
2+
use wasm_bindgen_test::*;
23

34
#[test]
5+
#[wasm_bindgen_test]
46
fn drop_encoder() {
57
initialize_test(TestParameters::default(), |ctx| {
68
let encoder = ctx

wgpu/tests/instance.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use wasm_bindgen_test::*;
2+
13
#[test]
4+
#[wasm_bindgen_test]
25
fn initialize() {
36
let _ = wgpu::Instance::new(
47
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),

wgpu/tests/poll.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use wgpu::{
77
};
88

99
use crate::common::{initialize_test, TestParameters, TestingContext};
10+
use wasm_bindgen_test::*;
1011

1112
fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer {
1213
let buffer = ctx.device.create_buffer(&BufferDescriptor {
@@ -53,6 +54,7 @@ fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer {
5354
}
5455

5556
#[test]
57+
#[wasm_bindgen_test]
5658
fn wait() {
5759
initialize_test(TestParameters::default().skip(), |ctx| {
5860
let cmd_buf = generate_dummy_work(&ctx);
@@ -63,6 +65,7 @@ fn wait() {
6365
}
6466

6567
#[test]
68+
#[wasm_bindgen_test]
6669
fn double_wait() {
6770
initialize_test(TestParameters::default().skip(), |ctx| {
6871
let cmd_buf = generate_dummy_work(&ctx);
@@ -74,6 +77,7 @@ fn double_wait() {
7477
}
7578

7679
#[test]
80+
#[wasm_bindgen_test]
7781
fn wait_on_submission() {
7882
initialize_test(TestParameters::default().skip(), |ctx| {
7983
let cmd_buf = generate_dummy_work(&ctx);
@@ -84,6 +88,7 @@ fn wait_on_submission() {
8488
}
8589

8690
#[test]
91+
#[wasm_bindgen_test]
8792
fn double_wait_on_submission() {
8893
initialize_test(TestParameters::default().skip(), |ctx| {
8994
let cmd_buf = generate_dummy_work(&ctx);
@@ -95,6 +100,7 @@ fn double_wait_on_submission() {
95100
}
96101

97102
#[test]
103+
#[wasm_bindgen_test]
98104
fn wait_out_of_order() {
99105
initialize_test(TestParameters::default().skip(), |ctx| {
100106
let cmd_buf1 = generate_dummy_work(&ctx);

0 commit comments

Comments
 (0)