1538: Implement Integration and Example Tests r=kvark a=cwfitzgerald

**Connections**

Closes #1379! Closes #1465.

**Description**

This adds a testing framework for wgpu. This includes testing infrastructure for integration and example based reftests and other tests.

I have added env-based adapter and backend choices as a common utility in wgpu-rs. This is how the framework based examples choose their adapters, so WGPU_ADAPTER_NAME is how you can choose adapters based on a substring of the name.

I added explicit seeding to all examples that use random numbers so reftesting was deterministic. conservative-raster, mipmap, and texture-arrays (#1532) are broken and are marked as such. This testing framework will make CI fails if this is fixed, so tests don't fall out of date.

Note: as we get more integration tests, we can factor out more of the `pulling_common` to be common between all the tests. 

**Testing**

It is testing :)


Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
This commit is contained in:
bors[bot]
2021-06-24 04:43:25 +00:00
committed by GitHub
54 changed files with 1553 additions and 196 deletions

View File

@@ -133,7 +133,10 @@ jobs:
- if: matrix.clippy_params != ''
run: cargo clippy ${{ matrix.clippy_params }}
- if: matrix.channel == 'nightly'
run: cargo test -- --nocapture
run: cargo test -p wgpu-core -- --nocapture
# - if: matrix.channel == 'nightly'
# run: cargo run --bin wgpu-info -- cargo test -p wgpu -- --nocapture
docs:
runs-on: [ubuntu-latest]

4
.gitignore vendored
View File

@@ -15,3 +15,7 @@
# Output from capture example
wgpu/red.png
# Output from invalid comparison tests
**/*-actual.png
**/*-difference.png

47
Cargo.lock generated
View File

