diff --git a/assets/shaders/custom_material.wesl b/assets/shaders/custom_material.wesl deleted file mode 100644 index ca946687845ab..0000000000000 --- a/assets/shaders/custom_material.wesl +++ /dev/null @@ -1,20 +0,0 @@ -import super::util::make_polka_dots; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(2) uv: vec2, -} - -struct CustomMaterial { - // Needed for 16-bit alignment on WebGL2 - time: vec4, -} - -@group(3) @binding(0) var material: CustomMaterial; - -@fragment -fn fragment( - mesh: VertexOutput, -) -> @location(0) vec4 { - return make_polka_dots(mesh.uv, material.time.x); -} diff --git a/assets/shaders/custom_material_import.wesl b/assets/shaders/custom_material_import.wesl new file mode 100644 index 0000000000000..1eefaa2e05a65 --- /dev/null +++ b/assets/shaders/custom_material_import.wesl @@ -0,0 +1,4 @@ +struct CustomMaterial { + // Needed for 16-bit alignment on WebGL2 + time: vec4, +} diff --git a/crates/bevy_shader/src/shader_cache.rs b/crates/bevy_shader/src/shader_cache.rs index c9f8c068bfb26..6aafab5a8be72 100644 --- a/crates/bevy_shader/src/shader_cache.rs +++ b/crates/bevy_shader/src/shader_cache.rs @@ -65,7 +65,7 @@ pub struct ShaderCache { &ValidateShader, ) -> Result, #[cfg(feature = "shader_format_wesl")] - asset_paths: HashMap>, + asset_paths: HashMap>, shaders: HashMap, Shader>, import_path_shaders: HashMap>, waiting_on_import: HashMap>>, @@ -210,7 +210,7 @@ impl ShaderCache { if let ShaderImport::AssetPath(path) = shader.import_path() { let shader_resolver = ShaderResolver::new(&self.asset_paths, &self.shaders); - let module_path = wesl::syntax::ModulePath::from_path(path); + let module_path = wesl_module_path_from_asset_path(path); let mut compiler_options = wesl::CompileOptions { imports: true, condcomp: true, @@ -361,7 +361,7 @@ impl ShaderCache { && let ShaderImport::AssetPath(path) = shader.import_path() { self.asset_paths - .insert(wesl::syntax::ModulePath::from_path(path), id); + .insert(wesl_module_path_from_asset_path(path), id); } self.shaders.insert(id, shader); pipelines_to_queue @@ -371,6 +371,14 @@ impl ShaderCache { let pipelines_to_queue = self.clear(id); if let Some(shader) = self.shaders.remove(&id) { self.import_path_shaders.remove(shader.import_path()); + + #[cfg(feature = "shader_format_wesl")] + if let Source::Wesl(_) = shader.source + && let ShaderImport::AssetPath(path) = shader.import_path() + { + self.asset_paths + .remove(&wesl_module_path_from_asset_path(path)); + } } pipelines_to_queue @@ -378,15 +386,15 @@ impl ShaderCache { } #[cfg(feature = "shader_format_wesl")] -pub struct ShaderResolver<'a> { - asset_paths: &'a HashMap>, +struct ShaderResolver<'a> { + asset_paths: &'a HashMap>, shaders: &'a HashMap, Shader>, } #[cfg(feature = "shader_format_wesl")] impl<'a> ShaderResolver<'a> { pub fn new( - asset_paths: &'a HashMap>, + asset_paths: &'a HashMap>, shaders: &'a HashMap, Shader>, ) -> Self { Self { @@ -400,7 +408,7 @@ impl<'a> ShaderResolver<'a> { impl<'a> wesl::Resolver for ShaderResolver<'a> { fn resolve_source( &self, - module_path: &wesl::syntax::ModulePath, + module_path: &wesl::ModulePath, ) -> Result, wesl::ResolveError> { let asset_id = self.asset_paths.get(module_path).ok_or_else(|| { wesl::ResolveError::ModuleNotFound(module_path.clone(), "Invalid asset id".to_string()) @@ -411,6 +419,27 @@ impl<'a> wesl::Resolver for ShaderResolver<'a> { } } +#[cfg(feature = "shader_format_wesl")] +fn wesl_module_path_from_asset_path(path: &String) -> wesl::ModulePath { + use bevy_asset::{io::AssetSourceId, AssetPath}; + use std::path::PathBuf; + + let asset_path = AssetPath::from(path); + if let AssetSourceId::Name(source) = asset_path.source() { + let mut comp = vec!["bevy_asset".to_string()]; + let mut path = PathBuf::new(); + path.push(source.as_ref()); + path.push(asset_path.path()); + comp.extend(wesl::ModulePath::from_path(path).components); + wesl::ModulePath { + origin: wesl::syntax::PathOrigin::Package, + components: comp, + } + } else { + wesl::ModulePath::from_path(asset_path.path()) + } +} + /// Type of error returned by a `PipelineCache` when the creation of a GPU pipeline object failed. #[cfg_attr( not(target_arch = "wasm32"), diff --git a/examples/shader/files/custom_material.wesl b/examples/shader/files/custom_material.wesl new file mode 100644 index 0000000000000..543f6378e029b --- /dev/null +++ b/examples/shader/files/custom_material.wesl @@ -0,0 +1,12 @@ +import super::util::make_polka_dots; +import bevy_asset::embedded::shader_material_wesl::files::vertex_attr; +import package::shaders::custom_material_import::CustomMaterial; + +@group(3) @binding(0) var material: CustomMaterial; + +@fragment +fn fragment( + mesh: vertex_attr::VertexOutput, +) -> @location(0) vec4 { + return make_polka_dots(mesh.uv, material.time.x); +} diff --git a/assets/shaders/util.wesl b/examples/shader/files/util.wesl similarity index 100% rename from assets/shaders/util.wesl rename to examples/shader/files/util.wesl diff --git a/examples/shader/files/vertex_attr.wesl b/examples/shader/files/vertex_attr.wesl new file mode 100644 index 0000000000000..8cffb8cbf595a --- /dev/null +++ b/examples/shader/files/vertex_attr.wesl @@ -0,0 +1,4 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(2) uv: vec2, +} diff --git a/examples/shader/shader_material_wesl.rs b/examples/shader/shader_material_wesl.rs index b13d8325401f5..5c5faf0a2c245 100644 --- a/examples/shader/shader_material_wesl.rs +++ b/examples/shader/shader_material_wesl.rs @@ -1,5 +1,6 @@ //! A shader that uses the WESL shading language. +use bevy::asset::embedded_asset; use bevy::{ mesh::MeshVertexBufferLayoutRef, pbr::{MaterialPipeline, MaterialPipelineKey}, @@ -12,7 +13,8 @@ use bevy::{ }; /// This example uses shader source files from the assets subdirectory -const FRAGMENT_SHADER_ASSET_PATH: &str = "shaders/custom_material.wesl"; +const FRAGMENT_SHADER_ASSET_PATH: &str = + "embedded://shader_material_wesl/files/custom_material.wesl"; fn main() { App::new() @@ -37,13 +39,36 @@ pub struct CustomMaterialPlugin; #[derive(Resource)] struct UtilityShader(Handle); +#[expect( + dead_code, + reason = "used to kept a strong handle, shader is referenced by the material" +)] +#[derive(Resource)] +struct VertexAttrShader(Handle); + +#[expect( + dead_code, + reason = "used to kept a strong handle, shader is referenced by the material" +)] +#[derive(Resource)] +struct CustomMaterialImportShader(Handle); + impl Plugin for CustomMaterialPlugin { fn build(&self, app: &mut App) { - let handle = app - .world_mut() - .resource_mut::() - .load::("shaders/util.wesl"); - app.insert_resource(UtilityShader(handle)); + embedded_asset!(app, "examples/shader", "files/custom_material.wesl"); + embedded_asset!(app, "examples/shader", "files/vertex_attr.wesl"); + embedded_asset!(app, "examples/shader", "files/util.wesl"); + + let asset_server = app.world_mut().resource_mut::(); + let utils_handle = + asset_server.load::("embedded://shader_material_wesl/files/util.wesl"); + let vertex_attr_handle = + asset_server.load::("embedded://shader_material_wesl/files/vertex_attr.wesl"); + let custom_material_import_handle = + asset_server.load::("shaders/custom_material_import.wesl"); + app.insert_resource(UtilityShader(utils_handle)) + .insert_resource(VertexAttrShader(vertex_attr_handle)) + .insert_resource(CustomMaterialImportShader(custom_material_import_handle)); } }