diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 235786bd0f..b8fa59cbc9 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -63,8 +63,8 @@ unsafe extern "system" fn output_debug_string_handler( }); if cfg!(debug_assertions) && level == log::Level::Error { - // Panicking behind FFI is UB, so we just exit. - std::process::exit(1); + // Set canary and continue + crate::VALIDATION_CANARY.set(); } excpt::EXCEPTION_CONTINUE_EXECUTION diff --git a/wgpu-hal/src/gles/egl.rs b/wgpu-hal/src/gles/egl.rs index a35d48980b..9539fe211e 100644 --- a/wgpu-hal/src/gles/egl.rs +++ b/wgpu-hal/src/gles/egl.rs @@ -274,7 +274,8 @@ fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, m }); if cfg!(debug_assertions) && log_severity == log::Level::Error { - std::process::exit(1); + // Set canary and continue + crate::VALIDATION_CANARY.set(); } } diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index feb7bf7773..d6bd281677 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -84,6 +84,7 @@ use std::{ num::{NonZeroU32, NonZeroU8}, ops::{Range, RangeInclusive}, ptr::NonNull, + sync::atomic::AtomicBool, }; use bitflags::bitflags; @@ -1148,6 +1149,39 @@ pub struct ComputePassDescriptor<'a> { pub label: Label<'a>, } +/// Stores if any API validation error has occurred in this process +/// since it was last reset. +/// +/// This is used for internal wgpu testing only and _must not_ be used +/// as a way to check for errors. +/// +/// This works as a static because `cargo nextest` runs all of our +/// tests in separate processes, so each test gets its own canary. +/// +/// This prevents the issue of one validation error terminating the +/// entire process. +pub static VALIDATION_CANARY: ValidationCanary = ValidationCanary { + inner: AtomicBool::new(false), +}; + +/// Flag for internal testing. +pub struct ValidationCanary { + inner: AtomicBool, +} + +impl ValidationCanary { + #[allow(dead_code)] // in some configurations this function is dead + fn set(&self) { + self.inner.store(true, std::sync::atomic::Ordering::SeqCst); + } + + /// Returns true if any API validation error has occurred in this process + /// since the last call to this function. + pub fn get_and_reset(&self) -> bool { + self.inner.swap(false, std::sync::atomic::Ordering::SeqCst) + } +} + #[test] fn test_default_limits() { let limits = wgt::Limits::default(); diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index f6f5195356..2370c62eee 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -112,8 +112,10 @@ unsafe extern "system" fn debug_utils_messenger_callback( }); } - // uncommenting this is useful for tests - //assert_ne!(level, log::Level::Error); + if cfg!(debug_assertions) && level == log::Level::Error { + // Set canary and continue + crate::VALIDATION_CANARY.set(); + } vk::FALSE } diff --git a/wgpu/examples/skybox/main.rs b/wgpu/examples/skybox/main.rs index 643fb04fd1..0343bd4313 100644 --- a/wgpu/examples/skybox/main.rs +++ b/wgpu/examples/skybox/main.rs @@ -494,7 +494,7 @@ fn skybox_bc1() { Some(wgpu::Backends::GL), None, Some("ANGLE"), - true, + false, ), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 tolerance: 5, max_outliers: 10, @@ -512,7 +512,7 @@ fn skybox_etc2() { Some(wgpu::Backends::GL), None, Some("ANGLE"), - true, + false, ), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 tolerance: 5, max_outliers: 105, // Bounded by llvmpipe @@ -530,7 +530,7 @@ fn skybox_astc() { Some(wgpu::Backends::GL), None, Some("ANGLE"), - true, + false, ), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 tolerance: 5, max_outliers: 300, // Bounded by rp4 on vk diff --git a/wgpu/tests/clear_texture.rs b/wgpu/tests/clear_texture.rs index 26f0a331c5..269c5da62c 100644 --- a/wgpu/tests/clear_texture.rs +++ b/wgpu/tests/clear_texture.rs @@ -315,7 +315,7 @@ fn clear_texture_2d_bc() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_BC) - .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), true), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { clear_texture_tests(&ctx, TEXTURE_FORMATS_BC, false, true); }, @@ -327,7 +327,7 @@ fn clear_texture_2d_astc() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) - .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), true), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC, false, true); }, @@ -339,7 +339,7 @@ fn clear_texture_2d_etc2() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ETC2) - .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), true), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2, false, true); }, diff --git a/wgpu/tests/common/mod.rs b/wgpu/tests/common/mod.rs index 0cf7a6a6f6..a98de71ac6 100644 --- a/wgpu/tests/common/mod.rs +++ b/wgpu/tests/common/mod.rs @@ -221,7 +221,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te queue, }; - let failure_reason = parameters.failures.iter().find_map(|failure| { + let expected_failure_reason = parameters.failures.iter().find_map(|failure| { let always = failure.backends.is_none() && failure.vendor.is_none() && failure.adapter.is_none(); @@ -261,25 +261,38 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te } }); - if let Some((reason, true)) = failure_reason { + if let Some((reason, true)) = expected_failure_reason { println!("EXPECTED TEST FAILURE SKIPPED: {:?}", reason); return; } let panicked = catch_unwind(AssertUnwindSafe(|| test_function(context))).is_err(); + let canary_set = hal::VALIDATION_CANARY.get_and_reset(); - let expect_failure = failure_reason.is_some(); + let failed = panicked || canary_set; - if panicked == expect_failure { + let failure_cause = match (panicked, canary_set) { + (true, true) => "PANIC AND VALIDATION ERROR", + (true, false) => "PANIC", + (false, true) => "VALIDATION ERROR", + (false, false) => "", + }; + + let expect_failure = expected_failure_reason.is_some(); + + if failed == expect_failure { // We got the conditions we expected - if let Some((reason, _)) = failure_reason { + if let Some((expected_reason, _)) = expected_failure_reason { // Print out reason for the failure - println!("GOT EXPECTED TEST FAILURE: {:?}", reason); + println!( + "GOT EXPECTED TEST FAILURE DUE TO {}: {:?}", + failure_cause, expected_reason + ); } - } else if let Some((reason, _)) = failure_reason { + } else if let Some((reason, _)) = expected_failure_reason { // We expected to fail, but things passed panic!("UNEXPECTED TEST PASS: {:?}", reason); } else { - panic!("UNEXPECTED TEST FAILURE") + panic!("UNEXPECTED TEST FAILURE DUE TO {}", failure_cause) } }