@@ -664,19 +664,6 @@ dependencies = [
"byteorder",
]
[[package]]
name = "generator"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061d3be1afec479d56fa3bd182bf966c7999ec175fcfdb87ac14d417241366c6"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"winapi 0.3.9",
]
[[package]]
name = "getrandom"
version = "0.1.16"
@@ -915,17 +902,6 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "loom"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed"
dependencies = [
"cfg-if 0.1.10",
"generator",
"scoped-tls",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -1557,12 +1533,6 @@ dependencies = [
"owned_ttf_parser",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "same-file"
version = "1.0.6"
@@ -1921,10 +1891,11 @@ checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
[[package]]
name = "wgpu"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"arrayvec",
"async-executor",
"bitflags",
"bytemuck",
"cgmath",
"console_error_panic_hook",
@@ -1954,7 +1925,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"arrayvec",
"bitflags",
@@ -1962,7 +1933,6 @@ dependencies = [
"copyless",
"fxhash",
"log",
"loom",
"naga",
"parking_lot",
"profiling",
@@ -1977,7 +1947,7 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "0.1.0"
version = "0.9.0"
dependencies = [
"arrayvec",
"ash",
@@ -2004,9 +1974,16 @@ dependencies = [
"winit",
]
[[package]]
name = "wgpu-info"
version = "0.9.0"
dependencies = [
"wgpu",
]
[[package]]
name = "wgpu-types"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"bitflags",
"serde",

View File

@@ -6,9 +6,10 @@ members = [
"wgpu",
"wgpu-core",
"wgpu-hal",
"wgpu-info",
"wgpu-types",
]
default-members = ["wgpu", "player", "wgpu-hal"]
default-members = ["wgpu", "player", "wgpu-hal", "wgpu-info"]
[patch."https://github.com/gfx-rs/naga"]
#naga = { path = "../naga" }

View File

@@ -14,6 +14,7 @@ The repository hosts the following parts:
- [![Crates.io](https://img.shields.io/crates/v/wgpu.svg?label=wgpu)](https://crates.io/crates/wgpu) [![docs.rs](https://docs.rs/wgpu/badge.svg)](https://docs.rs/wgpu/) - public Rust API for users
- [![Crates.io](https://img.shields.io/crates/v/wgpu-core.svg?label=wgpu-core)](https://crates.io/crates/wgpu-core) [![docs.rs](https://docs.rs/wgpu-core/badge.svg)](https://docs.rs/wgpu-core/) - internal Rust API for WebGPU implementations to use
- [![Crates.io](https://img.shields.io/crates/v/wgpu-hal.svg?label=wgpu-hal)](https://crates.io/crates/wgpu-hal) [![docs.rs](https://docs.rs/wgpu-hal/badge.svg)](https://docs.rs/wgpu-hal/) - internal unsafe GPU abstraction API
- [![Crates.io](https://img.shields.io/crates/v/wgpu-info.svg?label=wgpu-types)](https://crates.io/crates/wgpu-info) - Program that prints out information about all the adapters on the system or invokes a command for every adapter.
- [![Crates.io](https://img.shields.io/crates/v/wgpu-types.svg?label=wgpu-types)](https://crates.io/crates/wgpu-types) [![docs.rs](https://docs.rs/wgpu-types/badge.svg)](https://docs.rs/wgpu-types/) - Rust types shared between `wgpu-core` and `wgpu-rs`
- `player` - standalone application for replaying the API traces, uses `winit`
@@ -25,10 +26,38 @@ If you are looking for the native implementation or bindings to the API in other
API | Windows 7/10 | Linux & Android | macOS & iOS |
----- | ------------------ | ------------------ | ------------------ |
DX11 | | | |
DX12 | | | |
DX11 | :construction: | | |
DX12 | :construction: | | |
Vulkan | :white_check_mark: | :white_check_mark: | |
Metal | | | :white_check_mark: |
GLes3 | | | |
GLes3 | | :construction: | |
:white_check_mark: = Primary support — :ok: = Secondary support — :construction: = Unsupported, but support in progress
## Testing Infrastructure
wgpu features a set of unit, integration, and example based tests. All framework based examples are automatically reftested against the screenshot in the example directory. The `wgpu-info` example contains the logic which can automatically run the tests multiple times for all the adapters present on the system. These tests are also run on CI on windows and linux over Vulkan/DX12/DX11/GL on software adapters.
To run the test suite, run the following command:
```
cargo run --bin wgpu-info -- cargo test
```
To run any individual test on a specific adapter, populate the following environment variables:
- `WGPU_ADAPTER_NAME` with a substring of the name of the adapter you want to use (ex. "1080" will match "NVIDIA GeForce 1080ti").
- `WGPU_BACKEND` with the name of the backend you want to use (`vulkan`, `metal`, `dx12`, `dx11`, or `gl`).
Then to run an example's reftests, run:
```
cargo test --example <example-name>
```
Or run a part of the integration test suite:
```
cargo test -p wgpu -- <name-of-test>
```
If you are a user and want a way to help contribute to wgpu, we always need more help writing test cases.

View File

@@ -1,6 +1,6 @@
[package]
name = "wgpu-core"
version = "0.8.0"
version = "0.9.0"
authors = ["wgpu developers"]
edition = "2018"
description = "WebGPU core logic on gfx-hal"
@@ -42,12 +42,12 @@ features = ["wgsl-in"]
[dependencies.wgt]
path = "../wgpu-types"
package = "wgpu-types"
version = "0.8"
version = "0.9"
[dependencies.hal]
path = "../wgpu-hal"
package = "wgpu-hal"
version = "0.1"
version = "0.9"
[target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))'.dependencies]
hal = { path = "../wgpu-hal", package = "wgpu-hal", features = ["metal"] }
@@ -59,8 +59,5 @@ hal = { path = "../wgpu-hal", package = "wgpu-hal", features = ["vulkan"] }
[target.'cfg(all(not(target_arch = "wasm32"), windows))'.dependencies]
hal = { path = "../wgpu-hal", package = "wgpu-hal", features = ["vulkan"] }
[dev-dependencies]
loom = "0.3"
[build-dependencies]
cfg_aliases = "0.1"

View File

@@ -43,14 +43,9 @@ mod validation;
pub use hal::api;
#[cfg(test)]
use loom::sync::atomic;
#[cfg(not(test))]
use std::sync::atomic;
use atomic::{AtomicU64, AtomicUsize, Ordering};
use std::{borrow::Cow, os::raw::c_char, ptr};
use std::{borrow::Cow, os::raw::c_char, ptr, sync::atomic};
type SubmissionIndex = hal::FenceValue;
type Index = u32;
@@ -124,28 +119,6 @@ impl Drop for RefCount {
}
}
#[cfg(test)]
#[test]
fn loom() {
loom::model(move || {
let bx = Box::new(AtomicUsize::new(1));
let ref_count_main = ptr::NonNull::new(Box::into_raw(bx)).map(RefCount).unwrap();
let ref_count_spawned = ref_count_main.clone();
let join_handle = loom::thread::spawn(move || {
let _ = ref_count_spawned.clone();
ref_count_spawned.rich_drop_outer()
});
let dropped_in_main = ref_count_main.rich_drop_outer();
let dropped_in_spawned = join_handle.join().unwrap();
assert_ne!(
dropped_in_main, dropped_in_spawned,
"must drop exactly once"
);
});
}
/// Reference count object that tracks multiple references.
/// Unlike `RefCount`, it's manually inc()/dec() called.
#[derive(Debug)]

View File

@@ -1,6 +1,6 @@
[package]
name = "wgpu-hal"
version = "0.1.0"
version = "0.9.0"
authors = ["wgpu developers"]
edition = "2018"
description = "WebGPU hardware abstraction layer"

13
wgpu-info/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "wgpu-info"
version = "0.9.0"
authors = ["wgpu developers"]
edition = "2018"
description = "Adapter information and per-adapter test program"
homepage = "https://github.com/gfx-rs/wgpu"
repository = "https://github.com/gfx-rs/wgpu"
keywords = ["graphics"]
license = "MIT OR Apache-2.0"
[dependencies]
wgpu = { version = "0.9", path = "../wgpu" }

17
wgpu-info/README.md Normal file
View File

@@ -0,0 +1,17 @@
# wgpu-info
This is a command line utility that does two different functions.
#### Listing Adapters
When called with no arguments, wgpu-info will list all adapters visible to wgpu and all the information about them we have.
```
cargo run --bin wgpu-info
```
#### Running Test on many Adapters
When called with any amount of arguments it will interpret all of the arguments as a command to run. It will run this command N different times, one for every combination of adapter and backend on the system.
For every command invocation, it will set `WGPU_ADAPTER_NAME` to the name of the adapter name and `WGPU_BACKEND` to the name of the backend. This is used as the primary means of testing across many adapters.

153
wgpu-info/src/main.rs Normal file
View File

@@ -0,0 +1,153 @@
use std::{
mem::size_of,
process::{exit, Command},
time::Instant,
};
// Lets keep these on one line
#[rustfmt::skip]
fn print_info_from_adapter(adapter: &wgpu::Adapter, idx: usize) {
let info = adapter.get_info();
let downlevel = adapter.get_downlevel_properties();
let features = adapter.features();
let limits = adapter.limits();
println!("Adapter {}:", idx);
println!("\tBackend: {:?}", info.backend);
println!("\tName: {:?}", info.name);
println!("\tVendorID: {:?}", info.vendor);
println!("\tDeviceID: {:?}", info.device);
println!("\tType: {:?}", info.device_type);
println!("\tCompliant: {:?}", downlevel.is_webgpu_compliant());
println!("\tFeatures:");
for i in 0..(size_of::<wgpu::Features>() * 8) {
let bit = wgpu::Features::from_bits(1 << i as u64);
if let Some(bit) = bit {
if wgpu::Features::all().contains(bit) {
println!("\t\t{:<44} {}", format!("{:?}:", bit), features.contains(bit));
}
}
}
println!("\tLimits:");
let wgpu::Limits {
max_texture_dimension_1d,
max_texture_dimension_2d,
max_texture_dimension_3d,
max_texture_array_layers,
max_bind_groups,
max_dynamic_uniform_buffers_per_pipeline_layout,
max_dynamic_storage_buffers_per_pipeline_layout,
max_sampled_textures_per_shader_stage,
max_samplers_per_shader_stage,
max_storage_buffers_per_shader_stage,
max_storage_textures_per_shader_stage,
max_uniform_buffers_per_shader_stage,
max_uniform_buffer_binding_size,
max_storage_buffer_binding_size,
max_vertex_buffers,
max_vertex_attributes,
max_vertex_buffer_array_stride,
max_push_constant_size,
} = limits;
println!("\t\tMax Texture Dimension 1d: {}", max_texture_dimension_1d);
println!("\t\tMax Texture Dimension 2d: {}", max_texture_dimension_2d);
println!("\t\tMax Texture Dimension 3d: {}", max_texture_dimension_3d);
println!("\t\tMax Texture Array Layers: {}", max_texture_array_layers);
println!("\t\tMax Bind Groups: {}", max_bind_groups);
println!("\t\tMax Dynamic Uniform Buffers Per Pipeline Layout: {}", max_dynamic_uniform_buffers_per_pipeline_layout);
println!("\t\tMax Dynamic Storage Buffers Per Pipeline Layout: {}", max_dynamic_storage_buffers_per_pipeline_layout);
println!("\t\tMax Sampled Textures Per Shader Stage: {}", max_sampled_textures_per_shader_stage);
println!("\t\tMax Samplers Per Shader Stage: {}", max_samplers_per_shader_stage);
println!("\t\tMax Storage Buffers Per Shader Stage: {}", max_storage_buffers_per_shader_stage);
println!("\t\tMax Storage Textures Per Shader Stage: {}", max_storage_textures_per_shader_stage);
println!("\t\tMax Uniform Buffers Per Shader Stage: {}", max_uniform_buffers_per_shader_stage);
println!("\t\tMax Uniform Buffer Binding Size: {}", max_uniform_buffer_binding_size);
println!("\t\tMax Storage Buffer Binding Size: {}", max_storage_buffer_binding_size);
println!("\t\tMax Vertex Buffers: {}", max_vertex_buffers);
println!("\t\tMax Vertex Attributes: {}", max_vertex_attributes);
println!("\t\tMax Vertex Buffer Array Stride: {}", max_vertex_buffer_array_stride);
println!("\t\tMax Push Constant Size: {}", max_push_constant_size);
println!("\tDownlevel Properties:");
let wgpu::DownlevelCapabilities {
shader_model,
flags
} = downlevel;
println!("\t\tShader Model: {:?}", shader_model);
for i in 0..(size_of::<wgpu::DownlevelFlags>() * 8) {
let bit = wgpu::DownlevelFlags::from_bits(1 << i as u64);
if let Some(bit) = bit {
if wgpu::DownlevelFlags::all().contains(bit) {
println!("\t\t{:<36} {}", format!("{:?}:", bit), flags.contains(bit));
}
}
}
}
fn main() {
let args: Vec<_> = std::env::args().skip(1).collect();
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
let adapters: Vec<_> = instance
.enumerate_adapters(wgpu::BackendBit::all())
.collect();
let adapter_count = adapters.len();
if args.is_empty() {
for (idx, adapter) in adapters.into_iter().enumerate() {
print_info_from_adapter(&adapter, idx)
}
} else {
let all_start = Instant::now();
for (idx, adapter) in adapters.into_iter().enumerate() {
let adapter_start_time = Instant::now();
let idx = idx + 1;
let info = adapter.get_info();
println!(
"=========== TESTING {} on {:?} ({} of {}) ===========",
info.name, info.backend, idx, adapter_count
);
let exit_status = Command::new(&args[0])
.args(&args[1..])
.env("WGPU_ADAPTER_NAME", &info.name)
.env(
"WGPU_BACKEND",
match info.backend {
wgpu::Backend::Empty => unreachable!(),
wgpu::Backend::Vulkan => "vulkan",
wgpu::Backend::Metal => "metal",
wgpu::Backend::Dx12 => "dx12",
wgpu::Backend::Dx11 => "dx11",
wgpu::Backend::Gl => "gl",
wgpu::Backend::BrowserWebGpu => "webgpu",
},
)
.spawn()
.unwrap()
.wait()
.unwrap();
let adapter_time = adapter_start_time.elapsed().as_secs_f32();
if exit_status.success() {
println!(
"=========== PASSED! {} on {:?} ({} of {}) in {:.3}s ===========",
info.name, info.backend, idx, adapter_count, adapter_time
);
} else {
println!(
"=========== FAILED! {} on {:?} ({} of {}) in {:.3}s ===========",
info.name, info.backend, idx, adapter_count, adapter_time
);
exit(1);
}
}
let all_time = all_start.elapsed().as_secs_f32();
println!(
"=========== {} adapters PASSED in {:.3}s ===========",
adapter_count, all_time
);
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "wgpu-types"
version = "0.8.0"
version = "0.9.0"
authors = ["wgpu developers"]
edition = "2018"
description = "WebGPU types"

View File

@@ -602,11 +602,6 @@ bitflags::bitflags! {
///
/// This is a native only feature.
const SPIRV_SHADER_PASSTHROUGH = 0x0000_0400_0000_0000;
/// Features which are part of the upstream WebGPU standard.
const ALL_WEBGPU = 0x0000_0000_0000_FFFF;
/// Features that are only available when targeting native (not web).
const ALL_NATIVE = 0xFFFF_FFFF_FFFF_0000;
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "wgpu"
version = "0.8.0"
version = "0.9.0"
authors = ["wgpu developers"]
edition = "2018"
description = "Rusty WebGPU API wrapper"
@@ -9,12 +9,17 @@ repository = "https://github.com/gfx-rs/wgpu"
keywords = ["graphics"]
license = "MIT OR Apache-2.0"
exclude = ["etc/**/*", "examples/**/*", "tests/**/*", "Cargo.lock", "target/**/*"]
autotests = false
[package.metadata.docs.rs]
all-features = true
[lib]
[[test]]
name = "wgpu-tests"
path = "tests/root.rs"
[features]
default = []
spirv = ["naga/spv-in"]
@@ -50,6 +55,7 @@ serde = { version = "1", features = ["derive"], optional = true }
smallvec = "1"
[dev-dependencies]
bitflags = "1"
bytemuck = { version = "1.4", features = ["derive"] }
cgmath = "0.18"
ddsfile = "0.4"
@@ -82,13 +88,50 @@ git = "https://github.com/gfx-rs/naga"
tag = "gfx-26"
features = ["wgsl-in", "spv-out"]
[[example]]
name="boids"
test = true
[[example]]
name="bunnymark"
test = true
[[example]]
name="conservative-raster"
test = true
[[example]]
name="cube"
test = true
[[example]]
name="hello-compute"
test = true
[[example]]
name="mipmap"
test = true
[[example]]
name="msaa-line"
test = true
[[example]]
name="shadow"
test = true
[[example]]
name="skybox"
test = true
[[example]]
name="texture-arrays"
required-features = ["spirv"]
test = true
[[example]]
name="water"
test = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.73" # remember to change version in wiki as well

View File

@@ -9,7 +9,7 @@ Notably, `capture` example shows rendering without a surface/window. It reads ba
All the examples use [WGSL](https://gpuweb.github.io/gpuweb/wgsl.html) shaders unless specified otherwise.
All framework-based examples render to the window.
All framework-based examples render to the window and are reftested against the screenshot in the directory.
## Feature matrix
| Feature | boids | bunnymark | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | conservative-raster |

View File

@@ -1,7 +1,10 @@
// Flocking boids example with gpu compute update pass
// adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts
use rand::distributions::{Distribution, Uniform};
use rand::{
distributions::{Distribution, Uniform},
SeedableRng,
};
use std::{borrow::Cow, mem};
use wgpu::util::DeviceExt;
@@ -168,7 +171,7 @@ impl framework::Example for Example {
// buffer for all particles data of type [(posx,posy,velx,vely),...]
let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize];
let mut rng = rand::thread_rng();
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let unif = Uniform::new_inclusive(-1.0, 1.0);
for particle_instance_chunk in initial_particle_data.chunks_mut(4) {
particle_instance_chunk[0] = unif.sample(&mut rng); // posx
@@ -254,14 +257,14 @@ impl framework::Example for Example {
/// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
) {
// create render pass descriptor and its color attachments
let color_attachments = [wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
@@ -314,3 +317,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("boids");
}
#[test]
fn boids() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/boids/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 0,
max_outliers: 50,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -284,7 +284,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -323,7 +323,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(clear_color),
@@ -349,3 +349,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("bunnymark");
}
#[test]
fn bunnymark() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/bunnymark/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 1,
max_outliers: 50,
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -73,7 +73,7 @@ impl framework::Example for Example {
}
fn init(
sc_desc: &wgpu::SwapChainDescriptor,
adapter: &wgpu::Adapter,
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> Self {
@@ -133,7 +133,7 @@ impl framework::Example for Example {
multisample: wgpu::MultisampleState::default(),
});
let pipeline_lines = if adapter
let pipeline_lines = if device
.features()
.contains(wgpu::Features::NON_FILL_POLYGON_MODE)
{
@@ -253,7 +253,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -285,7 +285,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("full resolution"),
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
@@ -312,3 +312,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("conservative-raster");
}
#[test]
fn conservative_raster() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/conservative-raster/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default().failure(),
tollerance: 0,
max_outliers: 0,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -333,7 +333,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -344,7 +344,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
@@ -379,3 +379,29 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("cube");
}
#[test]
fn cube() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/cube/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 1,
max_outliers: 3,
});
}
#[test]
fn cube_lines() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/cube/screenshot-lines.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::NON_FILL_POLYGON_MODE,
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 2,
max_outliers: 400, // Line rasterization is very different between vendors
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -6,6 +6,10 @@ use winit::{
event_loop::{ControlFlow, EventLoop},
};
#[cfg(test)]
#[path = "../tests/common/mod.rs"]
pub mod test_common;
#[rustfmt::skip]
#[allow(unused)]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@@ -54,7 +58,7 @@ pub trait Example: 'static + Sized {
fn update(&mut self, event: WindowEvent);
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
spawner: &Spawner,
@@ -106,39 +110,15 @@ async fn setup<E: Example>(title: &str) -> Setup {
log::info!("Initializing the surface...");
let backend = if let Ok(backend) = std::env::var("WGPU_BACKEND") {
match backend.to_lowercase().as_str() {
"vulkan" => wgpu::BackendBit::VULKAN,
"metal" => wgpu::BackendBit::METAL,
"dx12" => wgpu::BackendBit::DX12,
"dx11" => wgpu::BackendBit::DX11,
"gl" => wgpu::BackendBit::GL,
"webgpu" => wgpu::BackendBit::BROWSER_WEBGPU,
other => panic!("Unknown backend: {}", other),
}
} else {
wgpu::BackendBit::PRIMARY
};
let power_preference = if let Ok(power_preference) = std::env::var("WGPU_POWER_PREF") {
match power_preference.to_lowercase().as_str() {
"low" => wgpu::PowerPreference::LowPower,
"high" => wgpu::PowerPreference::HighPerformance,
other => panic!("Unknown power preference: {}", other),
}
} else {
wgpu::PowerPreference::default()
};
let backend = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::BackendBit::PRIMARY);
let instance = wgpu::Instance::new(backend);
let (size, surface) = unsafe {
let size = window.inner_size();
let surface = instance.create_surface(&window);
(size, surface)
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface: Some(&surface),
})
let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, backend)
.await
.expect("No suitable GPU adapters found on the system!");
@@ -304,7 +284,7 @@ fn start<E: Example>(
}
};
example.render(&frame.output, &device, &queue, &spawner);
example.render(&frame.output.view, &device, &queue, &spawner);
}
_ => {}
}
@@ -385,6 +365,120 @@ pub fn run<E: Example>(title: &str) {
});
}
#[cfg(test)]
pub struct FrameworkRefTest {
pub image_path: &'static str,
pub width: u32,
pub height: u32,
pub optional_features: wgpu::Features,
pub base_test_parameters: test_common::TestParameters,
pub tollerance: u8,
pub max_outliers: usize,
}
#[cfg(test)]
#[allow(dead_code)]
pub fn test<E: Example>(mut params: FrameworkRefTest) {
use std::{mem, num::NonZeroU32};
assert_eq!(params.width % 64, 0, "width needs to be aligned 64");
let features = E::required_features() | params.optional_features;
let mut limits = E::required_limits();
if limits == wgpu::Limits::default() {
limits = test_common::lowest_reasonable_limits();
}
test_common::initialize_test(
mem::take(&mut params.base_test_parameters)
.features(features)
.limits(limits),
|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::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::COPY_SRC,
});
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::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
mapped_at_creation: false,
});
let mut example = E::init(
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
width: params.width,
height: params.height,
present_mode: wgpu::PresentMode::Fifo,
},
&ctx.adapter,
&ctx.device,
&ctx.queue,
);
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: NonZeroU32::new(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(..);
let _ = dst_buffer_slice.map_async(wgpu::MapMode::Read);
ctx.device.poll(wgpu::Maintain::Wait);
let bytes = dst_buffer_slice.get_mapped_range().to_vec();
test_common::image::compare_image_output(
env!("CARGO_MANIFEST_DIR").to_string() + params.image_path,
params.width,
params.height,
&bytes,
params.tollerance,
params.max_outliers,
);
},
);
}
// This allows treating the framework as a standalone example,
// thus avoiding listing the example names in `Cargo.toml`.
#[allow(dead_code)]

View File

@@ -54,6 +54,20 @@ async fn execute_gpu(numbers: &[u32]) -> Option<Vec<u32>> {
.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
}
async fn execute_gpu_inner(
device: &wgpu::Device,
queue: &wgpu::Queue,
numbers: &[u32],
) -> Option<Vec<u32>> {
// Loads the shader from WGSL
let cs_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: None,
@@ -182,52 +196,4 @@ fn main() {
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
#[test]
fn test_compute_1() {
let input = &[1, 2, 3, 4];
pollster::block_on(assert_execute_gpu(input, &[0, 1, 7, 2]));
}
#[test]
fn test_compute_2() {
let input = &[5, 23, 10, 9];
pollster::block_on(assert_execute_gpu(input, &[5, 15, 6, 19]));
}
#[test]
fn test_compute_overflow() {
let input = &[77031, 837799, 8400511, 63728127];
pollster::block_on(assert_execute_gpu(input, &[350, 524, OVERFLOW, OVERFLOW]));
}
#[test]
fn test_multithreaded_compute() {
use std::{sync::mpsc, thread, time::Duration};
let thread_count = 8;
let (tx, rx) = mpsc::channel();
for _ in 0..thread_count {
let tx = tx.clone();
thread::spawn(move || {
let input = &[100, 100, 100];
pollster::block_on(assert_execute_gpu(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.");
}
}
async fn assert_execute_gpu(input: &[u32], expected: &[u32]) {
if let Some(produced) = execute_gpu(input).await {
assert_eq!(produced, expected);
}
}
}
mod tests;

View File

@@ -0,0 +1,91 @@
#[path = "../../tests/common/mod.rs"]
mod common;
use std::sync::Arc;
use super::*;
use common::{initialize_test, TestParameters};
#[test]
fn test_compute_1() {
initialize_test(TestParameters::default(), |ctx| {
let input = &[1, 2, 3, 4];
pollster::block_on(assert_execute_gpu(
&ctx.device,
&ctx.queue,
input,
&[0, 1, 7, 2],
));
});
}
#[test]
fn test_compute_2() {
initialize_test(TestParameters::default(), |ctx| {
let input = &[5, 23, 10, 9];
pollster::block_on(assert_execute_gpu(
&ctx.device,
&ctx.queue,
input,
&[5, 15, 6, 19],
));
});
}
#[test]
fn test_compute_overflow() {
initialize_test(TestParameters::default(), |ctx| {
let input = &[77031, 837799, 8400511, 63728127];
pollster::block_on(assert_execute_gpu(
&ctx.device,
&ctx.queue,
input,
&[350, 524, OVERFLOW, OVERFLOW],
));
});
}
#[test]
fn test_multithreaded_compute() {
initialize_test(TestParameters::default(), |ctx| {
use std::{sync::mpsc, thread, time::Duration};
let ctx = Arc::new(ctx);
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();
});
}
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,
queue: &wgpu::Queue,
input: &[u32],
expected: &[u32],
) {
if let Some(produced) = execute_gpu_inner(device, queue, input).await {
assert_eq!(produced, expected);
}
}

View File

@@ -436,7 +436,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -453,7 +453,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(clear_color),
@@ -474,3 +474,17 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("mipmap");
}
#[test]
fn mipmap() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/mipmap/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default()
.backend_failures(wgpu::BackendBit::VULKAN),
tollerance: 25,
max_outliers: 3000, // Mipmap sampling is highly variant between impls. This is currently bounded by AMD on mac
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 542 KiB

View File

@@ -225,7 +225,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -254,14 +254,14 @@ impl framework::Example for Example {
};
let rpass_color_attachment = if self.sample_count == 1 {
wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops,
}
} else {
wgpu::RenderPassColorAttachment {
view: &self.multisampled_framebuffer,
resolve_target: Some(&frame.view),
resolve_target: Some(&view),
ops,
}
};
@@ -282,3 +282,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("msaa-line");
}
#[test]
fn msaa_line() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/msaa-line/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 64,
max_outliers: 1 << 16, // MSAA is comically different between vendors, 32k is a decent limit
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

@@ -688,7 +688,7 @@ impl framework::Example for Example {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -781,7 +781,7 @@ impl framework::Example for Example {
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
@@ -821,3 +821,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("shadow");
}
#[test]
fn shadow() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/shadow/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 2,
max_outliers: 5,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -389,7 +389,7 @@ impl framework::Example for Skybox {
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
spawner: &framework::Spawner,
@@ -415,7 +415,7 @@ impl framework::Example for Skybox {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
@@ -459,3 +459,56 @@ impl framework::Example for Skybox {
fn main() {
framework::run::<Skybox>("skybox");
}
#[test]
fn skybox() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default()
.backend_failures(wgpu::BackendBit::VULKAN),
tollerance: 2,
max_outliers: 3,
});
}
#[test]
fn skybox_bc1() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-bc1.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::TEXTURE_COMPRESSION_BC,
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 5,
max_outliers: 10,
});
}
#[test]
fn skybox_etc2() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-etc2.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::TEXTURE_COMPRESSION_ETC2,
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 5, // TODO
max_outliers: 10, // TODO
});
}
#[test]
fn skybox_astc() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-astc.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR,
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 5, // TODO
max_outliers: 10, // TODO
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 455 KiB

