From 543f9216392e2dad528718bbdf326d44599b5024 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Thu, 19 Oct 2023 12:06:42 -0400 Subject: [PATCH] GPU Test Framework (#3873) --- .deny.toml | 3 +- .github/workflows/ci.yml | 32 +- .gitignore | 3 + Cargo.lock | 551 ++++++++----- Cargo.toml | 15 + README.md | 2 +- examples/boids/Cargo.toml | 1 + examples/boids/src/main.rs | 19 +- examples/bunnymark/Cargo.toml | 1 + examples/bunnymark/src/main.rs | 19 +- examples/common/src/framework.rs | 229 +++--- examples/conservative-raster/Cargo.toml | 1 + examples/conservative-raster/src/main.rs | 19 +- examples/cube/Cargo.toml | 1 + examples/cube/src/main.rs | 32 +- examples/hello-compute/Cargo.toml | 4 +- examples/hello-compute/src/main.rs | 18 +- examples/hello-compute/src/tests.rs | 144 ++-- examples/hello-synchronization/Cargo.toml | 2 +- examples/hello-synchronization/src/main.rs | 4 +- examples/hello-synchronization/src/tests.rs | 29 +- examples/mipmap/Cargo.toml | 1 + examples/mipmap/src/main.rs | 34 +- examples/msaa-line/Cargo.toml | 1 + examples/msaa-line/src/main.rs | 19 +- examples/shadow/Cargo.toml | 1 + examples/shadow/src/main.rs | 42 +- examples/skybox/Cargo.toml | 1 + examples/skybox/src/main.rs | 68 +- examples/stencil-triangles/Cargo.toml | 1 + examples/stencil-triangles/src/main.rs | 27 +- examples/texture-arrays/Cargo.toml | 1 + examples/texture-arrays/src/main.rs | 46 +- examples/timestamp-queries/src/main.rs | 30 +- examples/water/Cargo.toml | 1 + examples/water/src/main.rs | 19 +- tests/Cargo.toml | 20 +- tests/src/config.rs | 113 +++ tests/src/image.rs | 172 ++-- tests/src/init.rs | 129 +++ tests/src/lib.rs | 624 +-------------- tests/src/native.rs | 104 +++ tests/src/params.rs | 363 +++++++++ tests/src/report.rs | 52 ++ tests/src/run.rs | 126 +++ tests/tests/bgra8unorm_storage.rs | 26 +- tests/tests/bind_group_layout_dedup.rs | 11 +- tests/tests/buffer.rs | 149 ++-- tests/tests/buffer_copy.rs | 53 +- tests/tests/buffer_usages.rs | 123 ++- tests/tests/clear_texture.rs | 106 ++- tests/tests/cpu.rs | 1 + tests/tests/create_surface_error.rs | 13 +- tests/tests/device.rs | 737 +++++++++--------- tests/tests/encoder.rs | 49 +- tests/tests/example_wgsl.rs | 2 +- tests/tests/external_texture.rs | 236 +++--- tests/tests/{root.rs => gpu.rs} | 5 +- tests/tests/instance.rs | 40 +- tests/tests/occlusion_query/mod.rs | 12 +- tests/tests/partially_bounded_arrays/mod.rs | 191 +++-- tests/tests/pipeline.rs | 26 +- tests/tests/poll.rs | 215 +++-- tests/tests/query_set.rs | 17 +- tests/tests/queue_transfer.rs | 11 +- tests/tests/regression/issue_3457.rs | 13 +- tests/tests/regression/issue_4024.rs | 13 +- tests/tests/regression/issue_4122.rs | 11 +- tests/tests/resource_descriptor_accessor.rs | 35 +- tests/tests/resource_error.rs | 93 +-- tests/tests/scissor_tests/mod.rs | 64 +- tests/tests/shader/mod.rs | 6 +- tests/tests/shader/numeric_builtins.rs | 27 +- tests/tests/shader/struct_layout.rs | 75 +- tests/tests/shader/zero_init_workgroup_mem.rs | 279 ++++--- tests/tests/shader_primitive_index/mod.rs | 93 ++- tests/tests/shader_view_format/mod.rs | 31 +- tests/tests/texture_bounds.rs | 46 +- tests/tests/transfer.rs | 126 ++- tests/tests/vertex_indices/mod.rs | 67 +- tests/tests/write_texture.rs | 29 +- .../tests/zero_init_texture_after_discard.rs | 144 ++-- wgpu-hal/src/metal/mod.rs | 3 +- wgpu-info/src/report.rs | 6 + wgpu-macros/Cargo.toml | 20 + wgpu-macros/src/lib.rs | 44 ++ wgpu/Cargo.toml | 3 +- wgpu/src/util/belt.rs | 8 +- xtask/Cargo.lock | 16 + xtask/Cargo.toml | 1 + xtask/src/cli.rs | 4 + xtask/src/main.rs | 5 +- xtask/src/test.rs | 58 ++ 93 files changed, 3508 insertions(+), 2959 deletions(-) create mode 100644 tests/src/config.rs create mode 100644 tests/src/init.rs create mode 100644 tests/src/native.rs create mode 100644 tests/src/params.rs create mode 100644 tests/src/report.rs create mode 100644 tests/src/run.rs create mode 100644 tests/tests/cpu.rs rename tests/tests/{root.rs => gpu.rs} (84%) create mode 100644 wgpu-macros/Cargo.toml create mode 100644 wgpu-macros/src/lib.rs create mode 100644 xtask/src/test.rs diff --git a/.deny.toml b/.deny.toml index 29e876c4b..e29302dec 100644 --- a/.deny.toml +++ b/.deny.toml @@ -6,7 +6,8 @@ skip-tree = [ { name = "wgpu-info" }, ] skip = [ - { name = "wgpu" } + { name = "wgpu" }, + { name = "fastrand" } ] wildcards = "deny" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e85c569d..a20cd0f84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -270,17 +270,14 @@ jobs: # Windows - name: Windows x86_64 os: windows-2022 - backends: dx12 # Mac - name: Mac aarch64 os: [self-hosted, macOS] - backends: vulkan metal # Linux - name: Linux x86_64 os: ubuntu-22.04 - backends: vulkan gl name: Test ${{ matrix.name }} runs-on: ${{ matrix.os }} @@ -294,16 +291,10 @@ jobs: with: tool: cargo-nextest,cargo-llvm-cov - - name: install swiftshader - if: matrix.os == 'ubuntu-22.04' - shell: bash + - name: Install Repo MSRV toolchain run: | - set -e - - mkdir -p swiftshader - curl -LsSf https://github.com/gfx-rs/ci-build/releases/latest/download/swiftshader-linux-x86_64.tar.xz | tar -xf - -C swiftshader - - echo "VK_ICD_FILENAMES=$PWD/swiftshader/vk_swiftshader_icd.json" >> $GITHUB_ENV + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal + cargo -V - name: install llvmpipe, vulkan sdk if: matrix.os == 'ubuntu-22.04' @@ -317,8 +308,10 @@ jobs: wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add - sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-jammy.list https://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list + sudo add-apt-repository ppa:kisak/kisak-mesa + sudo apt-get update - sudo apt install -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev vulkan-sdk + sudo apt install -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev vulkan-sdk mesa-vulkan-drivers - name: disable debug shell: bash @@ -334,22 +327,12 @@ jobs: with: key: test-${{ matrix.os }}-${{ env.CACHE_SUFFIX }} - - name: run wgpu-info - shell: bash - run: | - set -e - - cargo llvm-cov --no-cfg-coverage run --bin wgpu-info --no-report --features vulkan-portability - - name: run tests shell: bash run: | set -e - for backend in ${{ matrix.backends }}; do - echo "======= NATIVE TESTS $backend ======"; - WGPU_BACKEND=$backend cargo llvm-cov --no-cfg-coverage nextest --no-fail-fast --no-report --features vulkan-portability - done + cargo xtask test --llvm-cov - uses: actions/upload-artifact@v3 if: always() # We want artifacts even if the tests fail. @@ -421,6 +404,7 @@ jobs: - name: run rustfmt run: | cargo fmt -- --check + cargo fmt --manifest-path xtask/Cargo.toml -- --check check-cts-runner: name: Clippy cts_runner diff --git a/.gitignore b/.gitignore index 6192f9ded..089a7f2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ cts/ # Readme says to put angle in working directory *.dll + +# Cached GPU config +.gpuconfig diff --git a/Cargo.lock b/Cargo.lock index bae16c723..98b91772c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -61,9 +61,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-activity" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bc1575e653f158cbdc6ebcd917b9564e66321c5325c232c3591269c257be69" +checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" dependencies = [ "android-properties", "bitflags 1.3.2", @@ -92,6 +92,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -137,35 +185,35 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand", + "fastrand 2.0.1", "futures-lite", "slab", ] [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -176,9 +224,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -191,9 +239,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64-simd" @@ -262,9 +310,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" @@ -277,26 +325,26 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "calloop" @@ -314,11 +362,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -342,6 +391,46 @@ dependencies = [ "libc", ] +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + [[package]] name = "cmake" version = "0.1.50" @@ -369,15 +458,14 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", "libc", "objc", ] @@ -398,6 +486,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "com-rs" version = "0.2.1" @@ -406,9 +500,9 @@ checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] @@ -538,6 +632,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctor" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +dependencies = [ + "quote", + "syn 2.0.38", +] + [[package]] name = "cts_runner" version = "0.1.0" @@ -639,7 +743,7 @@ checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f" dependencies = [ "deno-proc-macro-rules-macros", "proc-macro2", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -651,7 +755,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -674,7 +778,7 @@ dependencies = [ "deno_ops", "deno_unsync", "futures", - "indexmap 2.0.0", + "indexmap", "libc", "log", "once_cell", @@ -706,7 +810,7 @@ dependencies = [ "regex", "strum", "strum_macros", - "syn 2.0.28", + "syn 2.0.38", "thiserror", ] @@ -821,9 +925,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encase" @@ -854,7 +958,7 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -896,25 +1000,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -931,6 +1024,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -954,15 +1056,27 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "libz-ng-sys", "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -996,7 +1110,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -1117,8 +1231,13 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ + "fastrand 1.9.0", "futures-core", + "futures-io", + "memchr", + "parking", "pin-project-lite", + "waker-fn", ] [[package]] @@ -1129,7 +1248,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -1188,9 +1307,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "gl_generator" @@ -1336,29 +1455,23 @@ checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.4.1", "gpu-descriptor-types", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", ] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash", "allocator-api2", @@ -1387,9 +1500,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hexf-parse" @@ -1397,6 +1510,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1441,22 +1563,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", "serde", ] @@ -1497,9 +1609,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -1550,7 +1662,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -1585,6 +1697,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libtest-mimic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" +dependencies = [ + "clap", + "termcolor", + "threadpool", +] + [[package]] name = "libz-ng-sys" version = "1.1.12" @@ -1603,9 +1726,9 @@ checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1628,9 +1751,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" @@ -1701,7 +1824,7 @@ dependencies = [ "bitflags 2.4.1", "codespan-reporting", "hexf-parse", - "indexmap 2.0.0", + "indexmap", "log", "num-traits 0.2.17", "petgraph", @@ -1719,6 +1842,9 @@ name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.10", +] [[package]] name = "ndk" @@ -1826,9 +1952,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -1924,7 +2050,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -1998,9 +2124,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -2013,11 +2139,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "orbclient" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" +checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" dependencies = [ - "redox_syscall", + "redox_syscall 0.3.5", ] [[package]] @@ -2044,6 +2170,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2056,13 +2188,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -2081,12 +2213,12 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap", ] [[package]] @@ -2097,29 +2229,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -2155,7 +2287,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2210,9 +2342,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -2225,9 +2357,9 @@ checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" [[package]] name = "quote" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -2328,10 +2460,19 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.9.1" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -2341,9 +2482,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -2352,9 +2493,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "renderdoc-sys" @@ -2401,7 +2542,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.18", + "semver 1.0.20", ] [[package]] @@ -2486,9 +2627,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "semver-parser" @@ -2522,7 +2663,7 @@ checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2531,7 +2672,7 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "indexmap 2.0.0", + "indexmap", "itoa", "ryu", "serde", @@ -2595,15 +2736,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -2625,9 +2766,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ "bitflags 1.3.2", "calloop", @@ -2644,9 +2785,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -2668,6 +2809,15 @@ dependencies = [ "url", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.2.0+1.5.4" @@ -2707,15 +2857,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2731,9 +2881,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -2766,7 +2916,16 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", ] [[package]] @@ -2861,7 +3020,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2872,20 +3031,20 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap", "toml_datetime", "winnow", ] [[package]] name = "ttf-parser" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" [[package]] name = "unic-char-property" @@ -2936,15 +3095,15 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-id" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2957,9 +3116,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -2969,9 +3128,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -2993,10 +3152,16 @@ dependencies = [ ] [[package]] -name = "uuid" -version = "1.4.1" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom 0.2.10", "serde", @@ -3032,6 +3197,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3065,7 +3236,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -3099,7 +3270,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3233,6 +3404,7 @@ version = "0.17.0" dependencies = [ "arrayvec 0.7.4", "cfg-if", + "flume", "js-sys", "log", "naga", @@ -3399,7 +3571,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "env_logger", - "futures-intrusive", + "flume", "log", "pollster", "wasm-bindgen-futures", @@ -3432,7 +3604,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "env_logger", - "futures-intrusive", + "flume", "log", "pollster", "wasm-bindgen-futures", @@ -3501,6 +3673,15 @@ dependencies = [ "wgpu-types", ] +[[package]] +name = "wgpu-macros" +version = "0.17.0" +dependencies = [ + "heck", + "quote", + "syn 2.0.38", +] + [[package]] name = "wgpu-mipmap-example" version = "0.17.0" @@ -3634,13 +3815,19 @@ dependencies = [ name = "wgpu-test" version = "0.17.0" dependencies = [ + "anyhow", + "arrayvec 0.7.4", "bitflags 2.4.1", "bytemuck", "cfg-if", "console_log", + "ctor", "env_logger", + "futures-lite", + "heck", "image", "js-sys", + "libtest-mimic", "log", "naga", "nv-flip", @@ -3648,11 +3835,14 @@ dependencies = [ "png", "pollster", "raw-window-handle 0.5.2", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", "web-sys", "wgpu", + "wgpu-macros", "wgpu-types", ] @@ -3731,13 +3921,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -3764,9 +3955,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -4026,7 +4217,7 @@ dependencies = [ "orbclient", "percent-encoding", "raw-window-handle 0.5.2", - "redox_syscall", + "redox_syscall 0.3.5", "sctk-adwaita 0.5.4", "smithay-client-toolkit", "wasm-bindgen", @@ -4041,9 +4232,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] @@ -4079,6 +4270,6 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" diff --git a/Cargo.toml b/Cargo.toml index 195231857..bdee6f2eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "wgpu-core", "wgpu-hal", "wgpu-info", + "wgpu-macros", "wgpu-types", "tests", ] @@ -20,6 +21,7 @@ default-members = [ "wgpu-core", "wgpu-hal", "wgpu-info", + "wgpu-macros", "wgpu-types", "tests" ] @@ -64,21 +66,27 @@ bytemuck = { version = "1.14", features = ["derive"] } cfg_aliases = "0.1" cfg-if = "1" codespan-reporting = "0.11" +ctor = "0.2" ddsfile = "0.5" env_logger = "0.10" +flume = "0.11" +futures-lite = "1" futures-intrusive = "0.5" rustc-hash = "1.1.0" glam = "0.24.2" +heck = "0.4.0" image = { version = "0.24", default-features = false, features = ["png"] } # libloading 0.8 switches from `winapi` to `windows-sys`; permit either libloading = ">=0.7, <0.9" libc = "0.2" +libtest-mimic = "0.6" log = "0.4" nanorand = { version = "0.7", default-features = false, features = ["wyrand"] } nv-flip = "0.1" num-traits = { version = "0.2" } noise = "0.8" obj = "0.10" +once_cell = "1" # parking_lot 0.12 switches from `winapi` to `windows`; permit either parking_lot = ">=0.11,<0.13" pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } @@ -96,10 +104,17 @@ thiserror = "1" wgpu = { version = "0.17.0", path = "./wgpu" } wgpu-core = { version = "0.17.0", path = "./wgpu-core" } wgpu-example = { version = "0.17.0", path = "./examples/common" } +wgpu-macros = { version = "0.17.0", path = "./wgpu-macros" } wgpu-test = { version = "0.17", path = "./tests"} wgpu-types = { version = "0.17.0", path = "./wgpu-types" } winit = { version = "0.28.7", features = [ "android-native-activity" ] } +# Metal dependencies +block = "0.1" +metal = "0.25.0" +objc = "0.2.5" +core-graphics-types = "0.1" + # Vulkan dependencies ash = "0.37.3" gpu-alloc = "0.6" diff --git a/README.md b/README.md index 564136aa7..a3a1a6cf6 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ To install it, run `cargo install cargo-nextest`. To run the test suite: ``` -cargo nextest run --no-fail-fast +cargo xtask test ``` To run the test suite on WebGL (currently incomplete): diff --git a/examples/boids/Cargo.toml b/examples/boids/Cargo.toml index 933acd64e..45e2cd3e7 100644 --- a/examples/boids/Cargo.toml +++ b/examples/boids/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "boids" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/boids/src/main.rs b/examples/boids/src/main.rs index eb5146f8b..75d42273d 100644 --- a/examples/boids/src/main.rs +++ b/examples/boids/src/main.rs @@ -326,16 +326,16 @@ impl wgpu_example::framework::Example for Example { } /// run example +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("boids"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn boids() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "boids", // Generated on 1080ti on Vk/Windows image_path: "examples/boids/screenshot.png", width: 1024, @@ -347,5 +347,8 @@ fn boids() { // Lots of validation errors, maybe related to https://github.com/gfx-rs/wgpu/issues/3160 .expect_fail(wgpu_test::FailureCase::molten_vk()), comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/bunnymark/Cargo.toml b/examples/bunnymark/Cargo.toml index 43e0be0d6..4bd3e7734 100644 --- a/examples/bunnymark/Cargo.toml +++ b/examples/bunnymark/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "bunnymark" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/bunnymark/src/main.rs b/examples/bunnymark/src/main.rs index ca8dbbee0..baebe40fd 100644 --- a/examples/bunnymark/src/main.rs +++ b/examples/bunnymark/src/main.rs @@ -353,16 +353,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("bunnymark"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn bunnymark() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "bunnymark", image_path: "/examples/bunnymark/screenshot.png", width: 1024, height: 768, @@ -376,5 +376,8 @@ fn bunnymark() { threshold: 0.05, }, ], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/common/src/framework.rs b/examples/common/src/framework.rs index bedcc2dfe..9e9e949ef 100644 --- a/examples/common/src/framework.rs +++ b/examples/common/src/framework.rs @@ -7,6 +7,8 @@ use std::time::Instant; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use web_sys::{ImageBitmapRenderingContext, OffscreenCanvas}; +use wgpu::{WasmNotSend, WasmNotSync}; +use wgpu_test::GpuTestConfiguration; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -493,7 +495,9 @@ pub fn parse_url_query_string<'a>(query: &'a str, search_key: &str) -> Option<&' pub use wgpu_test::image::ComparisonType; -pub struct FrameworkRefTest { +#[derive(Clone)] +pub struct ExampleTestParams { + pub name: &'static str, // Path to the reference image, relative to the root of the repo. pub image_path: &'static str, pub width: u32, @@ -502,129 +506,128 @@ pub struct FrameworkRefTest { pub base_test_parameters: wgpu_test::TestParameters, /// Comparisons against FLIP statistics that determine if the test passes or fails. pub comparisons: &'static [ComparisonType], + pub _phantom: std::marker::PhantomData, } -#[allow(dead_code)] -pub fn test(mut params: FrameworkRefTest) { - use std::mem; +impl From> for GpuTestConfiguration { + fn from(params: ExampleTestParams) -> Self { + GpuTestConfiguration::new() + .name(params.name) + .parameters({ + assert_eq!(params.width % 64, 0, "width needs to be aligned 64"); - assert_eq!(params.width % 64, 0, "width needs to be aligned 64"); + let features = E::required_features() | params.optional_features; - let features = E::required_features() | params.optional_features; - - wgpu_test::initialize_test( - mem::take(&mut params.base_test_parameters).features(features), - |ctx| { - let spawner = Spawner::new(); - - let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: Some("destination"), - size: wgpu::Extent3d { - width: params.width, - height: params.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("image map buffer"), - size: params.width as u64 * params.height as u64 * 4, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - - let mut example = E::init( - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - width: params.width, - height: params.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb], - }, - &ctx.adapter, - &ctx.device, - &ctx.queue, - ); - - example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); - - // Handle specific case for bunnymark - #[allow(deprecated)] - if params.image_path == "/examples/bunnymark/screenshot.png" { - // Press spacebar to spawn bunnies - example.update(winit::event::WindowEvent::KeyboardInput { - input: winit::event::KeyboardInput { - scancode: 0, - state: winit::event::ElementState::Pressed, - virtual_keycode: Some(winit::event::VirtualKeyCode::Space), - modifiers: winit::event::ModifiersState::empty(), + params.base_test_parameters.clone().features(features) + }) + .run_async(move |ctx| async move { + let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("destination"), + size: wgpu::Extent3d { + width: params.width, + height: params.height, + depth_or_array_layers: 1, }, - device_id: unsafe { winit::event::DeviceId::dummy() }, - is_synthetic: false, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], }); - // Step 3 extra frames - for _ in 0..3 { - example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); - } - } + let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let mut cmd_buf = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("image map buffer"), + size: params.width as u64 * params.height as u64 * 4, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); - cmd_buf.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture: &dst_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: &dst_buffer, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(params.width * 4), - rows_per_image: None, + let mut example = E::init( + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + width: params.width, + height: params.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb], }, - }, - wgpu::Extent3d { - width: params.width, - height: params.height, - depth_or_array_layers: 1, - }, - ); + &ctx.adapter, + &ctx.device, + &ctx.queue, + ); - ctx.queue.submit(Some(cmd_buf.finish())); + { + let spawner = Spawner::new(); + example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); - let dst_buffer_slice = dst_buffer.slice(..); - dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); - let bytes = dst_buffer_slice.get_mapped_range().to_vec(); + // Handle specific case for bunnymark + #[allow(deprecated)] + if params.image_path == "/examples/bunnymark/screenshot.png" { + // Press spacebar to spawn bunnies + example.update(winit::event::WindowEvent::KeyboardInput { + input: winit::event::KeyboardInput { + scancode: 0, + state: winit::event::ElementState::Pressed, + virtual_keycode: Some(winit::event::VirtualKeyCode::Space), + modifiers: winit::event::ModifiersState::empty(), + }, + device_id: unsafe { winit::event::DeviceId::dummy() }, + is_synthetic: false, + }); - wgpu_test::image::compare_image_output( - env!("CARGO_MANIFEST_DIR").to_string() + "/../../" + params.image_path, - &ctx.adapter_info, - params.width, - params.height, - &bytes, - params.comparisons, - ); - }, - ); + // Step 3 extra frames + for _ in 0..3 { + example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); + } + } + } + + let mut cmd_buf = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + cmd_buf.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &dst_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &dst_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(params.width * 4), + rows_per_image: None, + }, + }, + wgpu::Extent3d { + width: params.width, + height: params.height, + depth_or_array_layers: 1, + }, + ); + + ctx.queue.submit(Some(cmd_buf.finish())); + + let dst_buffer_slice = dst_buffer.slice(..); + dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); + ctx.device.poll(wgpu::Maintain::Wait); + let bytes = dst_buffer_slice.get_mapped_range().to_vec(); + + wgpu_test::image::compare_image_output( + env!("CARGO_MANIFEST_DIR").to_string() + "/../../" + params.image_path, + &ctx.adapter_info, + params.width, + params.height, + &bytes, + params.comparisons, + ) + .await; + }) + } } - -// This allows treating the framework as a standalone example, -// thus avoiding listing the example names in `Cargo.toml`. -#[allow(dead_code)] -fn main() {} diff --git a/examples/conservative-raster/Cargo.toml b/examples/conservative-raster/Cargo.toml index 1b4de4839..4d9435e79 100644 --- a/examples/conservative-raster/Cargo.toml +++ b/examples/conservative-raster/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "conservative-raster" path = "src/main.rs" +harness = false [dependencies] wasm-bindgen-test.workspace = true diff --git a/examples/conservative-raster/src/main.rs b/examples/conservative-raster/src/main.rs index 093740a20..9be7a0610 100644 --- a/examples/conservative-raster/src/main.rs +++ b/examples/conservative-raster/src/main.rs @@ -312,21 +312,24 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("conservative-raster"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn conservative_raster() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "conservative-raster", image_path: "/examples/conservative-raster/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/cube/Cargo.toml b/examples/cube/Cargo.toml index 697aa240e..51afe8947 100644 --- a/examples/cube/Cargo.toml +++ b/examples/cube/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "cube" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/cube/src/main.rs b/examples/cube/src/main.rs index b031e1004..33a1260d4 100644 --- a/examples/cube/src/main.rs +++ b/examples/cube/src/main.rs @@ -405,16 +405,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("cube"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn cube() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "cube", // Generated on 1080ti on Vk/Windows image_path: "/examples/cube/screenshot.png", width: 1024, @@ -424,13 +424,14 @@ fn cube() { comparisons: &[ wgpu_test::ComparisonType::Mean(0.04), // Bounded by Intel 630 on Vk/Windows ], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn cube_lines() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_LINES: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "cube-lines", // Generated on 1080ti on Vk/Windows image_path: "/examples/cube/screenshot-lines.png", width: 1024, @@ -445,5 +446,8 @@ fn cube_lines() { threshold: 0.36, }, // Bounded by 1080ti on DX12 ], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/hello-compute/Cargo.toml b/examples/hello-compute/Cargo.toml index af5b29d73..b9a485775 100644 --- a/examples/hello-compute/Cargo.toml +++ b/examples/hello-compute/Cargo.toml @@ -9,11 +9,12 @@ publish = false [[bin]] name = "hello-compute" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true env_logger.workspace = true -futures-intrusive.workspace = true +flume.workspace = true pollster.workspace = true wgpu.workspace = true winit.workspace = true @@ -27,4 +28,3 @@ wasm-bindgen-futures.workspace = true [dev-dependencies] wasm-bindgen-test.workspace = true wgpu-test.workspace = true - diff --git a/examples/hello-compute/src/main.rs b/examples/hello-compute/src/main.rs index 3b102f4e0..c7d09c6a4 100644 --- a/examples/hello-compute/src/main.rs +++ b/examples/hello-compute/src/main.rs @@ -4,6 +4,7 @@ use wgpu::util::DeviceExt; // Indicates a u32 overflow in an intermediate Collatz value const OVERFLOW: u32 = 0xffffffff; +#[cfg_attr(test, allow(dead_code))] async fn run() { let numbers = if std::env::args().len() <= 1 { let default = vec![1, 2, 3, 4]; @@ -31,6 +32,7 @@ async fn run() { log::info!("Steps: [{}]", disp_steps.join(", ")); } +#[cfg_attr(test, allow(dead_code))] async fn execute_gpu(numbers: &[u32]) -> Option> { // Instantiates instance of WebGPU let instance = wgpu::Instance::default(); @@ -54,12 +56,6 @@ async fn execute_gpu(numbers: &[u32]) -> Option> { .await .unwrap(); - let info = adapter.get_info(); - // skip this on LavaPipe temporarily - if info.vendor == 0x10005 { - return None; - } - execute_gpu_inner(&device, &queue, numbers).await } @@ -150,7 +146,7 @@ async fn execute_gpu_inner( // Note that we're not calling `.await` here. let buffer_slice = staging_buffer.slice(..); // Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished. - let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); + let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap()); // Poll the device in a blocking manner so that our future resolves. @@ -159,7 +155,7 @@ async fn execute_gpu_inner( device.poll(wgpu::Maintain::Wait); // Awaits until `buffer_future` can be read from - if let Some(Ok(())) = receiver.receive().await { + if let Ok(Ok(())) = receiver.recv_async().await { // Gets contents of buffer let data = buffer_slice.get_mapped_range(); // Since contents are got in bytes, this converts these bytes back to u32 @@ -181,6 +177,7 @@ async fn execute_gpu_inner( } } +#[cfg(not(test))] fn main() { #[cfg(not(target_arch = "wasm32"))] { @@ -195,5 +192,8 @@ fn main() { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod tests; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/hello-compute/src/tests.rs b/examples/hello-compute/src/tests.rs index 7f8649f72..d435b011f 100644 --- a/examples/hello-compute/src/tests.rs +++ b/examples/hello-compute/src/tests.rs @@ -1,122 +1,96 @@ -use std::sync::Arc; - use super::*; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_1() { - initialize_test( +#[gpu_test] +static COMPUTE_1: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY) .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[1, 2, 3, 4]; + ) + .run_async(|ctx| { + let input = &[1, 2, 3, 4]; - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[0, 1, 7, 2], - )); - }, - ); -} + async move { assert_execute_gpu(&ctx.device, &ctx.queue, input, &[0, 1, 7, 2]).await } + }); -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_2() { - initialize_test( +#[gpu_test] +static COMPUTE_2: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY) .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[5, 23, 10, 9]; + ) + .run_async(|ctx| { + let input = &[5, 23, 10, 9]; - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[5, 15, 6, 19], - )); - }, - ); -} + async move { assert_execute_gpu(&ctx.device, &ctx.queue, input, &[5, 15, 6, 19]).await } + }); -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_overflow() { - initialize_test( +#[gpu_test] +static COMPUTE_OVERFLOW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY) .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[77031, 837799, 8400511, 63728127]; - pollster::block_on(assert_execute_gpu( + ) + .run_async(|ctx| { + let input = &[77031, 837799, 8400511, 63728127]; + async move { + assert_execute_gpu( &ctx.device, &ctx.queue, input, &[350, 524, OVERFLOW, OVERFLOW], - )); - }, - ); -} + ) + .await + } + }); -#[test] -// Wasm doesn't support threads -fn test_multithreaded_compute() { - initialize_test( +#[cfg(not(target_arch = "wasm32"))] +#[gpu_test] +static MULTITHREADED_COMPUTE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY) - .skip(FailureCase::adapter("V3D")) - // https://github.com/gfx-rs/wgpu/issues/3944 - .skip(FailureCase::backend_adapter( - wgpu::Backends::VULKAN, - "swiftshader", - )) - // https://github.com/gfx-rs/wgpu/issues/3250 - .skip(FailureCase::backend_adapter(wgpu::Backends::GL, "llvmpipe")) - .skip(FailureCase::molten_vk()), - |ctx| { - use std::{sync::mpsc, thread, time::Duration}; + .skip(FailureCase::adapter("V3D")), + ) + .run_sync(|ctx| { + use std::{sync::mpsc, sync::Arc, thread, time::Duration}; - let ctx = Arc::new(ctx); + let ctx = Arc::new(ctx); - let thread_count = 8; + let thread_count = 8; - let (tx, rx) = mpsc::channel(); - for _ in 0..thread_count { - let tx = tx.clone(); - let ctx = Arc::clone(&ctx); - thread::spawn(move || { - let input = &[100, 100, 100]; - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[25, 25, 25], - )); - tx.send(true).unwrap(); - }); - } + let (tx, rx) = mpsc::channel(); + for _ in 0..thread_count { + let tx = tx.clone(); + let ctx = Arc::clone(&ctx); + thread::spawn(move || { + let input = &[100, 100, 100]; + pollster::block_on(assert_execute_gpu( + &ctx.device, + &ctx.queue, + input, + &[25, 25, 25], + )); + tx.send(true).unwrap(); + }); + } - for _ in 0..thread_count { - rx.recv_timeout(Duration::from_secs(10)) - .expect("A thread never completed."); - } - }, - ); -} + for _ in 0..thread_count { + rx.recv_timeout(Duration::from_secs(10)) + .expect("A thread never completed."); + } + }); async fn assert_execute_gpu( device: &wgpu::Device, diff --git a/examples/hello-synchronization/Cargo.toml b/examples/hello-synchronization/Cargo.toml index fb31c55ca..ce3ed56e7 100644 --- a/examples/hello-synchronization/Cargo.toml +++ b/examples/hello-synchronization/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" [dependencies] bytemuck.workspace = true env_logger.workspace = true -futures-intrusive.workspace = true +flume.workspace = true log.workspace = true pollster.workspace = true wgpu.workspace = true diff --git a/examples/hello-synchronization/src/main.rs b/examples/hello-synchronization/src/main.rs index 2a8ecac91..36ce83f00 100644 --- a/examples/hello-synchronization/src/main.rs +++ b/examples/hello-synchronization/src/main.rs @@ -179,10 +179,10 @@ async fn get_data( ); queue.submit(Some(command_encoder.finish())); let buffer_slice = staging_buffer.slice(..); - let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); + let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device.poll(wgpu::Maintain::Wait); - receiver.receive().await.unwrap().unwrap(); + receiver.recv_async().await.unwrap().unwrap(); output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..])); staging_buffer.unmap(); } diff --git a/examples/hello-synchronization/src/tests.rs b/examples/hello-synchronization/src/tests.rs index c62693823..756289a36 100644 --- a/examples/hello-synchronization/src/tests.rs +++ b/examples/hello-synchronization/src/tests.rs @@ -1,23 +1,18 @@ use super::*; -use pollster::FutureExt; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn hello_synchronization_test_results() { - initialize_test( +#[gpu_test] +static SYNC: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( // Taken from hello-compute tests. TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()), - |ctx| { - let ExecuteResults { - patient_workgroup_results, - hasty_workgroup_results: _, - } = execute(&ctx.device, &ctx.queue, ARR_SIZE).block_on(); - assert_eq!(patient_workgroup_results, [16_u32; ARR_SIZE]); - }, - ); -} + ) + .run_async(|ctx| async move { + let ExecuteResults { + patient_workgroup_results, + hasty_workgroup_results: _, + } = execute(&ctx.device, &ctx.queue, ARR_SIZE).await; + assert_eq!(patient_workgroup_results, [16_u32; ARR_SIZE]); + }); diff --git a/examples/mipmap/Cargo.toml b/examples/mipmap/Cargo.toml index ab117e409..dff04a4f2 100644 --- a/examples/mipmap/Cargo.toml +++ b/examples/mipmap/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "mipmap" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/mipmap/src/main.rs b/examples/mipmap/src/main.rs index 5536579b0..f6c1c66ed 100644 --- a/examples/mipmap/src/main.rs +++ b/examples/mipmap/src/main.rs @@ -506,16 +506,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("mipmap"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn mipmap() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "mipmap", image_path: "/examples/mipmap/screenshot.png", width: 1024, height: 768, @@ -523,19 +523,23 @@ fn mipmap() { base_test_parameters: wgpu_test::TestParameters::default() .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::GL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn mipmap_query() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_QUERY: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "mipmap-query", image_path: "/examples/mipmap/screenshot-query.png", width: 1024, height: 768, optional_features: QUERY_FEATURES, base_test_parameters: wgpu_test::TestParameters::default() .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::GL)), - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} + comparisons: &[wgpu_test::ComparisonType::Mean(0.025)], + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/msaa-line/Cargo.toml b/examples/msaa-line/Cargo.toml index c84d2676c..ae2ca5b05 100644 --- a/examples/msaa-line/Cargo.toml +++ b/examples/msaa-line/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "msaa-line" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/msaa-line/src/main.rs b/examples/msaa-line/src/main.rs index 07cc4eaf5..ac71d4c09 100644 --- a/examples/msaa-line/src/main.rs +++ b/examples/msaa-line/src/main.rs @@ -313,16 +313,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("msaa-line"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn msaa_line() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "msaa-line", image_path: "/examples/msaa-line/screenshot.png", width: 1024, height: 768, @@ -343,5 +343,8 @@ fn msaa_line() { threshold: 0.29, }, ], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/shadow/Cargo.toml b/examples/shadow/Cargo.toml index 0f7847a88..d536eb0e4 100644 --- a/examples/shadow/Cargo.toml +++ b/examples/shadow/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "shadow" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/shadow/src/main.rs b/examples/shadow/src/main.rs index c63076e6a..2492dad9b 100644 --- a/examples/shadow/src/main.rs +++ b/examples/shadow/src/main.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, f32::consts, iter, mem, ops::Range, rc::Rc}; +use std::{borrow::Cow, f32::consts, iter, mem, ops::Range, sync::Arc}; use bytemuck::{Pod, Zeroable}; use wgpu::util::{align_to, DeviceExt}; @@ -80,8 +80,8 @@ struct Entity { mx_world: glam::Mat4, rotation_speed: f32, color: wgpu::Color, - vertex_buf: Rc, - index_buf: Rc, + vertex_buf: Arc, + index_buf: Arc, index_format: wgpu::IndexFormat, index_count: usize, uniform_offset: wgpu::DynamicOffset, @@ -221,7 +221,7 @@ impl wgpu_example::framework::Example for Example { // Create the vertex and index buffers let vertex_size = mem::size_of::(); let (cube_vertex_data, cube_index_data) = create_cube(); - let cube_vertex_buf = Rc::new(device.create_buffer_init( + let cube_vertex_buf = Arc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Vertex Buffer"), contents: bytemuck::cast_slice(&cube_vertex_data), @@ -229,7 +229,7 @@ impl wgpu_example::framework::Example for Example { }, )); - let cube_index_buf = Rc::new(device.create_buffer_init( + let cube_index_buf = Arc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Index Buffer"), contents: bytemuck::cast_slice(&cube_index_data), @@ -306,8 +306,8 @@ impl wgpu_example::framework::Example for Example { mx_world: glam::Mat4::IDENTITY, rotation_speed: 0.0, color: wgpu::Color::WHITE, - vertex_buf: Rc::new(plane_vertex_buf), - index_buf: Rc::new(plane_index_buf), + vertex_buf: Arc::new(plane_vertex_buf), + index_buf: Arc::new(plane_index_buf), index_format, index_count: plane_index_data.len(), uniform_offset: 0, @@ -327,8 +327,8 @@ impl wgpu_example::framework::Example for Example { mx_world, rotation_speed: cube.rotation, color: wgpu::Color::GREEN, - vertex_buf: Rc::clone(&cube_vertex_buf), - index_buf: Rc::clone(&cube_index_buf), + vertex_buf: Arc::clone(&cube_vertex_buf), + index_buf: Arc::clone(&cube_index_buf), index_format, index_count: cube_index_data.len(), uniform_offset: ((i + 1) * uniform_alignment as usize) as _, @@ -840,16 +840,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("shadow"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn shadow() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "shadow", image_path: "/examples/shadow/screenshot.png", width: 1024, height: 768, @@ -860,12 +860,10 @@ fn shadow() { .expect_fail(wgpu_test::FailureCase::backend_adapter( wgpu::Backends::VULKAN, "V3D", - )) - // llvmpipe versions in CI are flaky: https://github.com/gfx-rs/wgpu/issues/2594 - .skip(wgpu_test::FailureCase::backend_adapter( - wgpu::Backends::VULKAN, - "llvmpipe", )), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/skybox/Cargo.toml b/examples/skybox/Cargo.toml index f70e78e9b..c96b75004 100644 --- a/examples/skybox/Cargo.toml +++ b/examples/skybox/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "skybox" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/skybox/src/main.rs b/examples/skybox/src/main.rs index 5d91c6286..bfc0e909d 100644 --- a/examples/skybox/src/main.rs +++ b/examples/skybox/src/main.rs @@ -52,7 +52,7 @@ impl Camera { } } -pub struct Skybox { +pub struct Example { camera: Camera, sky_pipeline: wgpu::RenderPipeline, entity_pipeline: wgpu::RenderPipeline, @@ -63,7 +63,7 @@ pub struct Skybox { staging_belt: wgpu::util::StagingBelt, } -impl Skybox { +impl Example { const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; fn create_depth_texture( @@ -89,7 +89,7 @@ impl Skybox { } } -impl wgpu_example::framework::Example for Skybox { +impl wgpu_example::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_ASTC | wgpu::Features::TEXTURE_COMPRESSION_ETC2 @@ -356,7 +356,7 @@ impl wgpu_example::framework::Example for Skybox { let depth_view = Self::create_depth_texture(config, device); - Skybox { + Example { camera, sky_pipeline, entity_pipeline, @@ -461,16 +461,16 @@ impl wgpu_example::framework::Example for Skybox { } } +#[cfg(not(test))] fn main() { - wgpu_example::framework::run::("skybox"); + wgpu_example::framework::run::("skybox"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "skybox", image_path: "/examples/skybox/screenshot.png", width: 1024, height: 768, @@ -479,44 +479,50 @@ fn skybox() { wgpu_test::FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_bc1() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_BCN: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "skybox-bc1", image_path: "/examples/skybox/screenshot-bc1.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_BC, base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_etc2() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_ETC2: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "skybox-etc2", image_path: "/examples/skybox/screenshot-etc2.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_ETC2, base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_astc() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_ASTC: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "skybox-astc", image_path: "/examples/skybox/screenshot-astc.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_ASTC, base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 comparisons: &[wgpu_test::ComparisonType::Mean(0.016)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/stencil-triangles/Cargo.toml b/examples/stencil-triangles/Cargo.toml index cd8e42676..729b3d9ff 100644 --- a/examples/stencil-triangles/Cargo.toml +++ b/examples/stencil-triangles/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "stencil-triangles" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/stencil-triangles/src/main.rs b/examples/stencil-triangles/src/main.rs index 9d918500d..3e51d2d0a 100644 --- a/examples/stencil-triangles/src/main.rs +++ b/examples/stencil-triangles/src/main.rs @@ -15,7 +15,7 @@ fn vertex(x: f32, y: f32) -> Vertex { } } -struct Triangles { +struct Example { outer_vertex_buffer: wgpu::Buffer, mask_vertex_buffer: wgpu::Buffer, outer_pipeline: wgpu::RenderPipeline, @@ -23,7 +23,7 @@ struct Triangles { stencil_buffer: wgpu::Texture, } -impl wgpu_example::framework::Example for Triangles { +impl wgpu_example::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, @@ -155,7 +155,7 @@ impl wgpu_example::framework::Example for Triangles { }); // Done - Triangles { + Example { outer_vertex_buffer, mask_vertex_buffer, outer_pipeline, @@ -230,21 +230,24 @@ impl wgpu_example::framework::Example for Triangles { } } +#[cfg(not(test))] fn main() { - wgpu_example::framework::run::("stencil-triangles"); + wgpu_example::framework::run::("stencil-triangles"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn stencil_triangles() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "stencil-triangles", image_path: "/examples/stencil-triangles/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.03)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/texture-arrays/Cargo.toml b/examples/texture-arrays/Cargo.toml index af30e9305..b78821930 100644 --- a/examples/texture-arrays/Cargo.toml +++ b/examples/texture-arrays/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "texture-arrays" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/texture-arrays/src/main.rs b/examples/texture-arrays/src/main.rs index af9cfa56f..a87cae6d0 100644 --- a/examples/texture-arrays/src/main.rs +++ b/examples/texture-arrays/src/main.rs @@ -406,29 +406,44 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("texture-arrays"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn texture_arrays_uniform() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "texture-arrays", image_path: "/examples/texture-arrays/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::empty(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} + _phantom: std::marker::PhantomData::, + }; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn texture_arrays_non_uniform() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_UNIFORM: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "texture-arrays-uniform", + image_path: "/examples/texture-arrays/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::empty(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_NON_UNIFORM: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "texture-arrays-non-uniform", image_path: "/examples/texture-arrays/screenshot.png", width: 1024, height: 768, @@ -436,5 +451,8 @@ fn texture_arrays_non_uniform() { wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/timestamp-queries/src/main.rs b/examples/timestamp-queries/src/main.rs index 9e53a7a2e..d3e2676ad 100644 --- a/examples/timestamp-queries/src/main.rs +++ b/examples/timestamp-queries/src/main.rs @@ -419,35 +419,29 @@ fn main() { #[cfg(test)] mod tests { + use wgpu_test::{gpu_test, GpuTestConfiguration}; + use crate::{submit_render_and_compute_pass_with_queries, QueryResults}; - #[test] - #[wasm_bindgen_test::wasm_bindgen_test] - fn test_timestamps_encoder() { - wgpu_test::initialize_test( + #[gpu_test] + static TIMESTAMPS_ENCODER: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY), - |ctx| { - test_timestamps(ctx, false); - }, - ); - } + ) + .run_sync(|ctx| test_timestamps(ctx, false)); - #[test] - #[wasm_bindgen_test::wasm_bindgen_test] - fn test_timestamps_passes() { - wgpu_test::initialize_test( + #[gpu_test] + static TIMESTAMPS_PASSES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features( wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES, ), - |ctx| { - test_timestamps(ctx, true); - }, - ); - } + ) + .run_sync(|ctx| test_timestamps(ctx, true)); fn test_timestamps(ctx: wgpu_test::TestingContext, timestamps_inside_passes: bool) { let queries = submit_render_and_compute_pass_with_queries(&ctx.device, &ctx.queue); diff --git a/examples/water/Cargo.toml b/examples/water/Cargo.toml index 6310f83f8..de2514203 100644 --- a/examples/water/Cargo.toml +++ b/examples/water/Cargo.toml @@ -9,6 +9,7 @@ publish = false [[bin]] name = "water" path = "src/main.rs" +harness = false [dependencies] bytemuck.workspace = true diff --git a/examples/water/src/main.rs b/examples/water/src/main.rs index da9f1238a..7f31b2b59 100644 --- a/examples/water/src/main.rs +++ b/examples/water/src/main.rs @@ -819,16 +819,16 @@ impl wgpu_example::framework::Example for Example { } } +#[cfg(not(test))] fn main() { wgpu_example::framework::run::("water"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn water() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "water", image_path: "/examples/water/screenshot.png", width: 1024, height: 768, @@ -836,5 +836,8 @@ fn water() { base_test_parameters: wgpu_test::TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::READ_ONLY_DEPTH_STENCIL), comparisons: &[wgpu_test::ComparisonType::Mean(0.01)], - }); -} + _phantom: std::marker::PhantomData::, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 5738ed1bd..56ce8d58a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -12,23 +12,37 @@ autotests = false publish = false [[test]] -name = "wgpu-tests" -path = "tests/root.rs" +name = "gpu-tests" +path = "tests/gpu.rs" +harness = false + +[[test]] +name = "cpu-tests" +path = "tests/cpu.rs" [features] webgl = ["wgpu/webgl"] [dependencies] +anyhow.workspace = true +arrayvec.workspace = true bitflags.workspace = true bytemuck.workspace = true cfg-if.workspace = true +ctor.workspace = true env_logger.workspace = true +futures-lite.workspace = true +heck.workspace = true +libtest-mimic.workspace = true log.workspace = true parking_lot.workspace = true png.workspace = true pollster.workspace = true +serde_json.workspace = true +serde.workspace = true wgpu.workspace = true -wgt.workspace = true +wgpu-macros.workspace = true +wgt = { workspace = true, features = ["replay"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] nv-flip.workspace = true diff --git a/tests/src/config.rs b/tests/src/config.rs new file mode 100644 index 000000000..c6fe02b05 --- /dev/null +++ b/tests/src/config.rs @@ -0,0 +1,113 @@ +use std::{future::Future, pin::Pin, sync::Arc}; + +use crate::{TestParameters, TestingContext}; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + pub type RunTestAsync = Arc Pin>>>; + + // We can't use WasmNonSend and WasmNonSync here, as we need these to not require Send/Sync + // even with the `fragile-send-sync-non-atomic-wasm` enabled. + pub trait RunTestSendSync {} + impl RunTestSendSync for T {} + } else { + pub type RunTestAsync = Arc Pin + Send + Sync>> + Send + Sync>; + + pub trait RunTestSendSync: Send + Sync {} + impl RunTestSendSync for T where T: Send + Sync {} + } +} + +/// Configuration for a GPU test. +#[derive(Clone)] +pub struct GpuTestConfiguration { + pub(crate) name: String, + pub(crate) params: TestParameters, + pub(crate) test: Option, +} + +impl GpuTestConfiguration { + pub fn new() -> Self { + Self { + name: String::new(), + params: TestParameters::default(), + test: None, + } + } + + /// Set the name of the test. Must be unique across all tests in the binary. + pub fn name(self, name: &str) -> Self { + Self { + name: String::from(name), + ..self + } + } + + #[doc(hidden)] + /// Derives the name from a `struct S` in the function initializing the test. + /// + /// Does not overwrite a given name if a name has already been set + pub fn name_from_init_function_typename(self, name: &'static str) -> Self { + if !self.name.is_empty() { + return self; + } + let type_name = std::any::type_name::(); + + // We end up with a string like: + // + // module::path::we::want::test_name_initializer::S + // + // So we reverse search for the 4th colon from the end, and take everything before that. + let mut colons = 0; + let mut colon_4_index = type_name.len(); + for i in (0..type_name.len()).rev() { + if type_name.as_bytes()[i] == b':' { + colons += 1; + } + if colons == 4 { + colon_4_index = i; + break; + } + } + + let full = format!("{}::{}", &type_name[..colon_4_index], name); + Self { name: full, ..self } + } + + /// Set the parameters that the test needs to succeed. + pub fn parameters(self, parameters: TestParameters) -> Self { + Self { + params: parameters, + ..self + } + } + + /// Make the test function an synchronous function. + pub fn run_sync( + self, + test: impl Fn(TestingContext) + Copy + RunTestSendSync + 'static, + ) -> Self { + Self { + test: Some(Arc::new(move |ctx| Box::pin(async move { test(ctx) }))), + ..self + } + } + + /// Make the test function an asynchronous function/future. + pub fn run_async(self, test: F) -> Self + where + F: Fn(TestingContext) -> R + RunTestSendSync + 'static, + R: Future + RunTestSendSync + 'static, + { + Self { + test: Some(Arc::new(move |ctx| Box::pin(test(ctx)))), + ..self + } + } +} + +impl Default for GpuTestConfiguration { + fn default() -> Self { + Self::new() + } +} diff --git a/tests/src/image.rs b/tests/src/image.rs index e50fd43e7..0e3ea9ea8 100644 --- a/tests/src/image.rs +++ b/tests/src/image.rs @@ -1,9 +1,12 @@ -use std::{borrow::Cow, ffi::OsStr, io, path::Path}; +//! Image comparison utilities + +use std::{borrow::Cow, ffi::OsStr, path::Path}; use wgpu::util::{align_to, DeviceExt}; use wgpu::*; -fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> { +#[cfg(not(target_arch = "wasm32"))] +async fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> { let data = match std::fs::read(&path) { Ok(f) => f, Err(e) => { @@ -15,7 +18,7 @@ fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> return None; } }; - let decoder = png::Decoder::new(io::Cursor::new(data)); + let decoder = png::Decoder::new(std::io::Cursor::new(data)); let mut reader = decoder.read_info().ok()?; let mut buffer = vec![0; reader.output_buffer_size()]; @@ -40,32 +43,26 @@ fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> Some(buffer) } -#[allow(unused_variables)] -fn write_png( +#[cfg(not(target_arch = "wasm32"))] +async fn write_png( path: impl AsRef, width: u32, height: u32, data: &[u8], compression: png::Compression, ) { - #[cfg(not(target_arch = "wasm32"))] - { - let file = io::BufWriter::new(std::fs::File::create(path).unwrap()); + let file = std::io::BufWriter::new(std::fs::File::create(path).unwrap()); - let mut encoder = png::Encoder::new(file, width, height); - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_compression(compression); - let mut writer = encoder.write_header().unwrap(); + let mut encoder = png::Encoder::new(file, width, height); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_compression(compression); + let mut writer = encoder.write_header().unwrap(); - writer.write_image_data(data).unwrap(); - } -} - -pub fn calc_difference(lhs: u8, rhs: u8) -> u8 { - (lhs as i16 - rhs as i16).unsigned_abs() as u8 + writer.write_image_data(data).unwrap(); } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn add_alpha(input: &[u8]) -> Vec { input .chunks_exact(3) @@ -73,6 +70,7 @@ fn add_alpha(input: &[u8]) -> Vec { .collect() } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn remove_alpha(input: &[u8]) -> Vec { input .chunks_exact(4) @@ -148,7 +146,8 @@ impl ComparisonType { } } -pub fn compare_image_output( +#[cfg(not(target_arch = "wasm32"))] +pub async fn compare_image_output( path: impl AsRef + AsRef, adapter_info: &wgt::AdapterInfo, width: u32, @@ -156,29 +155,47 @@ pub fn compare_image_output( test_with_alpha: &[u8], checks: &[ComparisonType], ) { - #[cfg(not(target_arch = "wasm32"))] + use std::{ffi::OsString, str::FromStr}; + + let reference_path = Path::new(&path); + let reference_with_alpha = read_png(&path, width, height).await; + + let reference = match reference_with_alpha { + Some(v) => remove_alpha(&v), + None => { + write_png( + &path, + width, + height, + test_with_alpha, + png::Compression::Best, + ) + .await; + return; + } + }; + let test = remove_alpha(test_with_alpha); + + assert_eq!(reference.len(), test.len()); + + let file_stem = reference_path.file_stem().unwrap().to_string_lossy(); + let renderer = format!( + "{}-{}-{}", + adapter_info.backend.to_str(), + sanitize_for_path(&adapter_info.name), + sanitize_for_path(&adapter_info.driver) + ); + // Determine the paths to write out the various intermediate files + let actual_path = Path::new(&path).with_file_name( + OsString::from_str(&format!("{}-{}-actual.png", file_stem, renderer)).unwrap(), + ); + let difference_path = Path::new(&path).with_file_name( + OsString::from_str(&format!("{}-{}-difference.png", file_stem, renderer,)).unwrap(), + ); + + let mut all_passed; + let magma_image_with_alpha; { - use std::{ffi::OsString, str::FromStr}; - - let reference_with_alpha = read_png(&path, width, height); - - let reference = match reference_with_alpha { - Some(v) => remove_alpha(&v), - None => { - write_png( - &path, - width, - height, - test_with_alpha, - png::Compression::Best, - ); - return; - } - }; - let test = remove_alpha(test_with_alpha); - - assert_eq!(reference.len(), test.len()); - let reference_flip = nv_flip::FlipImageRgb8::with_data(width, height, &reference); let test_flip = nv_flip::FlipImageRgb8::with_data(width, height, &test); @@ -189,7 +206,6 @@ pub fn compare_image_output( ); let mut pool = nv_flip::FlipPool::from_image(&error_map_flip); - let reference_path = Path::new(&path); println!( "Starting image comparison test with reference image \"{}\"", reference_path.display() @@ -198,59 +214,57 @@ pub fn compare_image_output( print_flip(&mut pool); // If there are no checks, we want to fail the test. - let mut all_passed = !checks.is_empty(); + all_passed = !checks.is_empty(); // We always iterate all of these, as the call to check prints for check in checks { all_passed &= check.check(&mut pool); } - let file_stem = reference_path.file_stem().unwrap().to_string_lossy(); - let renderer = format!( - "{}-{}-{}", - adapter_info.backend.to_str(), - sanitize_for_path(&adapter_info.name), - sanitize_for_path(&adapter_info.driver) - ); - // Determine the paths to write out the various intermediate files - let actual_path = Path::new(&path).with_file_name( - OsString::from_str(&format!("{}-{}-actual.png", file_stem, renderer)).unwrap(), - ); - let difference_path = Path::new(&path).with_file_name( - OsString::from_str(&format!("{}-{}-difference.png", file_stem, renderer,)).unwrap(), - ); - // Convert the error values to a false color reprensentation let magma_image = error_map_flip .apply_color_lut(&nv_flip::magma_lut()) .to_vec(); - let magma_image_with_alpha = add_alpha(&magma_image); - - write_png( - actual_path, - width, - height, - test_with_alpha, - png::Compression::Fast, - ); - write_png( - difference_path, - width, - height, - &magma_image_with_alpha, - png::Compression::Fast, - ); - - if !all_passed { - panic!("Image data mismatch!") - } + magma_image_with_alpha = add_alpha(&magma_image); } + write_png( + actual_path, + width, + height, + test_with_alpha, + png::Compression::Fast, + ) + .await; + write_png( + difference_path, + width, + height, + &magma_image_with_alpha, + png::Compression::Fast, + ) + .await; + + if !all_passed { + panic!("Image data mismatch!") + } +} + +#[cfg(target_arch = "wasm32")] +pub async fn compare_image_output( + path: impl AsRef + AsRef, + adapter_info: &wgt::AdapterInfo, + width: u32, + height: u32, + test_with_alpha: &[u8], + checks: &[ComparisonType], +) { #[cfg(target_arch = "wasm32")] { let _ = (path, adapter_info, width, height, test_with_alpha, checks); } } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn sanitize_for_path(s: &str) -> String { s.chars() .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) diff --git a/tests/src/init.rs b/tests/src/init.rs new file mode 100644 index 000000000..51a129eaf --- /dev/null +++ b/tests/src/init.rs @@ -0,0 +1,129 @@ +use wgpu::{Adapter, Device, Instance, Queue}; +use wgt::{Backends, Features, Limits}; + +/// Initialize a wgpu instance with the options from the environment. +pub fn initialize_instance() -> 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. + let backends = Backends::all(); + let dx12_shader_compiler = 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, taking the `n`th adapter from the instance. +pub async fn initialize_adapter(adapter_index: usize) -> (Adapter, Option) { + let instance = initialize_instance(); + #[allow(unused_variables)] + let _surface: wgpu::Surface; + let surface_guard: Option; + + // Create a canvas iff we need a WebGL2RenderingContext to have a working device. + #[cfg(not(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "webgl") + )))] + { + 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 = instance + .create_surface_from_canvas(canvas.clone()) + .expect("could not create surface from canvas"); + + surface_guard = Some(SurfaceGuard { canvas }); + } + + cfg_if::cfg_if! { + if #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] { + let adapter_iter = instance.enumerate_adapters(wgpu::Backends::all()); + let adapter_count = adapter_iter.len(); + let adapter = adapter_iter.into_iter() + .nth(adapter_index) + .unwrap_or_else(|| panic!("Tried to get index {adapter_index} adapter, but adapter list was only {adapter_count} long. Is .gpuconfig out of date?")); + } else { + assert_eq!(adapter_index, 0); + let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await.unwrap(); + } + } + + log::info!("Testing using adapter: {:#?}", adapter.get_info()); + + (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, + features, + limits, + }, + 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::().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::() + .unwrap() + .get_error() + != web_sys::WebGl2RenderingContext::NO_ERROR + } +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 6887e392f..91545f3a2 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,595 +1,27 @@ -//! This module contains common test-only code that needs to be shared between the examples and the tests. -#![allow(dead_code)] // This module is used in a lot of contexts and only parts of it will be used - -use std::panic::{catch_unwind, AssertUnwindSafe}; - -use wgpu::{Adapter, Device, DownlevelFlags, Instance, Queue, Surface}; -use wgt::{Backends, DeviceDescriptor, DownlevelCapabilities, Features, Limits}; +//! Test utilities for the wgpu repository. +mod config; pub mod image; +mod init; mod isolation; - -pub use self::image::ComparisonType; - -const CANVAS_ID: &str = "test-canvas"; - -async fn initialize_device( - adapter: &Adapter, - features: Features, - limits: Limits, -) -> (Device, Queue) { - let bundle = adapter - .request_device( - &DeviceDescriptor { - label: None, - features, - limits, - }, - None, - ) - .await; - - match bundle { - Ok(b) => b, - Err(e) => panic!("Failed to initialize device: {e}"), - } -} - -pub struct TestingContext { - pub adapter: Adapter, - pub adapter_info: wgt::AdapterInfo, - pub adapter_downlevel_capabilities: wgt::DownlevelCapabilities, - pub device: Device, - pub device_features: wgt::Features, - pub device_limits: wgt::Limits, - pub queue: Queue, -} - -fn lowest_downlevel_properties() -> DownlevelCapabilities { - DownlevelCapabilities { - flags: wgt::DownlevelFlags::empty(), - limits: wgt::DownlevelLimits {}, - shader_model: wgt::ShaderModel::Sm2, - } -} - -/// Conditions under which a test should fail or be skipped. -/// -/// By passing a `FailureCase` to [`TestParameters::expect_fail`], you can -/// mark a test as expected to fail under the indicated conditions. By -/// passing it to [`TestParameters::skip`], you can request that the -/// test be skipped altogether. -/// -/// If a field is `None`, then that field does not restrict matches. For -/// example: -/// -/// ``` -/// # use wgpu_test::FailureCase; -/// FailureCase { -/// backends: Some(wgpu::Backends::DX11 | wgpu::Backends::DX12), -/// vendor: None, -/// adapter: Some("RTX"), -/// driver: None, -/// } -/// # ; -/// ``` -/// -/// This applies to all cards with `"RTX'` in their name on either -/// Direct3D backend, no matter the vendor ID or driver name. -/// -/// The strings given here need only appear as a substring in the -/// corresponding [`AdapterInfo`] fields. The comparison is -/// case-insensitive. -/// -/// The default value of `FailureCase` applies to any test case. That -/// is, there are no criteria to constrain the match. -/// -/// [`AdapterInfo`]: wgt::AdapterInfo -#[derive(Default)] -pub struct FailureCase { - /// Backends expected to fail, or `None` for any backend. - /// - /// If this is `None`, or if the test is using one of the backends - /// in `backends`, then this `FailureCase` applies. - pub backends: Option, - - /// Vendor expected to fail, or `None` for any vendor. - /// - /// If `Some`, this must match [`AdapterInfo::device`], which is - /// usually the PCI device id. Otherwise, this `FailureCase` - /// applies regardless of vendor. - /// - /// [`AdapterInfo::device`]: wgt::AdapterInfo::device - pub vendor: Option, - - /// Name of adaper expected to fail, or `None` for any adapter name. - /// - /// If this is `Some(s)` and `s` is a substring of - /// [`AdapterInfo::name`], then this `FailureCase` applies. If - /// this is `None`, the adapter name isn't considered. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub adapter: Option<&'static str>, - - /// Name of driver expected to fail, or `None` for any driver name. - /// - /// If this is `Some(s)` and `s` is a substring of - /// [`AdapterInfo::driver`], then this `FailureCase` applies. If - /// this is `None`, the driver name isn't considered. - /// - /// [`AdapterInfo::driver`]: wgt::AdapterInfo::driver - pub driver: Option<&'static str>, -} - -impl FailureCase { - /// This case applies to all tests. - pub fn always() -> Self { - FailureCase::default() - } - - /// This case applies to no tests. - pub fn never() -> Self { - FailureCase { - backends: Some(wgpu::Backends::empty()), - ..FailureCase::default() - } - } - - /// Tests running on any of the given backends. - pub fn backend(backends: wgpu::Backends) -> Self { - FailureCase { - backends: Some(backends), - ..FailureCase::default() - } - } - - /// Tests running on `adapter`. - /// - /// For this case to apply, the `adapter` string must appear as a substring - /// of the adapter's [`AdapterInfo::name`]. The comparison is - /// case-insensitive. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub fn adapter(adapter: &'static str) -> Self { - FailureCase { - adapter: Some(adapter), - ..FailureCase::default() - } - } - - /// Tests running on `backend` and `adapter`. - /// - /// For this case to apply, the test must be using an adapter for one of the - /// given `backend` bits, and `adapter` string must appear as a substring of - /// the adapter's [`AdapterInfo::name`]. The string comparison is - /// case-insensitive. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub fn backend_adapter(backends: wgpu::Backends, adapter: &'static str) -> Self { - FailureCase { - backends: Some(backends), - adapter: Some(adapter), - ..FailureCase::default() - } - } - - /// Tests running under WebGL. - /// - /// Because of wasm's limited ability to recover from errors, we - /// usually need to skip the test altogether if it's not - /// supported, so this should be usually used with - /// [`TestParameters::skip`]. - pub fn webgl2() -> Self { - #[cfg(target_arch = "wasm32")] - let case = FailureCase::backend(wgpu::Backends::GL); - #[cfg(not(target_arch = "wasm32"))] - let case = FailureCase::never(); - case - } - - /// Tests running on the MoltenVK Vulkan driver on macOS. - pub fn molten_vk() -> Self { - FailureCase { - backends: Some(wgpu::Backends::VULKAN), - driver: Some("MoltenVK"), - ..FailureCase::default() - } - } - - /// Test whether `self` applies to `info`. - /// - /// If it does, return a `FailureReasons` whose set bits indicate - /// why. If it doesn't, return `None`. - /// - /// The caller is responsible for converting the string-valued - /// fields of `info` to lower case, to ensure case-insensitive - /// matching. - fn applies_to(&self, info: &wgt::AdapterInfo) -> Option { - let mut reasons = FailureReasons::empty(); - - if let Some(backends) = self.backends { - if !backends.contains(wgpu::Backends::from(info.backend)) { - return None; - } - reasons.set(FailureReasons::BACKEND, true); - } - if let Some(vendor) = self.vendor { - if vendor != info.vendor { - return None; - } - reasons.set(FailureReasons::VENDOR, true); - } - if let Some(adapter) = self.adapter { - let adapter = adapter.to_lowercase(); - if !info.name.contains(&adapter) { - return None; - } - reasons.set(FailureReasons::ADAPTER, true); - } - if let Some(driver) = self.driver { - let driver = driver.to_lowercase(); - if !info.driver.contains(&driver) { - return None; - } - reasons.set(FailureReasons::DRIVER, true); - } - - // If we got this far but no specific reasons were triggered, then this - // must be a wildcard. - if reasons.is_empty() { - Some(FailureReasons::ALWAYS) - } else { - Some(reasons) - } - } -} - -// This information determines if a test should run. -pub struct TestParameters { - pub required_features: Features, - pub required_downlevel_properties: DownlevelCapabilities, - pub required_limits: Limits, - - /// Conditions under which this test should be skipped. - pub skips: Vec, - - /// Conditions under which this test should be run, but is expected to fail. - pub failures: Vec, -} - -impl Default for TestParameters { - fn default() -> Self { - Self { - required_features: Features::empty(), - required_downlevel_properties: lowest_downlevel_properties(), - required_limits: Limits::downlevel_webgl2_defaults(), - skips: Vec::new(), - failures: Vec::new(), - } - } -} - -bitflags::bitflags! { - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct FailureReasons: u8 { - const BACKEND = 1 << 0; - const VENDOR = 1 << 1; - const ADAPTER = 1 << 2; - const DRIVER = 1 << 3; - const ALWAYS = 1 << 4; - } -} - -// Builder pattern to make it easier -impl TestParameters { - /// Set of common features that most internal tests require for readback. - pub fn test_features_limits(self) -> Self { - self.features(Features::MAPPABLE_PRIMARY_BUFFERS | Features::VERTEX_WRITABLE_STORAGE) - .limits(wgpu::Limits::downlevel_defaults()) - } - - /// Set the list of features this test requires. - pub fn features(mut self, features: Features) -> Self { - self.required_features |= features; - self - } - - pub fn downlevel_flags(mut self, downlevel_flags: DownlevelFlags) -> Self { - self.required_downlevel_properties.flags |= downlevel_flags; - self - } - - /// Set the limits needed for the test. - pub fn limits(mut self, limits: Limits) -> Self { - self.required_limits = limits; - self - } - - /// Mark the test as always failing, but not to be skipped. - pub fn expect_fail(mut self, when: FailureCase) -> Self { - self.failures.push(when); - self - } - - /// Mark the test as always failing, and needing to be skipped. - pub fn skip(mut self, when: FailureCase) -> Self { - self.skips.push(when); - self - } -} - -pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(TestingContext)) { - // 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); - - let _test_guard = isolation::OneTestPerProcessGuard::new(); - - let (adapter, _surface_guard) = initialize_adapter(); - - let adapter_info = adapter.get_info(); - - // Produce a lower-case version of the adapter info, for comparison against - // `parameters.skips` and `parameters.failures`. - let adapter_lowercase_info = wgt::AdapterInfo { - name: adapter_info.name.to_lowercase(), - driver: adapter_info.driver.to_lowercase(), - ..adapter_info.clone() - }; - - let adapter_features = adapter.features(); - let adapter_limits = adapter.limits(); - let adapter_downlevel_capabilities = adapter.get_downlevel_capabilities(); - - let missing_features = parameters.required_features - adapter_features; - if !missing_features.is_empty() { - log::info!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features); - return; - } - - if !parameters.required_limits.check_limits(&adapter_limits) { - log::info!("TEST SKIPPED: LIMIT TOO LOW"); - return; - } - - let missing_downlevel_flags = - parameters.required_downlevel_properties.flags - adapter_downlevel_capabilities.flags; - if !missing_downlevel_flags.is_empty() { - log::info!( - "TEST SKIPPED: MISSING DOWNLEVEL FLAGS {:?}", - missing_downlevel_flags - ); - return; - } - - if adapter_downlevel_capabilities.shader_model - < parameters.required_downlevel_properties.shader_model - { - log::info!( - "TEST SKIPPED: LOW SHADER MODEL {:?}", - adapter_downlevel_capabilities.shader_model - ); - return; - } - - let (device, queue) = pollster::block_on(initialize_device( - &adapter, - parameters.required_features, - parameters.required_limits.clone(), - )); - - let context = TestingContext { - adapter, - adapter_info, - adapter_downlevel_capabilities, - device, - device_features: parameters.required_features, - device_limits: parameters.required_limits, - queue, - }; - - // Check if we should skip the test altogether. - if let Some(skip_reason) = parameters - .skips - .iter() - .find_map(|case| case.applies_to(&adapter_lowercase_info)) - { - log::info!("EXPECTED TEST FAILURE SKIPPED: {:?}", skip_reason); - return; - } - - // Determine if we expect this test to fail, and if so, why. - let expected_failure_reason = parameters - .failures - .iter() - .find_map(|case| case.applies_to(&adapter_lowercase_info)); - - // Run the test, and catch panics (possibly due to failed assertions). - let panicked = catch_unwind(AssertUnwindSafe(|| test_function(context))).is_err(); - - // Check whether any validation errors were reported during the test run. - cfg_if::cfg_if!( - if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] { - let canary_set = wgpu::hal::VALIDATION_CANARY.get_and_reset(); - } else { - let canary_set = _surface_guard.unwrap().check_for_unreported_errors(); - } - ); - - // Summarize reasons for actual failure, if any. - let failure_cause = match (panicked, canary_set) { - (true, true) => Some("PANIC AND VALIDATION ERROR"), - (true, false) => Some("PANIC"), - (false, true) => Some("VALIDATION ERROR"), - (false, false) => None, - }; - - // Compare actual results against expectations. - match (failure_cause, expected_failure_reason) { - // The test passed, as expected. - (None, None) => {} - // The test failed unexpectedly. - (Some(cause), None) => { - panic!("UNEXPECTED TEST FAILURE DUE TO {cause}") - } - // The test passed unexpectedly. - (None, Some(reason)) => { - panic!("UNEXPECTED TEST PASS: {reason:?}"); - } - // The test failed, as expected. - (Some(cause), Some(reason_expected)) => { - log::info!( - "EXPECTED FAILURE DUE TO {} (expected because of {:?})", - cause, - reason_expected - ); - } - } -} - -pub fn initialize_adapter() -> (Adapter, Option) { - let instance = initialize_instance(); - let surface_guard: Option; - let compatible_surface; - - // Create a canvas iff we need a WebGL2RenderingContext to have a working device. - #[cfg(not(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") - )))] - { - surface_guard = None; - compatible_surface = 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 = create_html_canvas(); - - // We use raw_window_handle here, as create_surface_from_canvas is not implemented on emscripten. - struct WindowHandle; - unsafe impl raw_window_handle::HasRawWindowHandle for WindowHandle { - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { - raw_window_handle::RawWindowHandle::Web({ - let mut handle = raw_window_handle::WebWindowHandle::empty(); - handle.id = 1; - handle - }) - } - } - unsafe impl raw_window_handle::HasRawDisplayHandle for WindowHandle { - fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { - raw_window_handle::RawDisplayHandle::Web( - raw_window_handle::WebDisplayHandle::empty(), - ) - } - } - - let surface = unsafe { - instance - .create_surface(&WindowHandle) - .expect("could not create surface from canvas") - }; - - surface_guard = Some(SurfaceGuard { canvas }); - - compatible_surface = Some(surface); - } - - let compatible_surface: Option<&Surface> = compatible_surface.as_ref(); - let adapter = pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default( - &instance, - compatible_surface, - )) - .expect("could not find suitable adapter on the system"); - - (adapter, surface_guard) -} - -pub fn initialize_instance() -> Instance { - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all); - let dx12_shader_compiler = 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, - }) -} - -// Public because it is used by tests of interacting with canvas -pub struct SurfaceGuard { - #[cfg(target_arch = "wasm32")] - pub canvas: web_sys::HtmlCanvasElement, -} - -impl SurfaceGuard { - fn check_for_unreported_errors(&self) -> bool { - cfg_if::cfg_if! { - if #[cfg(all(target_arch = "wasm32", any(target_os = "emscripten", feature = "webgl")))] { - use wasm_bindgen::JsCast; - - self.canvas - .get_context("webgl2") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap() - .get_error() - != web_sys::WebGl2RenderingContext::NO_ERROR - } else { - false - } - } - } -} - -#[cfg(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") -))] -impl Drop for SurfaceGuard { - fn drop(&mut self) { - delete_html_canvas(); - } -} +pub mod native; +mod params; +mod report; +mod run; #[cfg(target_arch = "wasm32")] -pub fn create_html_canvas() -> web_sys::HtmlCanvasElement { - use wasm_bindgen::JsCast; +pub use init::initialize_html_canvas; - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let body = doc.body().unwrap(); - let canvas = doc.create_element("Canvas").unwrap(); - canvas.set_attribute("data-raw-handle", "1").unwrap(); - canvas.set_id(CANVAS_ID); - body.append_child(&canvas).unwrap(); - canvas.dyn_into::().ok() - }) - .expect("couldn't append canvas to document body") -} +pub use self::image::ComparisonType; +pub use config::GpuTestConfiguration; +#[doc(hidden)] +pub use ctor::ctor; +pub use init::{initialize_adapter, initialize_device, initialize_instance}; +pub use params::{FailureCase, FailureReasons, TestParameters}; +pub use run::{execute_test, TestingContext}; +pub use wgpu_macros::gpu_test; -#[cfg(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") -))] -fn delete_html_canvas() { - if let Some(document) = web_sys::window().and_then(|win| win.document()) { - if let Some(element) = document.get_element_by_id(CANVAS_ID) { - element.remove(); - } - }; -} - -// Run some code in an error scope and assert that validation fails. +/// Run some code in an error scope and assert that validation fails. pub fn fail(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { device.push_error_scope(wgpu::ErrorFilter::Validation); let result = callback(); @@ -598,7 +30,7 @@ pub fn fail(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { result } -// Run some code in an error scope and assert that validation succeeds. +/// Run some code in an error scope and assert that validation succeeds. pub fn valid(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { device.push_error_scope(wgpu::ErrorFilter::Validation); let result = callback(); @@ -607,8 +39,8 @@ pub fn valid(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { result } -// Run some code in an error scope and assert that validation succeeds or fails depending on the -// provided `should_fail` boolean. +/// Run some code in an error scope and assert that validation succeeds or fails depending on the +/// provided `should_fail` boolean. pub fn fail_if(device: &wgpu::Device, should_fail: bool, callback: impl FnOnce() -> T) -> T { if should_fail { fail(device, callback) @@ -616,3 +48,19 @@ pub fn fail_if(device: &wgpu::Device, should_fail: bool, callback: impl FnOnc valid(device, callback) } } + +/// Adds the necissary main function for our gpu test harness. +#[macro_export] +macro_rules! gpu_test_main { + () => { + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + #[cfg(target_arch = "wasm32")] + fn main() {} + + #[cfg(not(target_arch = "wasm32"))] + fn main() -> $crate::native::MainResult { + $crate::native::main() + } + }; +} diff --git a/tests/src/native.rs b/tests/src/native.rs new file mode 100644 index 000000000..e7aaa5b1e --- /dev/null +++ b/tests/src/native.rs @@ -0,0 +1,104 @@ +#![cfg(not(target_arch = "wasm32"))] +//! Infrastructure for the native, `cargo-nextest` based harness. +//! +//! This is largly used by [`gpu_test_main`](crate::gpu_test_main) and [`gpu_test`](crate::gpu_test). + +use std::{future::Future, pin::Pin}; + +use parking_lot::Mutex; + +use crate::{ + config::GpuTestConfiguration, params::TestInfo, report::AdapterReport, run::execute_test, +}; + +type NativeTestFuture = Pin + Send + Sync>>; + +struct NativeTest { + name: String, + future: NativeTestFuture, +} + +impl NativeTest { + fn from_configuration( + config: GpuTestConfiguration, + adapter: &AdapterReport, + adapter_index: usize, + ) -> Self { + let backend = adapter.info.backend; + let device_name = &adapter.info.name; + + let test_info = TestInfo::from_configuration(&config, adapter); + + let full_name = format!( + "[{running_msg}] [{backend:?}/{device_name}/{adapter_index}] {base_name}", + running_msg = test_info.running_msg, + base_name = config.name, + ); + Self { + name: full_name, + future: Box::pin(async move { + // Enable metal validation layers if we're running on metal. + // + // This is a process-wide setting as it's via environment variable, but all + // tests are run in separate processes. + // + // We don't do this in the instance initializer as we don't want to enable + // validation layers for the entire process, or other instances. + // + // We do not enable metal validation when running on moltenvk. + let metal_validation = backend == wgpu::Backend::Metal; + + let env_value = if metal_validation { "1" } else { "0" }; + std::env::set_var("MTL_DEBUG_LAYER", env_value); + std::env::set_var("MTL_SHADER_VALIDATION", env_value); + + execute_test(config, Some(test_info), adapter_index).await; + }), + } + } + + pub fn into_trial(self) -> libtest_mimic::Trial { + libtest_mimic::Trial::test(self.name, || { + pollster::block_on(self.future); + Ok(()) + }) + } +} + +#[doc(hidden)] +pub static TEST_LIST: Mutex> = Mutex::new(Vec::new()); + +/// Return value for the main function. +pub type MainResult = anyhow::Result<()>; + +/// Main function that runs every gpu function once for every adapter on the system. +pub fn main() -> MainResult { + use anyhow::Context; + + use crate::report::GpuReport; + + let config_text = + &std::fs::read_to_string(format!("{}/../.gpuconfig", env!("CARGO_MANIFEST_DIR"))) + .context("Failed to read .gpuconfig, did you run the tests via `cargo xtask test`?")?; + let report = GpuReport::from_json(config_text).context("Could not pare .gpuconfig JSON")?; + + let mut test_guard = TEST_LIST.lock(); + execute_native(test_guard.drain(..).flat_map(|test| { + report + .devices + .iter() + .enumerate() + .map(move |(adapter_index, adapter)| { + NativeTest::from_configuration(test.clone(), adapter, adapter_index) + }) + })); + + Ok(()) +} + +fn execute_native(tests: impl IntoIterator) { + let args = libtest_mimic::Arguments::from_args(); + let trials = tests.into_iter().map(NativeTest::into_trial).collect(); + + libtest_mimic::run(&args, trials).exit_if_failed(); +} diff --git a/tests/src/params.rs b/tests/src/params.rs new file mode 100644 index 000000000..4da998987 --- /dev/null +++ b/tests/src/params.rs @@ -0,0 +1,363 @@ +use arrayvec::ArrayVec; +use wgt::{DownlevelCapabilities, DownlevelFlags, Features, Limits}; + +use crate::{report::AdapterReport, GpuTestConfiguration}; + +/// Conditions under which a test should fail or be skipped. +/// +/// By passing a `FailureCase` to [`TestParameters::expect_fail`], you can +/// mark a test as expected to fail under the indicated conditions. By +/// passing it to [`TestParameters::skip`], you can request that the +/// test be skipped altogether. +/// +/// If a field is `None`, then that field does not restrict matches. For +/// example: +/// +/// ``` +/// # use wgpu_test::FailureCase; +/// FailureCase { +/// backends: Some(wgpu::Backends::DX11 | wgpu::Backends::DX12), +/// vendor: None, +/// adapter: Some("RTX"), +/// driver: None, +/// } +/// # ; +/// ``` +/// +/// This applies to all cards with `"RTX'` in their name on either +/// Direct3D backend, no matter the vendor ID or driver name. +/// +/// The strings given here need only appear as a substring in the +/// corresponding [`AdapterInfo`] fields. The comparison is +/// case-insensitive. +/// +/// The default value of `FailureCase` applies to any test case. That +/// is, there are no criteria to constrain the match. +/// +/// [`AdapterInfo`]: wgt::AdapterInfo +#[derive(Default, Clone)] +pub struct FailureCase { + /// Backends expected to fail, or `None` for any backend. + /// + /// If this is `None`, or if the test is using one of the backends + /// in `backends`, then this `FailureCase` applies. + pub backends: Option, + + /// Vendor expected to fail, or `None` for any vendor. + /// + /// If `Some`, this must match [`AdapterInfo::device`], which is + /// usually the PCI device id. Otherwise, this `FailureCase` + /// applies regardless of vendor. + /// + /// [`AdapterInfo::device`]: wgt::AdapterInfo::device + pub vendor: Option, + + /// Name of adaper expected to fail, or `None` for any adapter name. + /// + /// If this is `Some(s)` and `s` is a substring of + /// [`AdapterInfo::name`], then this `FailureCase` applies. If + /// this is `None`, the adapter name isn't considered. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub adapter: Option<&'static str>, + + /// Name of driver expected to fail, or `None` for any driver name. + /// + /// If this is `Some(s)` and `s` is a substring of + /// [`AdapterInfo::driver`], then this `FailureCase` applies. If + /// this is `None`, the driver name isn't considered. + /// + /// [`AdapterInfo::driver`]: wgt::AdapterInfo::driver + pub driver: Option<&'static str>, +} + +impl FailureCase { + /// This case applies to all tests. + pub fn always() -> Self { + FailureCase::default() + } + + /// This case applies to no tests. + pub fn never() -> Self { + FailureCase { + backends: Some(wgpu::Backends::empty()), + ..FailureCase::default() + } + } + + /// Tests running on any of the given backends. + pub fn backend(backends: wgpu::Backends) -> Self { + FailureCase { + backends: Some(backends), + ..FailureCase::default() + } + } + + /// Tests running on `adapter`. + /// + /// For this case to apply, the `adapter` string must appear as a substring + /// of the adapter's [`AdapterInfo::name`]. The comparison is + /// case-insensitive. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub fn adapter(adapter: &'static str) -> Self { + FailureCase { + adapter: Some(adapter), + ..FailureCase::default() + } + } + + /// Tests running on `backend` and `adapter`. + /// + /// For this case to apply, the test must be using an adapter for one of the + /// given `backend` bits, and `adapter` string must appear as a substring of + /// the adapter's [`AdapterInfo::name`]. The string comparison is + /// case-insensitive. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub fn backend_adapter(backends: wgpu::Backends, adapter: &'static str) -> Self { + FailureCase { + backends: Some(backends), + adapter: Some(adapter), + ..FailureCase::default() + } + } + + /// Tests running under WebGL. + /// + /// Because of wasm's limited ability to recover from errors, we + /// usually need to skip the test altogether if it's not + /// supported, so this should be usually used with + /// [`TestParameters::skip`]. + pub fn webgl2() -> Self { + #[cfg(target_arch = "wasm32")] + let case = FailureCase::backend(wgpu::Backends::GL); + #[cfg(not(target_arch = "wasm32"))] + let case = FailureCase::never(); + case + } + + /// Tests running on the MoltenVK Vulkan driver on macOS. + pub fn molten_vk() -> Self { + FailureCase { + backends: Some(wgpu::Backends::VULKAN), + driver: Some("MoltenVK"), + ..FailureCase::default() + } + } + + /// Test whether `self` applies to `info`. + /// + /// If it does, return a `FailureReasons` whose set bits indicate + /// why. If it doesn't, return `None`. + /// + /// The caller is responsible for converting the string-valued + /// fields of `info` to lower case, to ensure case-insensitive + /// matching. + pub(crate) fn applies_to(&self, info: &wgt::AdapterInfo) -> Option { + let mut reasons = FailureReasons::empty(); + + if let Some(backends) = self.backends { + if !backends.contains(wgpu::Backends::from(info.backend)) { + return None; + } + reasons.set(FailureReasons::BACKEND, true); + } + if let Some(vendor) = self.vendor { + if vendor != info.vendor { + return None; + } + reasons.set(FailureReasons::VENDOR, true); + } + if let Some(adapter) = self.adapter { + let adapter = adapter.to_lowercase(); + if !info.name.contains(&adapter) { + return None; + } + reasons.set(FailureReasons::ADAPTER, true); + } + if let Some(driver) = self.driver { + let driver = driver.to_lowercase(); + if !info.driver.contains(&driver) { + return None; + } + reasons.set(FailureReasons::DRIVER, true); + } + + // If we got this far but no specific reasons were triggered, then this + // must be a wildcard. + if reasons.is_empty() { + Some(FailureReasons::ALWAYS) + } else { + Some(reasons) + } + } +} + +const LOWEST_DOWNLEVEL_PROPERTIES: wgpu::DownlevelCapabilities = DownlevelCapabilities { + flags: wgt::DownlevelFlags::empty(), + limits: wgt::DownlevelLimits {}, + shader_model: wgt::ShaderModel::Sm2, +}; + +/// This information determines if a test should run. +#[derive(Clone)] +pub struct TestParameters { + pub required_features: Features, + pub required_downlevel_caps: DownlevelCapabilities, + pub required_limits: Limits, + + /// Conditions under which this test should be skipped. + pub skips: Vec, + + /// Conditions under which this test should be run, but is expected to fail. + pub failures: Vec, +} + +impl Default for TestParameters { + fn default() -> Self { + Self { + required_features: Features::empty(), + required_downlevel_caps: LOWEST_DOWNLEVEL_PROPERTIES, + required_limits: Limits::downlevel_webgl2_defaults(), + skips: Vec::new(), + failures: Vec::new(), + } + } +} + +bitflags::bitflags! { + /// Ways that a given test can be expected to fail. + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct FailureReasons: u8 { + const BACKEND = 1 << 0; + const VENDOR = 1 << 1; + const ADAPTER = 1 << 2; + const DRIVER = 1 << 3; + const ALWAYS = 1 << 4; + } +} + +// Builder pattern to make it easier +impl TestParameters { + /// Set of common features that most internal tests require for readback. + pub fn test_features_limits(self) -> Self { + self.features(Features::MAPPABLE_PRIMARY_BUFFERS | Features::VERTEX_WRITABLE_STORAGE) + .limits(wgpu::Limits::downlevel_defaults()) + } + + /// Set the list of features this test requires. + pub fn features(mut self, features: Features) -> Self { + self.required_features |= features; + self + } + + pub fn downlevel_flags(mut self, downlevel_flags: DownlevelFlags) -> Self { + self.required_downlevel_caps.flags |= downlevel_flags; + self + } + + /// Set the limits needed for the test. + pub fn limits(mut self, limits: Limits) -> Self { + self.required_limits = limits; + self + } + + /// Mark the test as always failing, but not to be skipped. + pub fn expect_fail(mut self, when: FailureCase) -> Self { + self.failures.push(when); + self + } + + /// Mark the test as always failing, and needing to be skipped. + pub fn skip(mut self, when: FailureCase) -> Self { + self.skips.push(when); + self + } +} + +/// Information about a test, including if if it should be skipped. +pub struct TestInfo { + pub skip: bool, + pub expected_failure_reason: Option, + pub running_msg: String, +} + +impl TestInfo { + pub(crate) fn from_configuration(test: &GpuTestConfiguration, adapter: &AdapterReport) -> Self { + // Figure out if we should skip the test and if so, why. + let mut skipped_reasons: ArrayVec<_, 4> = ArrayVec::new(); + let missing_features = test.params.required_features - adapter.features; + if !missing_features.is_empty() { + skipped_reasons.push("Features"); + } + + if !test.params.required_limits.check_limits(&adapter.limits) { + skipped_reasons.push("Limits"); + } + + let missing_downlevel_flags = + test.params.required_downlevel_caps.flags - adapter.downlevel_caps.flags; + if !missing_downlevel_flags.is_empty() { + skipped_reasons.push("Downlevel Flags"); + } + + if test.params.required_downlevel_caps.shader_model > adapter.downlevel_caps.shader_model { + skipped_reasons.push("Shader Model"); + } + + // Produce a lower-case version of the adapter info, for comparison against + // `parameters.skips` and `parameters.failures`. + let adapter_lowercase_info = wgt::AdapterInfo { + name: adapter.info.name.to_lowercase(), + driver: adapter.info.driver.to_lowercase(), + ..adapter.info.clone() + }; + + // Check if we should skip the test altogether. + let skip_reason = test + .params + .skips + .iter() + .find_map(|case| case.applies_to(&adapter_lowercase_info)); + + let expected_failure_reason = test + .params + .failures + .iter() + .find_map(|case| case.applies_to(&adapter_lowercase_info)); + + let mut skip = false; + let running_msg = if let Some(reasons) = skip_reason { + skip = true; + + let names: ArrayVec<_, 4> = reasons.iter_names().map(|(name, _)| name).collect(); + let names_text = names.join(" | "); + + format!("Skipped Failure: {}", names_text) + } else if !skipped_reasons.is_empty() { + skip = true; + format!("Skipped: {}", skipped_reasons.join(" | ")) + } else if let Some(failure_resasons) = expected_failure_reason { + if cfg!(target_arch = "wasm32") { + skip = true; + } + + let names: ArrayVec<_, 4> = failure_resasons + .iter_names() + .map(|(name, _)| name) + .collect(); + let names_text = names.join(" | "); + + format!("Executed Failure: {}", names_text) + } else { + String::from("Executed") + }; + + Self { + skip, + expected_failure_reason, + running_msg, + } + } +} diff --git a/tests/src/report.rs b/tests/src/report.rs new file mode 100644 index 000000000..11ffa245b --- /dev/null +++ b/tests/src/report.rs @@ -0,0 +1,52 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use wgpu::{ + AdapterInfo, DownlevelCapabilities, Features, Limits, TextureFormat, TextureFormatFeatures, +}; + +/// Report specifying the capabilities of the GPUs on the system. +/// +/// Must be synchronized with the definition on wgpu-info/src/report.rs. +#[derive(Deserialize)] +pub(crate) struct GpuReport { + #[cfg_attr(target_arch = "wasm32", allow(unused))] + pub devices: Vec, +} + +impl GpuReport { + #[cfg_attr(target_arch = "wasm32", allow(unused))] + pub(crate) fn from_json(file: &str) -> serde_json::Result { + serde_json::from_str(file) + } +} + +/// A single report of the capabilities of an Adapter. +/// +/// Must be synchronized with the definition on wgpu-info/src/report.rs. +#[derive(Deserialize)] +pub(crate) struct AdapterReport { + pub info: AdapterInfo, + pub features: Features, + pub limits: Limits, + pub downlevel_caps: DownlevelCapabilities, + #[allow(unused)] + pub texture_format_features: HashMap, +} + +impl AdapterReport { + pub(crate) fn from_adapter(adapter: &wgpu::Adapter) -> Self { + let info = adapter.get_info(); + let features = adapter.features(); + let limits = adapter.limits(); + let downlevel_caps = adapter.get_downlevel_capabilities(); + + Self { + info, + features, + limits, + downlevel_caps, + texture_format_features: HashMap::new(), // todo + } + } +} diff --git a/tests/src/run.rs b/tests/src/run.rs new file mode 100644 index 000000000..ebc4c1237 --- /dev/null +++ b/tests/src/run.rs @@ -0,0 +1,126 @@ +use std::panic::AssertUnwindSafe; + +use futures_lite::FutureExt; +use wgpu::{Adapter, Device, Queue}; + +use crate::{ + init::{initialize_adapter, initialize_device}, + isolation, + params::TestInfo, + report::AdapterReport, + GpuTestConfiguration, +}; + +/// Parameters and resources hadned to the test function. +pub struct TestingContext { + pub adapter: Adapter, + pub adapter_info: wgpu::AdapterInfo, + pub adapter_downlevel_capabilities: wgpu::DownlevelCapabilities, + pub device: Device, + pub device_features: wgpu::Features, + pub device_limits: wgpu::Limits, + pub queue: Queue, +} + +/// Execute the given test configuration with the given adapter index. +/// +/// If test_info is specified, will use the information whether to skip the test. +/// If it is not, we'll create the test info from the adapter itself. +pub async fn execute_test( + config: GpuTestConfiguration, + test_info: Option, + adapter_index: usize, +) { + // If we get information externally, skip based on that information before we do anything. + if let Some(TestInfo { skip: true, .. }) = test_info { + return; + } + + // 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); + + let _test_guard = isolation::OneTestPerProcessGuard::new(); + + let (adapter, _surface_guard) = initialize_adapter(adapter_index).await; + + let adapter_info = adapter.get_info(); + let adapter_downlevel_capabilities = adapter.get_downlevel_capabilities(); + + let test_info = test_info.unwrap_or_else(|| { + let adapter_report = AdapterReport::from_adapter(&adapter); + TestInfo::from_configuration(&config, &adapter_report) + }); + + // We are now guaranteed to have information about this test, so skip if we need to. + if test_info.skip { + log::info!("TEST RESULT: SKIPPED"); + return; + } + + let (device, queue) = pollster::block_on(initialize_device( + &adapter, + config.params.required_features, + config.params.required_limits.clone(), + )); + + let context = TestingContext { + adapter, + adapter_info, + adapter_downlevel_capabilities, + device, + device_features: config.params.required_features, + device_limits: config.params.required_limits.clone(), + queue, + }; + + // Run the test, and catch panics (possibly due to failed assertions). + let panicked = AssertUnwindSafe((config.test.as_ref().unwrap())(context)) + .catch_unwind() + .await + .is_err(); + + // Check whether any validation errors were reported during the test run. + cfg_if::cfg_if!( + if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] { + let canary_set = wgpu::hal::VALIDATION_CANARY.get_and_reset(); + } else if #[cfg(all(target_arch = "wasm32", feature = "webgl"))] { + let canary_set = _surface_guard.unwrap().check_for_unreported_errors(); + } else { + // TODO: WebGPU + let canary_set = false; + } + ); + + // Summarize reasons for actual failure, if any. + let failure_cause = match (panicked, canary_set) { + (true, true) => Some("PANIC AND VALIDATION ERROR"), + (true, false) => Some("PANIC"), + (false, true) => Some("VALIDATION ERROR"), + (false, false) => None, + }; + + // Compare actual results against expectations. + match (failure_cause, test_info.expected_failure_reason) { + // The test passed, as expected. + (None, None) => log::info!("TEST RESULT: PASSED"), + // The test failed unexpectedly. + (Some(cause), None) => { + panic!("UNEXPECTED TEST FAILURE DUE TO {cause}") + } + // The test passed unexpectedly. + (None, Some(reason)) => { + panic!("UNEXPECTED TEST PASS: {reason:?}"); + } + // The test failed, as expected. + (Some(cause), Some(reason_expected)) => { + log::info!( + "TEST RESULT: EXPECTED FAILURE DUE TO {} (expected because of {:?})", + cause, + reason_expected + ); + } + } +} diff --git a/tests/tests/bgra8unorm_storage.rs b/tests/tests/bgra8unorm_storage.rs index 6acd543f5..5cfcb27a2 100644 --- a/tests/tests/bgra8unorm_storage.rs +++ b/tests/tests/bgra8unorm_storage.rs @@ -2,8 +2,7 @@ use std::borrow::Cow; -use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; const SHADER_SRC: &str = " @group(0) @binding(0) var tex: texture_storage_2d; @@ -14,17 +13,17 @@ fn main(@builtin(workgroup_id) wgid: vec3) { } "; -#[test] -#[wasm_bindgen_test] -fn bgra8unorm_storage() { - let parameters = TestParameters::default() - .limits(wgpu::Limits { - max_storage_textures_per_shader_stage: 1, - ..Default::default() - }) - .features(wgpu::Features::BGRA8UNORM_STORAGE); - - initialize_test(parameters, |ctx| { +#[gpu_test] +static BGRA8_UNORM_STORAGE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .limits(wgpu::Limits { + max_storage_textures_per_shader_stage: 1, + ..Default::default() + }) + .features(wgpu::Features::BGRA8UNORM_STORAGE), + ) + .run_sync(|ctx| { let device = &ctx.device; let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, @@ -155,4 +154,3 @@ fn bgra8unorm_storage() { readback_buffer.unmap(); }); -} diff --git a/tests/tests/bind_group_layout_dedup.rs b/tests/tests/bind_group_layout_dedup.rs index 03bc1f1c5..3f2e6fe17 100644 --- a/tests/tests/bind_group_layout_dedup.rs +++ b/tests/tests/bind_group_layout_dedup.rs @@ -1,8 +1,8 @@ -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -#[test] -fn bind_group_layout_deduplication() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static BIND_GROUP_LAYOUT_DEDUPLICATION: GpuTestConfiguration = GpuTestConfiguration::new() + .run_sync(|ctx| { let entries_1 = &[]; let entries_2 = &[wgpu::BindGroupLayoutEntry { @@ -135,8 +135,7 @@ fn bind_group_layout_deduplication() { } ctx.queue.submit(Some(encoder.finish())); - }) -} + }); const SHADER_SRC: &str = " @vertex fn vs_main() -> @builtin(position) vec4 { return vec4(1.0); } diff --git a/tests/tests/buffer.rs b/tests/tests/buffer.rs index 1e7445d89..9913a0d6b 100644 --- a/tests/tests/buffer.rs +++ b/tests/tests/buffer.rs @@ -1,4 +1,4 @@ -use wgpu_test::{initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) { let r = wgpu::BufferUsages::MAP_READ; @@ -80,82 +80,77 @@ fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) ctx.device.poll(wgpu::MaintainBase::Wait); } -#[test] -#[ignore] -fn empty_buffer() { - // TODO: Currently wgpu does not accept empty buffer slices, which - // is what test is about. - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static EMPTY_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::always())) + .run_sync(|ctx| { test_empty_buffer_range(&ctx, 2048, "regular buffer"); test_empty_buffer_range(&ctx, 0, "zero-sized buffer"); - }) -} - -#[test] -fn test_map_offset() { - initialize_test(TestParameters::default(), |ctx| { - // This test writes 16 bytes at the beginning of buffer mapped mapped with - // an offset of 32 bytes. Then the buffer is copied into another buffer that - // is read back and we check that the written bytes are correctly placed at - // offset 32..48. - // The goal is to check that get_mapped_range did not accidentally double-count - // the mapped offset. - - let write_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - let read_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - write_buf - .slice(32..) - .map_async(wgpu::MapMode::Write, move |result| { - result.unwrap(); - }); - - ctx.device.poll(wgpu::MaintainBase::Wait); - - { - let slice = write_buf.slice(32..48); - let mut view = slice.get_mapped_range_mut(); - for byte in &mut view[..] { - *byte = 2; - } - } - - write_buf.unmap(); - - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - encoder.copy_buffer_to_buffer(&write_buf, 0, &read_buf, 0, 256); - - ctx.queue.submit(Some(encoder.finish())); - - read_buf - .slice(..) - .map_async(wgpu::MapMode::Read, Result::unwrap); - - ctx.device.poll(wgpu::MaintainBase::Wait); - - let slice = read_buf.slice(..); - let view = slice.get_mapped_range(); - for byte in &view[0..32] { - assert_eq!(*byte, 0); - } - for byte in &view[32..48] { - assert_eq!(*byte, 2); - } - for byte in &view[48..] { - assert_eq!(*byte, 0); - } }); -} + +#[gpu_test] +static MAP_OFFSET: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + // This test writes 16 bytes at the beginning of buffer mapped mapped with + // an offset of 32 bytes. Then the buffer is copied into another buffer that + // is read back and we check that the written bytes are correctly placed at + // offset 32..48. + // The goal is to check that get_mapped_range did not accidentally double-count + // the mapped offset. + + let write_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let read_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + write_buf + .slice(32..) + .map_async(wgpu::MapMode::Write, move |result| { + result.unwrap(); + }); + + ctx.device.poll(wgpu::MaintainBase::Wait); + + { + let slice = write_buf.slice(32..48); + let mut view = slice.get_mapped_range_mut(); + for byte in &mut view[..] { + *byte = 2; + } + } + + write_buf.unmap(); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.copy_buffer_to_buffer(&write_buf, 0, &read_buf, 0, 256); + + ctx.queue.submit(Some(encoder.finish())); + + read_buf + .slice(..) + .map_async(wgpu::MapMode::Read, Result::unwrap); + + ctx.device.poll(wgpu::MaintainBase::Wait); + + let slice = read_buf.slice(..); + let view = slice.get_mapped_range(); + for byte in &view[0..32] { + assert_eq!(*byte, 0); + } + for byte in &view[32..48] { + assert_eq!(*byte, 2); + } + for byte in &view[48..] { + assert_eq!(*byte, 0); + } +}); diff --git a/tests/tests/buffer_copy.rs b/tests/tests/buffer_copy.rs index 5fcafe68f..9733255ba 100644 --- a/tests/tests/buffer_copy.rs +++ b/tests/tests/buffer_copy.rs @@ -1,36 +1,37 @@ //! Tests for buffer copy validation. -use wasm_bindgen_test::wasm_bindgen_test; use wgt::BufferAddress; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn copy_alignment() { - fn try_copy(offset: BufferAddress, size: BufferAddress, should_fail: bool) { - initialize_test(TestParameters::default(), |ctx| { - let buffer = ctx.device.create_buffer(&BUFFER_DESCRIPTOR); - let data = vec![255; size as usize]; - fail_if(&ctx.device, should_fail, || { - ctx.queue.write_buffer(&buffer, offset, &data) - }); - }); - } - - try_copy(0, 0, false); - try_copy(4, 16 + 1, true); - try_copy(64, 20 + 2, true); - try_copy(256, 44 + 3, true); - try_copy(1024, 8 + 4, false); - - try_copy(0, 4, false); - try_copy(4 + 1, 8, true); - try_copy(64 + 2, 12, true); - try_copy(256 + 3, 16, true); - try_copy(1024 + 4, 4, false); +fn try_copy( + ctx: &wgpu_test::TestingContext, + offset: BufferAddress, + size: BufferAddress, + should_fail: bool, +) { + let buffer = ctx.device.create_buffer(&BUFFER_DESCRIPTOR); + let data = vec![255; size as usize]; + fail_if(&ctx.device, should_fail, || { + ctx.queue.write_buffer(&buffer, offset, &data) + }); } +#[gpu_test] +static COPY_ALIGNMENT: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + try_copy(&ctx, 0, 0, false); + try_copy(&ctx, 4, 16 + 1, true); + try_copy(&ctx, 64, 20 + 2, true); + try_copy(&ctx, 256, 44 + 3, true); + try_copy(&ctx, 1024, 8 + 4, false); + + try_copy(&ctx, 0, 4, false); + try_copy(&ctx, 4 + 1, 8, true); + try_copy(&ctx, 64 + 2, 12, true); + try_copy(&ctx, 256 + 3, 16, true); + try_copy(&ctx, 1024 + 4, 4, false); +}); + const BUFFER_SIZE: BufferAddress = 1234; const BUFFER_DESCRIPTOR: wgpu::BufferDescriptor = wgpu::BufferDescriptor { diff --git a/tests/tests/buffer_usages.rs b/tests/tests/buffer_usages.rs index e0f5164f6..657b8a41b 100644 --- a/tests/tests/buffer_usages.rs +++ b/tests/tests/buffer_usages.rs @@ -1,74 +1,69 @@ //! Tests for buffer usages validation. -use wasm_bindgen_test::*; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu::BufferUsages as Bu; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration, TestParameters}; use wgt::BufferAddress; const BUFFER_SIZE: BufferAddress = 1234; -#[test] -#[wasm_bindgen_test] -fn buffer_usage() { - fn try_create(enable_mappable_primary_buffers: bool, usages: &[(bool, &[wgpu::BufferUsages])]) { - let mut parameters = TestParameters::default(); - if enable_mappable_primary_buffers { - parameters = parameters.features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS); - } +const ALWAYS_VALID: &[Bu; 4] = &[ + Bu::MAP_READ, + Bu::MAP_WRITE, + Bu::MAP_READ.union(Bu::COPY_DST), + Bu::MAP_WRITE.union(Bu::COPY_SRC), +]; +// MAP_READ can only be paired with COPY_DST and MAP_WRITE can only be paired with COPY_SRC +// (unless Features::MAPPABlE_PRIMARY_BUFFERS is enabled). +const NEEDS_MAPPABLE_PRIMARY_BUFFERS: &[Bu; 7] = &[ + Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::COPY_SRC)), + Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::COPY_DST)), + Bu::MAP_READ.union(Bu::MAP_WRITE), + Bu::MAP_WRITE.union(Bu::MAP_READ), + Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::STORAGE)), + Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::STORAGE)), + Bu::all(), +]; +const INVALID_BITS: Bu = Bu::from_bits_retain(0b1111111111111); +const ALWAYS_FAIL: &[Bu; 2] = &[Bu::empty(), INVALID_BITS]; - initialize_test(parameters, |ctx| { - for (expect_validation_error, usage) in - usages.iter().flat_map(|&(expect_error, usages)| { - usages.iter().copied().map(move |u| (expect_error, u)) - }) - { - fail_if(&ctx.device, expect_validation_error, || { - let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: BUFFER_SIZE, - usage, - mapped_at_creation: false, - }); - }); - } +fn try_create(ctx: wgpu_test::TestingContext, usages: &[(bool, &[wgpu::BufferUsages])]) { + for (expect_validation_error, usage) in usages + .iter() + .flat_map(|&(expect_error, usages)| usages.iter().copied().map(move |u| (expect_error, u))) + { + fail_if(&ctx.device, expect_validation_error, || { + let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: BUFFER_SIZE, + usage, + mapped_at_creation: false, + }); }); } - - use wgpu::BufferUsages as Bu; - - let always_valid = &[ - Bu::MAP_READ, - Bu::MAP_WRITE, - Bu::MAP_READ | Bu::COPY_DST, - Bu::MAP_WRITE | Bu::COPY_SRC, - ]; - // MAP_READ can only be paired with COPY_DST and MAP_WRITE can only be paired with COPY_SRC - // (unless Features::MAPPABlE_PRIMARY_BUFFERS is enabled). - let needs_mappable_primary_buffers = &[ - Bu::MAP_READ | Bu::COPY_DST | Bu::COPY_SRC, - Bu::MAP_WRITE | Bu::COPY_SRC | Bu::COPY_DST, - Bu::MAP_READ | Bu::MAP_WRITE, - Bu::MAP_WRITE | Bu::MAP_READ, - Bu::MAP_READ | Bu::COPY_DST | Bu::STORAGE, - Bu::MAP_WRITE | Bu::COPY_SRC | Bu::STORAGE, - Bu::all(), - ]; - let invalid_bits = Bu::from_bits_retain(0b1111111111111); - let always_fail = &[Bu::empty(), invalid_bits]; - - try_create( - false, - &[ - (false, always_valid), - (true, needs_mappable_primary_buffers), - (true, always_fail), - ], - ); - try_create( - true, // enable Features::MAPPABLE_PRIMARY_BUFFERS - &[ - (false, always_valid), - (false, needs_mappable_primary_buffers), - (true, always_fail), - ], - ); } + +#[gpu_test] +static BUFFER_USAGE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + try_create( + ctx, + &[ + (false, ALWAYS_VALID), + (true, NEEDS_MAPPABLE_PRIMARY_BUFFERS), + (true, ALWAYS_FAIL), + ], + ); +}); + +#[gpu_test] +static BUFFER_USAGE_MAPPABLE_PRIMARY_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS)) + .run_sync(|ctx| { + try_create( + ctx, + &[ + (false, ALWAYS_VALID), + (false, NEEDS_MAPPABLE_PRIMARY_BUFFERS), + (true, ALWAYS_FAIL), + ], + ); + }); diff --git a/tests/tests/clear_texture.rs b/tests/tests/clear_texture.rs index e3894f782..ad3a55d71 100644 --- a/tests/tests/clear_texture.rs +++ b/tests/tests/clear_texture.rs @@ -1,6 +1,6 @@ -use wasm_bindgen_test::*; use wgpu_test::{ - image::ReadbackBuffers, initialize_test, FailureCase, TestParameters, TestingContext, + gpu_test, image::ReadbackBuffers, FailureCase, GpuTestConfiguration, TestParameters, + TestingContext, }; static TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT: &[wgpu::TextureFormat] = &[ @@ -326,81 +326,71 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { } } -#[test] -#[wasm_bindgen_test] -fn clear_texture_uncompressed_gles_compat() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_UNCOMPRESSED_GLES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) - .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT); - }, + .features(wgpu::Features::CLEAR_TEXTURE) + .skip(FailureCase::webgl2()), ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT); + }); -#[test] -#[wasm_bindgen_test] -fn clear_texture_uncompressed() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_UNCOMPRESSED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) .expect_fail(FailureCase::backend(wgpu::Backends::GL)) .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED); + }); -#[test] -#[wasm_bindgen_test] -fn clear_texture_depth() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_DEPTH: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) .downlevel_flags( wgpu::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | wgpu::DownlevelFlags::COMPUTE_SHADERS, ) + .skip(FailureCase::webgl2()) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH); + }); -#[test] -#[wasm_bindgen_test] -fn clear_texture_d32_s8() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_DEPTH32_STENCIL8: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::DEPTH32FLOAT_STENCIL8), - |ctx| { - clear_texture_tests(&ctx, &[wgpu::TextureFormat::Depth32FloatStencil8]); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, &[wgpu::TextureFormat::Depth32FloatStencil8]); + }); -#[test] -fn clear_texture_bc() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_BCN: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_BC) // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_BC); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_BC); + }); -#[test] -fn clear_texture_astc() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_ASTC: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ASTC) .limits(wgpu::Limits { @@ -411,23 +401,21 @@ fn clear_texture_astc() { .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC); + }); -#[test] -fn clear_texture_etc2() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_ETC2: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ETC2) // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2); - }, ) -} + .run_sync(|ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2); + }); diff --git a/tests/tests/cpu.rs b/tests/tests/cpu.rs new file mode 100644 index 000000000..e7858ddb0 --- /dev/null +++ b/tests/tests/cpu.rs @@ -0,0 +1 @@ +mod example_wgsl; diff --git a/tests/tests/create_surface_error.rs b/tests/tests/create_surface_error.rs index f8962697c..9749642f2 100644 --- a/tests/tests/create_surface_error.rs +++ b/tests/tests/create_surface_error.rs @@ -5,18 +5,17 @@ #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] #[wasm_bindgen_test::wasm_bindgen_test] fn canvas_get_context_returned_null() { - // Not using initialize_test() because that goes straight to creating the canvas for us. + // Not using the normal testing infrastructure because that goes straight to creating the canvas for us. let instance = wgpu_test::initialize_instance(); - // Create canvas and cleanup on drop - let canvas_g = wgpu_test::SurfaceGuard { - canvas: wgpu_test::create_html_canvas(), - }; + // Create canvas + let canvas = wgpu_test::initialize_html_canvas(); + // Using a context id that is not "webgl2" or "webgpu" will render the canvas unusable by wgpu. - canvas_g.canvas.get_context("2d").unwrap(); + canvas.get_context("2d").unwrap(); #[allow(clippy::redundant_clone)] // false positive — can't and shouldn't move out. let error = instance - .create_surface_from_canvas(canvas_g.canvas.clone()) + .create_surface_from_canvas(canvas.clone()) .unwrap_err(); assert!( diff --git a/tests/tests/device.rs b/tests/tests/device.rs index 981b55dfb..3135587fc 100644 --- a/tests/tests/device.rs +++ b/tests/tests/device.rs @@ -1,44 +1,30 @@ -use wasm_bindgen_test::*; +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; +#[gpu_test] +static CROSS_DEVICE_BIND_GROUP_USAGE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::always())) + .run_sync(|ctx| { + // Create a bind group uisng a layout from another device. This should be a validation + // error but currently crashes. + let (device2, _) = + pollster::block_on(ctx.adapter.request_device(&Default::default(), None)).unwrap(); -#[test] -#[wasm_bindgen_test] -fn device_initialization() { - initialize_test(TestParameters::default(), |_ctx| { - // intentionally empty - }) -} - -#[test] -fn device_mismatch() { - initialize_test( - // https://github.com/gfx-rs/wgpu/issues/3927 - TestParameters::default().expect_fail(FailureCase::always()), - |ctx| { - // Create a bind group uisng a lyaout from another device. This should be a validation - // error but currently crashes. - let (device2, _) = - pollster::block_on(ctx.adapter.request_device(&Default::default(), None)).unwrap(); - - { - let bind_group_layout = - device2.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[], - }); - - let _bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + { + let bind_group_layout = + device2.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, - layout: &bind_group_layout, entries: &[], }); - } - ctx.device.poll(wgpu::Maintain::Poll); - }, - ); -} + let _bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[], + }); + } + + ctx.device.poll(wgpu::Maintain::Poll); + }); #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] #[test] @@ -53,7 +39,7 @@ fn request_device_error_on_native() { async fn request_device_error_message() { // Not using initialize_test() because that doesn't let us catch the error // nor .await anything - let (adapter, _surface_guard) = wgpu_test::initialize_adapter(); + let (adapter, _surface_guard) = wgpu_test::initialize_adapter(0).await; let device_error = adapter .request_device( @@ -91,47 +77,187 @@ async fn request_device_error_message() { assert!(device_error.contains(expected), "{device_error}"); } -#[test] -fn device_destroy_then_more() { - // This is a test of device behavior after device.destroy. Specifically, all operations - // should trigger errors since the device is lost. - // - // On DX12 this test fails with a validation error in the very artifical actions taken - // after lose the device. The error is "ID3D12CommandAllocator::Reset: The command - // allocator cannot be reset because a command list is currently being recorded with the - // allocator." That may indicate that DX12 doesn't like opened command buffers staying - // open even after they return an error. For now, this test is skipped on DX12. - // - // The DX12 issue may be related to https://github.com/gfx-rs/wgpu/issues/3193. - initialize_test( +// This is a test of device behavior after device.destroy. Specifically, all operations +// should trigger errors since the device is lost. +// +// On DX12 this test fails with a validation error in the very artifical actions taken +// after lose the device. The error is "ID3D12CommandAllocator::Reset: The command +// allocator cannot be reset because a command list is currently being recorded with the +// allocator." That may indicate that DX12 doesn't like opened command buffers staying +// open even after they return an error. For now, this test is skipped on DX12. +// +// The DX12 issue may be related to https://github.com/gfx-rs/wgpu/issues/3193. +#[gpu_test] +static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE) - .skip(FailureCase::backend(wgpu::Backends::DX12)), - |ctx| { - // Create some resources on the device that we will attempt to use *after* losing - // the device. + .expect_fail(FailureCase::backend(wgpu::Backends::DX12)), + ) + .run_sync(|ctx| { + // Create some resources on the device that we will attempt to use *after* losing + // the device. - // Create some 512 x 512 2D textures. - let texture_extent = wgpu::Extent3d { - width: 512, - height: 512, - depth_or_array_layers: 1, - }; - let texture_for_view = ctx.device.create_texture(&wgpu::TextureDescriptor { + // Create some 512 x 512 2D textures. + let texture_extent = wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }; + let texture_for_view = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default()); + + let texture_for_read = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let texture_for_write = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + // Create some buffers. + let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let buffer_for_map = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let buffer_for_unmap = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: true, + }); + + // Create a bind group layout. + let bind_group_layout = + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + + // Create a shader module. + let shader_module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { label: None, - size: texture_extent, - mip_level_count: 2, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rg8Uint, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), }); - let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default()); - let texture_for_read = ctx.device.create_texture(&wgpu::TextureDescriptor { + // Create some command encoders. + let mut encoder_for_clear = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_compute_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_render_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + // Destroy the device. This will cause all other requests to return some variation of + // a device invalid error. + ctx.device.destroy(); + + // TODO: verify the following operations will return an invalid device error: + // * Run a compute or render pass + // * Finish a render bundle encoder + // * Create a texture from HAL + // * Create a buffer from HAL + // * Create a sampler + // * Validate a surface configuration + // * Start or stop capture + // * Get or set buffer sub data + + // TODO: figure out how to structure a test around these operations which panic when + // the device is invalid: + // * device.features() + // * device.limits() + // * device.downlevel_properties() + // * device.create_query_set() + + // TODO: change these fail calls to check for the specific errors which indicate that + // the device is not valid. + + // Creating a commmand encoder should fail. + fail(&ctx.device, || { + ctx.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + }); + + // Creating a buffer should fail. + fail(&ctx.device, || { + ctx.device.create_buffer(&wgpu::BufferDescriptor { label: None, - size: texture_extent, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + }); + + // Creating a texture should fail. + fail(&ctx.device, || { + ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, mip_level_count: 2, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -139,328 +265,187 @@ fn device_destroy_then_more() { usage: wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); + }); - let texture_for_write = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: texture_extent, - mip_level_count: 2, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rg8Uint, - usage: wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); + // Texture clear should fail. + fail(&ctx.device, || { + encoder_for_clear.clear_texture( + &texture_for_write, + &wgpu::ImageSubresourceRange { + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }, + ); + }); - // Create some buffers. - let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor { + // Creating a compute pass should fail. + fail(&ctx.device, || { + encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let buffer_for_map = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - let buffer_for_unmap = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: true, + timestamp_writes: None, }); + }); - // Create a bind group layout. - let bind_group_layout = - ctx.device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[], - }); + // Creating a render pass should fail. + fail(&ctx.device, || { + encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &target_view, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + }); - // Create a shader module. - let shader_module = ctx - .device + // Copying a buffer to a buffer should fail. + fail(&ctx.device, || { + encoder_for_buffer_buffer_copy.copy_buffer_to_buffer( + &buffer_source, + 0, + &buffer_dest, + 0, + 256, + ); + }); + + // Copying a buffer to a texture should fail. + fail(&ctx.device, || { + encoder_for_buffer_texture_copy.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Copying a texture to a buffer should fail. + fail(&ctx.device, || { + encoder_for_texture_buffer_copy.copy_texture_to_buffer( + texture_for_read.as_image_copy(), + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_extent, + ); + }); + + // Copying a texture to a texture should fail. + fail(&ctx.device, || { + encoder_for_texture_texture_copy.copy_texture_to_texture( + texture_for_read.as_image_copy(), + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Creating a bind group layout should fail. + fail(&ctx.device, || { + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + }); + + // Creating a bind group should fail. + fail(&ctx.device, || { + ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + buffer_source.as_entire_buffer_binding(), + ), + }], + }); + }); + + // Creating a pipeline layout should fail. + fail(&ctx.device, || { + ctx.device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + }); + + // Creating a shader module should fail. + fail(&ctx.device, || { + ctx.device .create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), }); + }); - // Create some command encoders. - let mut encoder_for_clear = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_compute_pass = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_render_pass = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_buffer_buffer_copy = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_buffer_texture_copy = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_texture_buffer_copy = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - let mut encoder_for_texture_texture_copy = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - // Destroy the device. This will cause all other requests to return some variation of - // a device invalid error. - ctx.device.destroy(); - - // TODO: verify the following operations will return an invalid device error: - // * Run a compute or render pass - // * Finish a render bundle encoder - // * Create a texture from HAL - // * Create a buffer from HAL - // * Create a sampler - // * Validate a surface configuration - // * Start or stop capture - // * Get or set buffer sub data - - // TODO: figure out how to structure a test around these operations which panic when - // the device is invalid: - // * device.features() - // * device.limits() - // * device.downlevel_properties() - // * device.create_query_set() - - // TODO: change these fail calls to check for the specific errors which indicate that - // the device is not valid. - - // Creating a commmand encoder should fail. - fail(&ctx.device, || { - ctx.device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - }); - - // Creating a buffer should fail. - fail(&ctx.device, || { - ctx.device.create_buffer(&wgpu::BufferDescriptor { + // Creating a shader module spirv should fail. + fail(&ctx.device, || unsafe { + ctx.device + .create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV { label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, + source: std::borrow::Cow::Borrowed(&[]), }); - }); + }); - // Creating a texture should fail. - fail(&ctx.device, || { - ctx.device.create_texture(&wgpu::TextureDescriptor { + // Creating a render pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - size: wgpu::Extent3d { - width: 512, - height: 512, - depth_or_array_layers: 1, - }, - mip_level_count: 2, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rg8Uint, - usage: wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - }); - - // Texture clear should fail. - fail(&ctx.device, || { - encoder_for_clear.clear_texture( - &texture_for_write, - &wgpu::ImageSubresourceRange { - aspect: wgpu::TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - }, - ); - }); - - // Creating a compute pass should fail. - fail(&ctx.device, || { - encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, - }); - }); - - // Creating a render pass should fail. - fail(&ctx.device, || { - encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - ops: wgpu::Operations::default(), - resolve_target: None, - view: &target_view, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - }); - - // Copying a buffer to a buffer should fail. - fail(&ctx.device, || { - encoder_for_buffer_buffer_copy.copy_buffer_to_buffer( - &buffer_source, - 0, - &buffer_dest, - 0, - 256, - ); - }); - - // Copying a buffer to a texture should fail. - fail(&ctx.device, || { - encoder_for_buffer_texture_copy.copy_buffer_to_texture( - wgpu::ImageCopyBuffer { - buffer: &buffer_source, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4), - rows_per_image: None, - }, - }, - texture_for_write.as_image_copy(), - texture_extent, - ); - }); - - // Copying a texture to a buffer should fail. - fail(&ctx.device, || { - encoder_for_texture_buffer_copy.copy_texture_to_buffer( - texture_for_read.as_image_copy(), - wgpu::ImageCopyBuffer { - buffer: &buffer_source, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4), - rows_per_image: None, - }, - }, - texture_extent, - ); - }); - - // Copying a texture to a texture should fail. - fail(&ctx.device, || { - encoder_for_texture_texture_copy.copy_texture_to_texture( - texture_for_read.as_image_copy(), - texture_for_write.as_image_copy(), - texture_extent, - ); - }); - - // Creating a bind group layout should fail. - fail(&ctx.device, || { - ctx.device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[], - }); - }); - - // Creating a bind group should fail. - fail(&ctx.device, || { - ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - buffer_source.as_entire_buffer_binding(), - ), - }], - }); - }); - - // Creating a pipeline layout should fail. - fail(&ctx.device, || { - ctx.device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - }); - - // Creating a shader module should fail. - fail(&ctx.device, || { - ctx.device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), - }); - }); - - // Creating a shader module spirv should fail. - fail(&ctx.device, || unsafe { - ctx.device - .create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV { - label: None, - source: std::borrow::Cow::Borrowed(&[]), - }); - }); - - // Creating a render pipeline should fail. - fail(&ctx.device, || { - ctx.device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: None, - vertex: wgpu::VertexState { - module: &shader_module, - entry_point: "", - buffers: &[], - }, - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - fragment: None, - multiview: None, - }); - }); - - // Creating a compute pipeline should fail. - fail(&ctx.device, || { - ctx.device - .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: None, + layout: None, + vertex: wgpu::VertexState { module: &shader_module, entry_point: "", - }); - }); + buffers: &[], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: None, + multiview: None, + }); + }); - // Buffer map should fail. - fail(&ctx.device, || { - buffer_for_map - .slice(..) - .map_async(wgpu::MapMode::Write, |_| ()); - }); + // Creating a compute pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &shader_module, + entry_point: "", + }); + }); - // Buffer unmap should fail. - fail(&ctx.device, || { - buffer_for_unmap.unmap(); - }); - }, - ) -} + // Buffer map should fail. + fail(&ctx.device, || { + buffer_for_map + .slice(..) + .map_async(wgpu::MapMode::Write, |_| ()); + }); + + // Buffer unmap should fail. + fail(&ctx.device, || { + buffer_for_unmap.unmap(); + }); + }); diff --git a/tests/tests/encoder.rs b/tests/tests/encoder.rs index 5914cd22d..3487eb05d 100644 --- a/tests/tests/encoder.rs +++ b/tests/tests/encoder.rs @@ -1,30 +1,24 @@ -use wasm_bindgen_test::*; -use wgpu::RenderPassDescriptor; -use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -#[wasm_bindgen_test] -fn drop_encoder() { - initialize_test(TestParameters::default(), |ctx| { - let encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - drop(encoder); - }) -} +#[gpu_test] +static DROP_ENCODER: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + drop(encoder); +}); -#[test] -fn drop_encoder_after_error() { - // This test crashes on DX12 with the exception: - // - // ID3D12CommandAllocator::Reset: The command allocator cannot be reset because a - // command list is currently being recorded with the allocator. [ EXECUTION ERROR - // #543: COMMAND_ALLOCATOR_CANNOT_RESET] - // - // For now, we mark the test as failing on DX12. - let parameters = - TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12)); - initialize_test(parameters, |ctx| { +// This test crashes on DX12 with the exception: +// +// ID3D12CommandAllocator::Reset: The command allocator cannot be reset because a +// command list is currently being recorded with the allocator. [ EXECUTION ERROR +// #543: COMMAND_ALLOCATOR_CANNOT_RESET] +// +// For now, we mark the test as failing on DX12. +#[gpu_test] +static DROP_ENCODER_AFTER_ERROR: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12))) + .run_sync(|ctx| { let mut encoder = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); @@ -45,7 +39,7 @@ fn drop_encoder_after_error() { }); let target_view = target_tex.create_view(&wgpu::TextureViewDescriptor::default()); - let mut renderpass = encoder.begin_render_pass(&RenderPassDescriptor { + let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("renderpass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { ops: wgpu::Operations::default(), @@ -67,5 +61,4 @@ fn drop_encoder_after_error() { // a CommandEncoder which errored out when processing a command. // The encoder is still open! drop(encoder); - }) -} + }); diff --git a/tests/tests/example_wgsl.rs b/tests/tests/example_wgsl.rs index d645dd4ed..10ebd4ad3 100644 --- a/tests/tests/example_wgsl.rs +++ b/tests/tests/example_wgsl.rs @@ -3,7 +3,7 @@ use std::{fs, path::PathBuf}; /// Runs through all example shaders and ensures they are valid wgsl. #[test] -fn parse_example_wgsl() { +pub fn parse_example_wgsl() { let read_dir = match PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("examples") .read_dir() diff --git a/tests/tests/external_texture.rs b/tests/tests/external_texture.rs index 2c3a8b987..18a5d193a 100644 --- a/tests/tests/external_texture.rs +++ b/tests/tests/external_texture.rs @@ -1,138 +1,137 @@ #![cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] use wasm_bindgen::JsCast; -use wasm_bindgen_test::*; use wgpu::ExternalImageSource; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[wasm_bindgen_test] -async fn image_bitmap_import() { - let image_encoded = include_bytes!("3x3_colors.png"); +#[gpu_test] +static IMAGE_BITMAP_IMPORT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let image_encoded = include_bytes!("3x3_colors.png"); - // Create an array-of-arrays for Blob's constructor - let array = js_sys::Array::new(); - array.push(&js_sys::Uint8Array::from(&image_encoded[..])); + // Create an array-of-arrays for Blob's constructor + let array = js_sys::Array::new(); + array.push(&js_sys::Uint8Array::from(&image_encoded[..])); - // We're passing an array of Uint8Arrays - let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap(); + // We're passing an array of Uint8Arrays + let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap(); - // Parse the image from the blob + // Parse the image from the blob - // Because we need to call the function in a way that isn't bound by - // web_sys, we need to manually construct the options struct and call - // the function. - let image_bitmap_function: js_sys::Function = web_sys::window() - .unwrap() - .get("createImageBitmap") - .unwrap() - .dyn_into() - .unwrap(); - - let options_arg = js_sys::Object::new(); - js_sys::Reflect::set( - &options_arg, - &wasm_bindgen::JsValue::from_str("premultiplyAlpha"), - &wasm_bindgen::JsValue::from_str("none"), - ) - .unwrap(); - let image_bitmap_promise: js_sys::Promise = image_bitmap_function - .call2(&wasm_bindgen::JsValue::UNDEFINED, &blob, &options_arg) - .unwrap() - .dyn_into() - .unwrap(); - - // Wait for the parsing to be done - let image_bitmap: web_sys::ImageBitmap = - wasm_bindgen_futures::JsFuture::from(image_bitmap_promise) - .await + // Because we need to call the function in a way that isn't bound by + // web_sys, we need to manually construct the options struct and call + // the function. + let image_bitmap_function: js_sys::Function = web_sys::window() + .unwrap() + .get("createImageBitmap") .unwrap() .dyn_into() .unwrap(); - // Sanity checks - assert_eq!(image_bitmap.width(), 3); - assert_eq!(image_bitmap.height(), 3); - - // Due to restrictions with premultiplication with ImageBitmaps, we also create an HtmlCanvasElement - // by drawing the image bitmap onto the canvas. - let canvas: web_sys::HtmlCanvasElement = web_sys::window() - .unwrap() - .document() - .unwrap() - .create_element("canvas") - .unwrap() - .dyn_into() + let options_arg = js_sys::Object::new(); + js_sys::Reflect::set( + &options_arg, + &wasm_bindgen::JsValue::from_str("premultiplyAlpha"), + &wasm_bindgen::JsValue::from_str("none"), + ) .unwrap(); - canvas.set_width(3); - canvas.set_height(3); + let image_bitmap_promise: js_sys::Promise = image_bitmap_function + .call2(&wasm_bindgen::JsValue::UNDEFINED, &blob, &options_arg) + .unwrap() + .dyn_into() + .unwrap(); - let d2_context: web_sys::CanvasRenderingContext2d = canvas - .get_context("2d") - .unwrap() - .unwrap() - .dyn_into() - .unwrap(); - d2_context - .draw_image_with_image_bitmap(&image_bitmap, 0.0, 0.0) - .unwrap(); + // Wait for the parsing to be done + let image_bitmap: web_sys::ImageBitmap = + wasm_bindgen_futures::JsFuture::from(image_bitmap_promise) + .await + .unwrap() + .dyn_into() + .unwrap(); - // Decode it cpu side - let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png) - .unwrap() - .into_rgba8(); + // Sanity checks + assert_eq!(image_bitmap.width(), 3); + assert_eq!(image_bitmap.height(), 3); - // Set of test cases to test with image import - #[derive(Debug, Copy, Clone)] - enum TestCase { - // Import the image as normal - Normal, - // Sets the FlipY flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // - // Only works on canvases. - FlipY, - // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // - // Only works on canvases. - Premultiplied, - // Sets the color space to P3. - // - // Only works on canvases. - ColorSpace, - // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // Set both the input offset and output offset to 1 in x, so the first column is omitted. - TrimLeft, - // Set the size to 2 in x, so the last column is omitted - TrimRight, - // Set only the output offset to 1, so the second column gets the first column's data. - SlideRight, - // Try to copy from out of bounds of the source image - SourceOutOfBounds, - // Try to copy from out of bounds of the destination image - DestOutOfBounds, - // Try to copy more than one slice from the source - MultiSliceCopy, - // Copy into the second slice of a 2D array texture, - SecondSliceCopy, - } - let sources = [ - ExternalImageSource::ImageBitmap(image_bitmap), - ExternalImageSource::HTMLCanvasElement(canvas), - ]; - let cases = [ - TestCase::Normal, - TestCase::FlipY, - TestCase::Premultiplied, - TestCase::ColorSpace, - TestCase::TrimLeft, - TestCase::TrimRight, - TestCase::SlideRight, - TestCase::SourceOutOfBounds, - TestCase::DestOutOfBounds, - TestCase::MultiSliceCopy, - TestCase::SecondSliceCopy, - ]; + // Due to restrictions with premultiplication with ImageBitmaps, we also create an HtmlCanvasElement + // by drawing the image bitmap onto the canvas. + let canvas: web_sys::HtmlCanvasElement = web_sys::window() + .unwrap() + .document() + .unwrap() + .create_element("canvas") + .unwrap() + .dyn_into() + .unwrap(); + canvas.set_width(3); + canvas.set_height(3); + + let d2_context: web_sys::CanvasRenderingContext2d = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into() + .unwrap(); + d2_context + .draw_image_with_image_bitmap(&image_bitmap, 0.0, 0.0) + .unwrap(); + + // Decode it cpu side + let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png) + .unwrap() + .into_rgba8(); + + // Set of test cases to test with image import + #[derive(Debug, Copy, Clone)] + enum TestCase { + // Import the image as normal + Normal, + // Sets the FlipY flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // + // Only works on canvases. + FlipY, + // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // + // Only works on canvases. + Premultiplied, + // Sets the color space to P3. + // + // Only works on canvases. + ColorSpace, + // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // Set both the input offset and output offset to 1 in x, so the first column is omitted. + TrimLeft, + // Set the size to 2 in x, so the last column is omitted + TrimRight, + // Set only the output offset to 1, so the second column gets the first column's data. + SlideRight, + // Try to copy from out of bounds of the source image + SourceOutOfBounds, + // Try to copy from out of bounds of the destination image + DestOutOfBounds, + // Try to copy more than one slice from the source + MultiSliceCopy, + // Copy into the second slice of a 2D array texture, + SecondSliceCopy, + } + let sources = [ + ExternalImageSource::ImageBitmap(image_bitmap), + ExternalImageSource::HTMLCanvasElement(canvas), + ]; + let cases = [ + TestCase::Normal, + TestCase::FlipY, + TestCase::Premultiplied, + TestCase::ColorSpace, + TestCase::TrimLeft, + TestCase::TrimRight, + TestCase::SlideRight, + TestCase::SourceOutOfBounds, + TestCase::DestOutOfBounds, + TestCase::MultiSliceCopy, + TestCase::SecondSliceCopy, + ]; - initialize_test(TestParameters::default(), |ctx| { for source in sources { for case in cases { // Copy the data, so we can modify it for tests @@ -346,5 +345,4 @@ async fn image_bitmap_import() { } } } - }) -} + }); diff --git a/tests/tests/root.rs b/tests/tests/gpu.rs similarity index 84% rename from tests/tests/root.rs rename to tests/tests/gpu.rs index a97a160a2..a5fbcde9d 100644 --- a/tests/tests/root.rs +++ b/tests/tests/gpu.rs @@ -1,5 +1,3 @@ -use wasm_bindgen_test::wasm_bindgen_test_configure; - mod regression { mod issue_3457; mod issue_4024; @@ -15,7 +13,6 @@ mod clear_texture; mod create_surface_error; mod device; mod encoder; -mod example_wgsl; mod external_texture; mod instance; mod occlusion_query; @@ -36,4 +33,4 @@ mod vertex_indices; mod write_texture; mod zero_init_texture_after_discard; -wasm_bindgen_test_configure!(run_in_browser); +wgpu_test::gpu_test_main!(); diff --git a/tests/tests/instance.rs b/tests/tests/instance.rs index bd2045298..c74b7aedd 100644 --- a/tests/tests/instance.rs +++ b/tests/tests/instance.rs @@ -1,38 +1,4 @@ -use wasm_bindgen_test::*; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn initialize() { - let _ = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all), - flags: wgpu::InstanceFlags::debugging().with_env(), - dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), - gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), - }); -} - -fn request_adapter_inner(power: wgt::PowerPreference) { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all), - flags: wgpu::InstanceFlags::debugging().with_env(), - dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), - gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), - }); - - let _adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: power, - force_fallback_adapter: false, - compatible_surface: None, - })) - .unwrap(); -} - -#[test] -fn request_adapter_low_power() { - request_adapter_inner(wgt::PowerPreference::LowPower); -} - -#[test] -fn request_adapter_high_power() { - request_adapter_inner(wgt::PowerPreference::HighPerformance); -} +#[gpu_test] +static INITIALIZE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|_ctx| {}); diff --git a/tests/tests/occlusion_query/mod.rs b/tests/tests/occlusion_query/mod.rs index 7747eaa62..7a7f3fa9a 100644 --- a/tests/tests/occlusion_query/mod.rs +++ b/tests/tests/occlusion_query/mod.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -fn occlusion_query() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_sync(|ctx| { // Create depth texture let depth_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some("Depth texture"), @@ -124,5 +125,4 @@ fn occlusion_query() { assert_ne!(query_data[0], 0); assert_ne!(query_data[1], 0); assert_eq!(query_data[2], 0); - }) -} + }); diff --git a/tests/tests/partially_bounded_arrays/mod.rs b/tests/tests/partially_bounded_arrays/mod.rs index 43844e456..acadaad67 100644 --- a/tests/tests/partially_bounded_arrays/mod.rs +++ b/tests/tests/partially_bounded_arrays/mod.rs @@ -1,12 +1,10 @@ use std::{borrow::Cow, num::NonZeroU32}; -use wasm_bindgen_test::*; -use wgpu_test::{image::ReadbackBuffers, initialize_test, TestParameters}; +use wgpu_test::{gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters}; -#[test] -#[wasm_bindgen_test] -fn partially_bounded_array() { - initialize_test( +#[gpu_test] +static PARTIALLY_BOUNDED_ARRAY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features( wgpu::Features::TEXTURE_BINDING_ARRAY @@ -15,96 +13,93 @@ fn partially_bounded_array() { | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, ) .limits(wgpu::Limits::downlevel_defaults()), - |ctx| { - let device = &ctx.device; - - let texture_extent = wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }; - let storage_texture = device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba32Float, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::STORAGE_BINDING - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::StorageTexture { - access: wgpu::StorageTextureAccess::WriteOnly, - format: wgpu::TextureFormat::Rgba32Float, - view_dimension: wgpu::TextureViewDimension::D2, - }, - - count: NonZeroU32::new(4), - }], - }); - - let cs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("main"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let compute_pipeline = - device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &cs_module, - entry_point: "main", - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureViewArray(&[&texture_view]), - }], - layout: &bind_group_layout, - label: Some("bind group"), - }); - - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, - }); - cpass.set_pipeline(&compute_pipeline); - cpass.set_bind_group(0, &bind_group, &[]); - cpass.dispatch_workgroups(1, 1, 1); - } - - let readback_buffers = ReadbackBuffers::new(&ctx.device, &storage_texture); - readback_buffers.copy_from(&ctx.device, &mut encoder, &storage_texture); - - ctx.queue.submit(Some(encoder.finish())); - - assert!( - readback_buffers - .check_buffer_contents(device, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])), - "texture storage values are incorrect!" - ); - }, ) -} + .run_sync(|ctx| { + let device = &ctx.device; + + let texture_extent = wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }; + let storage_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba32Float, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::STORAGE_BINDING + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bind group layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba32Float, + view_dimension: wgpu::TextureViewDimension::D2, + }, + + count: NonZeroU32::new(4), + }], + }); + + let cs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("main"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &cs_module, + entry_point: "main", + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureViewArray(&[&texture_view]), + }], + layout: &bind_group_layout, + label: Some("bind group"), + }); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + cpass.set_pipeline(&compute_pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch_workgroups(1, 1, 1); + } + + let readback_buffers = ReadbackBuffers::new(&ctx.device, &storage_texture); + readback_buffers.copy_from(&ctx.device, &mut encoder, &storage_texture); + + ctx.queue.submit(Some(encoder.finish())); + + assert!( + readback_buffers + .check_buffer_contents(device, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])), + "texture storage values are incorrect!" + ); + }); diff --git a/tests/tests/pipeline.rs b/tests/tests/pipeline.rs index 37046dd6b..2733bf2f8 100644 --- a/tests/tests/pipeline.rs +++ b/tests/tests/pipeline.rs @@ -1,17 +1,16 @@ -use wasm_bindgen_test::*; -use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -#[wasm_bindgen_test] -fn pipeline_default_layout_bad_module() { - // Create an invalid shader and a compute pipeline that uses it - // with a default bindgroup layout, and then ask for that layout. - // Validation should fail, but wgpu should not panic. - let parameters = TestParameters::default() - .skip(FailureCase::webgl2()) - // https://github.com/gfx-rs/wgpu/issues/4167 - .expect_fail(FailureCase::always()); - initialize_test(parameters, |ctx| { +// Create an invalid shader and a compute pipeline that uses it +// with a default bindgroup layout, and then ask for that layout. +// Validation should fail, but wgpu should not panic. +#[gpu_test] +static PIPELINE_DEFAULT_LAYOUT_BAD_MODULE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + // https://github.com/gfx-rs/wgpu/issues/4167 + .expect_fail(FailureCase::always()), + ) + .run_sync(|ctx| { ctx.device.push_error_scope(wgpu::ErrorFilter::Validation); fail(&ctx.device, || { @@ -34,4 +33,3 @@ fn pipeline_default_layout_bad_module() { pipeline.get_bind_group_layout(0); }); }); -} diff --git a/tests/tests/poll.rs b/tests/tests/poll.rs index e27a47a42..b83223750 100644 --- a/tests/tests/poll.rs +++ b/tests/tests/poll.rs @@ -1,130 +1,115 @@ use std::num::NonZeroU64; use wgpu::{ - BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, - BindingResource, BindingType, BufferBindingType, BufferDescriptor, BufferUsages, CommandBuffer, - CommandEncoderDescriptor, ComputePassDescriptor, Maintain, ShaderStages, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBindingType, + BufferDescriptor, BufferUsages, CommandBuffer, CommandEncoderDescriptor, ComputePassDescriptor, + Maintain, ShaderStages, }; -use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestingContext}; -fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer { - let buffer = ctx.device.create_buffer(&BufferDescriptor { - label: None, - size: 16, - usage: BufferUsages::UNIFORM, - mapped_at_creation: false, - }); +struct DummyWorkData { + _buffer: Buffer, + _bgl: BindGroupLayout, + _bg: BindGroup, + cmd_buf: CommandBuffer, +} - let bind_group_layout = ctx - .device - .create_bind_group_layout(&BindGroupLayoutDescriptor { +impl DummyWorkData { + fn new(ctx: &TestingContext) -> Self { + let buffer = ctx.device.create_buffer(&BufferDescriptor { label: None, - entries: &[BindGroupLayoutEntry { + size: 16, + usage: BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(NonZeroU64::new(16).unwrap()), + }, + count: None, + }], + }); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[BindGroupEntry { binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: Some(NonZeroU64::new(16).unwrap()), - }, - count: None, + resource: BindingResource::Buffer(buffer.as_entire_buffer_binding()), }], }); - let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::Buffer(buffer.as_entire_buffer_binding()), - }], + let mut cmd_buf = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + + let mut cpass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); + cpass.set_bind_group(0, &bind_group, &[]); + drop(cpass); + + Self { + _buffer: buffer, + _bgl: bind_group_layout, + _bg: bind_group, + cmd_buf: cmd_buf.finish(), + } + } +} + +#[gpu_test] +static WAIT: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let data = DummyWorkData::new(&ctx); + + ctx.queue.submit(Some(data.cmd_buf)); + ctx.device.poll(Maintain::Wait); +}); + +#[gpu_test] +static DOUBLE_WAIT: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let data = DummyWorkData::new(&ctx); + + ctx.queue.submit(Some(data.cmd_buf)); + ctx.device.poll(Maintain::Wait); + ctx.device.poll(Maintain::Wait); +}); + +#[gpu_test] +static WAIT_ON_SUBMISSION: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let data = DummyWorkData::new(&ctx); + + let index = ctx.queue.submit(Some(data.cmd_buf)); + ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); +}); + +#[gpu_test] +static DOUBLE_WAIT_ON_SUBMISSION: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { + let data = DummyWorkData::new(&ctx); + + let index = ctx.queue.submit(Some(data.cmd_buf)); + ctx.device + .poll(Maintain::WaitForSubmissionIndex(index.clone())); + ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); }); - let mut cmd_buf = ctx - .device - .create_command_encoder(&CommandEncoderDescriptor::default()); +#[gpu_test] +static WAIT_OUT_OF_ORDER: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let data1 = DummyWorkData::new(&ctx); + let data2 = DummyWorkData::new(&ctx); - let mut cpass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); - cpass.set_bind_group(0, &bind_group, &[]); - drop(cpass); - - cmd_buf.finish() -} - -#[test] -#[wasm_bindgen_test] -fn wait() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::Wait); - }, - ) -} - -#[test] -#[wasm_bindgen_test] -fn double_wait() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::Wait); - ctx.device.poll(Maintain::Wait); - }, - ) -} - -#[test] -#[wasm_bindgen_test] -fn wait_on_submission() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - let index = ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); - }, - ) -} - -#[test] -#[wasm_bindgen_test] -fn double_wait_on_submission() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - let index = ctx.queue.submit(Some(cmd_buf)); - ctx.device - .poll(Maintain::WaitForSubmissionIndex(index.clone())); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); - }, - ) -} - -#[test] -#[wasm_bindgen_test] -fn wait_out_of_order() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf1 = generate_dummy_work(&ctx); - let cmd_buf2 = generate_dummy_work(&ctx); - - let index1 = ctx.queue.submit(Some(cmd_buf1)); - let index2 = ctx.queue.submit(Some(cmd_buf2)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index2)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index1)); - }, - ) -} + let index1 = ctx.queue.submit(Some(data1.cmd_buf)); + let index2 = ctx.queue.submit(Some(data2.cmd_buf)); + ctx.device.poll(Maintain::WaitForSubmissionIndex(index2)); + ctx.device.poll(Maintain::WaitForSubmissionIndex(index1)); +}); diff --git a/tests/tests/query_set.rs b/tests/tests/query_set.rs index 16e509408..1030518aa 100644 --- a/tests/tests/query_set.rs +++ b/tests/tests/query_set.rs @@ -1,11 +1,13 @@ -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -fn drop_failed_timestamp_query_set() { - let parameters = TestParameters::default() - // https://github.com/gfx-rs/wgpu/issues/4139 - .expect_fail(FailureCase::always()); - initialize_test(parameters, |ctx| { +#[gpu_test] +static DROP_FAILED_TIMESTAMP_QUERY_SET: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + // https://github.com/gfx-rs/wgpu/issues/4139 + .expect_fail(FailureCase::always()), + ) + .run_sync(|ctx| { // Enter an error scope, so the validation catch-all doesn't // report the error too early. ctx.device.push_error_scope(wgpu::ErrorFilter::Validation); @@ -23,4 +25,3 @@ fn drop_failed_timestamp_query_set() { assert!(pollster::block_on(ctx.device.pop_error_scope()).is_some()); }); -} diff --git a/tests/tests/queue_transfer.rs b/tests/tests/queue_transfer.rs index f17030f37..a1ae41572 100644 --- a/tests/tests/queue_transfer.rs +++ b/tests/tests/queue_transfer.rs @@ -1,12 +1,10 @@ //! Tests for buffer copy validation. -use wasm_bindgen_test::*; -use wgpu_test::{fail, initialize_test, TestParameters}; +use wgpu_test::{fail, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn queue_write_texture_overflow() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static QUEUE_WRITE_TEXTURE_OVERFLOW: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { @@ -47,4 +45,3 @@ fn queue_write_texture_overflow() { ); }); }); -} diff --git a/tests/tests/regression/issue_3457.rs b/tests/tests/regression/issue_3457.rs index 0d2c086ed..d7ea0e5bb 100644 --- a/tests/tests/regression/issue_3457.rs +++ b/tests/tests/regression/issue_3457.rs @@ -1,6 +1,5 @@ -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -use wasm_bindgen_test::wasm_bindgen_test; use wgpu::*; /// The core issue here was that we weren't properly disabling vertex attributes on GL @@ -15,10 +14,9 @@ use wgpu::*; /// /// We use non-consecutive vertex attribute locations (0 and 5) in order to also test /// that we unset the correct locations (see PR #3706). -#[wasm_bindgen_test] -#[test] -fn pass_reset_vertex_buffer() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { let module = ctx .device .create_shader_module(include_wgsl!("issue_3457.wgsl")); @@ -190,5 +188,4 @@ fn pass_reset_vertex_buffer() { drop(single_rpass); ctx.queue.submit(Some(encoder2.finish())); - }) -} + }); diff --git a/tests/tests/regression/issue_4024.rs b/tests/tests/regression/issue_4024.rs index 959f58ffa..0577b223f 100644 --- a/tests/tests/regression/issue_4024.rs +++ b/tests/tests/regression/issue_4024.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use parking_lot::Mutex; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -use wasm_bindgen_test::wasm_bindgen_test; use wgpu::*; /// The WebGPU specification has very specific requirements about the ordering of map_async @@ -13,10 +12,9 @@ use wgpu::*; /// /// We previously immediately invoked on_submitted_work_done callbacks if there was no active submission /// to add them to. This is incorrect, as we do not immediatley invoke map_async callbacks. -#[wasm_bindgen_test] -#[test] -fn queue_submitted_callback_ordering() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static QUEUE_SUBMITTED_CALLBACK_ORDERING: GpuTestConfiguration = GpuTestConfiguration::new() + .run_sync(|ctx| { // Create a mappable buffer let buffer = ctx.device.create_buffer(&BufferDescriptor { label: Some("mappable buffer"), @@ -87,5 +85,4 @@ fn queue_submitted_callback_ordering() { assert_eq!(ordering.value_read_map_async, Some(0)); // The queue submitted work done callback was invoked second. assert_eq!(ordering.value_read_queue_submitted, Some(1)); - }) -} + }); diff --git a/tests/tests/regression/issue_4122.rs b/tests/tests/regression/issue_4122.rs index 41b9cd423..c6f7afbdd 100644 --- a/tests/tests/regression/issue_4122.rs +++ b/tests/tests/regression/issue_4122.rs @@ -1,7 +1,6 @@ use std::{num::NonZeroU64, ops::Range}; -use wasm_bindgen_test::wasm_bindgen_test; -use wgpu_test::{initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestingContext}; fn fill_test(ctx: &TestingContext, range: Range, size: u64) -> bool { let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { @@ -88,10 +87,9 @@ fn fill_test(ctx: &TestingContext, range: Range, size: u64) -> bool { /// certain conditions. See https://github.com/gfx-rs/wgpu/issues/4122 for more information. /// /// This test will fail on nvidia if the bug is not properly worked around. -#[wasm_bindgen_test] -#[test] -fn clear_buffer_bug() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static CLEAR_BUFFER_RANGE_RESPECTED: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { // This hits most of the cases in nvidia's clear buffer bug let mut succeeded = true; for power in 4..14 { @@ -107,4 +105,3 @@ fn clear_buffer_bug() { } assert!(succeeded); }); -} diff --git a/tests/tests/resource_descriptor_accessor.rs b/tests/tests/resource_descriptor_accessor.rs index 5f70258ac..ee984da60 100644 --- a/tests/tests/resource_descriptor_accessor.rs +++ b/tests/tests/resource_descriptor_accessor.rs @@ -1,22 +1,17 @@ -use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -/// Buffer's size and usage can be read back. -#[test] -#[wasm_bindgen_test] -fn buffer_size_and_usage() { - initialize_test(TestParameters::default(), |ctx| { - let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 1234, - usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); +#[gpu_test] +static BUFFER_SIZE_AND_USAGE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 1234, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - assert_eq!(buffer.size(), 1234); - assert_eq!( - buffer.usage(), - wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST - ); - }) -} + assert_eq!(buffer.size(), 1234); + assert_eq!( + buffer.usage(), + wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST + ); +}); diff --git a/tests/tests/resource_error.rs b/tests/tests/resource_error.rs index c02033b33..0a81801a9 100644 --- a/tests/tests/resource_error.rs +++ b/tests/tests/resource_error.rs @@ -1,57 +1,48 @@ -use wasm_bindgen_test::*; -use wgpu_test::{fail, initialize_test, valid, TestParameters}; +use wgpu_test::{fail, gpu_test, valid, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn bad_buffer() { +#[gpu_test] +static BAD_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { // Create a buffer with bad parameters and call a few methods. // Validation should fail but there should be not panic. - initialize_test(TestParameters::default(), |ctx| { - let buffer = fail(&ctx.device, || { - ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 99999999, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::STORAGE, - mapped_at_creation: false, - }) - }); - - fail(&ctx.device, || { - buffer.slice(..).map_async(wgpu::MapMode::Write, |_| {}) - }); - fail(&ctx.device, || buffer.unmap()); - valid(&ctx.device, || buffer.destroy()); - valid(&ctx.device, || buffer.destroy()); + let buffer = fail(&ctx.device, || { + ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 99999999, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }) }); -} -#[test] -#[wasm_bindgen_test] -fn bad_texture() { - // Create a texture with bad parameters and call a few methods. - // Validation should fail but there should be not panic. - initialize_test(TestParameters::default(), |ctx| { - let texture = fail(&ctx.device, || { - ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: 0, - height: 12345678, - depth_or_array_layers: 9001, - }, - mip_level_count: 2000, - sample_count: 27, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::all(), - view_formats: &[], - }) - }); - - fail(&ctx.device, || { - let _ = texture.create_view(&wgpu::TextureViewDescriptor::default()); - }); - valid(&ctx.device, || texture.destroy()); - valid(&ctx.device, || texture.destroy()); + fail(&ctx.device, || { + buffer.slice(..).map_async(wgpu::MapMode::Write, |_| {}) }); -} + fail(&ctx.device, || buffer.unmap()); + valid(&ctx.device, || buffer.destroy()); + valid(&ctx.device, || buffer.destroy()); +}); + +#[gpu_test] +static BAD_TEXTURE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let texture = fail(&ctx.device, || { + ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 0, + height: 12345678, + depth_or_array_layers: 9001, + }, + mip_level_count: 2000, + sample_count: 27, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::all(), + view_formats: &[], + }) + }); + + fail(&ctx.device, || { + let _ = texture.create_view(&wgpu::TextureViewDescriptor::default()); + }); + valid(&ctx.device, || texture.destroy()); + valid(&ctx.device, || texture.destroy()); +}); diff --git a/tests/tests/scissor_tests/mod.rs b/tests/tests/scissor_tests/mod.rs index a921827e0..d53d31cda 100644 --- a/tests/tests/scissor_tests/mod.rs +++ b/tests/tests/scissor_tests/mod.rs @@ -1,4 +1,4 @@ -use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, image, GpuTestConfiguration, TestingContext}; struct Rect { x: u32, @@ -97,25 +97,23 @@ fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u assert!(readback_buffer.check_buffer_contents(&ctx.device, &expected_data)); } -#[test] -fn scissor_test_full_rect() { - initialize_test(TestParameters::default(), |ctx| { - scissor_test_impl( - &ctx, - Rect { - x: 0, - y: 0, - width: TEXTURE_WIDTH, - height: TEXTURE_HEIGHT, - }, - [255; BUFFER_SIZE], - ); - }) -} +#[gpu_test] +static SCISSOR_TEST_FULL_RECT: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + scissor_test_impl( + &ctx, + Rect { + x: 0, + y: 0, + width: TEXTURE_WIDTH, + height: TEXTURE_HEIGHT, + }, + [255; BUFFER_SIZE], + ); +}); -#[test] -fn scissor_test_empty_rect() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static SCISSOR_TEST_EMPTY_RECT: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { scissor_test_impl( &ctx, Rect { @@ -126,12 +124,11 @@ fn scissor_test_empty_rect() { }, [0; BUFFER_SIZE], ); - }) -} + }); -#[test] -fn scissor_test_empty_rect_with_offset() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static SCISSOR_TEST_EMPTY_RECT_WITH_OFFSET: GpuTestConfiguration = GpuTestConfiguration::new() + .run_sync(|ctx| { scissor_test_impl( &ctx, Rect { @@ -142,15 +139,15 @@ fn scissor_test_empty_rect_with_offset() { }, [0; BUFFER_SIZE], ); - }) -} + }); + +#[gpu_test] +static SCISSOR_TEST_CUSTOM_RECT: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { + let mut expected_result = [0; BUFFER_SIZE]; + expected_result[((3 * BUFFER_SIZE) / 4)..][..BUFFER_SIZE / 4] + .copy_from_slice(&[255; BUFFER_SIZE / 4]); -#[test] -fn scissor_test_custom_rect() { - let mut expected_result = [0; BUFFER_SIZE]; - expected_result[((3 * BUFFER_SIZE) / 4)..][..BUFFER_SIZE / 4] - .copy_from_slice(&[255; BUFFER_SIZE / 4]); - initialize_test(TestParameters::default(), |ctx| { scissor_test_impl( &ctx, Rect { @@ -161,5 +158,4 @@ fn scissor_test_custom_rect() { }, expected_result, ); - }) -} + }); diff --git a/tests/tests/shader/mod.rs b/tests/tests/shader/mod.rs index 498c16c33..a8ca9a27b 100644 --- a/tests/tests/shader/mod.rs +++ b/tests/tests/shader/mod.rs @@ -15,9 +15,9 @@ use wgpu::{ use wgpu_test::TestingContext; -mod numeric_builtins; -mod struct_layout; -mod zero_init_workgroup_mem; +pub mod numeric_builtins; +pub mod struct_layout; +pub mod zero_init_workgroup_mem; #[derive(Clone, Copy, PartialEq)] enum InputStorageType { diff --git a/tests/tests/shader/numeric_builtins.rs b/tests/tests/shader/numeric_builtins.rs index 2f6981ef7..7463e7a50 100644 --- a/tests/tests/shader/numeric_builtins.rs +++ b/tests/tests/shader/numeric_builtins.rs @@ -1,8 +1,7 @@ -use wasm_bindgen_test::*; use wgpu::{DownlevelFlags, Limits}; use crate::shader::{shader_input_output_test, InputStorageType, ShaderTest}; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; fn create_numeric_builtin_test() -> Vec { let mut tests = Vec::new(); @@ -38,19 +37,17 @@ fn create_numeric_builtin_test() -> Vec { tests } -#[test] -#[wasm_bindgen_test] -fn numeric_builtins() { - initialize_test( +#[gpu_test] +static NUMERIC_BUILTINS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Storage, - create_numeric_builtin_test(), - ); - }, - ); -} + ) + .run_sync(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Storage, + create_numeric_builtin_test(), + ); + }); diff --git a/tests/tests/shader/struct_layout.rs b/tests/tests/shader/struct_layout.rs index 7da8cfeef..f17dceac0 100644 --- a/tests/tests/shader/struct_layout.rs +++ b/tests/tests/shader/struct_layout.rs @@ -1,10 +1,9 @@ use std::fmt::Write; -use wasm_bindgen_test::*; use wgpu::{Backends, DownlevelFlags, Features, Limits}; use crate::shader::{shader_input_output_test, InputStorageType, ShaderTest, MAX_BUFFER_SIZE}; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec { let input_values: Vec<_> = (0..(MAX_BUFFER_SIZE as u32 / 4)).collect(); @@ -175,46 +174,41 @@ fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec tests } -#[test] -#[wasm_bindgen_test] -fn uniform_input() { - initialize_test( +#[gpu_test] +static UNIFORM_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) // Validation errors thrown by the SPIR-V validator https://github.com/gfx-rs/naga/issues/2034 .expect_fail(FailureCase::backend(wgpu::Backends::VULKAN)) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Uniform, - create_struct_layout_tests(InputStorageType::Uniform), - ); - }, - ); -} + ) + .run_sync(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Uniform, + create_struct_layout_tests(InputStorageType::Uniform), + ); + }); -#[test] -#[wasm_bindgen_test] -fn storage_input() { - initialize_test( +#[gpu_test] +static STORAGE_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Storage, - create_struct_layout_tests(InputStorageType::Storage), - ); - }, - ); -} + ) + .run_sync(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Storage, + create_struct_layout_tests(InputStorageType::Storage), + ); + }); -#[test] -#[wasm_bindgen_test] -fn push_constant_input() { - initialize_test( +#[gpu_test] +static PUSH_CONSTANT_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(Features::PUSH_CONSTANTS) .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) @@ -223,12 +217,11 @@ fn push_constant_input() { ..Limits::downlevel_defaults() }) .expect_fail(FailureCase::backend(Backends::GL)), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::PushConstant, - create_struct_layout_tests(InputStorageType::PushConstant), - ); - }, - ); -} + ) + .run_sync(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::PushConstant, + create_struct_layout_tests(InputStorageType::PushConstant), + ); + }); diff --git a/tests/tests/shader/zero_init_workgroup_mem.rs b/tests/tests/shader/zero_init_workgroup_mem.rs index cbd1b3e56..e5a30c3b1 100644 --- a/tests/tests/shader/zero_init_workgroup_mem.rs +++ b/tests/tests/shader/zero_init_workgroup_mem.rs @@ -8,29 +8,152 @@ use wgpu::{ ShaderStages, }; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -fn zero_init_workgroup_mem() { - initialize_test( +#[gpu_test] +static ZERO_INIT_WORKGROUP_MEMORY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()) - // remove both of these once we get to https://github.com/gfx-rs/wgpu/issues/3193 or - // https://github.com/gfx-rs/wgpu/issues/3160 + // remove once we get to https://github.com/gfx-rs/wgpu/issues/3193 .skip(FailureCase { backends: Some(Backends::DX12), vendor: Some(5140), adapter: Some("Microsoft Basic Render Driver"), ..FailureCase::default() }) - .skip(FailureCase::backend_adapter( - Backends::VULKAN, - "swiftshader", - )), - zero_init_workgroup_mem_impl, - ); -} + .skip(FailureCase::backend_adapter(Backends::VULKAN, "llvmpipe")), + ) + .run_sync(|ctx| { + let bgl = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + }], + }); + + let output_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: Some("output buffer"), + size: BUFFER_SIZE, + usage: BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE, + mapped_at_creation: false, + }); + + let mapping_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: Some("mapping buffer"), + size: BUFFER_SIZE, + usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bg = ctx.device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(BufferBinding { + buffer: &output_buffer, + offset: 0, + size: Some(NonZeroU64::new(BUFFER_BINDING_SIZE as u64).unwrap()), + }), + }], + }); + + let pll = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + let sm = ctx + .device + .create_shader_module(include_wgsl!("zero_init_workgroup_mem.wgsl")); + + let pipeline_read = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("pipeline read"), + layout: Some(&pll), + module: &sm, + entry_point: "read", + }); + + let pipeline_write = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("pipeline write"), + layout: None, + module: &sm, + entry_point: "write", + }); + + // -- Initializing data -- + + let output_pre_init_data = vec![1; OUTPUT_ARRAY_SIZE as usize]; + ctx.queue.write_buffer( + &output_buffer, + 0, + bytemuck::cast_slice(&output_pre_init_data), + ); + + // -- Run test -- + + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + + let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default()); + + cpass.set_pipeline(&pipeline_write); + for _ in 0..NR_OF_DISPATCHES { + cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); + } + + cpass.set_pipeline(&pipeline_read); + for i in 0..NR_OF_DISPATCHES { + cpass.set_bind_group(0, &bg, &[i * BUFFER_BINDING_SIZE]); + cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); + } + drop(cpass); + + // -- Pulldown data -- + + encoder.copy_buffer_to_buffer(&output_buffer, 0, &mapping_buffer, 0, BUFFER_SIZE); + + ctx.queue.submit(Some(encoder.finish())); + + mapping_buffer.slice(..).map_async(MapMode::Read, |_| ()); + ctx.device.poll(Maintain::Wait); + + let mapped = mapping_buffer.slice(..).get_mapped_range(); + + let typed: &[u32] = bytemuck::cast_slice(&mapped); + + // -- Check results -- + + let num_disptaches_failed = typed.iter().filter(|&&res| res != 0).count(); + let ratio = (num_disptaches_failed as f32 / OUTPUT_ARRAY_SIZE as f32) * 100.; + + assert!( + num_disptaches_failed == 0, + "Zero-initialization of workgroup memory failed ({ratio:.0}% of disptaches failed)." + ); + + drop(mapped); + mapping_buffer.unmap(); + }); const DISPATCH_SIZE: (u32, u32, u32) = (64, 64, 64); const TOTAL_WORK_GROUPS: u32 = DISPATCH_SIZE.0 * DISPATCH_SIZE.1 * DISPATCH_SIZE.2; @@ -45,133 +168,3 @@ const NR_OF_DISPATCHES: u32 = const OUTPUT_ARRAY_SIZE: u32 = TOTAL_WORK_GROUPS * NR_OF_DISPATCHES; const BUFFER_SIZE: u64 = OUTPUT_ARRAY_SIZE as u64 * 4; const BUFFER_BINDING_SIZE: u32 = TOTAL_WORK_GROUPS * 4; - -fn zero_init_workgroup_mem_impl(ctx: TestingContext) { - let bgl = ctx - .device - .create_bind_group_layout(&BindGroupLayoutDescriptor { - label: None, - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: true, - min_binding_size: None, - }, - count: None, - }], - }); - - let output_buffer = ctx.device.create_buffer(&BufferDescriptor { - label: Some("output buffer"), - size: BUFFER_SIZE, - usage: BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE, - mapped_at_creation: false, - }); - - let mapping_buffer = ctx.device.create_buffer(&BufferDescriptor { - label: Some("mapping buffer"), - size: BUFFER_SIZE, - usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - - let bg = ctx.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &bgl, - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::Buffer(BufferBinding { - buffer: &output_buffer, - offset: 0, - size: Some(NonZeroU64::new(BUFFER_BINDING_SIZE as u64).unwrap()), - }), - }], - }); - - let pll = ctx - .device - .create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bgl], - push_constant_ranges: &[], - }); - - let sm = ctx - .device - .create_shader_module(include_wgsl!("zero_init_workgroup_mem.wgsl")); - - let pipeline_read = ctx - .device - .create_compute_pipeline(&ComputePipelineDescriptor { - label: Some("pipeline read"), - layout: Some(&pll), - module: &sm, - entry_point: "read", - }); - - let pipeline_write = ctx - .device - .create_compute_pipeline(&ComputePipelineDescriptor { - label: Some("pipeline write"), - layout: None, - module: &sm, - entry_point: "write", - }); - - // -- Initializing data -- - - let output_pre_init_data = vec![1; OUTPUT_ARRAY_SIZE as usize]; - ctx.queue.write_buffer( - &output_buffer, - 0, - bytemuck::cast_slice(&output_pre_init_data), - ); - - // -- Run test -- - - let mut encoder = ctx - .device - .create_command_encoder(&CommandEncoderDescriptor::default()); - - let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default()); - - cpass.set_pipeline(&pipeline_write); - for _ in 0..NR_OF_DISPATCHES { - cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); - } - - cpass.set_pipeline(&pipeline_read); - for i in 0..NR_OF_DISPATCHES { - cpass.set_bind_group(0, &bg, &[i * BUFFER_BINDING_SIZE]); - cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); - } - drop(cpass); - - // -- Pulldown data -- - - encoder.copy_buffer_to_buffer(&output_buffer, 0, &mapping_buffer, 0, BUFFER_SIZE); - - ctx.queue.submit(Some(encoder.finish())); - - mapping_buffer.slice(..).map_async(MapMode::Read, |_| ()); - ctx.device.poll(Maintain::Wait); - - let mapped = mapping_buffer.slice(..).get_mapped_range(); - - let typed: &[u32] = bytemuck::cast_slice(&mapped); - - // -- Check results -- - - let num_disptaches_failed = typed.iter().filter(|&&res| res != 0).count(); - let ratio = (num_disptaches_failed as f32 / OUTPUT_ARRAY_SIZE as f32) * 100.; - - assert!( - num_disptaches_failed == 0, - "Zero-initialization of workgroup memory failed ({ratio:.0}% of disptaches failed)." - ); - - drop(mapped); - mapping_buffer.unmap(); -} diff --git a/tests/tests/shader_primitive_index/mod.rs b/tests/tests/shader_primitive_index/mod.rs index 2739b2e77..e5157a7c9 100644 --- a/tests/tests/shader_primitive_index/mod.rs +++ b/tests/tests/shader_primitive_index/mod.rs @@ -1,7 +1,5 @@ -use wasm_bindgen_test::*; - use wgpu::util::DeviceExt; -use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; // // These tests render two triangles to a 2x2 render target. The first triangle @@ -37,55 +35,52 @@ use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; // draw_indexed() draws the triangles in the opposite order, using index // buffer [3, 4, 5, 0, 1, 2]. This also swaps the resulting pixel colors. // -#[test] -#[wasm_bindgen_test] -fn draw() { - // - // +-----+-----+ - // |white|blue | - // +-----+-----+ - // | red |white| - // +-----+-----+ - // - let expected = [ - 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, - ]; - initialize_test( - TestParameters::default() - .test_features_limits() - .features(wgpu::Features::SHADER_PRIMITIVE_INDEX), - |ctx| { - pulling_common(ctx, &expected, |rpass| { - rpass.draw(0..6, 0..1); - }) - }, - ); -} -#[test] -#[wasm_bindgen_test] -fn draw_indexed() { - // - // +-----+-----+ - // |white| red | - // +-----+-----+ - // |blue |white| - // +-----+-----+ - // - let expected = [ - 255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, - ]; - initialize_test( +#[gpu_test] +static DRAW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() .features(wgpu::Features::SHADER_PRIMITIVE_INDEX), - |ctx| { - pulling_common(ctx, &expected, |rpass| { - rpass.draw_indexed(0..6, 0, 0..1); - }) - }, - ); -} + ) + .run_sync(|ctx| { + // + // +-----+-----+ + // |white|blue | + // +-----+-----+ + // | red |white| + // +-----+-----+ + // + let expected = [ + 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, + ]; + pulling_common(ctx, &expected, |rpass| { + rpass.draw(0..6, 0..1); + }) + }); + +#[gpu_test] +static DRAW_INDEXED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::SHADER_PRIMITIVE_INDEX), + ) + .run_sync(|ctx| { + // + // +-----+-----+ + // |white| red | + // +-----+-----+ + // |blue |white| + // +-----+-----+ + // + let expected = [ + 255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, + ]; + pulling_common(ctx, &expected, |rpass| { + rpass.draw_indexed(0..6, 0, 0..1); + }) + }); fn pulling_common( ctx: TestingContext, @@ -169,7 +164,7 @@ fn pulling_common( }); let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let readback_buffer = image::ReadbackBuffers::new(&ctx.device, &color_texture); + let readback_buffer = wgpu_test::image::ReadbackBuffers::new(&ctx.device, &color_texture); let mut encoder = ctx .device diff --git a/tests/tests/shader_view_format/mod.rs b/tests/tests/shader_view_format/mod.rs index 46741b4ea..5e7bed9b0 100644 --- a/tests/tests/shader_view_format/mod.rs +++ b/tests/tests/shader_view_format/mod.rs @@ -1,18 +1,14 @@ use wgpu::{util::DeviceExt, DownlevelFlags, Limits, TextureFormat}; -use wgpu_test::{ - image::calc_difference, initialize_test, FailureCase, TestParameters, TestingContext, -}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; -#[test] -fn reinterpret_srgb_ness() { - let parameters = TestParameters::default() - .downlevel_flags(DownlevelFlags::VIEW_FORMATS) - .limits(Limits::downlevel_defaults()) - .skip(FailureCase { - backends: Some(wgpu::Backends::GL), - ..FailureCase::default() - }); - initialize_test(parameters, |ctx| { +#[gpu_test] +static REINTERPRET_SRGB: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(DownlevelFlags::VIEW_FORMATS) + .limits(Limits::downlevel_defaults()), + ) + .run_sync(|ctx| { let unorm_data: [[u8; 4]; 4] = [ [180, 0, 0, 255], [0, 84, 0, 127], @@ -58,7 +54,6 @@ fn reinterpret_srgb_ness() { &unorm_data, ); }); -} fn reinterpret( ctx: &TestingContext, @@ -193,10 +188,10 @@ fn reinterpret( let expect = expect_data[(h * size.width + w) as usize]; let tolerance = tolerance_data[(h * size.width + w) as usize]; let index = (w * 4 + offset) as usize; - if calc_difference(expect[0], data[index]) > tolerance[0] - || calc_difference(expect[1], data[index + 1]) > tolerance[1] - || calc_difference(expect[2], data[index + 2]) > tolerance[2] - || calc_difference(expect[3], data[index + 3]) > tolerance[3] + if expect[0].abs_diff(data[index]) > tolerance[0] + || expect[1].abs_diff(data[index + 1]) > tolerance[1] + || expect[2].abs_diff(data[index + 2]) > tolerance[2] + || expect[3].abs_diff(data[index + 3]) > tolerance[3] { panic!( "Reinterpret {:?} as {:?} mismatch! expect {:?} get [{}, {}, {}, {}]", diff --git a/tests/tests/texture_bounds.rs b/tests/tests/texture_bounds.rs index da6cc6b52..48b933109 100644 --- a/tests/tests/texture_bounds.rs +++ b/tests/tests/texture_bounds.rs @@ -1,33 +1,27 @@ //! Tests for texture copy bounds checks. -use wasm_bindgen_test::*; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn bad_copy_origin() { - fn try_origin(origin: wgpu::Origin3d, size: wgpu::Extent3d, should_panic: bool) { - let parameters = TestParameters::default(); +#[gpu_test] +static BAD_COPY_ORIGIN_TEST: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let try_origin = |origin, size, should_panic| { + let texture = ctx.device.create_texture(&TEXTURE_DESCRIPTOR); + let data = vec![255; BUFFER_SIZE as usize]; - initialize_test(parameters, |ctx| { - let texture = ctx.device.create_texture(&TEXTURE_DESCRIPTOR); - let data = vec![255; BUFFER_SIZE as usize]; - - fail_if(&ctx.device, should_panic, || { - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin, - aspect: wgpu::TextureAspect::All, - }, - &data, - BUFFER_COPY_LAYOUT, - size, - ) - }); + fail_if(&ctx.device, should_panic, || { + ctx.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin, + aspect: wgpu::TextureAspect::All, + }, + &data, + BUFFER_COPY_LAYOUT, + size, + ) }); - } + }; try_origin(wgpu::Origin3d { x: 0, y: 0, z: 0 }, TEXTURE_SIZE, false); try_origin(wgpu::Origin3d { x: 1, y: 0, z: 0 }, TEXTURE_SIZE, true); @@ -86,7 +80,7 @@ fn bad_copy_origin() { }, true, ); -} +}); const TEXTURE_SIZE: wgpu::Extent3d = wgpu::Extent3d { width: 64, diff --git a/tests/tests/transfer.rs b/tests/tests/transfer.rs index 8263b217b..f8bd43530 100644 --- a/tests/tests/transfer.rs +++ b/tests/tests/transfer.rs @@ -1,69 +1,65 @@ -use wgpu_test::{fail, initialize_test, TestParameters}; +use wgpu_test::{fail, gpu_test, GpuTestConfiguration}; -#[test] -fn copy_overflow_z() { - // A simple crash test exercising validation that used to happen a bit too - // late, letting an integer overflow slip through. - initialize_test(TestParameters::default(), |ctx| { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); +#[gpu_test] +static COPY_OVERFLOW_Z: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - let t1 = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width: 256, - height: 256, - depth_or_array_layers: 1, + let t1 = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Rgba8Uint, + usage: wgpu::TextureUsages::COPY_DST, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + let t2 = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Rgba8Uint, + usage: wgpu::TextureUsages::COPY_DST, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + + fail(&ctx.device, || { + // Validation should catch the silly selected z layer range without panicking. + encoder.copy_texture_to_texture( + wgpu::ImageCopyTexture { + texture: &t1, + mip_level: 1, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, }, - format: wgpu::TextureFormat::Rgba8Uint, - usage: wgpu::TextureUsages::COPY_DST, - mip_level_count: 1, - sample_count: 1, - view_formats: &[], - }); - let t2 = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width: 256, - height: 256, - depth_or_array_layers: 1, + wgpu::ImageCopyTexture { + texture: &t2, + mip_level: 1, + origin: wgpu::Origin3d { + x: 0, + y: 0, + z: 3824276442, + }, + aspect: wgpu::TextureAspect::All, }, - format: wgpu::TextureFormat::Rgba8Uint, - usage: wgpu::TextureUsages::COPY_DST, - mip_level_count: 1, - sample_count: 1, - view_formats: &[], - }); - - fail(&ctx.device, || { - // Validation should catch the silly selected z layer range without panicking. - encoder.copy_texture_to_texture( - wgpu::ImageCopyTexture { - texture: &t1, - mip_level: 1, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyTexture { - texture: &t2, - mip_level: 1, - origin: wgpu::Origin3d { - x: 0, - y: 0, - z: 3824276442, - }, - aspect: wgpu::TextureAspect::All, - }, - wgpu::Extent3d { - width: 100, - height: 3, - depth_or_array_layers: 613286111, - }, - ); - ctx.queue.submit(Some(encoder.finish())); - }); - }) -} + wgpu::Extent3d { + width: 100, + height: 3, + depth_or_array_layers: 613286111, + }, + ); + ctx.queue.submit(Some(encoder.finish())); + }); +}); diff --git a/tests/tests/vertex_indices/mod.rs b/tests/tests/vertex_indices/mod.rs index edd4f7b05..fbfb18179 100644 --- a/tests/tests/vertex_indices/mod.rs +++ b/tests/tests/vertex_indices/mod.rs @@ -1,9 +1,8 @@ use std::num::NonZeroU64; -use wasm_bindgen_test::*; use wgpu::util::DeviceExt; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; fn pulling_common( ctx: TestingContext, @@ -134,54 +133,48 @@ fn pulling_common( assert_eq!(data, expected); } -#[test] -#[wasm_bindgen_test] -fn draw() { - initialize_test(TestParameters::default().test_features_limits(), |ctx| { +#[gpu_test] +static DRAW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits()) + .run_sync(|ctx| { pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { cmb.draw(0..6, 0..1); }) - }) -} + }); -#[test] -#[wasm_bindgen_test] -fn draw_vertex_offset() { - initialize_test( +#[gpu_test] +static DRAW_VERTEX: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits()) + .run_sync(|ctx| { + pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { + cmb.draw(0..3, 0..1); + cmb.draw(3..6, 0..1); + }) + }); + +#[gpu_test] +static DRAW_INSTANCED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() .expect_fail(FailureCase::backend(wgpu::Backends::DX11)), - |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..3, 0..1); - cmb.draw(3..6, 0..1); - }) - }, ) -} - -#[test] -#[wasm_bindgen_test] -fn draw_instanced() { - initialize_test(TestParameters::default().test_features_limits(), |ctx| { + .run_sync(|ctx| { pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { cmb.draw(0..3, 0..2); }) - }) -} + }); -#[test] -#[wasm_bindgen_test] -fn draw_instanced_offset() { - initialize_test( +#[gpu_test] +static DRAW_INSTANCED_OFFSET: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() .expect_fail(FailureCase::backend(wgpu::Backends::DX11)), - |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..3, 0..1); - cmb.draw(0..3, 1..2); - }) - }, ) -} + .run_sync(|ctx| { + pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { + cmb.draw(0..3, 0..1); + cmb.draw(0..3, 1..2); + }) + }); diff --git a/tests/tests/write_texture.rs b/tests/tests/write_texture.rs index 8b33cae7f..9b6771c68 100644 --- a/tests/tests/write_texture.rs +++ b/tests/tests/write_texture.rs @@ -1,16 +1,13 @@ //! Tests for texture copy -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -use wasm_bindgen_test::*; +#[gpu_test] +static WRITE_TEXTURE_SUBSET_2D: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12))) + .run_sync(|ctx| { + let size = 256; -#[test] -#[wasm_bindgen_test] -fn write_texture_subset_2d() { - let size = 256; - let parameters = - TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12)); - initialize_test(parameters, |ctx| { let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, dimension: wgpu::TextureDimension::D2, @@ -98,15 +95,12 @@ fn write_texture_subset_2d() { assert_eq!(*byte, 0); } }); -} -#[test] -#[wasm_bindgen_test] -fn write_texture_subset_3d() { - let size = 256; - let depth = 4; - let parameters = TestParameters::default(); - initialize_test(parameters, |ctx| { +#[gpu_test] +static WRITE_TEXTURE_SUBSET_3D: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { + let size = 256; + let depth = 4; let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, dimension: wgpu::TextureDimension::D3, @@ -194,4 +188,3 @@ fn write_texture_subset_3d() { assert_eq!(*byte, 0); } }); -} diff --git a/tests/tests/zero_init_texture_after_discard.rs b/tests/tests/zero_init_texture_after_discard.rs index e47f1aa0f..584cfdb7b 100644 --- a/tests/tests/zero_init_texture_after_discard.rs +++ b/tests/tests/zero_init_texture_after_discard.rs @@ -1,58 +1,81 @@ -use wasm_bindgen_test::*; use wgpu::*; use wgpu_test::{ - image::ReadbackBuffers, initialize_test, FailureCase, TestParameters, TestingContext, + gpu_test, image::ReadbackBuffers, FailureCase, GpuTestConfiguration, TestParameters, + TestingContext, }; // Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in after submit of the encoder. -#[test] -#[wasm_bindgen_test] -fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after_submit() { - initialize_test( - TestParameters::default().skip(FailureCase::webgl2()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); - case.create_command_encoder(); - case.discard(); - case.submit_command_encoder(); +#[gpu_test] +static DISCARDING_COLOR_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_AFTER_SUBMIT: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_sync(|mut ctx| { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.submit_command_encoder(); - case.create_command_encoder(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); + case.create_command_encoder(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); - case.assert_buffers_are_zero(); - }, - ); -} + case.assert_buffers_are_zero(); + }); -// Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in the same encoder to a buffer. -#[test] -#[wasm_bindgen_test] -fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { - initialize_test( - TestParameters::default().skip(FailureCase::webgl2()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); - case.create_command_encoder(); - case.discard(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); +#[gpu_test] +static DISCARDING_COLOR_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_IN_SAME_ENCODER: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_sync(|mut ctx| { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); - case.assert_buffers_are_zero(); - }, - ); -} + case.assert_buffers_are_zero(); + }); -#[test] -#[wasm_bindgen_test] -fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { - initialize_test( +#[gpu_test] +static DISCARDING_DEPTH_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_IN_SAME_ENCODER: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags( DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, ) .limits(Limits::downlevel_defaults()), - |mut ctx| { + ) + .run_sync(|mut ctx| { + for format in [ + TextureFormat::Stencil8, + TextureFormat::Depth16Unorm, + TextureFormat::Depth24Plus, + TextureFormat::Depth24PlusStencil8, + TextureFormat::Depth32Float, + ] { + let mut case = TestCase::new(&mut ctx, format); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero(); + } + }); + +#[gpu_test] +static DISCARDING_EITHER_DEPTH_OR_STENCIL_ASPECT_TEST: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags( + DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES + | DownlevelFlags::COMPUTE_SHADERS, + ) + .limits(Limits::downlevel_defaults()), + ) + .run_sync(|mut ctx| { for format in [ TextureFormat::Stencil8, TextureFormat::Depth16Unorm, @@ -62,43 +85,20 @@ fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_sa ] { let mut case = TestCase::new(&mut ctx, format); case.create_command_encoder(); - case.discard(); + case.discard_depth(); + case.submit_command_encoder(); + + case.create_command_encoder(); + case.discard_stencil(); + case.submit_command_encoder(); + + case.create_command_encoder(); case.copy_texture_to_buffer(); case.submit_command_encoder(); case.assert_buffers_are_zero(); } - }, - ); -} - -#[test] -#[wasm_bindgen_test] -fn discarding_either_depth_or_stencil_aspect() { - initialize_test( - TestParameters::default() - .downlevel_flags( - DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, - ) - .limits(Limits::downlevel_defaults()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Depth24PlusStencil8); - case.create_command_encoder(); - case.discard_depth(); - case.submit_command_encoder(); - - case.create_command_encoder(); - case.discard_stencil(); - case.submit_command_encoder(); - - case.create_command_encoder(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); - - case.assert_buffers_are_zero(); - }, - ); -} + }); struct TestCase<'ctx> { ctx: &'ctx mut TestingContext, diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 38ee2ebd4..a75439096 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -80,7 +80,8 @@ impl Instance { impl crate::Instance for Instance { unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result { - //TODO: enable `METAL_DEVICE_WRAPPER_TYPE` environment based on the flags? + // We do not enable metal validation based on the validation flags as it affects the entire + // process. Instead, we enable the validation inside the test harness itself in tests/src/native.rs. Ok(Instance { managed_metal_layer_delegate: surface::HalManagedMetalLayerDelegate::new(), }) diff --git a/wgpu-info/src/report.rs b/wgpu-info/src/report.rs index 656e96ee8..3b50ebcf0 100644 --- a/wgpu-info/src/report.rs +++ b/wgpu-info/src/report.rs @@ -7,6 +7,9 @@ use wgpu::{ use crate::texture; +/// Report specifying the capabilities of the GPUs on the system. +/// +/// Must be synchronized with the definition on tests/src/report.rs. #[derive(Deserialize, Serialize)] pub struct GpuReport { pub devices: Vec, @@ -48,6 +51,9 @@ impl GpuReport { } } +/// A single report of the capabilities of an Adapter. +/// +/// Must be synchronized with the definition on tests/src/report.rs. #[derive(Deserialize, Serialize)] pub struct AdapterReport { pub info: AdapterInfo, diff --git a/wgpu-macros/Cargo.toml b/wgpu-macros/Cargo.toml new file mode 100644 index 000000000..b06df02cc --- /dev/null +++ b/wgpu-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wgpu-macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +description = "Macros for wgpu" +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +license.workspace = true +exclude = ["Cargo.lock"] +publish = false + +[lib] +proc-macro = true + +[dependencies] +heck = "0.4" +quote = "1" +syn = { version = "2", features = ["full"] } diff --git a/wgpu-macros/src/lib.rs b/wgpu-macros/src/lib.rs new file mode 100644 index 000000000..0b0812507 --- /dev/null +++ b/wgpu-macros/src/lib.rs @@ -0,0 +1,44 @@ +use heck::ToSnakeCase; +use proc_macro::TokenStream; +use quote::quote; +use syn::Ident; + +/// Creates a test that will run on all gpus on a given system. +/// +/// Apply this macro to a static variable with a type that can be converted to a `GpuTestConfiguration`. +#[proc_macro_attribute] +pub fn gpu_test(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input_static = syn::parse_macro_input!(item as syn::ItemStatic); + let expr = &input_static.expr; + let ident = &input_static.ident; + let ident_str = ident.to_string(); + let ident_lower = ident_str.to_snake_case(); + + let register_test_name = Ident::new(&format!("{}_initializer", ident_lower), ident.span()); + let test_name_webgl = Ident::new(&format!("{}_webgl", ident_lower), ident.span()); + + quote! { + #[cfg(not(target_arch = "wasm32"))] + #[::wgpu_test::ctor] + fn #register_test_name() { + struct S; + + ::wgpu_test::native::TEST_LIST.lock().push( + // Allow any type that can be converted to a GpuTestConfiguration + ::wgpu_test::GpuTestConfiguration::from(#expr).name_from_init_function_typename::(#ident_lower) + ) + } + + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen_test::wasm_bindgen_test] + async fn #test_name_webgl() { + struct S; + + // Allow any type that can be converted to a GpuTestConfiguration + let test_config = ::wgpu_test::GpuTestConfiguration::from(#expr).name_from_init_function_typename::(#ident_lower); + + ::wgpu_test::execute_test(test_config, None, 0).await; + } + } + .into() +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 211471586..343ed2b05 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -95,6 +95,8 @@ optional = true [dependencies] arrayvec.workspace = true +cfg-if.workspace = true +flume.workspace = true log.workspace = true parking_lot.workspace = true profiling.workspace = true @@ -102,7 +104,6 @@ raw-window-handle.workspace = true serde = { workspace = true, features = ["derive"], optional = true } smallvec.workspace = true static_assertions.workspace = true -cfg-if.workspace = true [dependencies.naga] workspace = true diff --git a/wgpu/src/util/belt.rs b/wgpu/src/util/belt.rs index 9800718e8..0c019fa23 100644 --- a/wgpu/src/util/belt.rs +++ b/wgpu/src/util/belt.rs @@ -3,7 +3,7 @@ use crate::{ BufferViewMut, CommandEncoder, Device, MapMode, }; use std::fmt; -use std::sync::{mpsc, Arc}; +use std::sync::Arc; struct Chunk { buffer: Arc, @@ -36,9 +36,9 @@ pub struct StagingBelt { /// into `active_chunks`. free_chunks: Vec, /// When closed chunks are mapped again, the map callback sends them here. - sender: mpsc::Sender, + sender: flume::Sender, /// Free chunks are received here to be put on `self.free_chunks`. - receiver: mpsc::Receiver, + receiver: flume::Receiver, } impl StagingBelt { @@ -53,7 +53,7 @@ impl StagingBelt { /// (per [`StagingBelt::finish()`]); and /// * bigger is better, within these bounds. pub fn new(chunk_size: BufferAddress) -> Self { - let (sender, receiver) = mpsc::channel(); + let (sender, receiver) = flume::unbounded(); StagingBelt { chunk_size, active_chunks: Vec::new(), diff --git a/xtask/Cargo.lock b/xtask/Cargo.lock index 0cddb7b56..fca587e0e 100644 --- a/xtask/Cargo.lock +++ b/xtask/Cargo.lock @@ -626,6 +626,21 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "xshell" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" + [[package]] name = "xtask" version = "0.1.0" @@ -635,4 +650,5 @@ dependencies = [ "env_logger", "log", "pico-args", + "xshell", ] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 343a2166a..44f61320c 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -12,5 +12,6 @@ cargo-run-wasm = { version = "0.3.2", git = "https://github.com/ErichDonGubler/c env_logger = "0.10.0" log = "0.4.18" pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } +xshell = "0.2.3" [workspace] diff --git a/xtask/src/cli.rs b/xtask/src/cli.rs index 924e429b4..26831f769 100644 --- a/xtask/src/cli.rs +++ b/xtask/src/cli.rs @@ -8,6 +8,8 @@ Usage: xtask Commands: run-wasm + test + --llvm-cov Run tests with LLVM code coverage using the llvm-cov tool Options: -h, --help Print help @@ -40,6 +42,7 @@ impl Args { pub(crate) enum Subcommand { RunWasm { args: Arguments }, + Test { args: Arguments }, } impl Subcommand { @@ -50,6 +53,7 @@ impl Subcommand { .context("no subcommand specified; see `--help` for more details")?; match &*subcmd { "run-wasm" => Ok(Self::RunWasm { args }), + "test" => Ok(Self::Test { args }), other => { bail!("unrecognized subcommand {other:?}; see `--help` for more details") } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 2a79a4e24..cad9aa541 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -5,6 +5,7 @@ use cli::{Args, Subcommand}; use pico_args::Arguments; mod cli; +mod test; fn main() -> ExitCode { env_logger::builder() @@ -29,7 +30,8 @@ fn run(args: Args) -> anyhow::Result<()> { match subcommand { Subcommand::RunWasm { mut args } => { // Use top-level Cargo.toml instead of xtask/Cargo.toml by default - let manifest_path = args.value_from_str("--manifest-path") + let manifest_path = args + .value_from_str("--manifest-path") .unwrap_or_else(|_| "../Cargo.toml".to_string()); let mut arg_vec = args.finish(); arg_vec.push("--manifest-path".into()); @@ -44,5 +46,6 @@ fn run(args: Args) -> anyhow::Result<()> { ); Ok(()) } + Subcommand::Test { args } => test::run_tests(args), } } diff --git a/xtask/src/test.rs b/xtask/src/test.rs new file mode 100644 index 000000000..6d003c7a9 --- /dev/null +++ b/xtask/src/test.rs @@ -0,0 +1,58 @@ +use anyhow::Context; +use pico_args::Arguments; + +pub fn run_tests(mut args: Arguments) -> anyhow::Result<()> { + let llvm_cov = args.contains("--llvm-cov"); + let llvm_cov_flags: &[_] = if llvm_cov { + &["llvm-cov", "--no-cfg-coverage", "--no-report"] + } else { + &[] + }; + let llvm_cov_nextest_flags: &[_] = if llvm_cov { + &["llvm-cov", "--no-cfg-coverage", "--no-report", "nextest"] + } else { + &["nextest", "run"] + }; + + let shell = xshell::Shell::new().context("Couldn't create xshell shell")?; + + shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/.."); + + log::info!("Generating .gpuconfig file based on gpus on the system"); + + xshell::cmd!( + shell, + "cargo {llvm_cov_flags...} run --bin wgpu-info -- --json -o .gpuconfig" + ) + .quiet() + .run() + .context("Failed to run wgpu-info to generate .gpuconfig")?; + + let gpu_count = shell + .read_file(".gpuconfig") + .unwrap() + .lines() + .filter(|line| line.contains("name")) + .count(); + + log::info!( + "Found {} gpu{}", + gpu_count, + if gpu_count == 1 { "" } else { "s" } + ); + + log::info!("Running cargo tests"); + + xshell::cmd!( + shell, + "cargo {llvm_cov_nextest_flags...} --no-fail-fast --retries 2" + ) + .args(args.finish()) + .quiet() + .run() + .context("Tests failed")?; + + log::info!("Finished tests"); + + Ok(()) +}