mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 20:53:53 +00:00
report shader processing errors in RenderPipelineCache
(#3289)
### Problem - shader processing errors are not displayed - during hot reloading when encountering a shader with errors, the whole app crashes ### Solution - log `error!`s for shader processing errors - when `cfg(debug_assertions)` is enabled (i.e. you're running in `debug` mode), parse shaders before passing them to wgpu. This lets us handle errors early.
This commit is contained in:
parent
6c479649bf
commit
081350916c
3 changed files with 83 additions and 10 deletions
|
@ -38,6 +38,7 @@ image = { version = "0.23.12", default-features = false }
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
wgpu = { version = "0.12.0", features = ["spirv"] }
|
wgpu = { version = "0.12.0", features = ["spirv"] }
|
||||||
|
codespan-reporting = "0.11.0"
|
||||||
naga = { version = "0.8.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] }
|
naga = { version = "0.8.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
render_resource::{
|
render_resource::{
|
||||||
AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError,
|
AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError,
|
||||||
RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline,
|
RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline,
|
||||||
RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor,
|
RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, ShaderReflectError,
|
||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
RenderWorld,
|
RenderWorld,
|
||||||
|
@ -10,11 +10,13 @@ use crate::{
|
||||||
use bevy_app::EventReader;
|
use bevy_app::EventReader;
|
||||||
use bevy_asset::{AssetEvent, Assets, Handle};
|
use bevy_asset::{AssetEvent, Assets, Handle};
|
||||||
use bevy_ecs::system::{Res, ResMut};
|
use bevy_ecs::system::{Res, ResMut};
|
||||||
use bevy_utils::{HashMap, HashSet};
|
use bevy_utils::{tracing::error, HashMap, HashSet};
|
||||||
use std::{collections::hash_map::Entry, hash::Hash, ops::Deref, sync::Arc};
|
use std::{collections::hash_map::Entry, hash::Hash, ops::Deref, sync::Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout};
|
use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout};
|
||||||
|
|
||||||
|
use super::ProcessedShader;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ShaderData {
|
pub struct ShaderData {
|
||||||
pipelines: HashSet<CachedPipelineId>,
|
pipelines: HashSet<CachedPipelineId>,
|
||||||
|
@ -52,7 +54,16 @@ impl ShaderCache {
|
||||||
.get(handle)
|
.get(handle)
|
||||||
.ok_or_else(|| RenderPipelineError::ShaderNotLoaded(handle.clone_weak()))?;
|
.ok_or_else(|| RenderPipelineError::ShaderNotLoaded(handle.clone_weak()))?;
|
||||||
let data = self.data.entry(handle.clone_weak()).or_default();
|
let data = self.data.entry(handle.clone_weak()).or_default();
|
||||||
if shader.imports().len() != data.resolved_imports.len() {
|
let n_asset_imports = shader
|
||||||
|
.imports()
|
||||||
|
.filter(|import| matches!(import, ShaderImport::AssetPath(_)))
|
||||||
|
.count();
|
||||||
|
let n_resolved_asset_imports = data
|
||||||
|
.resolved_imports
|
||||||
|
.keys()
|
||||||
|
.filter(|import| matches!(import, ShaderImport::AssetPath(_)))
|
||||||
|
.count();
|
||||||
|
if n_asset_imports != n_resolved_asset_imports {
|
||||||
return Err(RenderPipelineError::ShaderImportNotYetAvailable);
|
return Err(RenderPipelineError::ShaderImportNotYetAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +79,12 @@ impl ShaderCache {
|
||||||
&self.shaders,
|
&self.shaders,
|
||||||
&self.import_path_shaders,
|
&self.import_path_shaders,
|
||||||
)?;
|
)?;
|
||||||
let module_descriptor = processed.get_module_descriptor()?;
|
let module_descriptor = match processed.get_module_descriptor() {
|
||||||
|
Ok(module_descriptor) => module_descriptor,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(RenderPipelineError::AsModuleDescriptorError(err, processed));
|
||||||
|
}
|
||||||
|
};
|
||||||
entry.insert(Arc::new(
|
entry.insert(Arc::new(
|
||||||
render_device.create_shader_module(&module_descriptor),
|
render_device.create_shader_module(&module_descriptor),
|
||||||
))
|
))
|
||||||
|
@ -206,8 +222,8 @@ pub enum RenderPipelineError {
|
||||||
ShaderNotLoaded(Handle<Shader>),
|
ShaderNotLoaded(Handle<Shader>),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ProcessShaderError(#[from] ProcessShaderError),
|
ProcessShaderError(#[from] ProcessShaderError),
|
||||||
#[error(transparent)]
|
#[error("{0}")]
|
||||||
AsModuleDescriptorError(#[from] AsModuleDescriptorError),
|
AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader),
|
||||||
#[error("Shader import not yet available.")]
|
#[error("Shader import not yet available.")]
|
||||||
ShaderImportNotYetAvailable,
|
ShaderImportNotYetAvailable,
|
||||||
}
|
}
|
||||||
|
@ -274,9 +290,13 @@ impl RenderPipelineCache {
|
||||||
match err {
|
match err {
|
||||||
RenderPipelineError::ShaderNotLoaded(_)
|
RenderPipelineError::ShaderNotLoaded(_)
|
||||||
| RenderPipelineError::ShaderImportNotYetAvailable => { /* retry */ }
|
| RenderPipelineError::ShaderImportNotYetAvailable => { /* retry */ }
|
||||||
RenderPipelineError::ProcessShaderError(_)
|
|
||||||
| RenderPipelineError::AsModuleDescriptorError(_) => {
|
|
||||||
// shader could not be processed ... retrying won't help
|
// shader could not be processed ... retrying won't help
|
||||||
|
RenderPipelineError::ProcessShaderError(err) => {
|
||||||
|
error!("failed to process shader: {}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RenderPipelineError::AsModuleDescriptorError(err, source) => {
|
||||||
|
log_shader_error(source, err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,3 +406,47 @@ impl RenderPipelineCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_shader_error(source: &ProcessedShader, err: &AsModuleDescriptorError) {
|
||||||
|
use codespan_reporting::{
|
||||||
|
diagnostic::{Diagnostic, Label},
|
||||||
|
files::SimpleFile,
|
||||||
|
term,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let ProcessedShader::Wgsl(source) = source {
|
||||||
|
if let AsModuleDescriptorError::ShaderReflectError(err) = err {
|
||||||
|
match err {
|
||||||
|
ShaderReflectError::WgslParse(parse) => {
|
||||||
|
let msg = parse.emit_to_string(source);
|
||||||
|
error!("failed to process shader:\n{}", msg);
|
||||||
|
}
|
||||||
|
ShaderReflectError::Validation(error) => {
|
||||||
|
let files = SimpleFile::new("wgsl", &source);
|
||||||
|
let config = term::Config::default();
|
||||||
|
let mut writer = term::termcolor::Ansi::new(Vec::new());
|
||||||
|
|
||||||
|
let diagnostic = Diagnostic::error().with_labels(
|
||||||
|
error
|
||||||
|
.spans()
|
||||||
|
.map(|(span, desc)| {
|
||||||
|
Label::primary((), span.to_range().unwrap())
|
||||||
|
.with_message(desc.to_owned())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
term::emit(&mut writer, &config, &files, &diagnostic)
|
||||||
|
.expect("cannot write error");
|
||||||
|
|
||||||
|
let msg = writer.into_inner();
|
||||||
|
let msg = String::from_utf8_lossy(&msg);
|
||||||
|
|
||||||
|
error!("failed to process shader: \n{}", msg);
|
||||||
|
}
|
||||||
|
ShaderReflectError::GlslParse(_) => {}
|
||||||
|
ShaderReflectError::SpirVParse(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -156,7 +156,15 @@ impl ProcessedShader {
|
||||||
Ok(ShaderModuleDescriptor {
|
Ok(ShaderModuleDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
source: match self {
|
source: match self {
|
||||||
ProcessedShader::Wgsl(source) => ShaderSource::Wgsl(source.clone()),
|
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.
|
||||||
|
let _ = self.reflect()?;
|
||||||
|
|
||||||
|
ShaderSource::Wgsl(source.clone())
|
||||||
|
}
|
||||||
ProcessedShader::Glsl(_source, _stage) => {
|
ProcessedShader::Glsl(_source, _stage) => {
|
||||||
let reflection = self.reflect()?;
|
let reflection = self.reflect()?;
|
||||||
// TODO: it probably makes more sense to convert this to spirv, but as of writing
|
// TODO: it probably makes more sense to convert this to spirv, but as of writing
|
||||||
|
|
Loading…
Reference in a new issue