View File

@@ -73,6 +73,6 @@ fn fs_entity(in: EntityOutput) -> [[location(0)]] vec4<f32> {
let normal = normalize(in.normal);
let reflected = incident - 2.0 * dot(normal, incident) * normal;
let reflected_color = textureSample(r_texture, r_sampler, reflected);
return vec4<f32>(0.1, 0.1, 0.1, 0.1) + 0.5 * reflected_color;
let reflected_color = textureSample(r_texture, r_sampler, reflected).rgb;
return vec4<f32>(vec3<f32>(0.1) + 0.5 * reflected_color, 1.0);
}

View File

@@ -100,7 +100,11 @@ impl framework::Example for Example {
f if f.contains(wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING) => {
wgpu::include_spirv_raw!("non-uniform.frag.spv")
}
f if f.contains(wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING) => {
f if f.contains(
wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING
| wgpu::Features::PUSH_CONSTANTS,
) =>
{
uniform_workaround = true;
wgpu::include_spirv_raw!("uniform.frag.spv")
}
@@ -278,7 +282,7 @@ impl framework::Example for Example {
}
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -290,7 +294,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
@@ -322,3 +326,61 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("texture-arrays");
}
// This fails due to an issue with naga https://github.com/gfx-rs/wgpu/issues/1532
#[test]
fn texture_arrays_constant() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default().failure(),
tollerance: 0,
max_outliers: 0,
});
}
// This fails due to an issue with naga https://github.com/gfx-rs/wgpu/issues/1532
#[test]
fn texture_arrays_uniform() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING
| wgpu::Features::PUSH_CONSTANTS,
base_test_parameters: framework::test_common::TestParameters::default().failure(),
tollerance: 0,
max_outliers: 0,
});
}
// This fails due to an issue with naga https://github.com/gfx-rs/wgpu/issues/1532
#[test]
fn texture_arrays_non_uniform() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
base_test_parameters: framework::test_common::TestParameters::default().failure(),
tollerance: 0,
max_outliers: 0,
});
}
// This fails due to an issue with naga https://github.com/gfx-rs/wgpu/issues/1532
#[test]
fn texture_arrays_unsized_non_uniform() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING
| wgpu::Features::UNSIZED_BINDING_ARRAY,
base_test_parameters: framework::test_common::TestParameters::default().failure(),
tollerance: 0,
max_outliers: 0,
});
}

