diff --git a/Cargo.toml b/Cargo.toml index 226d640bcdff7..58382027a3c53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1196,6 +1196,26 @@ description = "A shader that uses the GLSL shading language" category = "Shaders" wasm = true +[[example]] +name = "shader_material_override" +path = "examples/shader/shader_material_override.rs" + +[package.metadata.example.shader_material_override] +name = "Material with core function override" +description = "A shader that overrides a core pbr function for a material" +category = "Shaders" +wasm = true + +[[example]] +name = "pbr_global_override" +path = "examples/shader/pbr_global_override.rs" + +[package.metadata.example.pbr_global_override] +name = "core function default override" +description = "a global override for pbr functions for all materials" +category = "Shaders" +wasm = true + [[example]] name = "shader_instancing" path = "examples/shader/shader_instancing.rs" diff --git a/assets/shaders/animate_shader.wgsl b/assets/shaders/animate_shader.wgsl index 947c4a5420aff..c5816dc2092d5 100644 --- a/assets/shaders/animate_shader.wgsl +++ b/assets/shaders/animate_shader.wgsl @@ -1,10 +1,4 @@ -#import bevy_pbr::mesh_types -#import bevy_pbr::mesh_view_bindings - -@group(1) @binding(0) -var mesh: Mesh; - -// NOTE: Bindings must come before functions that use them! +#import bevy_pbr::mesh_bindings #import bevy_pbr::mesh_functions struct Vertex { @@ -21,7 +15,10 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip( + bevy_pbr::mesh_bindings::mesh.model, + vec4(vertex.position, 1.0) + ); out.uv = vertex.uv; return out; } @@ -30,7 +27,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { struct Time { time_since_startup: f32, }; -@group(2) @binding(0) +@group(1) @binding(0) var time: Time; diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index 91f4564e7fe27..c1305c6ac35a1 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -1,55 +1,46 @@ -#import bevy_pbr::mesh_view_bindings -#import bevy_pbr::mesh_bindings - -#import bevy_pbr::pbr_types -#import bevy_pbr::utils -#import bevy_pbr::clustered_forward -#import bevy_pbr::lighting -#import bevy_pbr::shadows +#import bevy_pbr::mesh_vertex_output #import bevy_pbr::pbr_functions +#import bevy_pbr::mesh_view_bindings @group(1) @binding(0) var my_array_texture: texture_2d_array; @group(1) @binding(1) var my_array_texture_sampler: sampler; -struct FragmentInput { - @builtin(front_facing) is_front: bool, - @builtin(position) frag_coord: vec4, - #import bevy_pbr::mesh_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { - let layer = i32(in.world_position.x) & 0x3; +fn fragment( + @builtin(front_facing) is_front: bool, + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, +) -> @location(0) vec4 { + let layer = i32(mesh.world_position.x) & 0x3; // Prepare a 'processed' StandardMaterial by sampling all textures to resolve // the material members - var pbr_input: PbrInput = pbr_input_new(); + var pbr_input: bevy_pbr::pbr_functions::PbrInput = bevy_pbr::pbr_functions::pbr_input_new(); - pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, in.uv, layer); + pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, mesh.uv, layer); #ifdef VERTEX_COLORS - pbr_input.material.base_color = pbr_input.material.base_color * in.color; + pbr_input.material.base_color = pbr_input.material.base_color * mesh.color; #endif - pbr_input.frag_coord = in.frag_coord; - pbr_input.world_position = in.world_position; - pbr_input.world_normal = in.world_normal; + pbr_input.frag_coord = mesh.clip_position; + pbr_input.world_position = mesh.world_position; + pbr_input.world_normal = mesh.world_normal; - pbr_input.is_orthographic = view.projection[3].w == 1.0; + pbr_input.is_orthographic = bevy_pbr::mesh_view_bindings::view.projection[3].w == 1.0; - pbr_input.N = prepare_normal( + pbr_input.N = bevy_pbr::pbr_functions::prepare_normal( pbr_input.material.flags, - in.world_normal, + mesh.world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP - in.world_tangent, + mesh.world_tangent, #endif #endif - in.uv, - in.is_front, + mesh.uv, + is_front, ); - pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); + pbr_input.V = bevy_pbr::pbr_functions::calculate_view(mesh.world_position, pbr_input.is_orthographic); - return tone_mapping(pbr(pbr_input)); + return bevy_pbr::pbr_functions::tone_mapping(bevy_pbr::pbr_functions::pbr(pbr_input)); } diff --git a/assets/shaders/cubemap_unlit.wgsl b/assets/shaders/cubemap_unlit.wgsl index 6837384dea3ac..a8bb5fda473a9 100644 --- a/assets/shaders/cubemap_unlit.wgsl +++ b/assets/shaders/cubemap_unlit.wgsl @@ -1,4 +1,4 @@ -#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_vertex_output #ifdef CUBEMAP_ARRAY @group(1) @binding(0) @@ -13,9 +13,9 @@ var base_color_sampler: sampler; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, ) -> @location(0) vec4 { - let fragment_position_view_lh = world_position.xyz * vec3(1.0, 1.0, -1.0); + let fragment_position_view_lh = mesh.world_position.xyz * vec3(1.0, 1.0, -1.0); return textureSample( base_color_texture, base_color_sampler, diff --git a/assets/shaders/custom_material.frag b/assets/shaders/custom_material.frag index bf46d1e5334fb..f4aeef2878b04 100644 --- a/assets/shaders/custom_material.frag +++ b/assets/shaders/custom_material.frag @@ -10,7 +10,9 @@ layout(set = 1, binding = 0) uniform CustomMaterial { layout(set = 1, binding = 1) uniform texture2D CustomMaterial_texture; layout(set = 1, binding = 2) uniform sampler CustomMaterial_sampler; +// wgsl modules can be imported and used in glsl +#import bevy_pbr::pbr_functions as PbrFuncs void main() { - o_Target = Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv); + o_Target = PbrFuncs::tone_mapping(Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv)); } diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl index 95b1b7d26a196..45aeba70af940 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output + struct CustomMaterial { color: vec4, }; @@ -11,7 +13,7 @@ var base_color_sampler: sampler; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, ) -> @location(0) vec4 { - return material.color * textureSample(base_color_texture, base_color_sampler, uv); + return material.color * textureSample(base_color_texture, base_color_sampler, mesh.uv); } diff --git a/assets/shaders/custom_material_chromatic_aberration.wgsl b/assets/shaders/custom_material_chromatic_aberration.wgsl index e8ccdcfb62513..a45718e938ef7 100644 --- a/assets/shaders/custom_material_chromatic_aberration.wgsl +++ b/assets/shaders/custom_material_chromatic_aberration.wgsl @@ -1,4 +1,5 @@ #import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_vertex_output @group(1) @binding(0) var texture: texture_2d; @@ -8,11 +9,11 @@ var our_sampler: sampler; @fragment fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput ) -> @location(0) vec4 { + let view = bevy_pbr::mesh_view_bindings::view; // Get screen position with coordinates from 0 to 1 - let uv = position.xy / vec2(view.width, view.height); + let uv = mesh.clip_position.xy / vec2(view.width, view.height); let offset_strength = 0.02; // Sample each color channel with an arbitrary shift diff --git a/assets/shaders/custom_material_override.wgsl b/assets/shaders/custom_material_override.wgsl new file mode 100644 index 0000000000000..df9e5de2db214 --- /dev/null +++ b/assets/shaders/custom_material_override.wgsl @@ -0,0 +1,35 @@ +#import bevy_pbr::lighting +#import bevy_pbr::mesh_view_types +#import bevy_pbr::mesh_vertex_output +#import bevy_pbr::fragment + +fn quantize_steps() -> f32 { + return 2.0; +} + +override fn bevy_pbr::lighting::point_light ( + world_position: vec3, + light: bevy_pbr::mesh_view_types::PointLight, + roughness: f32, + NdotV: f32, + N: vec3, + V: vec3, + R: vec3, + F0: vec3, + diffuseColor: vec3 +) -> vec3 { + // call original function + let original = + bevy_pbr::lighting::point_light(world_position, light, roughness, NdotV, N, V, R, F0, diffuseColor); + // quantize + let quantized = vec3(original * quantize_steps() + 0.5); + return clamp(vec3(quantized) / quantize_steps(), vec3(0.0), vec3(1.0)); +} + +@fragment +fn fragment( + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, + @builtin(front_facing) is_front: bool, +) -> @location(0) vec4 { + return bevy_pbr::fragment::fragment(mesh, is_front); +} diff --git a/assets/shaders/custom_material_screenspace_texture.wgsl b/assets/shaders/custom_material_screenspace_texture.wgsl index aad35920c093e..ddb79bd6f1c34 100644 --- a/assets/shaders/custom_material_screenspace_texture.wgsl +++ b/assets/shaders/custom_material_screenspace_texture.wgsl @@ -1,4 +1,5 @@ #import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_vertex_output @group(1) @binding(0) var texture: texture_2d; @@ -7,10 +8,10 @@ var texture_sampler: sampler; @fragment fn fragment( - @builtin(position) position: vec4, - #import bevy_pbr::mesh_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, ) -> @location(0) vec4 { - let uv = position.xy / vec2(view.width, view.height); + let view = bevy_pbr::mesh_view_bindings::view; + let uv = mesh.clip_position.xy / vec2(view.width, view.height); let color = textureSample(texture, texture_sampler, uv); return color; } diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index dd7cfc150308d..51ddfdb935c28 100644 --- a/assets/shaders/custom_vertex_attribute.wgsl +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -1,5 +1,5 @@ -#import bevy_pbr::mesh_view_bindings #import bevy_pbr::mesh_bindings +#import bevy_pbr::mesh_functions struct CustomMaterial { color: vec4, @@ -7,9 +7,6 @@ struct CustomMaterial { @group(1) @binding(0) var material: CustomMaterial; -// NOTE: Bindings must come before functions that use them! -#import bevy_pbr::mesh_functions - struct Vertex { @location(0) position: vec3, @location(1) blend_color: vec4, @@ -23,7 +20,10 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip( + bevy_pbr::mesh_bindings::mesh.model, + vec4(vertex.position, 1.0) + ); out.blend_color = vertex.blend_color; return out; } diff --git a/assets/shaders/global_light_override.wgsl b/assets/shaders/global_light_override.wgsl new file mode 100644 index 0000000000000..c25bc7ce718de --- /dev/null +++ b/assets/shaders/global_light_override.wgsl @@ -0,0 +1,43 @@ +#define_import_path quantize_lights + +#import bevy_pbr::lighting +#import bevy_pbr::mesh_view_types + +fn quantize_steps() -> f32 { + return 2.0; +} + +override fn bevy_pbr::lighting::point_light( + world_position: vec3, + light: bevy_pbr::mesh_view_types::PointLight, + roughness: f32, + NdotV: f32, + N: vec3, + V: vec3, + R: vec3, + F0: vec3, + diffuseColor: vec3 +) -> vec3 { + // call original function + let original = bevy_pbr::lighting::point_light(world_position, light, roughness, NdotV, N, V, R, F0, diffuseColor); + // quantize + let quantized = vec3(original * quantize_steps() + 0.5); + return clamp(vec3(quantized) / quantize_steps(), vec3(0.0), vec3(1.0)); +} + +override fn bevy_pbr::lighting::directional_light( + light: bevy_pbr::mesh_view_types::DirectionalLight, + roughness: f32, + NdotV: f32, + normal: vec3, + view: vec3, + R: vec3, + F0: vec3, + diffuseColor: vec3 +) -> vec3 { + // call original function + let original = bevy_pbr::lighting::directional_light(light, roughness, NdotV, normal, view, R, F0, diffuseColor); + // quantize + let quantized = vec3(original * quantize_steps() + 0.5); + return clamp(vec3(quantized) / quantize_steps(), vec3(0.0), vec3(1.0)); +} \ No newline at end of file diff --git a/assets/shaders/instancing.wgsl b/assets/shaders/instancing.wgsl index 7cb00b039a84a..068a17f9e32a6 100644 --- a/assets/shaders/instancing.wgsl +++ b/assets/shaders/instancing.wgsl @@ -1,11 +1,5 @@ -#import bevy_pbr::mesh_types -#import bevy_pbr::mesh_view_bindings - -@group(1) @binding(0) -var mesh: Mesh; - -// NOTE: Bindings must come before functions that use them! #import bevy_pbr::mesh_functions +#import bevy_pbr::mesh_bindings struct Vertex { @location(0) position: vec3, @@ -25,7 +19,10 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; var out: VertexOutput; - out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(position, 1.0)); + out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip( + bevy_pbr::mesh_bindings::mesh.model, + vec4(position, 1.0) + ); out.color = vertex.i_color; return out; } diff --git a/assets/shaders/line_material.wgsl b/assets/shaders/line_material.wgsl index e47ffe6e16acb..bed81498abecc 100644 --- a/assets/shaders/line_material.wgsl +++ b/assets/shaders/line_material.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output + struct LineMaterial { color: vec4, }; @@ -7,7 +9,7 @@ var material: LineMaterial; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, ) -> @location(0) vec4 { return material.color; } diff --git a/assets/shaders/shader_defs.wgsl b/assets/shaders/shader_defs.wgsl index 0efa91231a622..a0caaa22c6dda 100644 --- a/assets/shaders/shader_defs.wgsl +++ b/assets/shaders/shader_defs.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output + struct CustomMaterial { color: vec4, }; @@ -7,7 +9,7 @@ var material: CustomMaterial; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, ) -> @location(0) vec4 { #ifdef IS_RED return vec4(1.0, 0.0, 0.0, 1.0); diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 154e20907954f..69f3f15beea43 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -417,6 +417,31 @@ macro_rules! load_internal_asset { }}; } +/// Loads an internal asset with its path. +/// +/// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded +/// using the conventional API. See `DebugAssetServerPlugin`. +#[macro_export] +macro_rules! load_internal_asset_with_path { + ($app: ident, $handle: ident, $path_str: expr, $loader: expr) => {{ + let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); + assets.set_untracked( + $handle, + ($loader)( + include_str!($path_str), + format!( + "{}/{}", + std::path::Path::new(file!()) + .parent() + .unwrap() + .to_string_lossy(), + $path_str + ), + ), + ); + }}; +} + #[cfg(test)] mod tests { use bevy_app::App; diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index f5db34d983865..a02f173000355 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -30,3 +30,5 @@ bitflags = "1.2" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } radsort = "0.1" +naga_oil = { git = "https://github.com/robtfm/naga_oil", features=["prune"] } +thiserror = "1.0" \ No newline at end of file diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 5d190540579ea..8802912de3cf1 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -4,6 +4,7 @@ mod alpha; mod bundle; mod light; mod material; +mod overrides; mod pbr_material; mod render; @@ -11,6 +12,7 @@ pub use alpha::*; pub use bundle::*; pub use light::*; pub use material::*; +pub use overrides::*; pub use pbr_material::*; pub use render::*; @@ -38,7 +40,7 @@ pub mod draw_3d_graph { } use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::{ @@ -46,8 +48,8 @@ use bevy_render::{ extract_resource::ExtractResourcePlugin, prelude::Color, render_graph::RenderGraph, - render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, - render_resource::{Shader, SpecializedMeshPipelines}, + render_phase::{sort_phase_system, DrawFunctions}, + render_resource::Shader, view::VisibilitySystems, RenderApp, RenderStage, }; @@ -71,6 +73,8 @@ pub const PBR_FUNCTIONS_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 16550102964439850292); pub const SHADOW_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696); +pub const PBR_OVERRIDE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 5510646841753146043); /// Sets up the entire PBR infrastructure of bevy. #[derive(Default)] @@ -78,49 +82,67 @@ pub struct PbrPlugin; impl Plugin for PbrPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( + load_internal_asset_with_path!( app, PBR_TYPES_SHADER_HANDLE, "render/pbr_types.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, PBR_BINDINGS_SHADER_HANDLE, "render/pbr_bindings.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!(app, UTILS_HANDLE, "render/utils.wgsl", Shader::from_wgsl); - load_internal_asset!( + load_internal_asset_with_path!( + app, + UTILS_HANDLE, + "render/utils.wgsl", + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( app, CLUSTERED_FORWARD_HANDLE, "render/clustered_forward.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, PBR_LIGHTING_HANDLE, "render/pbr_lighting.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, SHADOWS_HANDLE, "render/shadows.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, PBR_FUNCTIONS_HANDLE, "render/pbr_functions.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( + app, + PBR_SHADER_HANDLE, + "render/pbr.wgsl", + Shader::from_wgsl_with_path ); - load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl); - load_internal_asset!( + load_internal_asset_with_path!( app, SHADOW_SHADER_HANDLE, "render/depth.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path + ); + + app.world.resource_mut::>().set_untracked( + PBR_OVERRIDE_HANDLE, + Shader::from_wgsl_with_path( + "#define_import_path bevy_pbr::user_overrides\n", + "< automatically generated by bevy_pbr::overrides::update_overrides() >", + ), ); app.register_type::() @@ -128,7 +150,6 @@ impl Plugin for PbrPlugin { .register_type::() .register_type::() .add_plugin(MeshRenderPlugin) - .add_plugin(MaterialPlugin::::default()) .register_type::() .register_type::() .register_type::() @@ -136,6 +157,7 @@ impl Plugin for PbrPlugin { .init_resource::() .init_resource::() .init_resource::() + .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) .add_system_to_stage( CoreStage::PostUpdate, @@ -187,18 +209,8 @@ impl Plugin for PbrPlugin { // because that resets entity ComputedVisibility for the first view // which would override any results from this otherwise .after(VisibilitySystems::CheckVisibility), - ); - - app.world - .resource_mut::>() - .set_untracked( - Handle::::default(), - StandardMaterial { - base_color: Color::rgb(1.0, 0.0, 0.5), - unlit: true, - ..Default::default() - }, - ); + ) + .add_system_to_stage(CoreStage::PostUpdate, update_shader_overrides); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, @@ -229,20 +241,14 @@ impl Plugin for PbrPlugin { // prepare_lights. render::prepare_clusters.label(RenderLightSystems::PrepareClusters), ) - .add_system_to_stage( - RenderStage::Queue, - render::queue_shadows.label(RenderLightSystems::QueueShadows), - ) .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .init_resource::() + .init_resource::() // TODO: this is here because `queue_mesh_view_bind_groups` uses the samplers, clean this up .init_resource::>() .init_resource::() - .init_resource::() - .init_resource::>(); + .init_resource::(); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); - render_app.add_render_command::(); let mut graph = render_app.world.resource_mut::(); let draw_3d_graph = graph .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) @@ -262,5 +268,17 @@ impl Plugin for PbrPlugin { ShadowPassNode::IN_VIEW, ) .unwrap(); + + app.add_plugin(MaterialPlugin::::default()); + app.world + .resource_mut::>() + .set_untracked( + Handle::::default(), + StandardMaterial { + base_color: Color::rgb(1.0, 0.0, 0.5), + unlit: true, + ..Default::default() + }, + ); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index dddb27a887749..f50b7c1ea61d6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,6 +1,12 @@ use crate::{ - AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, - SetMeshViewBindGroup, + depth_pipeline::{ + unextract_derived_shaders, DepthPipeline, DepthPipelineBuilder, DepthPipelineError, + DerivedShaders, + }, + AlphaMode, CubemapVisibleEntities, DrawMesh, ExtractedDirectionalLight, ExtractedPointLight, + LightEntity, MeshPipeline, MeshPipelineKey, MeshUniform, NotShadowCaster, RenderLightSystems, + SetMeshBindGroup, SetMeshViewBindGroup, SetShadowViewBindGroup, Shadow, ViewLightEntities, + With, Without, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; @@ -181,6 +187,17 @@ where prepare_materials::.after(PrepareAssetLabel::PreAssetPrepare), ) .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); + + render_app + .init_resource::>() + .init_resource::>() + .init_resource::>>() + .add_render_command::>() + .add_system_to_stage(RenderStage::Extract, unextract_derived_shaders::) + .add_system_to_stage( + RenderStage::Queue, + queue_material_shadows::.label(RenderLightSystems::QueueShadows), + ); } } } @@ -287,7 +304,7 @@ impl FromWorld for MaterialPipeline { } } -type DrawMaterial = ( +pub type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMaterialBindGroup, @@ -295,6 +312,14 @@ type DrawMaterial = ( DrawMesh, ); +pub type DrawMaterialDepth = ( + SetItemPipeline, + SetShadowViewBindGroup<0>, + SetMaterialBindGroup, + SetMeshBindGroup<2>, + DrawMesh, +); + /// Sets the bind group for a given [`Material`] at the configured `I` index. pub struct SetMaterialBindGroup(PhantomData); impl EntityRenderCommand for SetMaterialBindGroup { @@ -419,6 +444,70 @@ pub fn queue_material_meshes( } } +#[allow(clippy::too_many_arguments)] +pub fn queue_material_shadows( + shadow_draw_functions: Res>, + casting_meshes: Query<(&Handle, &Handle), Without>, + view_lights: Query<&ViewLightEntities>, + mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, + point_light_entities: Query<&CubemapVisibleEntities, With>, + directional_light_entities: Query<&VisibleEntities, With>, + spot_light_entities: Query<&VisibleEntities, With>, + mut depth_pipeline_builder: DepthPipelineBuilder, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + for view_lights in &view_lights { + let draw_shadow_mesh = shadow_draw_functions + .read() + .get_id::>() + .unwrap(); + + for view_light_entity in view_lights.lights.iter().copied() { + let (light_entity, mut shadow_phase) = + view_light_shadow_phases.get_mut(view_light_entity).unwrap(); + let visible_entities = match light_entity { + LightEntity::Directional { light_entity } => directional_light_entities + .get(*light_entity) + .expect("Failed to get directional light visible entities"), + LightEntity::Point { + light_entity, + face_index, + } => point_light_entities + .get(*light_entity) + .expect("Failed to get point light visible entities") + .get(*face_index), + LightEntity::Spot { light_entity } => spot_light_entities + .get(*light_entity) + .expect("Failed to get spot light visible entities"), + }; + // NOTE: Lights with shadow mapping disabled will have no visible entities + // so no meshes will be queued + for entity in visible_entities.iter().copied() { + if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) { + let pipeline_id = match depth_pipeline_builder + .depth_pipeline_id(material_handle, mesh_handle) + { + Ok(id) => id, + Err(DepthPipelineError::Retry) => continue, + Err(err) => { + error!("{}", err); + continue; + } + }; + + shadow_phase.add(Shadow { + draw_function: draw_shadow_mesh, + pipeline: pipeline_id, + entity, + distance: 0.0, // TODO: sort back-to-front + }); + } + } + } + } +} + /// Common [`Material`] properties, calculated for a specific material instance. pub struct MaterialProperties { /// The [`AlphaMode`] of this material. diff --git a/crates/bevy_pbr/src/overrides.rs b/crates/bevy_pbr/src/overrides.rs new file mode 100644 index 0000000000000..ab5007444ce83 --- /dev/null +++ b/crates/bevy_pbr/src/overrides.rs @@ -0,0 +1,65 @@ +use crate::PBR_OVERRIDE_HANDLE; +use bevy_asset::{AssetEvent, Assets, Handle}; +use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; +use bevy_render::render_resource::{Shader, ShaderImport}; +use bevy_utils::tracing::warn; +use naga_oil::compose::ImportDefinition; + +#[derive(Default, Resource)] +pub struct PbrShaderFunctionOverrides { + pub overrides: Vec>, +} + +pub(crate) fn update_shader_overrides( + override_list: Res, + mut load_events: EventReader>, + mut shaders: ResMut>, +) { + let run_condition = override_list.is_changed() + || load_events + .iter() + .map(|ev| match ev { + AssetEvent::Created { handle } + | AssetEvent::Modified { handle } + | AssetEvent::Removed { handle } => handle, + }) + .any(|handle| override_list.overrides.contains(handle)); + + load_events.clear(); + + if run_condition { + // collect user imports + let imports: Vec<_> = override_list + .overrides + .iter() + // skip any handles that are not (yet) loaded + .flat_map(|handle| shaders.get(handle).map(|shader| (handle, shader))) + .flat_map( + |(handle, shader)| match (&shader.import_path, &shader.path) { + // prefer import_path + (Some(import_path), _) => Some(import_path.clone()), + // fall back to "path" + (None, Some(path)) => Some(ShaderImport::AssetPath(path.clone())), + // skip any shaders that don't have an import path or a path + _ => { + warn!( + "pbr shader function with handle {:?} has no `import_path` or `path`", + handle + ); + None + } + }, + ) + .collect(); + + let mut shader = shaders.get_mut(&PBR_OVERRIDE_HANDLE.typed_weak()).unwrap(); + shader.additional_imports = imports + .iter() + .map(|import| ImportDefinition { + import: import.as_str().to_owned(), + as_name: None, + }) + .collect(); + shader.imports = imports; + } +} diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index 46c54ad6f6390..09197499036c3 100644 --- a/crates/bevy_pbr/src/render/clustered_forward.wgsl +++ b/crates/bevy_pbr/src/render/clustered_forward.wgsl @@ -1,28 +1,31 @@ #define_import_path bevy_pbr::clustered_forward +#import bevy_pbr::mesh_view_bindings as bindings +#import bevy_pbr::utils + // NOTE: Keep in sync with bevy_pbr/src/light.rs fn view_z_to_z_slice(view_z: f32, is_orthographic: bool) -> u32 { var z_slice: u32 = 0u; if (is_orthographic) { // NOTE: view_z is correct in the orthographic case - z_slice = u32(floor((view_z - lights.cluster_factors.z) * lights.cluster_factors.w)); + z_slice = u32(floor((view_z - bindings::lights.cluster_factors.z) * bindings::lights.cluster_factors.w)); } else { // NOTE: had to use -view_z to make it positive else log(negative) is nan - z_slice = u32(log(-view_z) * lights.cluster_factors.z - lights.cluster_factors.w + 1.0); + z_slice = u32(log(-view_z) * bindings::lights.cluster_factors.z - bindings::lights.cluster_factors.w + 1.0); } // NOTE: We use min as we may limit the far z plane used for clustering to be closeer than // the furthest thing being drawn. This means that we need to limit to the maximum cluster. - return min(z_slice, lights.cluster_dimensions.z - 1u); + return min(z_slice, bindings::lights.cluster_dimensions.z - 1u); } fn fragment_cluster_index(frag_coord: vec2, view_z: f32, is_orthographic: bool) -> u32 { - let xy = vec2(floor(frag_coord * lights.cluster_factors.xy)); + let xy = vec2(floor(frag_coord * bindings::lights.cluster_factors.xy)); let z_slice = view_z_to_z_slice(view_z, is_orthographic); // NOTE: Restricting cluster index to avoid undefined behavior when accessing uniform buffer // arrays based on the cluster index. return min( - (xy.y * lights.cluster_dimensions.x + xy.x) * lights.cluster_dimensions.z + z_slice, - lights.cluster_dimensions.w - 1u + (xy.y * bindings::lights.cluster_dimensions.x + xy.x) * bindings::lights.cluster_dimensions.z + z_slice, + bindings::lights.cluster_dimensions.w - 1u ); } @@ -30,7 +33,7 @@ fn fragment_cluster_index(frag_coord: vec2, view_z: f32, is_orthographic: b let CLUSTER_COUNT_SIZE = 9u; fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { #ifdef NO_STORAGE_BUFFERS_SUPPORT - let offset_and_counts = cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; + let offset_and_counts = bindings::cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; // [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] // [ offset | point light count | spot light count ] return vec3( @@ -39,7 +42,7 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { offset_and_counts & ((1u << CLUSTER_COUNT_SIZE) - 1u), ); #else - return cluster_offsets_and_counts.data[cluster_index].xyz; + return bindings::cluster_offsets_and_counts.data[cluster_index].xyz; #endif } @@ -47,11 +50,11 @@ fn get_light_id(index: u32) -> u32 { #ifdef NO_STORAGE_BUFFERS_SUPPORT // The index is correct but in cluster_light_index_lists we pack 4 u8s into a u32 // This means the index into cluster_light_index_lists is index / 4 - let indices = cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; + let indices = bindings::cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; // And index % 4 gives the sub-index of the u8 within the u32 so we shift by 8 * sub-index return (indices >> (8u * (index & ((1u << 2u) - 1u)))) & ((1u << 8u) - 1u); #else - return cluster_light_index_lists.data[index]; + return bindings::cluster_light_index_lists.data[index]; #endif } @@ -69,9 +72,9 @@ fn cluster_debug_visualization( var z_slice: u32 = view_z_to_z_slice(view_z, is_orthographic); // A hack to make the colors alternate a bit more if ((z_slice & 1u) == 1u) { - z_slice = z_slice + lights.cluster_dimensions.z / 2u; + z_slice = z_slice + bindings::lights.cluster_dimensions.z / 2u; } - let slice_color = hsv2rgb(f32(z_slice) / f32(lights.cluster_dimensions.z + 1u), 1.0, 0.5); + let slice_color = bevy_pbr::utils::hsv2rgb(f32(z_slice) / f32(bindings::lights.cluster_dimensions.z + 1u), 1.0, 0.5); output_color = vec4( (1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * slice_color, output_color.a @@ -90,7 +93,7 @@ fn cluster_debug_visualization( #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY // NOTE: Visualizes the cluster to which the fragment belongs let cluster_overlay_alpha = 0.1; - let cluster_color = hsv2rgb(random1D(f32(cluster_index)), 1.0, 0.5); + let cluster_color = bevy_pbr::utils::hsv2rgb(random1D(f32(cluster_index)), 1.0, 0.5); output_color = vec4( (1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * cluster_color, output_color.a diff --git a/crates/bevy_pbr/src/render/depth.wgsl b/crates/bevy_pbr/src/render/depth.wgsl index 5a238cb1e5f21..9576a5feb7f83 100644 --- a/crates/bevy_pbr/src/render/depth.wgsl +++ b/crates/bevy_pbr/src/render/depth.wgsl @@ -1,21 +1,16 @@ +#define_import_path depth + #import bevy_pbr::mesh_view_types -#import bevy_pbr::mesh_types +#import bevy_pbr::mesh_bindings +#import bevy_pbr::mesh_functions @group(0) @binding(0) -var view: View; - -@group(1) @binding(0) -var mesh: Mesh; +var view: bevy_pbr::mesh_view_types::View; #ifdef SKINNED -@group(1) @binding(1) -var joint_matrices: SkinnedMesh; #import bevy_pbr::skinning #endif -// NOTE: Bindings must come before functions that use them! -#import bevy_pbr::mesh_functions - struct Vertex { @location(0) position: vec3, #ifdef SKINNED @@ -31,12 +26,12 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { #ifdef SKINNED - let model = skin_model(vertex.joint_indices, vertex.joint_weights); + let model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else - let model = mesh.model; + let model = bevy_pbr::mesh_bindings::mesh.model; #endif - var out: VertexOutput; - out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); - return out; -} + var out_depth_pipeline: VertexOutput; + out_depth_pipeline.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); + return out_depth_pipeline; +} \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/depth_pipeline.rs b/crates/bevy_pbr/src/render/depth_pipeline.rs new file mode 100644 index 0000000000000..0e151c4225d33 --- /dev/null +++ b/crates/bevy_pbr/src/render/depth_pipeline.rs @@ -0,0 +1,443 @@ +use std::{hash::Hash, marker::PhantomData}; + +use bevy_asset::{Assets, Handle}; +use bevy_ecs::{ + prelude::{FromWorld, Res, Resource, World}, + system::{ResMut, SystemParam}, +}; +use bevy_render::{ + mesh::MeshVertexBufferLayout, + prelude::Mesh, + render_asset::RenderAssets, + render_resource::{ + BindGroupLayout, CachedRenderPipelineId, FragmentState, MultisampleState, PipelineCache, + PipelineCacheError, RenderPipelineDescriptor, Shader, Source, SpecializedMeshPipeline, + SpecializedMeshPipelineError, SpecializedMeshPipelines, VertexState, + }, + renderer::RenderDevice, + view::Msaa, + MainWorld, +}; +use bevy_utils::HashMap; + +use crate::{ + AlphaMode, Material, MaterialPipeline, MaterialPipelineKey, MeshPipelineKey, RenderMaterials, + ShadowPipeline, +}; + +use thiserror::Error; + +#[derive(Resource)] +pub struct DepthPipeline { + material_pipeline: MaterialPipeline, +} + +pub struct DepthPipelineKey { + params: Option, + material_key: MaterialPipelineKey, +} + +impl Eq for DepthPipelineKey where M::Data: PartialEq {} + +impl PartialEq for DepthPipelineKey +where + M::Data: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.material_key == other.material_key + } +} + +impl Clone for DepthPipelineKey +where + M::Data: Clone, +{ + fn clone(&self) -> Self { + Self { + params: self + .params + .as_ref() + .map(|params| DepthPipelineSpecializationParams { + view_layout: params.view_layout.clone(), + vertex_shader: params.vertex_shader.clone_weak(), + fragment_shader: params.fragment_shader.as_ref().map(Handle::clone_weak), + }), + material_key: self.material_key.clone(), + } + } +} + +impl Hash for DepthPipelineKey +where + M::Data: Hash, +{ + fn hash(&self, state: &mut H) { + self.material_key.hash(state); + } +} + +struct DepthPipelineSpecializationParams { + view_layout: BindGroupLayout, + vertex_shader: Handle, + fragment_shader: Option>, +} + +impl SpecializedMeshPipeline for DepthPipeline +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + type Key = DepthPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let DepthPipelineSpecializationParams { + view_layout, + vertex_shader, + fragment_shader, + } = key.params.unwrap(); + + let mut descriptor = self + .material_pipeline + .specialize(key.material_key, layout)?; + + if let Some(fragment_shader) = fragment_shader { + let fragment_state = descriptor.fragment.as_mut().unwrap(); + fragment_state.shader = fragment_shader; + fragment_state.targets.clear(); + } else { + descriptor.fragment = None; + } + + descriptor.vertex.shader = vertex_shader; + + descriptor.layout.as_mut().unwrap()[0] = view_layout; + descriptor.multisample = MultisampleState::default(); + + Ok(descriptor) + } +} + +impl FromWorld for DepthPipeline { + fn from_world(world: &mut World) -> Self { + let material_pipeline = MaterialPipeline::from_world(world); + + DepthPipeline { material_pipeline } + } +} + +#[derive(SystemParam)] +pub struct DepthPipelineBuilder<'w, 's, M: Material> +where + ::Data: Eq + Hash + Clone, +{ + render_device: Res<'w, RenderDevice>, + depth_pipeline: Res<'w, DepthPipeline>, + depth_pipelines: ResMut<'w, SpecializedMeshPipelines>>, + material_pipeline: Res<'w, MaterialPipeline>, + material_pipelines: ResMut<'w, SpecializedMeshPipelines>>, + shadow_pipeline: Res<'w, ShadowPipeline>, + pipeline_cache: ResMut<'w, PipelineCache>, + render_meshes: Res<'w, RenderAssets>, + render_materials: Res<'w, RenderMaterials>, + derived_shaders: ResMut<'w, DerivedShaders>, + msaa: Res<'w, Msaa>, + #[system_param(ignore)] + _p: PhantomData<&'s ()>, +} + +#[derive(Error, Debug)] +pub enum DepthPipelineError { + #[error(transparent)] + MaterialError(#[from] SpecializedMeshPipelineError), + #[error(transparent)] + PipelineCacheError(#[from] PipelineCacheError), + #[error("source is not compiled to naga")] + NotNagaError, + #[error("maybe next time")] + Retry, +} + +impl<'w, 's, M: Material> DepthPipelineBuilder<'w, 's, M> +where + ::Data: Eq + Hash + Clone, +{ + pub fn depth_pipeline_id( + &mut self, + material_handle: &Handle, + mesh_handle: &Handle, + ) -> Result { + let DepthPipelineBuilder { + render_device, + depth_pipelines, + depth_pipeline, + material_pipelines, + material_pipeline, + shadow_pipeline, + pipeline_cache, + render_meshes, + render_materials, + derived_shaders, + msaa, + .. + } = self; + + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); + + if let Some(material) = render_materials.get(material_handle) { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let mut mesh_key = + MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | msaa_key; + let alpha_mode = material.properties.alpha_mode; + if let AlphaMode::Blend = alpha_mode { + mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; + } + + let depth_key = DepthPipelineKey { + params: None, + material_key: MaterialPipelineKey { + mesh_key, + bind_group_data: material.key.clone(), + }, + }; + + // check for existing pipeline + if let Some(pipeline_id) = depth_pipelines.get(&depth_key, &mesh.layout) { + return Ok(pipeline_id); + } + + // check for pre-built shaders + if let Some(mut handles) = derived_shaders.added.remove(&DerivedShaderHandle { + key: depth_key.material_key.clone(), + }) { + let depth_key = DepthPipelineKey { + params: Some(DepthPipelineSpecializationParams { + view_layout: shadow_pipeline.view_layout.clone(), + vertex_shader: handles.pop().unwrap(), + fragment_shader: handles.pop(), + }), + material_key: depth_key.material_key, + }; + + return Ok(depth_pipelines.specialize( + pipeline_cache, + depth_pipeline, + depth_key, + &mesh.layout, + )?); + } + + // build shaders for next time + let mut shaders_to_queue = Vec::default(); + + let material_pipeline_id = material_pipelines.specialize( + pipeline_cache, + material_pipeline, + depth_key.material_key.clone(), + &mesh.layout, + )?; + + let material_pipeline_descriptor = + pipeline_cache.get_render_pipeline_descriptor(material_pipeline_id); + let VertexState { + shader: vertex_shader, + entry_point: vertex_entry_point, + shader_defs: vertex_shader_defs, + .. + } = material_pipeline_descriptor.vertex.clone(); + let mut required_frag_bindings = None; + + if let Some(FragmentState { + shader: fragment_shader, + entry_point: fragment_entry_point, + shader_defs: fragment_shader_defs, + .. + }) = material_pipeline_descriptor.fragment.clone() + { + let naga_module = pipeline_cache.get_shader_source( + render_device, + material_pipeline_id, + &fragment_shader, + &fragment_shader_defs, + )?; + + match naga_module { + None => return Err(DepthPipelineError::NotNagaError), + Some(fragment_module) => { + let mut pruner = naga_oil::prune::Pruner::new(fragment_module); + let fragment_entry_point = fragment_module + .entry_points + .iter() + .find(|ep| ep.name.as_str() == fragment_entry_point) + .unwrap(); + + let inputs = pruner.add_entrypoint( + fragment_entry_point, + Default::default(), + None, + ); + + if !inputs.is_empty() { + let fragment_depth_shader = pruner.rewrite(); + + shaders_to_queue.push(Shader { + source: Source::Naga(Box::new(fragment_depth_shader)), + path: None, + import_path: None, + imports: Default::default(), + additional_imports: Default::default(), + }); + required_frag_bindings = Some(inputs.input_to_bindings( + fragment_module, + &fragment_entry_point.function, + )); + } + } + } + } + + let naga_module = pipeline_cache.get_shader_source( + render_device, + material_pipeline_id, + &vertex_shader, + &vertex_shader_defs, + )?; + + match naga_module { + None => return Err(DepthPipelineError::NotNagaError), + Some(vertex_module) => { + let vertex_entrypoint = vertex_module + .entry_points + .iter() + .find(|ep| ep.name.as_str() == vertex_entry_point) + .unwrap(); + + let mut requirements = match required_frag_bindings { + Some(bindings) => { + naga_oil::prune::RequiredContext::output_from_bindings( + &bindings, + vertex_module, + &vertex_entrypoint.function, + ) + } + None => naga_oil::prune::RequiredContext::default(), + }; + + // note: add both forms of position to get whatever is available + requirements.add_binding( + naga_oil::prune::Binding::BuiltIn(naga_oil::prune::BuiltIn::Position { + invariant: true, + }), + vertex_module, + &vertex_entrypoint.function, + ); + requirements.add_binding( + naga_oil::prune::Binding::BuiltIn(naga_oil::prune::BuiltIn::Position { + invariant: false, + }), + vertex_module, + &vertex_entrypoint.function, + ); + + let mut pruner = naga_oil::prune::Pruner::new(vertex_module); + pruner.add_entrypoint( + vertex_entrypoint, + requirements.globals, + requirements.retval, + ); + + let vertex_shader = pruner.rewrite(); + + shaders_to_queue.push(Shader { + source: Source::Naga(Box::new(vertex_shader)), + path: None, + import_path: None, + imports: Default::default(), + additional_imports: Default::default(), + }); + } + } + + derived_shaders.to_add.insert( + DerivedShaderHandle { + key: depth_key.material_key, + }, + shaders_to_queue, + ); + return Err(DepthPipelineError::Retry); + } + } + + // mesh and/or material missing + Err(DepthPipelineError::Retry) + } +} + +pub struct DerivedShaderHandle { + key: MaterialPipelineKey, +} + +impl Eq for DerivedShaderHandle where M::Data: PartialEq {} + +impl PartialEq for DerivedShaderHandle +where + M::Data: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Clone for DerivedShaderHandle +where + M::Data: Clone, +{ + fn clone(&self) -> Self { + Self { + key: self.key.clone(), + } + } +} + +impl Hash for DerivedShaderHandle +where + M::Data: Hash, +{ + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +#[derive(Resource)] +pub struct DerivedShaders { + to_add: HashMap, Vec>, + added: HashMap, Vec>>, +} + +impl FromWorld for DerivedShaders { + fn from_world(_: &mut World) -> Self { + Self { + to_add: Default::default(), + added: Default::default(), + } + } +} + +pub fn unextract_derived_shaders( + mut world: ResMut, + mut derived_shaders: ResMut>, +) where + ::Data: Eq + Hash + Clone, +{ + let mut shaders = world.resource_mut::>(); + + for (derived_handle, shaders_to_add) in std::mem::take(&mut derived_shaders.to_add) { + let shader_handles = shaders_to_add + .into_iter() + .map(|shader| shaders.add(shader)) + .collect(); + derived_shaders.added.insert(derived_handle, shader_handles); + } +} diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6b79102d3b844..e02e77df3ce4b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,10 +1,8 @@ use crate::{ point_light_order, AmbientLight, Clusters, CubemapVisibleEntities, DirectionalLight, - DirectionalLightShadowMap, DrawMesh, GlobalVisiblePointLights, MeshPipeline, NotShadowCaster, - PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight, VisiblePointLights, - SHADOW_SHADER_HANDLE, + DirectionalLightShadowMap, GlobalVisiblePointLights, MeshPipeline, PointLight, + PointLightShadowMap, SpotLight, VisiblePointLights, SHADOW_SHADER_HANDLE, }; -use bevy_asset::Handle; use bevy_core_pipeline::core_3d::Transparent3d; use bevy_ecs::{ prelude::*, @@ -15,12 +13,10 @@ use bevy_render::{ camera::{Camera, CameraProjection}, color::Color, mesh::{Mesh, MeshVertexBufferLayout}, - render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, - EntityRenderCommand, PhaseItem, RenderCommandResult, RenderPhase, SetItemPipeline, - TrackedRenderPass, + EntityRenderCommand, PhaseItem, RenderCommandResult, RenderPhase, TrackedRenderPass, }, render_resource::*, renderer::{RenderContext, RenderDevice, RenderQueue}, @@ -33,11 +29,11 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::FloatOrd; -use bevy_utils::{ - tracing::{error, warn}, - HashMap, +use bevy_utils::{tracing::warn, HashMap}; +use std::{ + hash::Hash, + num::{NonZeroU32, NonZeroU64}, }; -use std::num::{NonZeroU32, NonZeroU64}; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] pub enum RenderLightSystems { @@ -320,7 +316,7 @@ impl SpecializedMeshPipeline for ShadowPipeline { let mut vertex_attributes = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)]; let mut bind_group_layout = vec![self.view_layout.clone()]; - let mut shader_defs = Vec::new(); + let mut shader_defs = vec!["MESH_BINDGROUP_1".to_owned()]; if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) @@ -1577,78 +1573,6 @@ pub fn queue_shadow_view_bind_group( } } -#[allow(clippy::too_many_arguments)] -pub fn queue_shadows( - shadow_draw_functions: Res>, - shadow_pipeline: Res, - casting_meshes: Query<&Handle, Without>, - render_meshes: Res>, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, - view_lights: Query<&ViewLightEntities>, - mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, - point_light_entities: Query<&CubemapVisibleEntities, With>, - directional_light_entities: Query<&VisibleEntities, With>, - spot_light_entities: Query<&VisibleEntities, With>, -) { - for view_lights in &view_lights { - let draw_shadow_mesh = shadow_draw_functions - .read() - .get_id::() - .unwrap(); - for view_light_entity in view_lights.lights.iter().copied() { - let (light_entity, mut shadow_phase) = - view_light_shadow_phases.get_mut(view_light_entity).unwrap(); - let visible_entities = match light_entity { - LightEntity::Directional { light_entity } => directional_light_entities - .get(*light_entity) - .expect("Failed to get directional light visible entities"), - LightEntity::Point { - light_entity, - face_index, - } => point_light_entities - .get(*light_entity) - .expect("Failed to get point light visible entities") - .get(*face_index), - LightEntity::Spot { light_entity } => spot_light_entities - .get(*light_entity) - .expect("Failed to get spot light visible entities"), - }; - // NOTE: Lights with shadow mapping disabled will have no visible entities - // so no meshes will be queued - for entity in visible_entities.iter().copied() { - if let Ok(mesh_handle) = casting_meshes.get(entity) { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = - ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline_id = pipelines.specialize( - &mut pipeline_cache, - &shadow_pipeline, - key, - &mesh.layout, - ); - - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - shadow_phase.add(Shadow { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, - entity, - distance: 0.0, // TODO: sort back-to-front - }); - } - } - } - } - } -} - pub struct Shadow { pub distance: f32, pub entity: Entity, @@ -1762,13 +1686,6 @@ impl Node for ShadowPassNode { } } -pub type DrawShadowMesh = ( - SetItemPipeline, - SetShadowViewBindGroup<0>, - SetMeshBindGroup<1>, - DrawMesh, -); - pub struct SetShadowViewBindGroup; impl EntityRenderCommand for SetShadowViewBindGroup { type Param = (SRes, SQuery>); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f5e9a9ae8519b..fb5533cfef010 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -4,7 +4,7 @@ use crate::{ CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, Assets, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem, SystemState}, @@ -56,39 +56,54 @@ pub const SKINNING_HANDLE: HandleUntyped = impl Plugin for MeshRenderPlugin { fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( + load_internal_asset_with_path!( app, MESH_VERTEX_OUTPUT, "mesh_vertex_output.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH_VIEW_TYPES_HANDLE, "mesh_view_types.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH_VIEW_BINDINGS_HANDLE, "mesh_view_bindings.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", Shader::from_wgsl); - load_internal_asset!( + load_internal_asset_with_path!( + app, + MESH_TYPES_HANDLE, + "mesh_types.wgsl", + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( app, MESH_BINDINGS_HANDLE, "mesh_bindings.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH_FUNCTIONS_HANDLE, "mesh_functions.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( + app, + MESH_SHADER_HANDLE, + "mesh.wgsl", + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( + app, + SKINNING_HANDLE, + "skinning.wgsl", + Shader::from_wgsl_with_path ); - load_internal_asset!(app, MESH_SHADER_HANDLE, "mesh.wgsl", Shader::from_wgsl); - load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 32cbe31c8d161..f832139a22e9f 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -1,8 +1,7 @@ -#import bevy_pbr::mesh_view_bindings #import bevy_pbr::mesh_bindings - -// NOTE: Bindings must come before functions that use them! -#import bevy_pbr::mesh_functions +#import bevy_pbr::mesh_functions as mesh_functions +#import bevy_pbr::skinning +#import bevy_pbr::mesh_vertex_output struct Vertex { @location(0) position: vec3, @@ -22,45 +21,38 @@ struct Vertex { #endif }; -struct VertexOutput { - @builtin(position) clip_position: vec4, - #import bevy_pbr::mesh_vertex_output -}; - @vertex -fn vertex(vertex: Vertex) -> VertexOutput { - var out: VertexOutput; +fn vertex(vertex: Vertex) -> bevy_pbr::mesh_vertex_output::MeshVertexOutput { + var out: bevy_pbr::mesh_vertex_output::MeshVertexOutput; #ifdef SKINNED - var model = skin_model(vertex.joint_indices, vertex.joint_weights); - out.world_normal = skin_normals(model, vertex.normal); + var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); + out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else - var model = mesh.model; - out.world_normal = mesh_normal_local_to_world(vertex.normal); + var model = bevy_pbr::mesh_bindings::mesh.model; + out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal); #endif - out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); + out.world_position = mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); #ifdef VERTEX_UVS out.uv = vertex.uv; #endif #ifdef VERTEX_TANGENTS - out.world_tangent = mesh_tangent_local_to_world(model, vertex.tangent); + out.world_tangent = mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent); #endif #ifdef VERTEX_COLORS out.color = vertex.color; #endif - out.clip_position = mesh_position_world_to_clip(out.world_position); + out.clip_position = mesh_functions::mesh_position_world_to_clip(out.world_position); return out; } -struct FragmentInput { - @builtin(front_facing) is_front: bool, - #import bevy_pbr::mesh_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + @builtin(front_facing) is_front: bool, + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, +) -> @location(0) vec4 { #ifdef VERTEX_COLORS - return in.color; + return mesh.color; #else return vec4(1.0, 0.0, 1.0, 1.0); #endif diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index d0781e149b420..5e0e9cffb32d8 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -2,10 +2,14 @@ #import bevy_pbr::mesh_types +#ifdef MESH_BINDGROUP_1 + +@group(1) @binding(0) +var mesh: bevy_pbr::mesh_types::Mesh; + +#else + @group(2) @binding(0) -var mesh: Mesh; -#ifdef SKINNED -@group(2) @binding(1) -var joint_matrices: SkinnedMesh; -#import bevy_pbr::skinning -#endif +var mesh: bevy_pbr::mesh_types::Mesh; + +#endif \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 40779b28d5c6e..c5df597be473e 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -1,11 +1,15 @@ #define_import_path bevy_pbr::mesh_functions +#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_bindings as mesh_bindings +#import bevy_pbr::mesh_types + fn mesh_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; } fn mesh_position_world_to_clip(world_position: vec4) -> vec4 { - return view.view_proj * world_position; + return bevy_pbr::mesh_view_bindings::view.view_proj * world_position; } // NOTE: The intermediate world_position assignment is important @@ -25,9 +29,9 @@ fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { // http://www.mikktspace.com/ return normalize( mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz + mesh_bindings::mesh.inverse_transpose_model[0].xyz, + mesh_bindings::mesh.inverse_transpose_model[1].xyz, + mesh_bindings::mesh.inverse_transpose_model[2].xyz ) * vertex_normal ); } @@ -38,7 +42,7 @@ fn sign_determinant_model_3x3() -> f32 { // bool(u32) is false if 0u else true // f32(bool) is 1.0 if true else 0.0 // * 2.0 - 1.0 remaps 0.0 or 1.0 to -1.0 or 1.0 respectively - return f32(bool(mesh.flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; + return f32(bool(mesh_bindings::mesh.flags & bevy_pbr::mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; } fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { diff --git a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl index ba0c6b2237c0a..00710ca695730 100644 --- a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl +++ b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl @@ -1,13 +1,16 @@ #define_import_path bevy_pbr::mesh_vertex_output -@location(0) world_position: vec4, -@location(1) world_normal: vec3, -#ifdef VERTEX_UVS -@location(2) uv: vec2, -#endif -#ifdef VERTEX_TANGENTS -@location(3) world_tangent: vec4, -#endif -#ifdef VERTEX_COLORS -@location(4) color: vec4, -#endif +struct MeshVertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, + @location(1) world_normal: vec3, + #ifdef VERTEX_UVS + @location(2) uv: vec2, + #endif + #ifdef VERTEX_TANGENTS + @location(3) world_tangent: vec4, + #endif + #ifdef VERTEX_COLORS + @location(4) color: vec4, + #endif +} diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 04780be3f0c96..6b00ef7694bfe 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -1,11 +1,11 @@ #define_import_path bevy_pbr::mesh_view_bindings -#import bevy_pbr::mesh_view_types +#import bevy_pbr::mesh_view_types as types @group(0) @binding(0) -var view: View; +var view: types::View; @group(0) @binding(1) -var lights: Lights; +var lights: types::Lights; #ifdef NO_ARRAY_TEXTURES_SUPPORT @group(0) @binding(2) var point_shadow_textures: texture_depth_cube; @@ -27,16 +27,16 @@ var directional_shadow_textures_sampler: sampler_comparison; #ifdef NO_STORAGE_BUFFERS_SUPPORT @group(0) @binding(6) -var point_lights: PointLights; +var point_lights: types::PointLights; @group(0) @binding(7) -var cluster_light_index_lists: ClusterLightIndexLists; +var cluster_light_index_lists: types::ClusterLightIndexLists; @group(0) @binding(8) -var cluster_offsets_and_counts: ClusterOffsetsAndCounts; +var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #else @group(0) @binding(6) -var point_lights: PointLights; +var point_lights: types::PointLights; @group(0) @binding(7) -var cluster_light_index_lists: ClusterLightIndexLists; +var cluster_light_index_lists: types::ClusterLightIndexLists; @group(0) @binding(8) -var cluster_offsets_and_counts: ClusterOffsetsAndCounts; +var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #endif diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 353f05be1db94..17ccc4ca11717 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -1,3 +1,4 @@ +pub mod depth_pipeline; mod light; mod mesh; diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index b70b5b333943c..db5904ae3df2d 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -1,56 +1,54 @@ -#import bevy_pbr::mesh_view_bindings -#import bevy_pbr::pbr_bindings -#import bevy_pbr::mesh_bindings +#define_import_path bevy_pbr::fragment -#import bevy_pbr::utils -#import bevy_pbr::clustered_forward -#import bevy_pbr::lighting -#import bevy_pbr::shadows -#import bevy_pbr::pbr_functions +#import bevy_pbr::mesh_vertex_output +#import bevy_pbr::pbr_functions as pbr_functions +#import bevy_pbr::pbr_bindings as pbr_bindings +#import bevy_pbr::pbr_types as pbr_types +#import bevy_pbr::mesh_view_bindings +// load user-defined function overrides +#import bevy_pbr::user_overrides -struct FragmentInput { +@fragment +fn fragment( + mesh: bevy_pbr::mesh_vertex_output::MeshVertexOutput, @builtin(front_facing) is_front: bool, - @builtin(position) frag_coord: vec4, - #import bevy_pbr::mesh_vertex_output -}; +) -> @location(0) vec4 { + var output_color: vec4 = pbr_bindings::material.base_color; -@fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { - var output_color: vec4 = material.base_color; #ifdef VERTEX_COLORS - output_color = output_color * in.color; + output_color = output_color * mesh.color; #endif #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { - output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { + output_color = output_color * textureSample(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, mesh.uv); } #endif // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit - if ((material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { // Prepare a 'processed' StandardMaterial by sampling all textures to resolve // the material members - var pbr_input: PbrInput; + var pbr_input: pbr_functions::PbrInput; pbr_input.material.base_color = output_color; - pbr_input.material.reflectance = material.reflectance; - pbr_input.material.flags = material.flags; - pbr_input.material.alpha_cutoff = material.alpha_cutoff; + pbr_input.material.reflectance = pbr_bindings::material.reflectance; + pbr_input.material.flags = pbr_bindings::material.flags; + pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff; // TODO use .a for exposure compensation in HDR - var emissive: vec4 = material.emissive; + var emissive: vec4 = pbr_bindings::material.emissive; #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { - emissive = vec4(emissive.rgb * textureSample(emissive_texture, emissive_sampler, in.uv).rgb, 1.0); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { + emissive = vec4(emissive.rgb * textureSample(pbr_bindings::emissive_texture, pbr_bindings::emissive_sampler, mesh.uv).rgb, 1.0); } #endif pbr_input.material.emissive = emissive; - var metallic: f32 = material.metallic; - var perceptual_roughness: f32 = material.perceptual_roughness; + var metallic: f32 = pbr_bindings::material.metallic; + var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness; #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { - let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, in.uv); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { + let metallic_roughness = textureSample(pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, mesh.uv); // Sampling from GLTF standard channels for now metallic = metallic * metallic_roughness.b; perceptual_roughness = perceptual_roughness * metallic_roughness.g; @@ -61,34 +59,34 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { var occlusion: f32 = 1.0; #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { - occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r; + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { + occlusion = textureSample(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, mesh.uv).r; } #endif pbr_input.occlusion = occlusion; - pbr_input.frag_coord = in.frag_coord; - pbr_input.world_position = in.world_position; - pbr_input.world_normal = in.world_normal; + pbr_input.frag_coord = mesh.clip_position; + pbr_input.world_position = mesh.world_position; + pbr_input.world_normal = mesh.world_normal; - pbr_input.is_orthographic = view.projection[3].w == 1.0; + pbr_input.is_orthographic = bevy_pbr::mesh_view_bindings::view.projection[3].w == 1.0; - pbr_input.N = prepare_normal( - material.flags, - in.world_normal, + pbr_input.N = pbr_functions::prepare_normal( + pbr_bindings::material.flags, + mesh.world_normal, #ifdef VERTEX_TANGENTS -#ifdef STANDARDMATERIAL_NORMAL_MAP - in.world_tangent, -#endif + #ifdef STANDARDMATERIAL_NORMAL_MAP + mesh.world_tangent, + #endif #endif #ifdef VERTEX_UVS - in.uv, + mesh.uv, #endif - in.is_front, + is_front, ); - pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); + pbr_input.V = pbr_functions::calculate_view(mesh.world_position, pbr_input.is_orthographic); - output_color = tone_mapping(pbr(pbr_input)); + output_color = pbr_functions::tone_mapping(pbr_functions::pbr(pbr_input)); } return output_color; diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index f4e4d34f3b4c2..97b503127a2c5 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -3,7 +3,7 @@ #import bevy_pbr::pbr_types @group(1) @binding(0) -var material: StandardMaterial; +var material: bevy_pbr::pbr_types::StandardMaterial; @group(1) @binding(1) var base_color_texture: texture_2d; @group(1) @binding(2) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index a77c065fca221..27cac6796fe5b 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -1,5 +1,15 @@ #define_import_path bevy_pbr::pbr_functions +#import bevy_pbr::pbr_types as pbr_types +#import bevy_pbr::pbr_bindings as pbr_bindings +#import bevy_pbr::mesh_types as mesh_types +#import bevy_pbr::mesh_bindings as mesh_bindings +#import bevy_pbr::mesh_view_types as view_types +#import bevy_pbr::mesh_view_bindings as view_bindings +#import bevy_pbr::lighting as lighting +#import bevy_pbr::clustered_forward as clustering +#import bevy_pbr::shadows as shadows + // NOTE: This ensures that the world_normal is normalized and if // vertex tangents and normal maps then normal mapping may be applied. fn prepare_normal( @@ -34,7 +44,7 @@ fn prepare_normal( #endif #endif - if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { + if ((standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { if (!is_front) { N = -N; #ifdef VERTEX_TANGENTS @@ -50,8 +60,8 @@ fn prepare_normal( #ifdef VERTEX_UVS #ifdef STANDARDMATERIAL_NORMAL_MAP // Nt is the tangent-space normal. - var Nt = textureSample(normal_map_texture, normal_map_sampler, uv).rgb; - if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) { + var Nt = textureSample(pbr_bindings::normal_map_texture, pbr_bindings::normal_map_sampler, uv).rgb; + if ((standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) { // Only use the xy components and derive z for 2-component normal maps. Nt = vec3(Nt.rg * 2.0 - 1.0, 0.0); Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y); @@ -59,7 +69,7 @@ fn prepare_normal( Nt = Nt * 2.0 - 1.0; } // Normal maps authored for DirectX require flipping the y component - if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { + if ((standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { Nt.y = -Nt.y; } // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from @@ -84,16 +94,16 @@ fn calculate_view( var V: vec3; if (is_orthographic) { // Orthographic view vector - V = normalize(vec3(view.view_proj[0].z, view.view_proj[1].z, view.view_proj[2].z)); + V = normalize(vec3(view_bindings::view.view_proj[0].z, view_bindings::view.view_proj[1].z, view_bindings::view.view_proj[2].z)); } else { // Only valid for a perpective projection - V = normalize(view.world_position.xyz - world_position.xyz); + V = normalize(view_bindings::view.world_position.xyz - world_position.xyz); } return V; } struct PbrInput { - material: StandardMaterial, + material: pbr_types::StandardMaterial, occlusion: f32, frag_coord: vec4, world_position: vec4, @@ -112,7 +122,7 @@ struct PbrInput { fn pbr_input_new() -> PbrInput { var pbr_input: PbrInput; - pbr_input.material = standard_material_new(); + pbr_input.material = pbr_types::standard_material_new(); pbr_input.occlusion = 1.0; pbr_input.frag_coord = vec4(0.0, 0.0, 0.0, 1.0); @@ -138,14 +148,14 @@ fn pbr( // calculate non-linear roughness from linear perceptualRoughness let metallic = in.material.metallic; let perceptual_roughness = in.material.perceptual_roughness; - let roughness = perceptualRoughnessToRoughness(perceptual_roughness); + let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness); let occlusion = in.occlusion; - if ((in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { + if ((in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0 output_color.a = 1.0; - } else if ((in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) { + } else if ((in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) { if (output_color.a >= in.material.alpha_cutoff) { // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque output_color.a = 1.0; @@ -173,62 +183,62 @@ fn pbr( var light_accum: vec3 = vec3(0.0); let view_z = dot(vec4( - view.inverse_view[0].z, - view.inverse_view[1].z, - view.inverse_view[2].z, - view.inverse_view[3].z + view_bindings::view.inverse_view[0].z, + view_bindings::view.inverse_view[1].z, + view_bindings::view.inverse_view[2].z, + view_bindings::view.inverse_view[3].z ), in.world_position); - let cluster_index = fragment_cluster_index(in.frag_coord.xy, view_z, in.is_orthographic); - let offset_and_counts = unpack_offset_and_counts(cluster_index); + let cluster_index = clustering::fragment_cluster_index(in.frag_coord.xy, view_z, in.is_orthographic); + let offset_and_counts = clustering::unpack_offset_and_counts(cluster_index); // point lights for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { - let light_id = get_light_id(i); - let light = point_lights.data[light_id]; + let light_id = clustering::get_light_id(i); + let light = view_bindings::point_lights.data[light_id]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal); + if ((mesh_bindings::mesh.flags & mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (light.flags & view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); } - let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = lighting::point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } // spot lights for (var i: u32 = offset_and_counts[0] + offset_and_counts[1]; i < offset_and_counts[0] + offset_and_counts[1] + offset_and_counts[2]; i = i + 1u) { - let light_id = get_light_id(i); - let light = point_lights.data[light_id]; + let light_id = clustering::get_light_id(i); + let light = view_bindings::point_lights.data[light_id]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_spot_shadow(light_id, in.world_position, in.world_normal); + if ((mesh_bindings::mesh.flags & mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (light.flags & view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal); } - let light_contrib = spot_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = lighting::spot_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } - let n_directional_lights = lights.n_directional_lights; + let n_directional_lights = view_bindings::lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { - let light = lights.directional_lights[i]; + let light = view_bindings::lights.directional_lights[i]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_directional_shadow(i, in.world_position, in.world_normal); + if ((mesh_bindings::mesh.flags & mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (light.flags & view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal); } - let light_contrib = directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = lighting::directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } - let diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV); - let specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); + let diffuse_ambient = lighting::EnvBRDFApprox(diffuse_color, 1.0, NdotV); + let specular_ambient = lighting::EnvBRDFApprox(F0, perceptual_roughness, NdotV); output_color = vec4( light_accum + - (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion + + (diffuse_ambient + specular_ambient) * view_bindings::lights.ambient_color.rgb * occlusion + emissive.rgb * output_color.a, output_color.a); - output_color = cluster_debug_visualization( + output_color = clustering::cluster_debug_visualization( output_color, view_z, in.is_orthographic, @@ -241,7 +251,7 @@ fn pbr( fn tone_mapping(in: vec4) -> vec4 { // tone_mapping - return vec4(reinhard_luminance(in.rgb), in.a); + return vec4(lighting::reinhard_luminance(in.rgb), in.a); // Gamma correction. // Not needed with sRGB buffer diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index ff9451ef302b4..97adc286ad844 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -1,5 +1,8 @@ #define_import_path bevy_pbr::lighting +#import bevy_pbr::utils as utils +#import bevy_pbr::mesh_view_types as view_types + // From the Filament design doc // https://google.github.io/filament/Filament.html#table_symbols // Symbol Definition @@ -41,7 +44,7 @@ // because otherwise every light affects every fragment in the scene fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 { let factor = distanceSquare * inverseRangeSquared; - let smoothFactor = saturate(1.0 - factor * factor); + let smoothFactor = utils::saturate(1.0 - factor * factor); let attenuation = smoothFactor * smoothFactor; return attenuation * 1.0 / max(distanceSquare, 0.0001); } @@ -57,7 +60,7 @@ fn D_GGX(roughness: f32, NoH: f32, h: vec3) -> f32 { let oneMinusNoHSquared = 1.0 - NoH * NoH; let a = NoH * roughness; let k = roughness / (oneMinusNoHSquared + a * a); - let d = k * k * (1.0 / PI); + let d = k * k * (1.0 / utils::PI); return d; } @@ -92,7 +95,7 @@ fn F_Schlick(f0: f32, f90: f32, VoH: f32) -> f32 { fn fresnel(f0: vec3, LoH: f32) -> vec3 { // f_90 suitable for ambient occlusion // see https://google.github.io/filament/Filament.html#lighting/occlusion - let f90 = saturate(dot(f0, vec3(50.0 * 0.33))); + let f90 = utils::saturate(dot(f0, vec3(50.0 * 0.33))); return F_Schlick_vec(f0, f90, LoH); } @@ -128,7 +131,7 @@ fn Fd_Burley(roughness: f32, NoV: f32, NoL: f32, LoH: f32) -> f32 { let f90 = 0.5 + 2.0 * roughness * LoH * LoH; let lightScatter = F_Schlick(1.0, f90, NoL); let viewScatter = F_Schlick(1.0, f90, NoV); - return lightScatter * viewScatter * (1.0 / PI); + return lightScatter * viewScatter * (1.0 / utils::PI); } // From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile @@ -185,7 +188,7 @@ fn reinhard_extended_luminance(color: vec3, max_white_l: f32) -> vec3 } fn point_light( - world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, + world_position: vec3, light: view_types::PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, R: vec3, F0: vec3, diffuseColor: vec3 ) -> vec3 { let light_to_frag = light.position_radius.xyz - world_position.xyz; @@ -198,16 +201,16 @@ fn point_light( // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 let a = roughness; let centerToRay = dot(light_to_frag, R) * R - light_to_frag; - let closestPoint = light_to_frag + centerToRay * saturate(light.position_radius.w * inverseSqrt(dot(centerToRay, centerToRay))); + let closestPoint = light_to_frag + centerToRay * utils::saturate(light.position_radius.w * inverseSqrt(dot(centerToRay, centerToRay))); let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint)); - let normalizationFactor = a / saturate(a + (light.position_radius.w * 0.5 * LspecLengthInverse)); + let normalizationFactor = a / utils::saturate(a + (light.position_radius.w * 0.5 * LspecLengthInverse)); let specularIntensity = normalizationFactor * normalizationFactor; var L: vec3 = closestPoint * LspecLengthInverse; // normalize() equivalent? var H: vec3 = normalize(L + V); - var NoL: f32 = saturate(dot(N, L)); - var NoH: f32 = saturate(dot(N, H)); - var LoH: f32 = saturate(dot(L, H)); + var NoL: f32 = utils::saturate(dot(N, L)); + var NoH: f32 = utils::saturate(dot(N, H)); + var LoH: f32 = utils::saturate(dot(L, H)); let specular_light = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); @@ -215,9 +218,9 @@ fn point_light( // Comes after specular since its NoL is used in the lighting equation. L = normalize(light_to_frag); H = normalize(L + V); - NoL = saturate(dot(N, L)); - NoH = saturate(dot(N, H)); - LoH = saturate(dot(L, H)); + NoL = utils::saturate(dot(N, L)); + NoH = utils::saturate(dot(N, H)); + LoH = utils::saturate(dot(L, H)); let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); @@ -240,7 +243,7 @@ fn point_light( } fn spot_light( - world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, + world_position: vec3, light: view_types::PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, R: vec3, F0: vec3, diffuseColor: vec3 ) -> vec3 { // reuse the point light calculations @@ -249,7 +252,7 @@ fn spot_light( // reconstruct spot dir from x/z and y-direction flag var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); spot_dir.y = sqrt(1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z); - if ((light.flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { + if ((light.flags & view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { spot_dir.y = -spot_dir.y; } let light_to_frag = light.position_radius.xyz - world_position.xyz; @@ -258,19 +261,19 @@ fn spot_light( // spot_scale and spot_offset have been precomputed // note we normalize here to get "l" from the filament listing. spot_dir is already normalized let cd = dot(-spot_dir, normalize(light_to_frag)); - let attenuation = saturate(cd * light.light_custom_data.z + light.light_custom_data.w); + let attenuation = utils::saturate(cd * light.light_custom_data.z + light.light_custom_data.w); let spot_attenuation = attenuation * attenuation; return point_light * spot_attenuation; } -fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, diffuseColor: vec3) -> vec3 { +fn directional_light(light: view_types::DirectionalLight, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, diffuseColor: vec3) -> vec3 { let incident_light = light.direction_to_light.xyz; let half_vector = normalize(incident_light + view); - let NoL = saturate(dot(normal, incident_light)); - let NoH = saturate(dot(normal, half_vector)); - let LoH = saturate(dot(incident_light, half_vector)); + let NoL = utils::saturate(dot(normal, incident_light)); + let NoH = utils::saturate(dot(normal, half_vector)); + let LoH = utils::saturate(dot(incident_light, half_vector)); let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); let specularIntensity = 1.0; diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index 3953edc041a10..cc9a606ef02b7 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -1,7 +1,10 @@ #define_import_path bevy_pbr::shadows +#import bevy_pbr::mesh_view_types as view_types +#import bevy_pbr::mesh_view_bindings as view_bindings + fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = point_lights.data[light_id]; + let light = view_bindings::point_lights.data[light_id]; // because the shadow maps align with the axes and the frustum planes are at 45 degrees // we can get the worldspace depth by taking the largest absolute axis @@ -35,14 +38,14 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v // mip-mapping functionality. The shadow maps have no mipmaps so Level just samples // from LOD 0. #ifdef NO_ARRAY_TEXTURES_SUPPORT - return textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls, depth); + return textureSampleCompare(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, frag_ls, depth); #else - return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth); + return textureSampleCompareLevel(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, frag_ls, i32(light_id), depth); #endif } fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = point_lights.data[light_id]; + let light = view_bindings::point_lights.data[light_id]; let surface_to_light = light.position_radius.xyz - frag_position.xyz; @@ -50,7 +53,7 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: ve var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); // reconstruct spot dir from x/z and y-direction flag spot_dir.y = sqrt(1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z); - if ((light.flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { + if ((light.flags & view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { spot_dir.y = -spot_dir.y; } @@ -90,16 +93,16 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: ve let depth = 0.1 / -projected_position.z; #ifdef NO_ARRAY_TEXTURES_SUPPORT - return textureSampleCompare(directional_shadow_textures, directional_shadow_textures_sampler, + return textureSampleCompare(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, shadow_uv, depth); #else - return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, - shadow_uv, i32(light_id) + lights.spot_light_shadowmap_offset, depth); + return textureSampleCompareLevel(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, + shadow_uv, i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset, depth); #endif } fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = lights.directional_lights[light_id]; + let light = view_bindings::lights.directional_lights[light_id]; // The normal bias is scaled to the texel size. let normal_offset = light.shadow_normal_bias * surface_normal.xyz; @@ -127,8 +130,8 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_nor // NOTE: Due to non-uniform control flow above, we must use the level variant of the texture // sampler to avoid use of implicit derivatives causing possible undefined behavior. #ifdef NO_ARRAY_TEXTURES_SUPPORT - return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, depth); + return textureSampleCompareLevel(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, light_local, depth); #else - return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), depth); + return textureSampleCompareLevel(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, light_local, i32(light_id), depth); #endif } diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 9fe80c904163d..28c0002c92a29 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -1,9 +1,22 @@ -// If using this WGSL snippet as an #import, a dedicated -// "joint_matricies" uniform of type SkinnedMesh must be added in the -// main shader. - #define_import_path bevy_pbr::skinning +#import bevy_pbr::mesh_types + +#ifdef SKINNED + +#ifdef MESH_BINDGROUP_1 + + @group(1) @binding(1) + var joint_matrices: bevy_pbr::mesh_types::SkinnedMesh; + +#else + + @group(2) @binding(1) + var joint_matrices: bevy_pbr::mesh_types::SkinnedMesh; + +#endif + + fn skin_model( indexes: vec4, weights: vec4, @@ -36,3 +49,5 @@ fn skin_normals( model[2].xyz )) * normal; } + +#endif \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index 651a10d491b06..affcdb5b43dd0 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -1,18 +1,10 @@ -#import bevy_pbr::mesh_types -#import bevy_pbr::mesh_view_bindings - -@group(1) @binding(0) -var mesh: Mesh; +#import bevy_pbr::mesh_bindings +#import bevy_pbr::mesh_functions #ifdef SKINNED -@group(1) @binding(1) -var joint_matrices: SkinnedMesh; -#import bevy_pbr::skinning + #import bevy_pbr::skinning #endif -// NOTE: Bindings must come before functions that use them! -#import bevy_pbr::mesh_functions - struct Vertex { @location(0) position: vec3, #ifdef SKINNED @@ -28,13 +20,13 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { #ifdef SKINNED - let model = skin_model(vertex.joint_indexes, vertex.joint_weights); + let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights); #else - let model = mesh.model; + let model = bevy_pbr::mesh_bindings::mesh.model; #endif var out: VertexOutput; - out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); + out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); return out; } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 6d08adb3025ad..6b915ab2ee316 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,7 +1,7 @@ use crate::MeshPipeline; use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}; use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, Handle, HandleUntyped}; use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; @@ -29,11 +29,11 @@ pub struct WireframePlugin; impl Plugin for WireframePlugin { fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( + load_internal_asset_with_path!( app, WIREFRAME_SHADER_HANDLE, "render/wireframe.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); app.register_type::() @@ -93,6 +93,10 @@ impl SpecializedMeshPipeline for WireframePipeline { ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone_weak(); + descriptor + .vertex + .shader_defs + .push("MESH_BINDGROUP_1".to_owned()); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.primitive.polygon_mode = PolygonMode::Line; descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 2bd7342aec3f3..fc9804be85357 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -68,6 +68,7 @@ regex = "1.5" copyless = "0.1.5" ddsfile = { version = "0.5.0", optional = true } ktx2 = { version = "0.3.0", optional = true } +naga_oil = { git = "https://github.com/robtfm/naga_oil" } # For ktx2 supercompression flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.2.4", optional = true } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index dbcc5ef93efd9..6c891e5a3e624 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,10 +1,8 @@ use crate::{ render_resource::{ - AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ComputePipeline, - ComputePipelineDescriptor, ProcessShaderError, ProcessedShader, + BindGroupLayout, BindGroupLayoutId, ComputePipeline, ComputePipelineDescriptor, RawComputePipelineDescriptor, RawFragmentState, RawRenderPipelineDescriptor, - RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, - ShaderProcessor, ShaderReflectError, + RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, Source, }, renderer::RenderDevice, Extract, @@ -17,11 +15,12 @@ use bevy_utils::{ tracing::{debug, error}, Entry, HashMap, HashSet, }; -use std::{hash::Hash, iter::FusedIterator, mem, ops::Deref, sync::Arc}; +use naga::valid::Capabilities; +use std::{hash::Hash, mem, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{ - BufferBindingType, PipelineLayoutDescriptor, ShaderModule, - VertexBufferLayout as RawVertexBufferLayout, + util::make_spirv, BufferBindingType, Features, PipelineLayoutDescriptor, ShaderModule, + ShaderModuleDescriptor, ShaderSource, VertexBufferLayout as RawVertexBufferLayout, }; /// A descriptor for a [`Pipeline`]. @@ -103,21 +102,80 @@ impl CachedPipelineState { #[derive(Default)] struct ShaderData { pipelines: HashSet, - processed_shaders: HashMap, Arc>, + processed_shaders: HashMap, (Option, Arc)>, resolved_imports: HashMap>, dependents: HashSet>, } -#[derive(Default)] struct ShaderCache { data: HashMap, ShaderData>, shaders: HashMap, Shader>, import_path_shaders: HashMap>, waiting_on_import: HashMap>>, - processor: ShaderProcessor, + composer: naga_oil::compose::Composer, } impl ShaderCache { + fn new(render_device: &RenderDevice) -> Self { + const CAPABILITIES: &[(Features, Capabilities)] = &[ + (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), + (Features::SHADER_FLOAT64, Capabilities::FLOAT64), + ( + Features::SHADER_PRIMITIVE_INDEX, + Capabilities::PRIMITIVE_INDEX, + ), + ]; + let features = render_device.features(); + let mut capabilities = Capabilities::empty(); + for (feature, capability) in CAPABILITIES { + if features.contains(*feature) { + capabilities |= *capability; + } + } + + #[cfg(debug_assertions)] + let composer = naga_oil::compose::Composer::default(); + #[cfg(not(debug_assertions))] + let composer = naga_oil::compose::Composer::non_validating(); + + let composer = composer.with_capabilities(capabilities); + + Self { + composer, + data: Default::default(), + shaders: Default::default(), + import_path_shaders: Default::default(), + waiting_on_import: Default::default(), + } + } + + fn add_import_to_composer( + composer: &mut naga_oil::compose::Composer, + import_path_shaders: &HashMap>, + shaders: &HashMap, Shader>, + import: &ShaderImport, + ) -> Result<(), PipelineCacheError> { + if !composer.contains_module(import.as_str()) { + if let Some(shader_handle) = import_path_shaders.get(import) { + if let Some(shader) = shaders.get(shader_handle) { + for import in &shader.imports { + Self::add_import_to_composer( + composer, + import_path_shaders, + shaders, + import, + )?; + } + + composer.add_composable_module(shader.into())?; + } + } + // if we fail to add a module the composer will tell us what is missing + } + + Ok(()) + } + fn get( &mut self, render_device: &RenderDevice, @@ -125,6 +183,28 @@ impl ShaderCache { handle: &Handle, shader_defs: &[String], ) -> Result, PipelineCacheError> { + self.get_entry(render_device, pipeline, handle, shader_defs) + .map(|entry| entry.1) + } + + fn get_shader_source( + &mut self, + render_device: &RenderDevice, + pipeline: CachedPipelineId, + handle: &Handle, + shader_defs: &[String], + ) -> Result, PipelineCacheError> { + self.get_entry(render_device, pipeline, handle, shader_defs) + .map(|entry| entry.0) + } + + fn get_entry( + &mut self, + render_device: &RenderDevice, + pipeline: CachedPipelineId, + handle: &Handle, + shader_defs: &[String], + ) -> Result<(Option<&naga::Module>, Arc), PipelineCacheError> { let shader = self .shaders .get(handle) @@ -146,7 +226,8 @@ impl ShaderCache { data.pipelines.insert(pipeline); // PERF: this shader_defs clone isn't great. use raw_entry_mut when it stabilizes - let module = match data.processed_shaders.entry(shader_defs.to_vec()) { + let (naga_module, shader_module) = match data.processed_shaders.entry(shader_defs.to_vec()) + { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { let mut shader_defs = shader_defs.to_vec(); @@ -170,21 +251,40 @@ impl ShaderCache { "processing shader {:?}, with shader defs {:?}", handle, shader_defs ); - let processed = self.processor.process( - shader, - &shader_defs, - &self.shaders, - &self.import_path_shaders, - )?; - let module_descriptor = match processed - .get_module_descriptor(render_device.features()) - { - Ok(module_descriptor) => module_descriptor, - Err(err) => { - return Err(PipelineCacheError::AsModuleDescriptorError(err, processed)); + let shader_source = match &shader.source { + Source::SpirV(data) => make_spirv(data), + Source::Naga(naga) => ShaderSource::Naga(naga_oil::util::clone_module(naga)), + _ => { + for import in shader.imports() { + Self::add_import_to_composer( + &mut self.composer, + &self.import_path_shaders, + &self.shaders, + import, + )?; + } + + let naga = self.composer.make_naga_module( + naga_oil::compose::NagaModuleDescriptor { + shader_defs: &shader_defs, + ..shader.into() + }, + )?; + + wgpu::ShaderSource::Naga(naga) } }; + let naga_module = match &shader_source { + ShaderSource::Naga(module) => Some(naga_oil::util::clone_module(module)), + _ => None, + }; + + let module_descriptor = ShaderModuleDescriptor { + label: None, + source: shader_source, + }; + render_device .wgpu_device() .push_error_scope(wgpu::ErrorFilter::Validation); @@ -201,11 +301,11 @@ impl ShaderCache { return Err(PipelineCacheError::CreateShaderModule(description)); } - entry.insert(Arc::new(shader_module)) + entry.insert((naga_module, Arc::new(shader_module))) } }; - Ok(module.clone()) + Ok((naga_module.as_ref(), shader_module.clone())) } fn clear(&mut self, handle: &Handle) -> Vec { @@ -216,6 +316,14 @@ impl ShaderCache { data.processed_shaders.clear(); pipelines_to_queue.extend(data.pipelines.iter().cloned()); shaders_to_clear.extend(data.dependents.iter().map(|h| h.clone_weak())); + + if let Some(Shader { + import_path: Some(import_path), + .. + }) = self.shaders.get(&handle) + { + self.composer.remove_composable_module(import_path.as_str()); + } } } @@ -325,9 +433,9 @@ impl PipelineCache { /// Create a new pipeline cache associated with the given render device. pub fn new(device: RenderDevice) -> Self { Self { + shader_cache: ShaderCache::new(&device), device, layout_cache: default(), - shader_cache: default(), waiting_pipelines: default(), pipelines: default(), } @@ -465,6 +573,17 @@ impl PipelineCache { id } + pub fn get_shader_source( + &mut self, + render_device: &RenderDevice, + pipeline: CachedRenderPipelineId, + handle: &Handle, + shader_defs: &[String], + ) -> Result, PipelineCacheError> { + self.shader_cache + .get_shader_source(render_device, pipeline.0, handle, shader_defs) + } + fn set_shader(&mut self, handle: &Handle, shader: &Shader) { let pipelines_to_queue = self.shader_cache.set_shader(handle, shader.clone()); for cached_pipeline in pipelines_to_queue { @@ -618,11 +737,8 @@ impl PipelineCache { | PipelineCacheError::ShaderImportNotYetAvailable => { /* retry */ } // shader could not be processed ... retrying won't help PipelineCacheError::ProcessShaderError(err) => { - error!("failed to process shader: {}", err); - continue; - } - PipelineCacheError::AsModuleDescriptorError(err, source) => { - log_shader_error(source, err); + let error_detail = err.emit_to_string(&self.shader_cache.composer); + error!("failed to process shader:\n{}", error_detail); continue; } PipelineCacheError::CreateShaderModule(description) => { @@ -672,97 +788,6 @@ impl PipelineCache { } } -fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { - use codespan_reporting::{ - diagnostic::{Diagnostic, Label}, - files::SimpleFile, - term, - }; - - match error { - AsModuleDescriptorError::ShaderReflectError(error) => match error { - ShaderReflectError::WgslParse(error) => { - let source = source - .get_wgsl_source() - .expect("non-wgsl source for wgsl error"); - let msg = error.emit_to_string(source); - error!("failed to process shader:\n{}", msg); - } - ShaderReflectError::GlslParse(errors) => { - let source = source - .get_glsl_source() - .expect("non-glsl source for glsl error"); - let files = SimpleFile::new("glsl", source); - let config = codespan_reporting::term::Config::default(); - let mut writer = term::termcolor::Ansi::new(Vec::new()); - - for err in errors { - let mut diagnostic = Diagnostic::error().with_message(err.kind.to_string()); - - if let Some(range) = err.meta.to_range() { - diagnostic = diagnostic.with_labels(vec![Label::primary((), range)]); - } - - 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::SpirVParse(error) => { - error!("failed to process shader:\n{}", error); - } - ShaderReflectError::Validation(error) => { - let (filename, source) = match source { - ProcessedShader::Wgsl(source) => ("wgsl", source.as_ref()), - ProcessedShader::Glsl(source, _) => ("glsl", source.as_ref()), - ProcessedShader::SpirV(_) => { - error!("failed to process shader:\n{}", error); - return; - } - }; - - let files = SimpleFile::new(filename, source); - let config = term::Config::default(); - let mut writer = term::termcolor::Ansi::new(Vec::new()); - - let diagnostic = Diagnostic::error() - .with_message(error.to_string()) - .with_labels( - error - .spans() - .map(|(span, desc)| { - Label::primary((), span.to_range().unwrap()) - .with_message(desc.to_owned()) - }) - .collect(), - ) - .with_notes( - ErrorSources::of(error) - .map(|source| source.to_string()) - .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); - } - }, - AsModuleDescriptorError::WgslConversion(error) => { - error!("failed to convert shader to wgsl: \n{}", error); - } - AsModuleDescriptorError::SpirVConversion(error) => { - error!("failed to convert shader to spirv: \n{}", error); - } - } -} - /// Type of error returned by a [`PipelineCache`] when the creation of a GPU pipeline object failed. #[derive(Error, Debug)] pub enum PipelineCacheError { @@ -771,35 +796,9 @@ pub enum PipelineCacheError { )] ShaderNotLoaded(Handle), #[error(transparent)] - ProcessShaderError(#[from] ProcessShaderError), - #[error("{0}")] - AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), + ProcessShaderError(#[from] naga_oil::compose::ComposerError), #[error("Shader import not yet available.")] ShaderImportNotYetAvailable, #[error("Could not create shader module: {0}")] CreateShaderModule(String), } - -struct ErrorSources<'a> { - current: Option<&'a (dyn std::error::Error + 'static)>, -} - -impl<'a> ErrorSources<'a> { - fn of(error: &'a dyn std::error::Error) -> Self { - Self { - current: error.source(), - } - } -} - -impl<'a> Iterator for ErrorSources<'a> { - type Item = &'a (dyn std::error::Error + 'static); - - fn next(&mut self) -> Option { - let current = self.current; - self.current = self.current.and_then(std::error::Error::source); - current - } -} - -impl<'a> FusedIterator for ErrorSources<'a> {} diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index c8faa95a7ca58..45f83b7caf6e9 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -99,6 +99,16 @@ impl Default for SpecializedMeshPipelines { } impl SpecializedMeshPipelines { + pub fn get( + &self, + key: &S::Key, + layout: &MeshVertexBufferLayout, + ) -> Option { + self.mesh_layout_cache + .get(layout) + .and_then(|map| map.get(key).copied()) + } + #[inline] pub fn specialize( &mut self, @@ -117,9 +127,11 @@ impl SpecializedMeshPipelines { .specialize(key.clone(), layout) .map_err(|mut err| { { - let SpecializedMeshPipelineError::MissingVertexAttribute(err) = - &mut err; - err.pipeline_type = Some(std::any::type_name::()); + if let SpecializedMeshPipelineError::MissingVertexAttribute(err) = + &mut err + { + err.pipeline_type = Some(std::any::type_name::()); + } } err })?; @@ -160,4 +172,6 @@ impl SpecializedMeshPipelines { pub enum SpecializedMeshPipelineError { #[error(transparent)] MissingVertexAttribute(#[from] MissingVertexAttributeError), + #[error("{0}")] + Custom(String), } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 5913d44dd3aab..c2316a979fb33 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -1,17 +1,10 @@ use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset}; use bevy_reflect::{TypeUuid, Uuid}; -use bevy_utils::{tracing::error, BoxedFuture, HashMap}; -use naga::back::wgsl::WriterFlags; -use naga::valid::Capabilities; -use naga::{valid::ModuleInfo, Module}; +use bevy_utils::{tracing::error, BoxedFuture}; use once_cell::sync::Lazy; use regex::Regex; -use std::{ - borrow::Cow, collections::HashSet, marker::Copy, ops::Deref, path::PathBuf, str::FromStr, -}; +use std::{borrow::Cow, marker::Copy, path::PathBuf, str::FromStr}; use thiserror::Error; -use wgpu::Features; -use wgpu::{util::make_spirv, ShaderModuleDescriptor, ShaderSource}; #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] pub struct ShaderId(Uuid); @@ -34,24 +27,50 @@ pub enum ShaderReflectError { #[error(transparent)] Validation(#[from] naga::WithSpan), } -/// A shader, as defined by its [`ShaderSource`] and [`ShaderStage`](naga::ShaderStage) +/// A shader, as defined by its [`ShaderSource`](wgpu::ShaderSource) and [`ShaderStage`](naga::ShaderStage) /// This is an "unprocessed" shader. It can contain preprocessor directives. #[derive(Debug, Clone, TypeUuid)] #[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"] pub struct Shader { - source: Source, - import_path: Option, - imports: Vec, + pub path: Option, + pub source: Source, + pub import_path: Option, + pub imports: Vec, + // extra imports not specified in the source string + pub additional_imports: Vec, } impl Shader { + pub fn from_wgsl_with_path( + source: impl Into>, + path: impl Into, + ) -> Shader { + Self { + path: Some(path.into()), + ..Self::from_wgsl(source) + } + } + pub fn from_wgsl(source: impl Into>) -> Shader { let source = source.into(); let shader_imports = SHADER_IMPORT_PROCESSOR.get_imports_from_str(&source); Shader { + path: None, imports: shader_imports.imports, import_path: shader_imports.import_path, source: Source::Wgsl(source), + additional_imports: Default::default(), + } + } + + pub fn from_glsl_with_path( + source: impl Into>, + stage: naga::ShaderStage, + path: impl Into, + ) -> Shader { + Self { + path: Some(path.into()), + ..Self::from_glsl(source, stage) } } @@ -59,17 +78,21 @@ impl Shader { let source = source.into(); let shader_imports = SHADER_IMPORT_PROCESSOR.get_imports_from_str(&source); Shader { + path: None, imports: shader_imports.imports, import_path: shader_imports.import_path, source: Source::Glsl(source, stage), + additional_imports: Default::default(), } } pub fn from_spirv(source: impl Into>) -> Shader { Shader { + path: None, imports: Vec::new(), import_path: None, source: Source::SpirV(source.into()), + additional_imports: Default::default(), } } @@ -93,141 +116,82 @@ impl Shader { } } -#[derive(Debug, Clone)] +impl<'a> From<&'a Shader> for naga_oil::compose::ComposableModuleDescriptor<'a> { + fn from(shader: &'a Shader) -> Self { + naga_oil::compose::ComposableModuleDescriptor { + source: shader.source.as_str(), + file_path: shader.path.as_deref().unwrap_or(""), + language: (&shader.source).into(), + additional_imports: &shader.additional_imports, + ..Default::default() + } + } +} + +impl<'a> From<&'a Shader> for naga_oil::compose::NagaModuleDescriptor<'a> { + fn from(shader: &'a Shader) -> Self { + naga_oil::compose::NagaModuleDescriptor { + source: shader.source.as_str(), + file_path: shader.path.as_deref().unwrap_or(""), + shader_type: (&shader.source).into(), + ..Default::default() + } + } +} + +#[derive(Debug)] pub enum Source { Wgsl(Cow<'static, str>), Glsl(Cow<'static, str>, naga::ShaderStage), SpirV(Cow<'static, [u8]>), + Naga(Box), // TODO: consider the following // PrecompiledSpirVMacros(HashMap, Vec>) - // NagaModule(Module) ... Module impls Serialize/Deserialize } -/// A processed [Shader]. This cannot contain preprocessor directions. It must be "ready to compile" -#[derive(PartialEq, Eq, Debug)] -pub enum ProcessedShader { - Wgsl(Cow<'static, str>), - Glsl(Cow<'static, str>, naga::ShaderStage), - SpirV(Cow<'static, [u8]>), +impl Clone for Source { + fn clone(&self) -> Self { + match self { + Self::Wgsl(arg0) => Self::Wgsl(arg0.clone()), + Self::Glsl(arg0, arg1) => Self::Glsl(arg0.clone(), *arg1), + Self::SpirV(arg0) => Self::SpirV(arg0.clone()), + Self::Naga(arg0) => Self::Naga(Box::new(naga_oil::util::clone_module(arg0))), + } + } } -impl ProcessedShader { - pub fn get_wgsl_source(&self) -> Option<&str> { - if let ProcessedShader::Wgsl(source) = self { - Some(source) - } else { - None +impl Source { + pub fn as_str(&self) -> &str { + match self { + Source::Wgsl(s) | Source::Glsl(s, _) => s, + Source::SpirV(_) | Source::Naga(_) => panic!("spirv/naga not yet implemented"), } } - pub fn get_glsl_source(&self) -> Option<&str> { - if let ProcessedShader::Glsl(source, _stage) = self { - Some(source) - } else { - None +} + +impl From<&Source> for naga_oil::compose::ShaderLanguage { + fn from(value: &Source) -> Self { + match value { + Source::Wgsl(_) => naga_oil::compose::ShaderLanguage::Wgsl, + Source::Glsl(_, _) => naga_oil::compose::ShaderLanguage::Glsl, + Source::SpirV(_) | Source::Naga(_) => panic!("spirv/naga not yet implemented"), } } +} - pub fn reflect(&self, features: Features) -> Result { - let module = match &self { - // TODO: process macros here - ProcessedShader::Wgsl(source) => naga::front::wgsl::parse_str(source)?, - ProcessedShader::Glsl(source, shader_stage) => { - let mut parser = naga::front::glsl::Parser::default(); - parser - .parse(&naga::front::glsl::Options::from(*shader_stage), source) - .map_err(ShaderReflectError::GlslParse)? +impl From<&Source> for naga_oil::compose::ShaderType { + fn from(value: &Source) -> Self { + match value { + Source::Wgsl(_) => naga_oil::compose::ShaderType::Wgsl, + Source::Glsl(_, naga::ShaderStage::Vertex) => naga_oil::compose::ShaderType::GlslVertex, + Source::Glsl(_, naga::ShaderStage::Fragment) => { + naga_oil::compose::ShaderType::GlslFragment } - ProcessedShader::SpirV(source) => naga::front::spv::parse_u8_slice( - source, - &naga::front::spv::Options { - adjust_coordinate_space: false, - ..naga::front::spv::Options::default() - }, - )?, - }; - const CAPABILITIES: &[(Features, Capabilities)] = &[ - (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), - (Features::SHADER_FLOAT64, Capabilities::FLOAT64), - ( - Features::SHADER_PRIMITIVE_INDEX, - Capabilities::PRIMITIVE_INDEX, - ), - ]; - let mut capabilities = Capabilities::empty(); - for (feature, capability) in CAPABILITIES { - if features.contains(*feature) { - capabilities |= *capability; + Source::Glsl(_, naga::ShaderStage::Compute) => { + panic!("glsl compute not yet implemented") } + Source::SpirV(_) | Source::Naga(_) => panic!("spirv/naga not yet implemented"), } - let module_info = - naga::valid::Validator::new(naga::valid::ValidationFlags::default(), capabilities) - .validate(&module)?; - - Ok(ShaderReflection { - module, - module_info, - }) - } - - pub fn get_module_descriptor( - &self, - features: Features, - ) -> Result { - Ok(ShaderModuleDescriptor { - label: None, - source: match self { - ProcessedShader::Wgsl(source) => { - #[cfg(debug_assertions)] - // 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(features)?; - - ShaderSource::Wgsl(source.clone()) - } - ProcessedShader::Glsl(_source, _stage) => { - let reflection = self.reflect(features)?; - // TODO: it probably makes more sense to convert this to spirv, but as of writing - // this comment, naga's spirv conversion is broken - let wgsl = reflection.get_wgsl()?; - ShaderSource::Wgsl(wgsl.into()) - } - ProcessedShader::SpirV(source) => make_spirv(source), - }, - }) - } -} - -#[derive(Error, Debug)] -pub enum AsModuleDescriptorError { - #[error(transparent)] - ShaderReflectError(#[from] ShaderReflectError), - #[error(transparent)] - WgslConversion(#[from] naga::back::wgsl::Error), - #[error(transparent)] - SpirVConversion(#[from] naga::back::spv::Error), -} - -pub struct ShaderReflection { - pub module: Module, - pub module_info: ModuleInfo, -} - -impl ShaderReflection { - pub fn get_spirv(&self) -> Result, naga::back::spv::Error> { - naga::back::spv::write_vec( - &self.module, - &self.module_info, - &naga::back::spv::Options { - flags: naga::back::spv::WriterFlags::empty(), - ..naga::back::spv::Options::default() - }, - None, - ) - } - - pub fn get_wgsl(&self) -> Result { - naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES) } } @@ -245,14 +209,19 @@ impl AssetLoader for ShaderLoader { let mut shader = match ext { "spv" => Shader::from_spirv(Vec::from(bytes)), - "wgsl" => Shader::from_wgsl(String::from_utf8(Vec::from(bytes))?), - "vert" => Shader::from_glsl( + "wgsl" => Shader::from_wgsl_with_path( + String::from_utf8(Vec::from(bytes))?, + load_context.path().to_string_lossy(), + ), + "vert" => Shader::from_glsl_with_path( String::from_utf8(Vec::from(bytes))?, naga::ShaderStage::Vertex, + load_context.path().to_string_lossy(), ), - "frag" => Shader::from_glsl( + "frag" => Shader::from_glsl_with_path( String::from_utf8(Vec::from(bytes))?, naga::ShaderStage::Fragment, + load_context.path().to_string_lossy(), ), _ => panic!("unhandled extension: {}", ext), }; @@ -283,24 +252,6 @@ impl AssetLoader for ShaderLoader { } } -#[derive(Error, Debug, PartialEq, Eq)] -pub enum ProcessShaderError { - #[error("Too many '# endif' lines. Each endif should be preceded by an if statement.")] - TooManyEndIfs, - #[error( - "Not enough '# endif' lines. Each if statement should be followed by an endif statement." - )] - NotEnoughEndIfs, - #[error("This Shader's format does not support processing shader defs.")] - ShaderFormatDoesNotSupportShaderDefs, - #[error("This Shader's formatdoes not support imports.")] - ShaderFormatDoesNotSupportImports, - #[error("Unresolved import: {0:?}.")] - UnresolvedImport(ShaderImport), - #[error("The shader import {0:?} does not match the source file type. Support for this might be added in the future.")] - MismatchedImportFormat(ShaderImport), -} - pub struct ShaderImportProcessor { import_asset_path_regex: Regex, import_custom_path_regex: Regex, @@ -313,12 +264,20 @@ pub enum ShaderImport { Custom(String), } +impl ShaderImport { + pub fn as_str(&self) -> &str { + match self { + ShaderImport::AssetPath(s) | ShaderImport::Custom(s) => s, + } + } +} + impl Default for ShaderImportProcessor { fn default() -> Self { Self { - import_asset_path_regex: Regex::new(r#"^\s*#\s*import\s+"(.+)""#).unwrap(), - import_custom_path_regex: Regex::new(r"^\s*#\s*import\s+(.+)").unwrap(), - define_import_path_regex: Regex::new(r"^\s*#\s*define_import_path\s+(.+)").unwrap(), + import_asset_path_regex: Regex::new(r#"^\s*#\s*import\s+"([^\s]+)""#).unwrap(), + import_custom_path_regex: Regex::new(r"^\s*#\s*import\s+([^\s]+)").unwrap(), + define_import_path_regex: Regex::new(r"^\s*#\s*define_import_path\s+([^\s]+)").unwrap(), } } } @@ -334,7 +293,7 @@ impl ShaderImportProcessor { match &shader.source { Source::Wgsl(source) => self.get_imports_from_str(source), Source::Glsl(source, _stage) => self.get_imports_from_str(source), - Source::SpirV(_source) => ShaderImports::default(), + Source::SpirV(_) | Source::Naga(_) => ShaderImports::default(), } } @@ -364,160 +323,6 @@ impl ShaderImportProcessor { pub static SHADER_IMPORT_PROCESSOR: Lazy = Lazy::new(ShaderImportProcessor::default); -pub struct ShaderProcessor { - ifdef_regex: Regex, - ifndef_regex: Regex, - else_regex: Regex, - endif_regex: Regex, -} - -impl Default for ShaderProcessor { - fn default() -> Self { - Self { - ifdef_regex: Regex::new(r"^\s*#\s*ifdef\s*([\w|\d|_]+)").unwrap(), - ifndef_regex: Regex::new(r"^\s*#\s*ifndef\s*([\w|\d|_]+)").unwrap(), - else_regex: Regex::new(r"^\s*#\s*else").unwrap(), - endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(), - } - } -} - -impl ShaderProcessor { - pub fn process( - &self, - shader: &Shader, - shader_defs: &[String], - shaders: &HashMap, Shader>, - import_handles: &HashMap>, - ) -> Result { - let shader_str = match &shader.source { - Source::Wgsl(source) => source.deref(), - Source::Glsl(source, _stage) => source.deref(), - Source::SpirV(source) => { - if shader_defs.is_empty() { - return Ok(ProcessedShader::SpirV(source.clone())); - } - return Err(ProcessShaderError::ShaderFormatDoesNotSupportShaderDefs); - } - }; - - let shader_defs_unique = HashSet::::from_iter(shader_defs.iter().cloned()); - let mut scopes = vec![true]; - let mut final_string = String::new(); - for line in shader_str.lines() { - if let Some(cap) = self.ifdef_regex.captures(line) { - let def = cap.get(1).unwrap(); - scopes.push(*scopes.last().unwrap() && shader_defs_unique.contains(def.as_str())); - } else if let Some(cap) = self.ifndef_regex.captures(line) { - let def = cap.get(1).unwrap(); - scopes.push(*scopes.last().unwrap() && !shader_defs_unique.contains(def.as_str())); - } else if self.else_regex.is_match(line) { - let mut is_parent_scope_truthy = true; - if scopes.len() > 1 { - is_parent_scope_truthy = scopes[scopes.len() - 2]; - } - if let Some(last) = scopes.last_mut() { - *last = is_parent_scope_truthy && !*last; - } - } else if self.endif_regex.is_match(line) { - scopes.pop(); - if scopes.is_empty() { - return Err(ProcessShaderError::TooManyEndIfs); - } - } else if *scopes.last().unwrap() { - if let Some(cap) = SHADER_IMPORT_PROCESSOR - .import_asset_path_regex - .captures(line) - { - let import = ShaderImport::AssetPath(cap.get(1).unwrap().as_str().to_string()); - self.apply_import( - import_handles, - shaders, - &import, - shader, - shader_defs, - &mut final_string, - )?; - } else if let Some(cap) = SHADER_IMPORT_PROCESSOR - .import_custom_path_regex - .captures(line) - { - let import = ShaderImport::Custom(cap.get(1).unwrap().as_str().to_string()); - self.apply_import( - import_handles, - shaders, - &import, - shader, - shader_defs, - &mut final_string, - )?; - } else if SHADER_IMPORT_PROCESSOR - .define_import_path_regex - .is_match(line) - { - // ignore import path lines - } else { - final_string.push_str(line); - final_string.push('\n'); - } - } - } - - if scopes.len() != 1 { - return Err(ProcessShaderError::NotEnoughEndIfs); - } - - let processed_source = Cow::from(final_string); - - match &shader.source { - Source::Wgsl(_source) => Ok(ProcessedShader::Wgsl(processed_source)), - Source::Glsl(_source, stage) => Ok(ProcessedShader::Glsl(processed_source, *stage)), - Source::SpirV(_source) => { - unreachable!("SpirV has early return"); - } - } - } - - fn apply_import( - &self, - import_handles: &HashMap>, - shaders: &HashMap, Shader>, - import: &ShaderImport, - shader: &Shader, - shader_defs: &[String], - final_string: &mut String, - ) -> Result<(), ProcessShaderError> { - let imported_shader = import_handles - .get(import) - .and_then(|handle| shaders.get(handle)) - .ok_or_else(|| ProcessShaderError::UnresolvedImport(import.clone()))?; - let imported_processed = - self.process(imported_shader, shader_defs, shaders, import_handles)?; - - match &shader.source { - Source::Wgsl(_) => { - if let ProcessedShader::Wgsl(import_source) = &imported_processed { - final_string.push_str(import_source); - } else { - return Err(ProcessShaderError::MismatchedImportFormat(import.clone())); - } - } - Source::Glsl(_, _) => { - if let ProcessedShader::Glsl(import_source, _) = &imported_processed { - final_string.push_str(import_source); - } else { - return Err(ProcessShaderError::MismatchedImportFormat(import.clone())); - } - } - Source::SpirV(_) => { - return Err(ProcessShaderError::ShaderFormatDoesNotSupportImports); - } - } - - Ok(()) - } -} - /// A reference to a shader asset. pub enum ShaderRef { /// Use the "default" shader for the current context. @@ -545,799 +350,3 @@ impl From<&'static str> for ShaderRef { Self::Path(AssetPath::from(path)) } } - -#[cfg(test)] -mod tests { - use bevy_asset::{Handle, HandleUntyped}; - use bevy_reflect::TypeUuid; - use bevy_utils::HashMap; - use naga::ShaderStage; - - use crate::render_resource::{ProcessShaderError, Shader, ShaderImport, ShaderProcessor}; - #[rustfmt::skip] -const WGSL: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#ifdef TEXTURE -@group(1) @binding(0) -var sprite_texture: texture_2d; -#endif - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - - const WGSL_ELSE: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#ifdef TEXTURE -@group(1) @binding(0) -var sprite_texture: texture_2d; -#else -@group(1) @binding(0) -var sprite_texture: texture_2d_array; -#endif - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - - const WGSL_NESTED_IFDEF: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -# ifdef TEXTURE -# ifdef ATTRIBUTE -@group(1) @binding(0) -var sprite_texture: texture_2d; -# endif -# endif - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - - const WGSL_NESTED_IFDEF_ELSE: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -# ifdef TEXTURE -# ifdef ATTRIBUTE -@group(1) @binding(0) -var sprite_texture: texture_2d; -#else -@group(1) @binding(0) -var sprite_texture: texture_2d_array; -# endif -# endif - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - - #[test] - fn process_shader_def_defined() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -@group(1) @binding(0) -var sprite_texture: texture_2d; - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL), - &["TEXTURE".to_string()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_not_defined() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -@group(1) @binding(0) -var sprite_texture: texture_2d_array; - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_ELSE), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_unclosed() { - #[rustfmt::skip] - const INPUT: &str = r" -#ifdef FOO -"; - let processor = ShaderProcessor::default(); - let result = processor.process( - &Shader::from_wgsl(INPUT), - &[], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!(result, Err(ProcessShaderError::NotEnoughEndIfs)); - } - - #[test] - fn process_shader_def_too_closed() { - #[rustfmt::skip] - const INPUT: &str = r" -#endif -"; - let processor = ShaderProcessor::default(); - let result = processor.process( - &Shader::from_wgsl(INPUT), - &[], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!(result, Err(ProcessShaderError::TooManyEndIfs)); - } - - #[test] - fn process_shader_def_commented() { - #[rustfmt::skip] - const INPUT: &str = r" -// #ifdef FOO -fn foo() { } -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(INPUT), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), INPUT); - } - - #[test] - fn process_import_wgsl() { - #[rustfmt::skip] - const FOO: &str = r" -fn foo() { } -"; - #[rustfmt::skip] - const INPUT: &str = r" -#import FOO -fn bar() { } -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - -fn foo() { } -fn bar() { } -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - let foo_handle = Handle::::default(); - shaders.insert(foo_handle.clone_weak(), Shader::from_wgsl(FOO)); - import_handles.insert( - ShaderImport::Custom("FOO".to_string()), - foo_handle.clone_weak(), - ); - let result = processor - .process(&Shader::from_wgsl(INPUT), &[], &shaders, &import_handles) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_import_glsl() { - #[rustfmt::skip] - const FOO: &str = r" -void foo() { } -"; - #[rustfmt::skip] - const INPUT: &str = r" -#import FOO -void bar() { } -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - -void foo() { } -void bar() { } -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - let foo_handle = Handle::::default(); - shaders.insert( - foo_handle.clone_weak(), - Shader::from_glsl(FOO, ShaderStage::Vertex), - ); - import_handles.insert( - ShaderImport::Custom("FOO".to_string()), - foo_handle.clone_weak(), - ); - let result = processor - .process( - &Shader::from_glsl(INPUT, ShaderStage::Vertex), - &[], - &shaders, - &import_handles, - ) - .unwrap(); - assert_eq!(result.get_glsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_outer_defined_inner_not() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["TEXTURE".to_string()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_outer_defined_inner_else() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -@group(1) @binding(0) -var sprite_texture: texture_2d_array; - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF_ELSE), - &["TEXTURE".to_string()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_neither_defined() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_neither_defined_else() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF_ELSE), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_inner_defined_outer_not() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["ATTRIBUTE".to_string()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_nested_shader_def_both_defined() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -@group(1) @binding(0) -var sprite_texture: texture_2d; - -struct VertexOutput { - @location(0) uv: vec2, - @builtin(position) position: vec4, -}; - -@vertex -fn vertex( - @location(0) vertex_position: vec3, - @location(1) vertex_uv: vec2 -) -> VertexOutput { - var out: VertexOutput; - out.uv = vertex_uv; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["TEXTURE".to_string(), "ATTRIBUTE".to_string()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_import_ifdef() { - #[rustfmt::skip] - const FOO: &str = r" -#ifdef IMPORT_MISSING -fn in_import_missing() { } -#endif -#ifdef IMPORT_PRESENT -fn in_import_present() { } -#endif -"; - #[rustfmt::skip] - const INPUT: &str = r" -#import FOO -#ifdef MAIN_MISSING -fn in_main_missing() { } -#endif -#ifdef MAIN_PRESENT -fn in_main_present() { } -#endif -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - -fn in_import_present() { } -fn in_main_present() { } -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - let foo_handle = Handle::::default(); - shaders.insert(foo_handle.clone_weak(), Shader::from_wgsl(FOO)); - import_handles.insert( - ShaderImport::Custom("FOO".to_string()), - foo_handle.clone_weak(), - ); - let result = processor - .process( - &Shader::from_wgsl(INPUT), - &["MAIN_PRESENT".to_string(), "IMPORT_PRESENT".to_string()], - &shaders, - &import_handles, - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_import_in_import() { - #[rustfmt::skip] - const BAR: &str = r" -#ifdef DEEP -fn inner_import() { } -#endif -"; - const FOO: &str = r" -#import BAR -fn import() { } -"; - #[rustfmt::skip] - const INPUT: &str = r" -#import FOO -fn in_main() { } -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - - -fn inner_import() { } -fn import() { } -fn in_main() { } -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - { - let bar_handle = Handle::::default(); - shaders.insert(bar_handle.clone_weak(), Shader::from_wgsl(BAR)); - import_handles.insert( - ShaderImport::Custom("BAR".to_string()), - bar_handle.clone_weak(), - ); - } - { - let foo_handle = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1).typed(); - shaders.insert(foo_handle.clone_weak(), Shader::from_wgsl(FOO)); - import_handles.insert( - ShaderImport::Custom("FOO".to_string()), - foo_handle.clone_weak(), - ); - } - let result = processor - .process( - &Shader::from_wgsl(INPUT), - &["DEEP".to_string()], - &shaders, - &import_handles, - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_import_in_ifdef() { - #[rustfmt::skip] - const BAR: &str = r" -fn bar() { } -"; - #[rustfmt::skip] - const BAZ: &str = r" -fn baz() { } -"; - #[rustfmt::skip] - const INPUT: &str = r" -#ifdef FOO - #import BAR -#else - #import BAZ -#endif -"; - #[rustfmt::skip] - const EXPECTED_FOO: &str = r" - -fn bar() { } -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - -fn baz() { } -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - { - let bar_handle = Handle::::default(); - shaders.insert(bar_handle.clone_weak(), Shader::from_wgsl(BAR)); - import_handles.insert( - ShaderImport::Custom("BAR".to_string()), - bar_handle.clone_weak(), - ); - } - { - let baz_handle = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1).typed(); - shaders.insert(baz_handle.clone_weak(), Shader::from_wgsl(BAZ)); - import_handles.insert( - ShaderImport::Custom("BAZ".to_string()), - baz_handle.clone_weak(), - ); - } - let result = processor - .process( - &Shader::from_wgsl(INPUT), - &["FOO".to_string()], - &shaders, - &import_handles, - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED_FOO); - - let result = processor - .process(&Shader::from_wgsl(INPUT), &[], &shaders, &import_handles) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } -} diff --git a/crates/bevy_sprite/src/mesh2d/color_material.rs b/crates/bevy_sprite/src/mesh2d/color_material.rs index b0f68db9795e4..f5aad0f0d9ce8 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.rs +++ b/crates/bevy_sprite/src/mesh2d/color_material.rs @@ -1,5 +1,5 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, Assets, Handle, HandleUntyped}; use bevy_math::Vec4; use bevy_reflect::TypeUuid; use bevy_render::{ @@ -16,11 +16,11 @@ pub struct ColorMaterialPlugin; impl Plugin for ColorMaterialPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( + load_internal_asset_with_path!( app, COLOR_MATERIAL_SHADER_HANDLE, "color_material.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); app.add_plugin(Material2dPlugin::::default()); diff --git a/crates/bevy_sprite/src/mesh2d/color_material.wgsl b/crates/bevy_sprite/src/mesh2d/color_material.wgsl index 3b5893d4ce285..a9c4318368d35 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.wgsl +++ b/crates/bevy_sprite/src/mesh2d/color_material.wgsl @@ -1,5 +1,5 @@ #import bevy_sprite::mesh2d_types -#import bevy_sprite::mesh2d_view_bindings +#import bevy_sprite::mesh2d_vertex_output struct ColorMaterial { color: vec4, @@ -16,21 +16,19 @@ var texture: texture_2d; var texture_sampler: sampler; @group(2) @binding(0) -var mesh: Mesh2d; - -struct FragmentInput { - @builtin(front_facing) is_front: bool, - #import bevy_sprite::mesh2d_vertex_output -}; +var mesh: bevy_sprite::mesh2d_types::Mesh2d; @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + @builtin(front_facing) is_front: bool, + mesh: bevy_sprite::mesh2d_vertex_output::MeshVertexOutput, +) -> @location(0) vec4 { var output_color: vec4 = material.color; #ifdef VERTEX_COLORS - output_color = output_color * in.color; + output_color = output_color * mesh.color; #endif if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) { - output_color = output_color * textureSample(texture, texture_sampler, in.uv); + output_color = output_color * textureSample(texture, texture_sampler, mesh.uv); } return output_color; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 54927a0077031..0a8c558163576 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,5 +1,5 @@ use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem, SystemState}, @@ -54,43 +54,48 @@ pub const MESH2D_SHADER_HANDLE: HandleUntyped = impl Plugin for Mesh2dRenderPlugin { fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_VERTEX_OUTPUT, "mesh2d_vertex_output.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_VIEW_TYPES_HANDLE, "mesh2d_view_types.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_VIEW_BINDINGS_HANDLE, "mesh2d_view_bindings.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_TYPES_HANDLE, "mesh2d_types.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_BINDINGS_HANDLE, "mesh2d_bindings.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path ); - load_internal_asset!( + load_internal_asset_with_path!( app, MESH2D_FUNCTIONS_HANDLE, "mesh2d_functions.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_path + ); + load_internal_asset_with_path!( + app, + MESH2D_SHADER_HANDLE, + "mesh2d.wgsl", + Shader::from_wgsl_with_path ); - load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl index 00c3a9ab74416..53532d9ac40ab 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl @@ -1,8 +1,6 @@ -#import bevy_sprite::mesh2d_view_bindings #import bevy_sprite::mesh2d_bindings - -// NOTE: Bindings must come before functions that use them! -#import bevy_sprite::mesh2d_functions +#import bevy_sprite::mesh2d_functions as mesh_functions +#import bevy_sprite::mesh2d_vertex_output struct Vertex { @location(0) position: vec3, @@ -16,20 +14,20 @@ struct Vertex { #endif }; -struct VertexOutput { - @builtin(position) clip_position: vec4, - #import bevy_sprite::mesh2d_vertex_output -} - @vertex -fn vertex(vertex: Vertex) -> VertexOutput { - var out: VertexOutput; +fn vertex(vertex: Vertex) -> bevy_sprite::mesh2d_vertex_output::MeshVertexOutput { + var out: bevy_sprite::mesh2d_vertex_output::MeshVertexOutput; +#ifdef VERTEX_UVS out.uv = vertex.uv; - out.world_position = mesh2d_position_local_to_world(mesh.model, vec4(vertex.position, 1.0)); - out.clip_position = mesh2d_position_world_to_clip(out.world_position); - out.world_normal = mesh2d_normal_local_to_world(vertex.normal); +#endif + out.world_position = mesh_functions::mesh2d_position_local_to_world( + bevy_sprite::mesh2d_bindings::mesh.model, + vec4(vertex.position, 1.0) + ); + out.clip_position = mesh_functions::mesh2d_position_world_to_clip(out.world_position); + out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal); #ifdef VERTEX_TANGENTS - out.world_tangent = mesh2d_tangent_local_to_world(vertex.tangent); + out.world_tangent = mesh_functions::mesh2d_tangent_local_to_world(vertex.tangent); #endif #ifdef VERTEX_COLORS out.color = vertex.color; @@ -37,12 +35,10 @@ fn vertex(vertex: Vertex) -> VertexOutput { return out; } -struct FragmentInput { - @builtin(front_facing) is_front: bool, - #import bevy_sprite::mesh2d_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + @builtin(front_facing) is_front: bool, + mesh: bevy_sprite::mesh2d_vertex_output::MeshVertexOutput, +) -> @location(0) vec4 { return vec4(1.0, 0.0, 1.0, 1.0); } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl index f26a0442c95db..6d51f963e083f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl @@ -3,4 +3,4 @@ #import bevy_sprite::mesh2d_types @group(2) @binding(0) -var mesh: Mesh2d; +var mesh: bevy_sprite::mesh2d_types::Mesh2d; diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl index 5342b638494de..b260efb9903e1 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl @@ -1,11 +1,14 @@ #define_import_path bevy_sprite::mesh2d_functions +#import bevy_sprite::mesh2d_view_bindings +#import bevy_sprite::mesh2d_bindings as mesh_bindings + fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; } fn mesh2d_position_world_to_clip(world_position: vec4) -> vec4 { - return view.view_proj * world_position; + return bevy_sprite::mesh2d_view_bindings::view.view_proj * world_position; } // NOTE: The intermediate world_position assignment is important @@ -18,9 +21,9 @@ fn mesh2d_position_local_to_clip(model: mat4x4, vertex_position: vec4) fn mesh2d_normal_local_to_world(vertex_normal: vec3) -> vec3 { return mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz + mesh_bindings::mesh.inverse_transpose_model[0].xyz, + mesh_bindings::mesh.inverse_transpose_model[1].xyz, + mesh_bindings::mesh.inverse_transpose_model[2].xyz ) * vertex_normal; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl index cd0c2e8f42f44..f4c8fff1a9785 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl @@ -1,11 +1,14 @@ #define_import_path bevy_sprite::mesh2d_vertex_output -@location(0) world_position: vec4, -@location(1) world_normal: vec3, -@location(2) uv: vec2, -#ifdef VERTEX_TANGENTS -@location(3) world_tangent: vec4, -#endif -#ifdef VERTEX_COLORS -@location(4) color: vec4, -#endif +struct MeshVertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, + @location(1) world_normal: vec3, + @location(2) uv: vec2, + #ifdef VERTEX_TANGENTS + @location(3) world_tangent: vec4, + #endif + #ifdef VERTEX_COLORS + @location(4) color: vec4, + #endif +} diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl index cc138d28facb7..173d37b879a35 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl @@ -3,4 +3,4 @@ #import bevy_sprite::mesh2d_view_types @group(0) @binding(0) -var view: View; +var view: bevy_sprite::mesh2d_view_types::View; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 4c3f602f54b2d..41dc9aa17fa38 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -7,7 +7,7 @@ pub use render_pass::*; use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset_with_path, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_math::{Mat4, Rect, Vec2, Vec3, Vec4Swizzles}; use bevy_reflect::TypeUuid; @@ -55,7 +55,12 @@ pub enum RenderUiSystem { } pub fn build_ui_render(app: &mut App) { - load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); + load_internal_asset_with_path!( + app, + UI_SHADER_HANDLE, + "ui.wgsl", + Shader::from_wgsl_with_path + ); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index cdd31c8f93e42..5e509dbab92bf 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -212,14 +212,11 @@ type DrawColoredMesh2d = ( // using `include_str!()`, or loaded like any other asset with `asset_server.load()`. const COLORED_MESH2D_SHADER: &str = r" // Import the standard 2d mesh uniforms and set their bind groups -#import bevy_sprite::mesh2d_types -#import bevy_sprite::mesh2d_view_bindings +#import bevy_sprite::mesh2d_types as MeshTypes +#import bevy_sprite::mesh2d_functions as MeshFunctions @group(1) @binding(0) -var mesh: Mesh2d; - -// NOTE: Bindings must come before functions that use them! -#import bevy_sprite::mesh2d_functions +var mesh: MeshTypes::Mesh2d; // The structure of the vertex buffer is as specified in `specialize()` struct Vertex { @@ -239,7 +236,7 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; // Project the world position of the mesh into screen position - out.clip_position = mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + out.clip_position = MeshFunctions::mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); // Unpack the `u32` from the vertex buffer into the `vec4` used by the fragment shader out.color = vec4((vec4(vertex.color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; return out; diff --git a/examples/README.md b/examples/README.md index 8f8b57759727a..12e84178be989 100644 --- a/examples/README.md +++ b/examples/README.md @@ -263,8 +263,10 @@ Example | Description [Material](../examples/shader/shader_material.rs) | A shader and a material that uses it [Material - GLSL](../examples/shader/shader_material_glsl.rs) | A shader that uses the GLSL shading language [Material - Screenspace Texture](../examples/shader/shader_material_screenspace_texture.rs) | A shader that samples a texture with view-independent UV coordinates +[Material with core function override](../examples/shader/shader_material_override.rs) | A shader that overrides a core pbr function for a material [Post Processing](../examples/shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one [Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader) +[core function default override](../examples/shader/pbr_global_override.rs) | a global override for pbr functions for all materials ## Stress Tests diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index ce8c4ed06830c..703fb69a96770 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -236,8 +236,8 @@ impl SpecializedMeshPipeline for CustomPipeline { descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), - self.mesh_pipeline.mesh_layout.clone(), self.time_bind_group_layout.clone(), + self.mesh_pipeline.mesh_layout.clone(), ]); Ok(descriptor) } @@ -246,8 +246,8 @@ impl SpecializedMeshPipeline for CustomPipeline { type DrawCustom = ( SetItemPipeline, SetMeshViewBindGroup<0>, - SetMeshBindGroup<1>, - SetTimeBindGroup<2>, + SetTimeBindGroup<1>, + SetMeshBindGroup<2>, DrawMesh, ); diff --git a/examples/shader/pbr_global_override.rs b/examples/shader/pbr_global_override.rs new file mode 100644 index 0000000000000..62b026f7c4443 --- /dev/null +++ b/examples/shader/pbr_global_override.rs @@ -0,0 +1,267 @@ +//! Illustrates different lights of various types and colors, some static, some moving over +//! a simple scene. + +use bevy::{pbr::PbrShaderFunctionOverrides, prelude::*}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(movement) + .add_system(animate_light_direction) + .run(); +} + +#[derive(Component)] +struct Movable; + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, + mut pbr_overrides: ResMut, +) { + println!("press enter to toggle global light overrides"); + + pbr_overrides + .overrides + .push(asset_server.load("shaders/global_light_override.wgsl")); + + // ground plane + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })), + material: materials.add(StandardMaterial { + base_color: Color::WHITE, + perceptual_roughness: 1.0, + ..default() + }), + ..default() + }); + + // left wall + let mut transform = Transform::from_xyz(2.5, 2.5, 0.0); + transform.rotate_z(std::f32::consts::FRAC_PI_2); + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))), + transform, + material: materials.add(StandardMaterial { + base_color: Color::INDIGO, + perceptual_roughness: 1.0, + ..default() + }), + ..default() + }); + // back (right) wall + let mut transform = Transform::from_xyz(0.0, 2.5, -2.5); + transform.rotate_x(std::f32::consts::FRAC_PI_2); + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))), + transform, + material: materials.add(StandardMaterial { + base_color: Color::INDIGO, + perceptual_roughness: 1.0, + ..default() + }), + ..default() + }); + + // cube + commands + .spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(StandardMaterial { + base_color: Color::PINK, + ..default() + }), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }) + .insert(Movable); + // sphere + commands + .spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::UVSphere { + radius: 0.5, + ..default() + })), + material: materials.add(StandardMaterial { + base_color: Color::LIME_GREEN, + ..default() + }), + transform: Transform::from_xyz(1.5, 1.0, 1.5), + ..default() + }) + .insert(Movable); + + // ambient light + commands.insert_resource(AmbientLight { + color: Color::ORANGE_RED, + brightness: 0.02, + }); + + // red point light + commands + .spawn_bundle(PointLightBundle { + // transform: Transform::from_xyz(5.0, 8.0, 2.0), + transform: Transform::from_xyz(1.0, 2.0, 0.0), + point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb + color: Color::RED, + shadows_enabled: true, + ..default() + }, + ..default() + }) + .with_children(|builder| { + builder.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::UVSphere { + radius: 0.1, + ..default() + })), + material: materials.add(StandardMaterial { + base_color: Color::RED, + emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0), + ..default() + }), + ..default() + }); + }); + + // green spot light + commands + .spawn_bundle(PointLightBundle { + transform: Transform::from_xyz(-1.0, 2.0, 0.0) + .looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z), + point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb + color: Color::GREEN, + shadows_enabled: true, + ..default() + }, + ..default() + }) + .with_children(|builder| { + builder.spawn_bundle(PbrBundle { + transform: Transform::from_rotation(Quat::from_rotation_x( + std::f32::consts::PI / 2.0, + )), + mesh: meshes.add(Mesh::from(shape::UVSphere { + radius: 0.1, + ..default() + })), + material: materials.add(StandardMaterial { + base_color: Color::GREEN, + emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0), + ..default() + }), + ..default() + }); + }); + + // blue point light + commands + .spawn_bundle(PointLightBundle { + // transform: Transform::from_xyz(5.0, 8.0, 2.0), + transform: Transform::from_xyz(0.0, 4.0, 0.0), + point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb + color: Color::BLUE, + shadows_enabled: true, + ..default() + }, + ..default() + }) + .with_children(|builder| { + builder.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::UVSphere { + radius: 0.1, + ..default() + })), + material: materials.add(StandardMaterial { + base_color: Color::BLUE, + emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0), + ..default() + }), + ..default() + }); + }); + + // directional 'sun' light + const HALF_SIZE: f32 = 10.0; + commands.spawn_bundle(DirectionalLightBundle { + directional_light: DirectionalLight { + // Configure the projection to better fit the scene + shadow_projection: OrthographicProjection { + left: -HALF_SIZE, + right: HALF_SIZE, + bottom: -HALF_SIZE, + top: HALF_SIZE, + near: -10.0 * HALF_SIZE, + far: 10.0 * HALF_SIZE, + ..default() + }, + shadows_enabled: true, + ..default() + }, + transform: Transform { + translation: Vec3::new(0.0, 2.0, 0.0), + rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4), + ..default() + }, + ..default() + }); + + // camera + commands.spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +fn animate_light_direction( + time: Res