Check for webgl2 validation errors and catch issues (#3296)

This commit is contained in:
Connor Fitzgerald 2022-12-20 15:51:17 -05:00 committed by GitHub
parent 5241633b3a
commit 6b6bc69ba0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 173 additions and 59 deletions

View File

@ -329,7 +329,10 @@ fn main() {
framework::run::<Example>("boids");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn boids() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/boids/screenshot.png",

View File

@ -357,7 +357,10 @@ fn main() {
framework::run::<Example>("bunnymark");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn bunnymark() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/bunnymark/screenshot.png",

View File

@ -225,7 +225,10 @@ mod tests {
use super::*;
use wgpu::BufferView;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn ensure_generated_data_matches_expected() {
assert_generated_data_matches_expected();
}

View File

@ -314,7 +314,10 @@ fn main() {
framework::run::<Example>("conservative-raster");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn conservative_raster() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/conservative-raster/screenshot.png",

View File

@ -406,7 +406,10 @@ fn main() {
framework::run::<Example>("cube");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn cube() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/cube/screenshot.png",
@ -420,6 +423,7 @@ fn cube() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn cube_lines() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/cube/screenshot-lines.png",

View File

@ -6,7 +6,10 @@ use std::sync::Arc;
use super::*;
use common::{initialize_test, TestParameters};
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn test_compute_1() {
initialize_test(
TestParameters::default()
@ -27,6 +30,7 @@ fn test_compute_1() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn test_compute_2() {
initialize_test(
TestParameters::default()
@ -47,6 +51,7 @@ fn test_compute_2() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn test_compute_overflow() {
initialize_test(
TestParameters::default()
@ -66,6 +71,7 @@ fn test_compute_overflow() {
}
#[test]
// Wasm doesn't support threads
fn test_multithreaded_compute() {
initialize_test(
TestParameters::default()

View File

@ -487,7 +487,10 @@ fn main() {
framework::run::<Example>("mipmap");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn mipmap() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/mipmap/screenshot.png",

View File

@ -310,7 +310,10 @@ fn main() {
framework::run::<Example>("msaa-line");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn msaa_line() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/msaa-line/screenshot.png",

View File

@ -841,7 +841,10 @@ fn main() {
framework::run::<Example>("shadow");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn shadow() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/shadow/screenshot.png",
@ -850,6 +853,7 @@ fn shadow() {
optional_features: wgpu::Features::default(),
base_test_parameters: framework::test_common::TestParameters::default()
.downlevel_flags(wgpu::DownlevelFlags::COMPARISON_SAMPLERS)
.specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false)
// rpi4 on VK doesn't work: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3916
.specific_failure(Some(wgpu::Backends::VULKAN), None, Some("V3D"), false)
// llvmpipe versions in CI are flaky: https://github.com/gfx-rs/wgpu/issues/2594

View File

@ -465,20 +465,29 @@ fn main() {
framework::run::<Skybox>("skybox");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_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(),
base_test_parameters: framework::test_common::TestParameters::default().specific_failure(
Some(wgpu::Backends::GL),
None,
Some("ANGLE"),
false,
),
tolerance: 3,
max_outliers: 207, // bounded by swiftshader
});
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn skybox_bc1() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-bc1.png",
@ -492,6 +501,7 @@ fn skybox_bc1() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn skybox_etc2() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-etc2.png",
@ -505,6 +515,7 @@ fn skybox_etc2() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn skybox_astc() {
framework::test::<Skybox>(framework::FrameworkRefTest {
image_path: "/examples/skybox/screenshot-astc.png",

View File

@ -406,7 +406,10 @@ fn main() {
framework::run::<Example>("texture-arrays");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn texture_arrays_uniform() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",
@ -420,6 +423,7 @@ fn texture_arrays_uniform() {
}
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn texture_arrays_non_uniform() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/texture-arrays/screenshot.png",

View File

@ -818,7 +818,10 @@ fn main() {
framework::run::<Example>("water");
}
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[wasm_bindgen_test::wasm_bindgen_test]
fn water() {
framework::test::<Example>(framework::FrameworkRefTest {
image_path: "/examples/water/screenshot.png",

View File

@ -2072,7 +2072,7 @@ impl crate::Context for Context {
fn queue_write_buffer(
&self,
queue: &Self::QueueId,
_queue_data: &Self::QueueData,
queue_data: &Self::QueueData,
buffer: &Self::BufferId,
_buffer_data: &Self::BufferData,
offset: wgt::BufferAddress,
@ -2083,46 +2083,54 @@ impl crate::Context for Context {
*queue => global.queue_write_buffer(*queue, *buffer, offset, data)
) {
Ok(()) => (),
Err(err) => self.handle_error_fatal(err, "Queue::write_buffer"),
Err(err) => {
self.handle_error_nolabel(&queue_data.error_sink, err, "Queue::write_buffer")
}
}
}
fn queue_validate_write_buffer(
&self,
queue: &Self::QueueId,
_queue_data: &Self::QueueData,
queue_data: &Self::QueueData,
buffer: &Self::BufferId,
_buffer_data: &Self::BufferData,
offset: wgt::BufferAddress,
size: wgt::BufferSize,
) {
) -> Option<()> {
let global = &self.0;
match wgc::gfx_select!(
*queue => global.queue_validate_write_buffer(*queue, *buffer, offset, size.get())
) {
Ok(()) => (),
Err(err) => self.handle_error_fatal(err, "Queue::write_buffer_with"),
Ok(()) => Some(()),
Err(err) => {
self.handle_error_nolabel(&queue_data.error_sink, err, "Queue::write_buffer_with");
None
}
}
}
fn queue_create_staging_buffer(
&self,
queue: &Self::QueueId,
_queue_data: &Self::QueueData,
queue_data: &Self::QueueData,
size: wgt::BufferSize,
) -> Box<dyn crate::context::QueueWriteBuffer> {
) -> Option<Box<dyn crate::context::QueueWriteBuffer>> {
let global = &self.0;
match wgc::gfx_select!(
*queue => global.queue_create_staging_buffer(*queue, size, ())
) {
Ok((buffer_id, ptr)) => Box::new(QueueWriteBuffer {
Ok((buffer_id, ptr)) => Some(Box::new(QueueWriteBuffer {
buffer_id,
mapping: BufferMappedRange {
ptr,
size: size.get() as usize,
},
}),
Err(err) => self.handle_error_fatal(err, "Queue::write_buffer_with"),
})),
Err(err) => {
self.handle_error_nolabel(&queue_data.error_sink, err, "Queue::write_buffer_with");
None
}
}
}

View File

@ -2250,27 +2250,33 @@ impl crate::context::Context for Context {
_buffer_data: &Self::BufferData,
offset: wgt::BufferAddress,
size: wgt::BufferSize,
) {
) -> Option<()> {
let usage = wgt::BufferUsages::from_bits_truncate(buffer.0.usage());
// TODO: actually send this down the error scope
if !usage.contains(wgt::BufferUsages::COPY_DST) {
panic!("Destination buffer is missing the `COPY_DST` usage flag");
log::error!("Destination buffer is missing the `COPY_DST` usage flag");
return None;
}
let write_size = u64::from(size);
if write_size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
panic!(
log::error!(
"Copy size {} does not respect `COPY_BUFFER_ALIGNMENT`",
size
);
return None;
}
if offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
panic!(
log::error!(
"Buffer offset {} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`",
offset
);
return None;
}
if write_size + offset > buffer.0.size() as u64 {
panic!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer.0.size());
log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer.0.size());
return None;
}
Some(())
}
fn queue_create_staging_buffer(
@ -2278,10 +2284,10 @@ impl crate::context::Context for Context {
_queue: &Self::QueueId,
_queue_data: &Self::QueueData,
size: wgt::BufferSize,
) -> Box<dyn QueueWriteBuffer> {
Box::new(WebQueueWriteBuffer(
) -> Option<Box<dyn QueueWriteBuffer>> {
Some(Box::new(WebQueueWriteBuffer(
vec![0; size.get() as usize].into_boxed_slice(),
))
)))
}
fn queue_write_staging_buffer(

View File

@ -538,13 +538,13 @@ pub trait Context: Debug + Send + Sized + Sync {
buffer_data: &Self::BufferData,
offset: wgt::BufferAddress,
size: wgt::BufferSize,
);
) -> Option<()>;
fn queue_create_staging_buffer(
&self,
queue: &Self::QueueId,
queue_data: &Self::QueueData,
size: BufferSize,
) -> Box<dyn QueueWriteBuffer>;
) -> Option<Box<dyn QueueWriteBuffer>>;
fn queue_write_staging_buffer(
&self,
queue: &Self::QueueId,
@ -1438,13 +1438,13 @@ pub(crate) trait DynContext: Debug + Send + Sync {
buffer_data: &crate::Data,
offset: wgt::BufferAddress,
size: wgt::BufferSize,
);
) -> Option<()>;
fn queue_create_staging_buffer(
&self,
queue: &ObjectId,
queue_data: &crate::Data,
size: BufferSize,
) -> Box<dyn QueueWriteBuffer>;
) -> Option<Box<dyn QueueWriteBuffer>>;
fn queue_write_staging_buffer(
&self,
queue: &ObjectId,
@ -2785,7 +2785,7 @@ where
buffer_data: &crate::Data,
offset: wgt::BufferAddress,
size: wgt::BufferSize,
) {
) -> Option<()> {
let queue = <T::QueueId>::from(*queue);
let queue_data = downcast_ref(queue_data);
let buffer = <T::BufferId>::from(*buffer);
@ -2806,7 +2806,7 @@ where
queue: &ObjectId,
queue_data: &crate::Data,
size: BufferSize,
) -> Box<dyn QueueWriteBuffer> {
) -> Option<Box<dyn QueueWriteBuffer>> {
let queue = <T::QueueId>::from(*queue);
let queue_data = downcast_ref(queue_data);
Context::queue_create_staging_buffer(self, &queue, queue_data, size)

View File

@ -59,7 +59,7 @@ static_assertions::assert_impl_all!(ErrorFilter: Send, Sync);
type C = dyn DynContext;
type Data = dyn Any + Send + Sync;
/// Context for all other wgpu objects. Instance of wgpu.
/// Context for all other wgpu objects. Instan ce of wgpu.
///
/// This is the first thing you create when using wgpu.
/// Its primary use is to create [`Adapter`]s and [`Surface`]s.
@ -3734,7 +3734,7 @@ impl Queue {
buffer: &'a Buffer,
offset: BufferAddress,
size: BufferSize,
) -> QueueWriteBufferView<'a> {
) -> Option<QueueWriteBufferView<'a>> {
DynContext::queue_validate_write_buffer(
&*self.context,
&self.id,
@ -3743,19 +3743,19 @@ impl Queue {
buffer.data.as_ref(),
offset,
size,
);
)?;
let staging_buffer = DynContext::queue_create_staging_buffer(
&*self.context,
&self.id,
self.data.as_ref(),
size,
);
QueueWriteBufferView {
)?;
Some(QueueWriteBufferView {
queue: self,
buffer,
offset,
inner: staging_buffer,
}
})
}
/// Schedule a write of some data into a texture.

View File

@ -1,21 +1,20 @@
//! Tests for buffer copy validation.
use wasm_bindgen_test::wasm_bindgen_test;
use wgt::BufferAddress;
use crate::common::{initialize_test, TestParameters};
use crate::common::{fail_if, initialize_test, TestParameters};
#[test]
#[wasm_bindgen_test]
fn copy_alignment() {
fn try_copy(offset: BufferAddress, size: BufferAddress, should_panic: bool) {
let mut parameters = TestParameters::default();
if should_panic {
parameters = parameters.failure();
}
initialize_test(parameters, |ctx| {
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];
ctx.queue.write_buffer(&buffer, offset, &data);
fail_if(&ctx.device, should_fail, || {
ctx.queue.write_buffer(&buffer, offset, &data)
});
});
}

View File

@ -307,9 +307,12 @@ fn clear_texture_tests(
}
#[test]
#[wasm_bindgen_test]
fn clear_texture_2d_uncompressed() {
initialize_test(
TestParameters::default().features(wgpu::Features::CLEAR_TEXTURE),
TestParameters::default()
.webgl2_failure()
.features(wgpu::Features::CLEAR_TEXTURE),
|ctx| {
clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED, true, true);
clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH, false, false);

View File

@ -1,7 +1,6 @@
use std::{
ffi::{OsStr, OsString},
fs::File,
io::{BufWriter, Cursor},
io,
path::Path,
str::FromStr,
};
@ -18,7 +17,7 @@ fn read_png(path: impl AsRef<Path>, width: u32, height: u32) -> Option<Vec<u8>>
return None;
}
};
let decoder = png::Decoder::new(Cursor::new(data));
let decoder = png::Decoder::new(io::Cursor::new(data));
let mut reader = decoder.read_info().ok()?;
let mut buffer = vec![0; reader.output_buffer_size()];
@ -43,6 +42,7 @@ fn read_png(path: impl AsRef<Path>, width: u32, height: u32) -> Option<Vec<u8>>
Some(buffer)
}
#[allow(unused_variables)]
fn write_png(
path: impl AsRef<Path>,
width: u32,
@ -50,15 +50,18 @@ fn write_png(
data: &[u8],
compression: png::Compression,
) {
let file = BufWriter::new(File::create(path).unwrap());
#[cfg(not(target_arch = "wasm32"))]
{
let file = 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();
writer.write_image_data(data).unwrap();
}
}
fn calc_difference(lhs: u8, rhs: u8) -> u8 {

View File

@ -27,7 +27,10 @@ pub struct OneTestPerProcessGuard(());
impl OneTestPerProcessGuard {
pub fn new() -> Self {
let other_tests_in_flight = TEST_ACTIVE_IN_PROCESS.swap(true, Ordering::SeqCst);
if other_tests_in_flight {
// We never abort if we're on wasm. Wasm tests are inherently single threaded, and panics cannot
// unwind the stack and trigger all the guards, so we don't actually need to check.
if other_tests_in_flight && !cfg!(target_arch = "wasm32") {
log::error!("{}", OTHER_TEST_IN_PROGRESS_ERROR);
// Hard exit to call attention to the error
std::process::abort();

View File

@ -145,6 +145,20 @@ impl TestParameters {
self
}
/// Mark the test as always failing on WebGL. Because limited ability of wasm to recover from errors, we need to wholesale
/// skip the test if it's not supported.
pub fn webgl2_failure(mut self) -> Self {
let _ = &mut self;
#[cfg(target_arch = "wasm32")]
self.failures.push(FailureCase {
backends: Some(wgpu::Backends::GL),
vendor: None,
adapter: None,
skip: true,
});
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.
@ -178,7 +192,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
let _test_guard = isolation::OneTestPerProcessGuard::new();
let (adapter, _) = initialize_adapter();
let (adapter, _surface_guard) = initialize_adapter();
let adapter_info = adapter.get_info();
let adapter_lowercase_name = adapter_info.name.to_lowercase();
@ -283,7 +297,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] {
let canary_set = hal::VALIDATION_CANARY.get_and_reset();
} else {
let canary_set = false;
let canary_set = _surface_guard.check_for_unreported_errors();
}
);
@ -319,10 +333,12 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
fn initialize_adapter() -> (Adapter, SurfaceGuard) {
let backend_bits = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all);
let instance = Instance::new(backend_bits);
let surface_guard;
let compatible_surface;
#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))]
{
surface_guard = SurfaceGuard {};
compatible_surface = None;
}
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
@ -334,6 +350,8 @@ fn initialize_adapter() -> (Adapter, SurfaceGuard) {
.create_surface_from_canvas(&canvas)
.expect("could not create surface from canvas");
surface_guard = SurfaceGuard { canvas };
compatible_surface = Some(surface);
}
@ -345,10 +363,34 @@ fn initialize_adapter() -> (Adapter, SurfaceGuard) {
))
.expect("could not find suitable adapter on the system");
(adapter, SurfaceGuard)
(adapter, surface_guard)
}
struct SurfaceGuard;
struct SurfaceGuard {
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
canvas: web_sys::HtmlCanvasElement,
}
impl SurfaceGuard {
fn check_for_unreported_errors(&self) -> bool {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "webgl"))] {
use wasm_bindgen::JsCast;
self.canvas
.get_context("webgl2")
.unwrap()
.unwrap()
.dyn_into::<web_sys::WebGl2RenderingContext>()
.unwrap()
.get_error()
!= web_sys::WebGl2RenderingContext::NO_ERROR
} else {
false
}
}
}
}
#[cfg(all(target_arch = "wasm32", feature = "webgl"))]
impl Drop for SurfaceGuard {

View File

@ -7,7 +7,7 @@ use wasm_bindgen_test::*;
#[test]
#[wasm_bindgen_test]
fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after_submit() {
initialize_test(TestParameters::default(), |ctx| {
initialize_test(TestParameters::default().webgl2_failure(), |ctx| {
let (texture, readback_buffer) =
create_white_texture_and_readback_buffer(&ctx, wgpu::TextureFormat::Rgba8UnormSrgb);
{
@ -43,7 +43,7 @@ fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after
#[test]
#[wasm_bindgen_test]
fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() {
initialize_test(TestParameters::default(), |ctx| {
initialize_test(TestParameters::default().webgl2_failure(), |ctx| {
let (texture, readback_buffer) =
create_white_texture_and_readback_buffer(&ctx, wgpu::TextureFormat::Rgba8UnormSrgb);
{