View File

@@ -5,6 +5,7 @@ mod point_gen;
use bytemuck::{Pod, Zeroable};
use cgmath::Point3;
use rand::SeedableRng;
use std::{borrow::Cow, iter, mem};
use wgpu::util::DeviceExt;
@@ -279,7 +280,7 @@ impl framework::Example for Example {
let terrain_noise = noise::OpenSimplex::new();
// Random colouration
let mut terrain_random = rand::thread_rng();
let mut terrain_random = rand::rngs::StdRng::seed_from_u64(42);
// Generate terrain. The closure determines what each hexagon will look like.
let terrain =
@@ -663,7 +664,7 @@ impl framework::Example for Example {
#[allow(clippy::eq_op)]
fn render(
&mut self,
frame: &wgpu::SwapChainTexture,
view: &wgpu::TextureView,
device: &wgpu::Device,
queue: &wgpu::Queue,
_spawner: &framework::Spawner,
@@ -734,7 +735,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(back_color),
@@ -761,7 +762,7 @@ impl framework::Example for Example {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &frame.view,
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
@@ -788,3 +789,16 @@ impl framework::Example for Example {
fn main() {
framework::run::<Example>("water");
}
#[test]
fn shadow() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/water/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default(),
tollerance: 5,
max_outliers: 10,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -990,11 +990,6 @@ impl crate::Context for Context {
if trace_dir.is_some() {
//Error: Tracing isn't supported on the Web target
}
assert!(
!desc.features.intersects(crate::Features::ALL_NATIVE),
"The web backend doesn't support any native extensions. Enabled native extensions: {:?}",
desc.features & crate::Features::ALL_NATIVE
);
// TODO: non-guaranteed limits
let mut mapped_desc = web_sys::GpuDeviceDescriptor::new();

View File

@@ -1524,6 +1524,11 @@ impl Adapter {
Context::adapter_get_info(&*self.context, &self.id)
}
/// Get info about the adapter itself.
pub fn get_downlevel_properties(&self) -> DownlevelCapabilities {
Context::adapter_downlevel_properties(&*self.context, &self.id)
}
/// Returns the features supported for a given texture format by this adapter.
///
/// Note that the WebGPU spec further restricts the available usages/features.

91
wgpu/src/util/init.rs Normal file
View File

@@ -0,0 +1,91 @@
use wgt::{BackendBit, PowerPreference, RequestAdapterOptions};
use crate::{Adapter, Instance};
/// Get a set of backend bits from the environment variable WGPU_BACKEND.
pub fn backend_bits_from_env() -> Option<BackendBit> {
Some(
match std::env::var("WGPU_BACKEND")
.as_deref()
.map(str::to_lowercase)
.as_deref()
{
Ok("vulkan") => BackendBit::VULKAN,
Ok("dx12") => BackendBit::DX12,
Ok("dx11") => BackendBit::DX11,
Ok("metal") => BackendBit::METAL,
Ok("gl") => BackendBit::GL,
Ok("webgpu") => BackendBit::BROWSER_WEBGPU,
_ => return None,
},
)
}
/// Get a power preference from the environment variable WGPU_POWER_PREF
pub fn power_preference_from_env() -> Option<PowerPreference> {
Some(
match std::env::var("WGPU_POWER_PREF")
.as_deref()
.map(str::to_lowercase)
.as_deref()
{
Ok("low") => PowerPreference::LowPower,
Ok("high") => PowerPreference::HighPerformance,
_ => return None,
},
)
}
/// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable.
#[cfg(not(target_arch = "wasm32"))]
pub fn initialize_adapter_from_env(
instance: &Instance,
backend_bits: BackendBit,
) -> Option<Adapter> {
let adapters = instance.enumerate_adapters(backend_bits);
let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
.as_deref()
.map(str::to_lowercase)
.ok()?;
let mut chosen_adapter = None;
for adapter in adapters {
let info = adapter.get_info();
if info.name.to_lowercase().contains(&desired_adapter_name) {
chosen_adapter = Some(adapter);
break;
}
}
Some(chosen_adapter.expect("WGPU_ADAPTER_NAME set but no matching adapter found!"))
}
/// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable.
#[cfg(target_arch = "wasm32")]
pub fn initialize_adapter_from_env(
_instance: &Instance,
_backend_bits: BackendBit,
) -> Option<Adapter> {
None
}
/// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable and if it doesn't exist fall back on a default adapter.
pub async fn initialize_adapter_from_env_or_default(
instance: &Instance,
backend_bits: wgt::BackendBit,
) -> Option<Adapter> {
match initialize_adapter_from_env(&instance, backend_bits) {
Some(a) => Some(a),
None => {
instance
.request_adapter(&RequestAdapterOptions {
power_preference: power_preference_from_env()
.unwrap_or_else(PowerPreference::default),
compatible_surface: None,
})
.await
}
}
}

View File

@@ -3,6 +3,7 @@
mod belt;
mod device;
mod encoder;
mod init;
use std::future::Future;
#[cfg(feature = "spirv")]
@@ -15,6 +16,10 @@ use std::{
pub use belt::StagingBelt;
pub use device::{BufferInitDescriptor, DeviceExt};
pub use encoder::RenderEncoder;
pub use init::{
backend_bits_from_env, initialize_adapter_from_env, initialize_adapter_from_env_or_default,
power_preference_from_env,
};
/// Treat the given byte slice as a SPIR-V module.
///

147
wgpu/tests/common/image.rs Normal file
View File

@@ -0,0 +1,147 @@
use std::{
ffi::{OsStr, OsString},
fs::File,
io::{BufWriter, Cursor},
path::Path,
str::FromStr,
};
fn read_png(path: impl AsRef<Path>, width: u32, height: u32) -> Option<Vec<u8>> {
let data = match std::fs::read(&path) {
Ok(f) => f,
Err(e) => {
log::warn!(
"image comparison invalid: file io error when comparing {}: {}",
path.as_ref().display(),
e
);
return None;
}
};
let decoder = png::Decoder::new(Cursor::new(data));
let (info, mut reader) = decoder.read_info().ok()?;
if info.width != width {
log::warn!("image comparison invalid: size mismatch");
return None;
}
if info.height != height {
log::warn!("image comparison invalid: size mismatch");
return None;
}
if info.color_type != png::ColorType::RGBA {
log::warn!("image comparison invalid: color type mismatch");
return None;
}
if info.bit_depth != png::BitDepth::Eight {
log::warn!("image comparison invalid: bit depth mismatch");
return None;
}
let mut buffer = vec![0; info.buffer_size()];
reader.next_frame(&mut buffer).ok()?;
Some(buffer)
}
fn write_png(
path: impl AsRef<Path>,
width: u32,
height: u32,
data: &[u8],
compression: png::Compression,
) {
let file = BufWriter::new(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();
writer.write_image_data(&data).unwrap();
}
fn calc_difference(lhs: u8, rhs: u8) -> u8 {
(lhs as i16 - rhs as i16).abs() as u8
}
pub fn compare_image_output(
path: impl AsRef<Path> + AsRef<OsStr>,
width: u32,
height: u32,
data: &[u8],
tollerance: u8,
max_outliers: usize,
) {
let comparison_data = read_png(&path, width, height);
if let Some(cmp) = comparison_data {
assert_eq!(cmp.len(), data.len());
let difference_data: Vec<_> = cmp
.chunks_exact(4)
.zip(data.chunks_exact(4))
.flat_map(|(cmp_chunk, data_chunk)| {
[
calc_difference(cmp_chunk[0], data_chunk[0]),
calc_difference(cmp_chunk[1], data_chunk[1]),
calc_difference(cmp_chunk[2], data_chunk[2]),
255,
]
})
.collect();
let outliers: usize = difference_data
.chunks_exact(4)
.map(|colors| {
(colors[0] > tollerance) as usize
+ (colors[1] > tollerance) as usize
+ (colors[2] > tollerance) as usize
})
.sum();
let max_difference = difference_data
.chunks_exact(4)
.map(|colors| colors[0].max(colors[1]).max(colors[2]))
.max()
.unwrap();
if outliers > max_outliers {
// Because the deta is mismatched, lets output the difference to a file.
let old_path = Path::new(&path);
let actual_path = Path::new(&path).with_file_name(
OsString::from_str(
&(old_path.file_stem().unwrap().to_string_lossy() + "-actual.png"),
)
.unwrap(),
);
let difference_path = Path::new(&path).with_file_name(
OsString::from_str(
&(old_path.file_stem().unwrap().to_string_lossy() + "-difference.png"),
)
.unwrap(),
);
write_png(&actual_path, width, height, &data, png::Compression::Fast);
write_png(
&difference_path,
width,
height,
&difference_data,
png::Compression::Fast,
);
panic!(
"Image data mismatch! Outlier count {} over limit {}. Max difference {}",
outliers, max_outliers, max_difference
)
} else {
println!(
"{} outliers over max difference {}",
outliers, max_difference
);
}
} else {
write_png(&path, width, height, data, png::Compression::Best);
}
}

280
wgpu/tests/common/mod.rs Normal file
View File

@@ -0,0 +1,280 @@
//! 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 wgt::{BackendBit, DeviceDescriptor, DownlevelCapabilities, Features, Limits};
use wgpu::{util, Adapter, Device, Instance, Queue};
pub mod image;
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 device: Device,
pub queue: Queue,
}
// A rather arbitrary set of limits which should be lower than all devices wgpu reasonably expects to run on and provides enough resources for most tests to run.
// Adjust as needed if they are too low/high.
pub fn lowest_reasonable_limits() -> Limits {
Limits {
max_texture_dimension_1d: 1024,
max_texture_dimension_2d: 1024,
max_texture_dimension_3d: 32,
max_texture_array_layers: 32,
max_bind_groups: 2,
max_dynamic_uniform_buffers_per_pipeline_layout: 2,
max_dynamic_storage_buffers_per_pipeline_layout: 2,
max_sampled_textures_per_shader_stage: 2,
max_samplers_per_shader_stage: 2,
max_storage_buffers_per_shader_stage: 2,
max_storage_textures_per_shader_stage: 2,
max_uniform_buffers_per_shader_stage: 2,
max_uniform_buffer_binding_size: 256,
max_storage_buffer_binding_size: 1 << 16,
max_vertex_buffers: 4,
max_vertex_attributes: 4,
max_vertex_buffer_array_stride: 32,
max_push_constant_size: 0,
}
}
fn lowest_downlevel_properties() -> DownlevelCapabilities {
DownlevelCapabilities {
flags: wgt::DownlevelFlags::empty(),
shader_model: wgt::ShaderModel::Sm2,
}
}
// This information determines if a test should run.
pub struct TestParameters {
pub required_features: Features,
pub required_limits: Limits,
pub required_downlevel_properties: DownlevelCapabilities,
// Backends where test should fail.
pub failures: Vec<(
Option<wgpu::BackendBit>,
Option<usize>,
Option<String>,
bool,
)>,
}
impl Default for TestParameters {
fn default() -> Self {
Self {
required_features: Features::empty(),
required_limits: lowest_reasonable_limits(),
required_downlevel_properties: lowest_downlevel_properties(),
failures: Vec::new(),
}
}
}
bitflags::bitflags! {
pub struct FailureReason: u8 {
const BACKEND = 0x1;
const VENDOR = 0x2;
const ADAPTER = 0x4;
const ALWAYS = 0x8;
}
}
// Builder pattern to make it easier
impl TestParameters {
/// Set of common features that most tests require.
pub fn test_features(self) -> Self {
self.features(Features::MAPPABLE_PRIMARY_BUFFERS | Features::VERTEX_WRITABLE_STORAGE)
}
/// Set the list of features this test requires.
pub fn features(mut self, features: Features) -> Self {
self.required_features |= features;
self
}
/// Set the list
pub fn limits(mut self, limits: Limits) -> Self {
self.required_limits = limits;
self
}
/// Mark the test as always failing, equivilant to specific_failure(None, None, None)
pub fn failure(mut self) -> Self {
self.failures.push((None, None, None, false));
self
}
/// Mark the test as always failing on a specific backend, equivilant to specific_failure(backend, None, None)
pub fn backend_failures(mut self, backends: wgpu::BackendBit) -> Self {
self.failures.push((Some(backends), None, None, false));
self
}
/// Determines if a test should fail under a particular set of conditions. If any of these are None, that means that it will match anything in that field.
///
/// ex.
/// `specific_failure(Some(wgpu::BackendBit::DX11 | wgpu::BackendBit::DX12), None, Some("RTX"), false)`
/// means that this test will fail on all cards with RTX in their name on either D3D backend, no matter the vendor ID.
///
/// If segfault is set to true, the test won't be run at all due to avoid segfaults.
pub fn specific_failure(
mut self,
backends: Option<BackendBit>,
vendor: Option<usize>,
device: Option<&'static str>,
segfault: bool,
) -> Self {
self.failures.push((
backends,
vendor,
device.as_ref().map(AsRef::as_ref).map(str::to_lowercase),
segfault,
));
self
}
}
pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(TestingContext)) {
// We don't actually care if it fails
let _ = env_logger::try_init();
let backend_bits = util::backend_bits_from_env().unwrap_or_else(BackendBit::all);
let instance = Instance::new(backend_bits);
let adapter = pollster::block_on(util::initialize_adapter_from_env_or_default(
&instance,
backend_bits,
))
.expect("could not find sutable adapter on the system");
let adapter_info = adapter.get_info();
let adapter_lowercase_name = adapter_info.name.to_lowercase();
let adapter_features = adapter.features();
let adapter_limits = adapter.limits();
let adapter_downlevel_properties = adapter.get_downlevel_properties();
let missing_features = parameters.required_features - adapter_features;
if !missing_features.is_empty() {
println!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features);
return;
}
if adapter_limits < parameters.required_limits {
println!("TEST SKIPPED: LIMIT TOO LOW");
return;
}
let missing_downlevel_flags =
parameters.required_downlevel_properties.flags - adapter_downlevel_properties.flags;
if !missing_downlevel_flags.is_empty() {
println!(
"TEST SKIPPED: MISSING DOWNLEVEL FLAGS {:?}",
missing_downlevel_flags
);
return;
}
if adapter_downlevel_properties.shader_model
< parameters.required_downlevel_properties.shader_model
{
println!(
"TEST SKIPPED: LOW SHADER MODEL {:?}",
adapter_downlevel_properties.shader_model
);
return;
}
let (device, queue) = pollster::block_on(initialize_device(
&adapter,
parameters.required_features,
parameters.required_limits,
));
let context = TestingContext {
adapter,
adapter_info: adapter_info.clone(),
device,
queue,
};
let failure_reason = parameters.failures.iter().find_map(
|(backend_failure, vendor_failure, adapter_failure, segfault)| {
let always =
backend_failure.is_none() && vendor_failure.is_none() && adapter_failure.is_none();
let expect_failure_backend = backend_failure
.map(|f| f.contains(wgpu::BackendBit::from(adapter_info.backend)))
.unwrap_or(true);
let expect_failure_vendor = vendor_failure
.map(|v| v == adapter_info.vendor)
.unwrap_or(true);
let expect_failure_adapter = adapter_failure
.as_deref()
.map(|f| adapter_lowercase_name.contains(f))
.unwrap_or(true);
if expect_failure_backend && expect_failure_vendor && expect_failure_adapter {
if always {
Some((FailureReason::ALWAYS, *segfault))
} else {
let mut reason = FailureReason::empty();
reason.set(FailureReason::BACKEND, expect_failure_backend);
reason.set(FailureReason::VENDOR, expect_failure_vendor);
reason.set(FailureReason::ADAPTER, expect_failure_adapter);
Some((reason, *segfault))
}
} else {
None
}
},
);
if let Some((reason, true)) = failure_reason {
println!(
"EXPECTED TEST FAILURE SKIPPED DUE TO SEGFAULT: {:?}",
reason
);
return;
}
let panicked = catch_unwind(AssertUnwindSafe(|| test_function(context))).is_err();
let expect_failure = failure_reason.is_some();
if panicked == expect_failure {
// We got the conditions we expected
if let Some((reason, _)) = failure_reason {
// Print out reason for the failure
println!("GOT EXPECTED TEST FAILURE: {:?}", reason);
}
} else if let Some(reason) = failure_reason {
// We expected to fail, but things passed
panic!("UNEXPECTED TEST PASS: {:?}", reason);
} else {
panic!("UNEXPECTED TEST FAILURE")
}
}

8
wgpu/tests/device.rs Normal file
View File

@@ -0,0 +1,8 @@
use crate::common::{initialize_test, TestParameters};
#[test]
fn device_initialization() {
initialize_test(TestParameters::default(), |_ctx| {
// intentionally empty
})
}

View File

@@ -1,6 +1,7 @@
use naga::{front::wgsl, valid::Validator};
use std::{fs, path::PathBuf};
/// Runs through all example shaders and ensures they are valid wgsl.
#[test]
fn parse_example_wgsl() {
let read_dir = match PathBuf::from(env!("CARGO_MANIFEST_DIR"))

28
wgpu/tests/instance.rs Normal file
View File

@@ -0,0 +1,28 @@
#[test]
fn initialize() {
let _ = wgpu::Instance::new(
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::BackendBit::all),
);
}
fn request_adapter_inner(power: wgt::PowerPreference) {
let instance = wgpu::Instance::new(
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::BackendBit::all),
);
let _adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: power,
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);
}

7
wgpu/tests/root.rs Normal file
View File

@@ -0,0 +1,7 @@
// All files containing tests
mod common;
mod device;
mod example_wgsl;
mod instance;
mod vertex_indices;

View File

@@ -0,0 +1,19 @@
[[block]]
struct Indices {
arr: array<u32>;
}; // this is used as both input and output for convenience
[[group(0), binding(0)]]
var<storage> indices: [[access(read_write)]] Indices;
[[stage(vertex)]]
fn vs_main([[builtin(instance_index)]] instance: u32, [[builtin(vertex_index)]] index: u32) -> [[builtin(position)]] vec4<f32> {
let idx = instance * 3u + index;
indices.arr[idx] = idx;
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
[[stage(fragment)]]
fn fs_main() -> [[location(0)]] vec4<f32> {
return vec4<f32>(0.0);
}

View File

@@ -0,0 +1,178 @@
use std::num::NonZeroU64;
use wgpu::util::DeviceExt;
use crate::common::{initialize_test, TestParameters, TestingContext};
fn pulling_common(
ctx: TestingContext,
expected: &[u32],
function: impl FnOnce(&mut wgpu::RenderPass<'_>),
) {
let shader = ctx
.device
.create_shader_module(&wgpu::include_wgsl!("draw.vert.wgsl"));
let bgl = ctx
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(4),
},
visibility: wgpu::ShaderStage::VERTEX,
count: None,
}],
});
let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 4 * expected.len() as u64,
usage: wgpu::BufferUsage::COPY_SRC
| wgpu::BufferUsage::STORAGE
| wgpu::BufferUsage::MAP_READ,
mapped_at_creation: false,
});
let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
let ppl = ctx
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bgl],
push_constant_ranges: &[],
});
let pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&ppl),
vertex: wgpu::VertexState {
buffers: &[],
entry_point: "vs_main",
module: &shader,
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
entry_point: "fs_main",
module: &shader,
targets: &[wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None,
write_mask: wgpu::ColorWrite::ALL,
}],
}),
});
let dummy = ctx
.device
.create_texture_with_data(
&ctx.queue,
&wgpu::TextureDescriptor {
label: Some("dummy"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::COPY_DST,
},
&[0, 0, 0, 1],
)
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachment {
ops: wgpu::Operations::default(),
resolve_target: None,
view: &dummy,
}],
depth_stencil_attachment: None,
label: None,
});
rpass.set_pipeline(&pipeline);
rpass.set_bind_group(0, &bg, &[]);
function(&mut rpass);
drop(rpass);
ctx.queue.submit(Some(encoder.finish()));
let slice = buffer.slice(..);
let _ = slice.map_async(wgpu::MapMode::Read);
ctx.device.poll(wgpu::Maintain::Wait);
let data: Vec<u32> = bytemuck::cast_slice(&*slice.get_mapped_range()).to_vec();
assert_eq!(data, expected);
}
#[test]
fn draw() {
initialize_test(TestParameters::default().test_features(), |ctx| {
pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| {
cmb.draw(0..6, 0..1);
})
})
}
#[test]
fn draw_vertex_offset() {
initialize_test(
TestParameters::default()
.test_features()
.backend_failures(wgpu::BackendBit::DX12 | wgpu::BackendBit::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]
fn draw_instanced() {
initialize_test(TestParameters::default().test_features(), |ctx| {
pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| {
cmb.draw(0..3, 0..2);
})
})
}
#[test]
fn draw_instanced_offset() {
initialize_test(
TestParameters::default()
.test_features()
.backend_failures(wgpu::BackendBit::DX12 | wgpu::BackendBit::DX11),
|ctx| {
pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| {
cmb.draw(0..3, 0..1);
cmb.draw(0..3, 1..2);
})
},
)
}