Shader imports within imports can fail depending on the order loaded
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
## Bevy version
`0.6.0` and `cb2ba19d`
## Operating system & version
WASM Firefox
## What you did
I believe there is a bug when loading shader imports within imports where the top shader can be processed before all the embedded shaders are ready. I haven't seen this when running via an OSX binary however when compiling to wasm this bug appears depending on how the assets load over the network.
## Additional information
https://github.com/dylanowen/art/blob/61f2c51451201beb4a51e4bf8e3a8ad34b3681dc/assets/shaders/phong.wgsl#L1 sometimes showcases the bug with this log
```
/pipeline_cache.rs:117 set-shader: Some(AssetPath("shaders/fractal.wgsl")) [Custom("bevy_pbr::mesh_view_bind_group"), Custom("bevy_pbr::mesh_struct"), AssetPath("shaders/sdf/mandelbulb.wgsl"), AssetPath("shaders/phong.wgsl"), AssetPath("shaders/sdf/lib.wgsl")] art.js:450:21
/pipeline_cache.rs:60 get-shader: Some(AssetPath("shaders/fractal.wgsl")) [Custom("bevy_pbr::mesh_view_bind_group"), Custom("bevy_pbr::mesh_struct"), AssetPath("shaders/sdf/mandelbulb.wgsl"), AssetPath("shaders/phong.wgsl"), AssetPath("shaders/sdf/lib.wgsl")] art.js:450:21
/pipeline_cache.rs:60 get-shader: Some(AssetPath("shaders/fractal.wgsl")) [Custom("bevy_pbr::mesh_view_bind_group"), Custom("bevy_pbr::mesh_struct"), AssetPath("shaders/sdf/mandelbulb.wgsl"), AssetPath("shaders/phong.wgsl"), AssetPath("shaders/sdf/lib.wgsl")] art.js:450:21
/pipeline_cache.rs:60 get-shader: Some(AssetPath("shaders/fractal.wgsl")) [Custom("bevy_pbr::mesh_view_bind_group"), Custom("bevy_pbr::mesh_struct"), AssetPath("shaders/sdf/mandelbulb.wgsl"), AssetPath("shaders/phong.wgsl"), AssetPath("shaders/sdf/lib.wgsl")] art.js:450:21
/pipeline_cache.rs:117 set-shader: Some(AssetPath("shaders/sdf/mandelbulb.wgsl")) [] art.js:450:21
/pipeline_cache.rs:117 set-shader: Some(AssetPath("shaders/phong.wgsl")) [AssetPath("shaders/bevy_utils.wgsl")] art.js:450:21
/pipeline_cache.rs:117 set-shader: Some(AssetPath("shaders/sdf/lib.wgsl")) [] art.js:450:21
/pipeline_cache.rs:60 get-shader: Some(AssetPath("shaders/fractal.wgsl")) [Custom("bevy_pbr::mesh_view_bind_group"), Custom("bevy_pbr::mesh_struct"), AssetPath("shaders/sdf/mandelbulb.wgsl"), AssetPath("shaders/phong.wgsl"), AssetPath("shaders/sdf/lib.wgsl")] art.js:450:21
/pipeline_cache.rs:301 failed to process shader: Unresolved import: AssetPath("shaders/bevy_utils.wgsl"). art.js:450:21
/pipeline_cache.rs:117 set-shader: Some(AssetPath("shaders/bevy_utils.wgsl")) [] art.js:450:21
```
I also created a test based on the order of these log statements, it's not perfect as creating the test data required making some fields public but it shows what events I would expect from `RenderPipelineCache`
`pipeline_cache.rs`
```rust
#[cfg(test)]
mod tests {
use crate::options::WgpuOptions;
use crate::render_resource::{
CachedPipelineId, Face, FrontFace, MultisampleState, PolygonMode, PrimitiveState,
PrimitiveTopology, RenderPipelineDescriptor, RenderPipelineError, ShaderImport,
VertexState,
};
use crate::{renderer, RenderPipelineCache, Shader};
use bevy_asset::Handle;
use wgpu::RequestAdapterOptions;
#[test]
fn process_import_in_import() {
#[rustfmt::skip]
const BAR: &str = r#"
fn inner_import() { }
"#;
const FOO: &str = r#"
#import "BAR"
fn import() { }
"#;
#[rustfmt::skip]
const INPUT: &str = r#"
#import "FOO"
fn in_main() { }
"#;
let mut options = WgpuOptions::default();
let instance = wgpu::Instance::new(options.backends.unwrap());
let (device, _) = futures_lite::future::block_on(renderer::initialize_renderer(
&instance,
&mut options,
&RequestAdapterOptions::default(),
));
let mut cache = RenderPipelineCache::new(device.clone());
let input_handle = Handle::::default();
let bar_handle = Handle::::default();
let foo_handle = Handle::::default();
let pipeline = CachedPipelineId(0);
cache.queue(RenderPipelineDescriptor {
vertex: VertexState {
shader: input_handle.clone(),
entry_point: "vertex".into(),
shader_defs: Vec::new(),
buffers: Vec::new(),
},
fragment: None,
layout: None,
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: PrimitiveTopology::PointList,
strip_index_format: None,
},
depth_stencil: None,
multisample: MultisampleState::default(),
label: None,
});
// input -dependsOn-> foo -dependsOn-> bar
let mut input_shader = Shader::from_wgsl(INPUT);
input_shader.import_path = Some(ShaderImport::AssetPath("INPUT".to_string()));
cache.set_shader(&input_handle.clone_weak(), &input_shader);
let input_result = cache
.shader_cache
.get(&device, pipeline, &input_handle, &[]);
assert!(matches!(
input_result,
Err(RenderPipelineError::ShaderImportNotYetAvailable)
));
// foo -dependsOn-> bar
let mut foo_shader = Shader::from_wgsl(FOO);
foo_shader.import_path = Some(ShaderImport::AssetPath("FOO".to_string()));
cache.set_shader(&foo_handle.clone_weak(), &foo_shader);
let input_result = cache
.shader_cache
.get(&device, pipeline, &input_handle, &[]);
// We're still waiting on `BAR` at this point so we should expect the ShaderImportNotYetAvailable error
assert!(
matches!(
input_result,
Err(RenderPipelineError::ShaderImportNotYetAvailable)
),
"{input_result:?} wasn't a ShaderImportNotYetAvailable Error"
);
let mut bar_shader = Shader::from_wgsl(BAR);
bar_shader.import_path = Some(ShaderImport::AssetPath("BAR".to_string()));
cache.set_shader(&bar_handle.clone_weak(), &bar_shader);
let input_result = cache
.shader_cache
.get(&device, pipeline, &input_handle, &[]);
assert!(matches!(input_result, Ok(_)), "{input_result:?} wasn't Ok");
}
}
```
Contributor guide
Assessment
This issue has not been assessed yet.