diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 484fb97302..559ff745fa 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -129,9 +129,24 @@ impl ShaderCache { return Err(PipelineCacheError::AsModuleDescriptorError(err, processed)); } }; - entry.insert(Arc::new( - render_device.create_shader_module(&module_descriptor), - )) + + render_device + .wgpu_device() + .push_error_scope(wgpu::ErrorFilter::Validation); + let shader_module = render_device.create_shader_module(&module_descriptor); + let error = render_device.wgpu_device().pop_error_scope(); + + // `now_or_never` will return Some if the future is ready and None otherwise. + // On native platforms, wgpu will yield the error immediatly while on wasm it may take longer since the browser APIs are asynchronous. + // So to keep the complexity of the ShaderCache low, we will only catch this error early on native platforms, + // and on wasm the error will be handled by wgpu and crash the application. + if let Some(Some(wgpu::Error::Validation { description, .. })) = + bevy_utils::futures::now_or_never(error) + { + return Err(PipelineCacheError::CreateShaderModule(description)); + } + + entry.insert(Arc::new(shader_module)) } }; @@ -479,6 +494,10 @@ impl PipelineCache { log_shader_error(source, err); continue; } + PipelineCacheError::CreateShaderModule(description) => { + error!("failed to create shader module: {}", description); + continue; + } } } } @@ -626,6 +645,8 @@ pub enum PipelineCacheError { AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), #[error("Shader import not yet available.")] ShaderImportNotYetAvailable, + #[error("Could not create shader module: {0}")] + CreateShaderModule(String), } struct ErrorSources<'a> { diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index b55ef0dab2..38b8843258 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -161,9 +161,9 @@ impl ProcessedShader { source: match self { ProcessedShader::Wgsl(source) => { #[cfg(debug_assertions)] - // This isn't neccessary, but catches errors early during hot reloading of invalid wgsl shaders. - // Eventually, wgpu will have features that will make this unneccessary like compilation info - // or error scopes, but until then parsing the shader twice during development the easiest solution. + // Parse and validate the shader early, so that (e.g. while hot reloading) we can + // display nicely formatted error messages instead of relying on just displaying the error string + // returned by wgpu upon creating the shader module. let _ = self.reflect()?; ShaderSource::Wgsl(source.clone()) diff --git a/crates/bevy_utils/src/futures.rs b/crates/bevy_utils/src/futures.rs new file mode 100644 index 0000000000..dc0f68d5d1 --- /dev/null +++ b/crates/bevy_utils/src/futures.rs @@ -0,0 +1,33 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +pub fn now_or_never(mut future: F) -> Option { + let noop_waker = noop_waker(); + let mut cx = Context::from_waker(&noop_waker); + + // Safety: `future` is not moved and the original value is shadowed + let future = unsafe { Pin::new_unchecked(&mut future) }; + + match future.poll(&mut cx) { + Poll::Ready(x) => Some(x), + _ => None, + } +} + +unsafe fn noop_clone(_data: *const ()) -> RawWaker { + noop_raw_waker() +} +unsafe fn noop(_data: *const ()) {} + +const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); + +fn noop_raw_waker() -> RawWaker { + RawWaker::new(std::ptr::null(), &NOOP_WAKER_VTABLE) +} + +fn noop_waker() -> Waker { + unsafe { Waker::from_raw(noop_raw_waker()) } +} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 4b2e459dca..f6b3f56ce4 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -2,6 +2,7 @@ pub mod prelude { pub use crate::default; } +pub mod futures; pub mod label; mod default;