diff --git a/assets/shaders/animate_shader.wgsl b/assets/shaders/animate_shader.wgsl index 6726fa6263cc6..76112e147776b 100644 --- a/assets/shaders/animate_shader.wgsl +++ b/assets/shaders/animate_shader.wgsl @@ -1,6 +1,6 @@ -#import bevy_pbr::mesh_types // The time since startup data is in the globals binding which is part of the mesh_view_bindings import -#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_view_bindings globals +#import bevy_pbr::mesh_vertex_output MeshVertexOutput fn oklab_to_linear_srgb(c: vec3) -> vec3 { let L = c.x; @@ -22,12 +22,8 @@ fn oklab_to_linear_srgb(c: vec3) -> vec3 { ); } -struct FragmentInput { - #import bevy_pbr::mesh_vertex_output -} - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment(in: MeshVertexOutput) -> @location(0) vec4 { let speed = 2.0; // The globals binding contains various global values like time // which is the time since startup in seconds diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index e7618ff97045f..17c714f420742 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -1,60 +1,51 @@ -#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::fog -#import bevy_pbr::pbr_functions -#import bevy_pbr::pbr_ambient +#import bevy_pbr::mesh_vertex_output MeshVertexOutput +#import bevy_pbr::mesh_view_bindings view +#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT +#import bevy_core_pipeline::tonemapping tone_mapping +#import bevy_pbr::pbr_functions as fns @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: 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: fns::PbrInput = fns::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 = prepare_world_normal( - in.world_normal, + pbr_input.frag_coord = mesh.position; + pbr_input.world_position = mesh.world_position; + pbr_input.world_normal = fns::prepare_world_normal( + mesh.world_normal, (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, - in.is_front, + is_front, ); pbr_input.is_orthographic = view.projection[3].w == 1.0; - pbr_input.N = apply_normal_mapping( + pbr_input.N = fns::apply_normal_mapping( pbr_input.material.flags, - pbr_input.world_normal, + mesh.world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP - in.world_tangent, + mesh.world_tangent, #endif #endif - in.uv, + mesh.uv, ); - pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); + pbr_input.V = fns::calculate_view(mesh.world_position, pbr_input.is_orthographic); - return tone_mapping(pbr(pbr_input)); + return tone_mapping(fns::pbr(pbr_input), view.color_grading); } diff --git a/assets/shaders/cubemap_unlit.wgsl b/assets/shaders/cubemap_unlit.wgsl index 6837384dea3ac..b0c3672848fcf 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 MeshVertexOutput #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: 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_gltf_2d.wgsl b/assets/shaders/custom_gltf_2d.wgsl index d841a17d37ef7..58058d9501033 100644 --- a/assets/shaders/custom_gltf_2d.wgsl +++ b/assets/shaders/custom_gltf_2d.wgsl @@ -1,6 +1,6 @@ -#import bevy_sprite::mesh2d_view_bindings -#import bevy_sprite::mesh2d_bindings -#import bevy_sprite::mesh2d_functions +#import bevy_sprite::mesh2d_view_bindings globals +#import bevy_sprite::mesh2d_bindings mesh +#import bevy_sprite::mesh2d_functions mesh2d_position_local_to_clip struct Vertex { @location(0) position: vec3, diff --git a/assets/shaders/custom_material.frag b/assets/shaders/custom_material.frag index bf46d1e5334fb..04b644fda876c 100644 --- a/assets/shaders/custom_material.frag +++ b/assets/shaders/custom_material.frag @@ -10,7 +10,11 @@ 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 +// FIXME - this doesn't work any more ... +// #import bevy_pbr::pbr_functions as PbrFuncs void main() { + // o_Target = PbrFuncs::tone_mapping(Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv)); o_Target = 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..d09f5b4e3d2a4 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output MeshVertexOutput + struct CustomMaterial { color: vec4, }; @@ -11,7 +13,7 @@ var base_color_sampler: sampler; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: 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_screenspace_texture.wgsl b/assets/shaders/custom_material_screenspace_texture.wgsl index e615afdfb1437..99c100d15e1ca 100644 --- a/assets/shaders/custom_material_screenspace_texture.wgsl +++ b/assets/shaders/custom_material_screenspace_texture.wgsl @@ -1,5 +1,6 @@ -#import bevy_pbr::mesh_view_bindings -#import bevy_pbr::utils +#import bevy_pbr::mesh_view_bindings view +#import bevy_pbr::mesh_vertex_output MeshVertexOutput +#import bevy_pbr::utils coords_to_viewport_uv @group(1) @binding(0) var texture: texture_2d; @@ -8,10 +9,9 @@ var texture_sampler: sampler; @fragment fn fragment( - @builtin(position) position: vec4, - #import bevy_pbr::mesh_vertex_output + mesh: MeshVertexOutput, ) -> @location(0) vec4 { - let viewport_uv = coords_to_viewport_uv(position.xy, view.viewport); + let viewport_uv = coords_to_viewport_uv(mesh.position.xy, view.viewport); let color = textureSample(texture, texture_sampler, viewport_uv); return color; } diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index dd7cfc150308d..2d1307bc7ec27 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_bindings mesh +#import bevy_pbr::mesh_functions mesh_position_local_to_clip 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 = mesh_position_local_to_clip( + mesh.model, + vec4(vertex.position, 1.0) + ); out.blend_color = vertex.blend_color; return out; } diff --git a/assets/shaders/instancing.wgsl b/assets/shaders/instancing.wgsl index 7cb00b039a84a..cf41bb01311fe 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_functions mesh_position_local_to_clip +#import bevy_pbr::mesh_bindings mesh 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 = mesh_position_local_to_clip( + 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..dcf4cac57a438 100644 --- a/assets/shaders/line_material.wgsl +++ b/assets/shaders/line_material.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output MeshVertexOutput + struct LineMaterial { color: vec4, }; @@ -7,7 +9,7 @@ var material: LineMaterial; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: MeshVertexOutput, ) -> @location(0) vec4 { return material.color; } diff --git a/assets/shaders/post_processing.wgsl b/assets/shaders/post_processing.wgsl index b25b5788cc8a6..9cfb83a68579d 100644 --- a/assets/shaders/post_processing.wgsl +++ b/assets/shaders/post_processing.wgsl @@ -20,7 +20,7 @@ // As you can see, the triangle ends up bigger than the screen. // // You don't need to worry about this too much since bevy will compute the correct UVs for you. -#import bevy_core_pipeline::fullscreen_vertex_shader +#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput @group(0) @binding(0) var screen_texture: texture_2d; diff --git a/assets/shaders/shader_defs.wgsl b/assets/shaders/shader_defs.wgsl index 0efa91231a622..fae9de396d0f8 100644 --- a/assets/shaders/shader_defs.wgsl +++ b/assets/shaders/shader_defs.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output MeshVertexOutput + struct CustomMaterial { color: vec4, }; @@ -7,7 +9,7 @@ var material: CustomMaterial; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: MeshVertexOutput, ) -> @location(0) vec4 { #ifdef IS_RED return vec4(1.0, 0.0, 0.0, 1.0); diff --git a/assets/shaders/show_prepass.wgsl b/assets/shaders/show_prepass.wgsl index cef987d8843cb..bd5abf401b599 100644 --- a/assets/shaders/show_prepass.wgsl +++ b/assets/shaders/show_prepass.wgsl @@ -1,6 +1,7 @@ #import bevy_pbr::mesh_types -#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_view_bindings globals #import bevy_pbr::prepass_utils +#import bevy_pbr::mesh_vertex_output MeshVertexOutput struct ShowPrepassSettings { show_depth: u32, @@ -14,23 +15,22 @@ var settings: ShowPrepassSettings; @fragment fn fragment( - @builtin(position) frag_coord: vec4, #ifdef MULTISAMPLED @builtin(sample_index) sample_index: u32, #endif - #import bevy_pbr::mesh_vertex_output + mesh: MeshVertexOutput, ) -> @location(0) vec4 { #ifndef MULTISAMPLED let sample_index = 0u; #endif if settings.show_depth == 1u { - let depth = prepass_depth(frag_coord, sample_index); + let depth = bevy_pbr::prepass_utils::prepass_depth(mesh.position, sample_index); return vec4(depth, depth, depth, 1.0); } else if settings.show_normals == 1u { - let normal = prepass_normal(frag_coord, sample_index); + let normal = bevy_pbr::prepass_utils::prepass_normal(mesh.position, sample_index); return vec4(normal, 1.0); } else if settings.show_motion_vectors == 1u { - let motion_vector = prepass_motion_vector(frag_coord, sample_index); + let motion_vector = bevy_pbr::prepass_utils::prepass_motion_vector(mesh.position, sample_index); return vec4(motion_vector / globals.delta_time, 0.0, 1.0); } diff --git a/assets/shaders/texture_binding_array.wgsl b/assets/shaders/texture_binding_array.wgsl index 625938e213c18..a88b8bcf227dd 100644 --- a/assets/shaders/texture_binding_array.wgsl +++ b/assets/shaders/texture_binding_array.wgsl @@ -1,3 +1,5 @@ +#import bevy_pbr::mesh_vertex_output MeshVertexOutput + @group(1) @binding(0) var textures: binding_array>; @group(1) @binding(1) @@ -7,11 +9,11 @@ var nearest_sampler: sampler; @fragment fn fragment( - #import bevy_pbr::mesh_vertex_output + mesh: MeshVertexOutput, ) -> @location(0) vec4 { // Select the texture to sample from using non-uniform uv coordinates - let coords = clamp(vec2(uv * 4.0), vec2(0u), vec2(3u)); + let coords = clamp(vec2(mesh.uv * 4.0), vec2(0u), vec2(3u)); let index = coords.y * 4u + coords.x; - let inner_uv = fract(uv * 4.0); + let inner_uv = fract(mesh.uv * 4.0); return textureSample(textures[index], nearest_sampler, inner_uv); } diff --git a/assets/shaders/tonemapping_test_patterns.wgsl b/assets/shaders/tonemapping_test_patterns.wgsl index cf5b025090730..18573981d8598 100644 --- a/assets/shaders/tonemapping_test_patterns.wgsl +++ b/assets/shaders/tonemapping_test_patterns.wgsl @@ -1,17 +1,12 @@ #import bevy_pbr::mesh_view_bindings #import bevy_pbr::mesh_bindings -#import bevy_pbr::utils +#import bevy_pbr::mesh_vertex_output MeshVertexOutput +#import bevy_pbr::utils PI #ifdef TONEMAP_IN_SHADER #import bevy_core_pipeline::tonemapping #endif -struct FragmentInput { - @builtin(front_facing) is_front: bool, - @builtin(position) frag_coord: vec4, - #import bevy_pbr::mesh_vertex_output -}; - // Sweep across hues on y axis with value from 0.0 to +15EV across x axis // quantized into 24 steps for both axis. fn color_sweep(uv: vec2) -> vec3 { @@ -47,7 +42,9 @@ fn continuous_hue(uv: vec2) -> vec3 { } @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + in: MeshVertexOutput, +) -> @location(0) vec4 { var uv = in.uv; var out = vec3(0.0); if uv.y > 0.5 { @@ -58,7 +55,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { } var color = vec4(out, 1.0); #ifdef TONEMAP_IN_SHADER - color = tone_mapping(color); + color = tone_mapping(color, bevy_pbr::mesh_view_bindings::view.color_grading); #endif return color; } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index a27e30cb01726..b0587e95f32bf 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -406,7 +406,9 @@ impl AddAsset for App { } } -/// Loads an internal asset. +/// Loads an internal asset from a project source file. +/// the file and its path are passed to the loader function, together with any additional parameters. +/// the resulting asset is stored in the app's asset server. /// /// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded /// using the conventional API. See [`DebugAssetServerPlugin`](crate::debug_asset_server::DebugAssetServerPlugin). @@ -427,20 +429,59 @@ macro_rules! load_internal_asset { ); } let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); - assets.set_untracked($handle, ($loader)(include_str!($path_str))); + assets.set_untracked( + $handle, + ($loader)( + include_str!($path_str), + std::path::Path::new(file!()) + .parent() + .unwrap() + .join($path_str) + .to_string_lossy(), + ) + ); + }}; + // we can't support params without variadic arguments, so internal assets with additional params can't be hot-reloaded + ($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{ + let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); + assets.set_untracked( + $handle, + ($loader)( + include_str!($path_str), + std::path::Path::new(file!()) + .parent() + .unwrap() + .join($path_str) + .to_string_lossy(), + $($param),+ + ), + ); }}; } -/// Loads an internal asset. +/// Loads an internal asset from a project source file. +/// the file and its path are passed to the loader function, together with any additional parameters. +/// the resulting asset is stored in the app's asset server. /// /// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded /// using the conventional API. See `DebugAssetServerPlugin`. #[cfg(not(feature = "debug_asset_server"))] #[macro_export] macro_rules! load_internal_asset { - ($app: ident, $handle: ident, $path_str: expr, $loader: expr) => {{ + ($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)*) => {{ let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); - assets.set_untracked($handle, ($loader)(include_str!($path_str))); + assets.set_untracked( + $handle, + ($loader)( + include_str!($path_str), + std::path::Path::new(file!()) + .parent() + .unwrap() + .join($path_str) + .to_string_lossy(), + $($param),* + ), + ); }}; } @@ -465,7 +506,18 @@ macro_rules! load_internal_binary_asset { ); } let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); - assets.set_untracked($handle, ($loader)(include_bytes!($path_str).as_ref())); + assets.set_untracked( + $handle, + ($loader)( + include_bytes!($path_str).as_ref(), + std::path::Path::new(file!()) + .parent() + .unwrap() + .join($path_str) + .to_string_lossy() + .into(), + ), + ); }}; } @@ -478,7 +530,18 @@ macro_rules! load_internal_binary_asset { macro_rules! load_internal_binary_asset { ($app: ident, $handle: ident, $path_str: expr, $loader: expr) => {{ let mut assets = $app.world.resource_mut::<$crate::Assets<_>>(); - assets.set_untracked($handle, ($loader)(include_bytes!($path_str).as_ref())); + assets.set_untracked( + $handle, + ($loader)( + include_bytes!($path_str).as_ref(), + std::path::Path::new(file!()) + .parent() + .unwrap() + .join($path_str) + .to_string_lossy() + .into(), + ), + ); }}; } diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index 6c2e3d4815102..3819b1005a312 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -114,7 +114,7 @@ pub(crate) fn sync_debug_assets( /// If this feels a bit odd ... that's because it is. This was built to improve the UX of the /// `load_internal_asset` macro. pub fn register_handle_with_loader( - _loader: fn(T) -> A, + _loader: fn(T, String) -> A, app: &mut DebugAssetApp, handle: HandleUntyped, file_path: &str, diff --git a/crates/bevy_core_pipeline/src/blit/blit.wgsl b/crates/bevy_core_pipeline/src/blit/blit.wgsl index f84c3724987ac..23c0e4eacd7d8 100644 --- a/crates/bevy_core_pipeline/src/blit/blit.wgsl +++ b/crates/bevy_core_pipeline/src/blit/blit.wgsl @@ -1,4 +1,4 @@ -#import bevy_core_pipeline::fullscreen_vertex_shader +#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput @group(0) @binding(0) var in_texture: texture_2d; diff --git a/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl b/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl index 390809d458372..d02aad0ff2a95 100644 --- a/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl +++ b/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl @@ -6,7 +6,7 @@ // // Tweaks by mrDIMAS - https://github.com/FyroxEngine/Fyrox/blob/master/src/renderer/shaders/fxaa_fs.glsl -#import bevy_core_pipeline::fullscreen_vertex_shader +#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput @group(0) @binding(0) var screenTexture: texture_2d; diff --git a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl index 963de3d192ac9..5f31b753072ea 100644 --- a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl +++ b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl @@ -1,4 +1,4 @@ -#import bevy_render::view +#import bevy_render::view View @group(0) @binding(0) var skybox: texture_cube; diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index fe0d4e5684572..8bc5bf376c7c8 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -1,6 +1,8 @@ -#import bevy_core_pipeline::fullscreen_vertex_shader +#define TONEMAPPING_PASS -#import bevy_render::view +#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput +#import bevy_render::view View +#import bevy_core_pipeline::tonemapping tone_mapping, powsafe, screen_space_dither @group(0) @binding(0) var view: View; @@ -20,11 +22,11 @@ var dt_lut_sampler: sampler; fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); - var output_rgb = tone_mapping(hdr_color).rgb; + var output_rgb = tone_mapping(hdr_color, view.color_grading).rgb; #ifdef DEBAND_DITHER output_rgb = powsafe(output_rgb.rgb, 1.0 / 2.2); - output_rgb = output_rgb + screen_space_dither(in.position.xy); + output_rgb = output_rgb + bevy_core_pipeline::tonemapping::screen_space_dither(in.position.xy); // This conversion back to linear space is required because our output texture format is // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. output_rgb = powsafe(output_rgb.rgb, 2.2); diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index a32beda4a31d9..6c04380d0a0f8 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -1,5 +1,19 @@ #define_import_path bevy_core_pipeline::tonemapping +#import bevy_render::view View, ColorGrading + +// hack !! not sure what to do with this +#ifdef TONEMAPPING_PASS + @group(0) @binding(3) + var dt_lut_texture: texture_3d; + @group(0) @binding(4) + var dt_lut_sampler: sampler; +#else + @group(0) @binding(15) + var dt_lut_texture: texture_3d; + @group(0) @binding(16) + var dt_lut_sampler: sampler; +#endif fn sample_current_lut(p: vec3) -> vec3 { // Don't include code that will try to sample from LUTs if tonemap method doesn't require it @@ -48,7 +62,7 @@ fn tonemap_curve(v: f32) -> f32 { #endif } -fn tonemap_curve3(v: vec3) -> vec3 { +fn tonemap_curve3_(v: vec3) -> vec3 { return vec3(tonemap_curve(v.r), tonemap_curve(v.g), tonemap_curve(v.b)); } @@ -65,7 +79,7 @@ fn somewhat_boring_display_transform(col: vec3) -> vec3 { let tm_luma = tonemap_curve(ycbcr.x); let tm0 = boring_color.rgb * max(0.0, tm_luma / max(1e-5, tonemapping_luminance(boring_color.rgb))); let final_mult = 0.97; - let tm1 = tonemap_curve3(desat_col); + let tm1 = tonemap_curve3_(desat_col); boring_color = mix(tm0, tm1, bt * bt); @@ -167,7 +181,7 @@ fn saturation(color: vec3, saturationAmount: f32) -> vec3 { Similar to OCIO lg2 AllocationTransform. ref[0] */ -fn convertOpenDomainToNormalizedLog2(color: vec3, minimum_ev: f32, maximum_ev: f32) -> vec3 { +fn convertOpenDomainToNormalizedLog2_(color: vec3, minimum_ev: f32, maximum_ev: f32) -> vec3 { let in_midgray = 0.18; // remove negative before log transform @@ -210,7 +224,7 @@ fn applyAgXLog(Image: vec3) -> vec3 { let b = dot(prepared_image, vec3(0.04237565, 0.0784336, 0.87914297)); prepared_image = vec3(r, g, b); - prepared_image = convertOpenDomainToNormalizedLog2(prepared_image, -10.0, 6.5); + prepared_image = convertOpenDomainToNormalizedLog2_(prepared_image, -10.0, 6.5); prepared_image = clamp(prepared_image, vec3(0.0), vec3(1.0)); return prepared_image; @@ -226,7 +240,7 @@ fn applyLUT3D(Image: vec3, block_size: f32) -> vec3 { fn sample_blender_filmic_lut(stimulus: vec3) -> vec3 { let block_size = 64.0; - let normalized = saturate(convertOpenDomainToNormalizedLog2(stimulus, -11.0, 12.0)); + let normalized = saturate(convertOpenDomainToNormalizedLog2_(stimulus, -11.0, 12.0)); return applyLUT3D(normalized, block_size); } @@ -270,7 +284,7 @@ fn screen_space_dither(frag_coord: vec2) -> vec3 { return (dither - 0.5) / 255.0; } -fn tone_mapping(in: vec4) -> vec4 { +fn tone_mapping(in: vec4, color_grading: ColorGrading) -> vec4 { var color = max(in.rgb, vec3(0.0)); // Possible future grading: @@ -282,9 +296,9 @@ fn tone_mapping(in: vec4) -> vec4 { // color += color * luma.xxx * 1.0; // Linear pre tonemapping grading - color = saturation(color, view.color_grading.pre_saturation); - color = powsafe(color, view.color_grading.gamma); - color = color * powsafe(vec3(2.0), view.color_grading.exposure); + color = saturation(color, color_grading.pre_saturation); + color = powsafe(color, color_grading.gamma); + color = color * powsafe(vec3(2.0), color_grading.exposure); color = max(color, vec3(0.0)); // tone_mapping @@ -308,7 +322,7 @@ fn tone_mapping(in: vec4) -> vec4 { #endif // Perceptual post tonemapping grading - color = saturation(color, view.color_grading.post_saturation); + color = saturation(color, color_grading.post_saturation); return vec4(color, in.a); } diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index 85a0d11da0e89..037a9fbcaefbf 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -1,8 +1,9 @@ -#ifdef GIZMO_3D - #import bevy_pbr::mesh_view_bindings -#else - #import bevy_sprite::mesh2d_view_bindings -#endif +// TODO use common view binding +#import bevy_render::view View + +@group(0) @binding(0) +var view: View; + struct LineGizmoUniform { line_width: f32, diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index bfd1ed1a4cd2f..2b0add317f569 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -12,10 +12,7 @@ use bevy_ecs::{ system::{Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; -use bevy_pbr::{ - MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup, MAX_CASCADES_PER_LIGHT, - MAX_DIRECTIONAL_LIGHTS, -}; +use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, @@ -78,15 +75,6 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { "SIXTEEN_BYTE_ALIGNMENT".into(), ]; - shader_defs.push(ShaderDefVal::Int( - "MAX_DIRECTIONAL_LIGHTS".to_string(), - MAX_DIRECTIONAL_LIGHTS as i32, - )); - shader_defs.push(ShaderDefVal::Int( - "MAX_CASCADES_PER_LIGHT".to_string(), - MAX_CASCADES_PER_LIGHT as i32, - )); - if key.perspective { shader_defs.push("PERSPECTIVE".into()); } diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 7230c59027816..5d197916169ff 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -30,3 +30,4 @@ bitflags = "2.3" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } radsort = "0.1" +naga_oil = "0.8" diff --git a/crates/bevy_pbr/src/environment_map/environment_map.wgsl b/crates/bevy_pbr/src/environment_map/environment_map.wgsl index 80062dd69b250..ed10e2d0ba894 100644 --- a/crates/bevy_pbr/src/environment_map/environment_map.wgsl +++ b/crates/bevy_pbr/src/environment_map/environment_map.wgsl @@ -1,5 +1,6 @@ #define_import_path bevy_pbr::environment_map +#import bevy_pbr::mesh_view_bindings as bindings struct EnvironmentMapLight { diffuse: vec3, @@ -20,9 +21,9 @@ fn environment_map_light( // Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf // Technically we could use textureNumLevels(environment_map_specular) - 1 here, but we use a uniform // because textureNumLevels() does not work on WebGL2 - let radiance_level = perceptual_roughness * f32(lights.environment_map_smallest_specular_mip_level); - let irradiance = textureSample(environment_map_diffuse, environment_map_sampler, vec3(N.xy, -N.z)).rgb; - let radiance = textureSampleLevel(environment_map_specular, environment_map_sampler, vec3(R.xy, -R.z), radiance_level).rgb; + let radiance_level = perceptual_roughness * f32(bindings::lights.environment_map_smallest_specular_mip_level); + let irradiance = textureSample(bindings::environment_map_diffuse, bindings::environment_map_sampler, vec3(N.xy, -N.z)).rgb; + let radiance = textureSampleLevel(bindings::environment_map_specular, bindings::environment_map_sampler, vec3(R.xy, -R.z), radiance_level).rgb; // Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf // Useful reference: https://bruop.github.io/ibl diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index af854b9c22860..b38cf3d539c48 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -31,8 +31,8 @@ use bevy_render::{ BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache, - PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, ShaderRef, - ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, + ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, TextureViewDimension, VertexState, }, @@ -47,7 +47,7 @@ use bevy_utils::tracing::error; use crate::{ prepare_lights, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, MeshUniform, RenderMaterials, - SetMaterialBindGroup, SetMeshBindGroup, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, + SetMaterialBindGroup, SetMeshBindGroup, }; use std::{hash::Hash, marker::PhantomData}; @@ -373,14 +373,6 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } - shader_defs.push(ShaderDefVal::UInt( - "MAX_DIRECTIONAL_LIGHTS".to_string(), - MAX_DIRECTIONAL_LIGHTS as u32, - )); - shader_defs.push(ShaderDefVal::UInt( - "MAX_CASCADES_PER_LIGHT".to_string(), - MAX_CASCADES_PER_LIGHT as u32, - )); if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) { shader_defs.push("DEPTH_CLAMP_ORTHO".into()); // PERF: This line forces the "prepass fragment shader" to always run in diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 141abcf195964..fee9cd9fcf15e 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -1,5 +1,8 @@ #import bevy_pbr::prepass_bindings #import bevy_pbr::mesh_functions +#import bevy_pbr::skinning +#import bevy_pbr::morph +#import bevy_pbr::mesh_bindings mesh // Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can // pass them to custom prepass shaders like pbr_prepass.wgsl. @@ -83,12 +86,12 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #endif #ifdef SKINNED - var model = skin_model(vertex.joint_indices, vertex.joint_weights); + var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED var model = mesh.model; #endif // SKINNED - 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)); #ifdef DEPTH_CLAMP_ORTHO out.clip_position_unclamped = out.clip_position; out.clip_position.z = min(out.clip_position.z, 1.0); @@ -100,19 +103,19 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef NORMAL_PREPASS #ifdef SKINNED - out.world_normal = skin_normals(model, vertex.normal); + out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else // SKINNED - out.world_normal = mesh_normal_local_to_world(vertex.normal); + out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world(vertex.normal); #endif // SKINNED #ifdef VERTEX_TANGENTS - out.world_tangent = mesh_tangent_local_to_world(model, vertex.tangent); + out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent); #endif // VERTEX_TANGENTS #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); - out.previous_world_position = mesh_position_local_to_world(mesh.previous_model, vec4(vertex.position, 1.0)); + out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); + out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh.previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS return out; @@ -165,9 +168,9 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #endif // DEPTH_CLAMP_ORTHO #ifdef MOTION_VECTOR_PREPASS - let clip_position_t = view.unjittered_view_proj * in.world_position; + let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position; let clip_position = clip_position_t.xy / clip_position_t.w; - let previous_clip_position_t = previous_view_proj * in.previous_world_position; + let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position; let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w; // These motion vectors are used as offsets to UV positions and are stored // in the range -1,1 to allow offsetting from the one corner to the diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index 65ae592a226a4..825b0fa245181 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -1,6 +1,6 @@ #define_import_path bevy_pbr::prepass_bindings - -#import bevy_pbr::mesh_view_types +#import bevy_render::view View +#import bevy_render::globals Globals #import bevy_pbr::mesh_types @group(0) @binding(0) @@ -16,18 +16,4 @@ var previous_view_proj: mat4x4; // Material bindings will be in @group(1) @group(2) @binding(0) -var mesh: Mesh; - -#ifdef SKINNED -@group(2) @binding(1) -var joint_matrices: SkinnedMesh; -#import bevy_pbr::skinning -#endif - -#ifdef MORPH_TARGETS -@group(2) @binding(2) -var morph_weights: MorphWeights; -@group(2) @binding(3) -var morph_targets: texture_3d; -#import bevy_pbr::morph -#endif +var mesh: bevy_pbr::mesh_types::Mesh; diff --git a/crates/bevy_pbr/src/prepass/prepass_utils.wgsl b/crates/bevy_pbr/src/prepass/prepass_utils.wgsl index 045ca4f4f54b0..d4e9b5992764a 100644 --- a/crates/bevy_pbr/src/prepass/prepass_utils.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_utils.wgsl @@ -1,11 +1,13 @@ #define_import_path bevy_pbr::prepass_utils +#import bevy_pbr::mesh_view_bindings as view_bindings + #ifndef DEPTH_PREPASS fn prepass_depth(frag_coord: vec4, sample_index: u32) -> f32 { #ifdef MULTISAMPLED - let depth_sample = textureLoad(depth_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); + let depth_sample = textureLoad(view_bindings::depth_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); #else - let depth_sample = textureLoad(depth_prepass_texture, vec2(frag_coord.xy), 0); + let depth_sample = textureLoad(view_bindings::depth_prepass_texture, vec2(frag_coord.xy), 0); #endif return depth_sample; } @@ -14,9 +16,9 @@ fn prepass_depth(frag_coord: vec4, sample_index: u32) -> f32 { #ifndef NORMAL_PREPASS fn prepass_normal(frag_coord: vec4, sample_index: u32) -> vec3 { #ifdef MULTISAMPLED - let normal_sample = textureLoad(normal_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); + let normal_sample = textureLoad(view_bindings::normal_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); #else - let normal_sample = textureLoad(normal_prepass_texture, vec2(frag_coord.xy), 0); + let normal_sample = textureLoad(view_bindings::normal_prepass_texture, vec2(frag_coord.xy), 0); #endif // MULTISAMPLED return normal_sample.xyz * 2.0 - vec3(1.0); } @@ -25,9 +27,9 @@ fn prepass_normal(frag_coord: vec4, sample_index: u32) -> vec3 { #ifndef MOTION_VECTOR_PREPASS fn prepass_motion_vector(frag_coord: vec4, sample_index: u32) -> vec2 { #ifdef MULTISAMPLED - let motion_vector_sample = textureLoad(motion_vector_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); + let motion_vector_sample = textureLoad(view_bindings::motion_vector_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); #else - let motion_vector_sample = textureLoad(motion_vector_prepass_texture, vec2(frag_coord.xy), 0); + let motion_vector_sample = textureLoad(view_bindings::motion_vector_prepass_texture, vec2(frag_coord.xy), 0); #endif return motion_vector_sample.rg; } diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index 9a467e3ad0944..e40ee57602235 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 hsv2rgb + // 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 closer 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 - view.viewport.xy) * lights.cluster_factors.xy)); + let xy = vec2(floor((frag_coord - bindings::view.viewport.xy) * 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,9 +33,9 @@ fn fragment_cluster_index(frag_coord: vec2, view_z: f32, is_orthographic: b const CLUSTER_COUNT_SIZE = 9u; fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 - return cluster_offsets_and_counts.data[cluster_index].xyz; + return bindings::cluster_offsets_and_counts.data[cluster_index].xyz; #else - 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( @@ -45,11 +48,11 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { fn get_light_id(index: u32) -> u32 { #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 - return cluster_light_index_lists.data[index]; + return bindings::cluster_light_index_lists.data[index]; #else // 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); #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 = 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 diff --git a/crates/bevy_pbr/src/render/fog.wgsl b/crates/bevy_pbr/src/render/fog.wgsl index e6c1416d93fe4..6f06aeb3572f3 100644 --- a/crates/bevy_pbr/src/render/fog.wgsl +++ b/crates/bevy_pbr/src/render/fog.wgsl @@ -1,5 +1,8 @@ #define_import_path bevy_pbr::fog +#import bevy_pbr::mesh_view_bindings fog +#import bevy_pbr::mesh_view_types Fog + // Fog formulas adapted from: // https://learn.microsoft.com/en-us/windows/win32/direct3d9/fog-formulas // https://catlikecoding.com/unity/tutorials/rendering/part-14/ diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 26aeadb0517e5..aa4ab31e5dc7d 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -86,7 +86,17 @@ impl Plugin for MeshRenderPlugin { app, MESH_VIEW_TYPES_HANDLE, "mesh_view_types.wgsl", - Shader::from_wgsl + Shader::from_wgsl_with_defs, + vec![ + ShaderDefVal::UInt( + "MAX_DIRECTIONAL_LIGHTS".into(), + MAX_DIRECTIONAL_LIGHTS as u32 + ), + ShaderDefVal::UInt( + "MAX_CASCADES_PER_LIGHT".into(), + MAX_CASCADES_PER_LIGHT as u32, + ) + ] ); load_internal_asset!( app, @@ -709,15 +719,6 @@ impl SpecializedMeshPipeline for MeshPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); } - shader_defs.push(ShaderDefVal::UInt( - "MAX_DIRECTIONAL_LIGHTS".to_string(), - MAX_DIRECTIONAL_LIGHTS as u32, - )); - shader_defs.push(ShaderDefVal::UInt( - "MAX_CASCADES_PER_LIGHT".to_string(), - MAX_CASCADES_PER_LIGHT as u32, - )); - if layout.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 864a0539d6eb4..a99017ce628dd 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -1,8 +1,8 @@ -#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::morph +#import bevy_pbr::mesh_bindings mesh +#import bevy_pbr::mesh_vertex_output MeshVertexOutput struct Vertex { #ifdef VERTEX_POSITIONS @@ -29,26 +29,21 @@ struct Vertex { #endif }; -struct VertexOutput { - @builtin(position) clip_position: vec4, - #import bevy_pbr::mesh_vertex_output -}; - #ifdef MORPH_TARGETS fn morph_vertex(vertex_in: Vertex) -> Vertex { var vertex = vertex_in; - let weight_count = layer_count(); + let weight_count = bevy_pbr::morph::layer_count(); for (var i: u32 = 0u; i < weight_count; i ++) { - let weight = weight_at(i); + let weight = bevy_pbr::morph::weight_at(i); if weight == 0.0 { continue; } - vertex.position += weight * morph(vertex.index, position_offset, i); + vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i); #ifdef VERTEX_NORMALS - vertex.normal += weight * morph(vertex.index, normal_offset, i); + vertex.normal += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::normal_offset, i); #endif #ifdef VERTEX_TANGENTS - vertex.tangent += vec4(weight * morph(vertex.index, tangent_offset, i), 0.0); + vertex.tangent += vec4(weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::tangent_offset, i), 0.0); #endif } return vertex; @@ -56,8 +51,8 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex { #endif @vertex -fn vertex(vertex_no_morph: Vertex) -> VertexOutput { - var out: VertexOutput; +fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { + var out: MeshVertexOutput; #ifdef MORPH_TARGETS var vertex = morph_vertex(vertex_no_morph); @@ -66,22 +61,22 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #endif #ifdef SKINNED - var model = skin_model(vertex.joint_indices, vertex.joint_weights); + var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else var model = mesh.model; #endif #ifdef VERTEX_NORMALS #ifdef SKINNED - out.world_normal = skin_normals(model, vertex.normal); + out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else - out.world_normal = mesh_normal_local_to_world(vertex.normal); + out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal); #endif #endif #ifdef VERTEX_POSITIONS - out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); - out.clip_position = mesh_position_world_to_clip(out.world_position); + out.world_position = mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); + out.position = mesh_functions::mesh_position_world_to_clip(out.world_position); #endif #ifdef VERTEX_UVS @@ -89,7 +84,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #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 @@ -99,14 +94,12 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { return out; } -struct FragmentInput { - #import bevy_pbr::mesh_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + mesh: 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 3d4c1b6acdde2..2b6d8826811b1 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -1,20 +1,15 @@ #define_import_path bevy_pbr::mesh_bindings -#import bevy_pbr::mesh_types +#import bevy_pbr::mesh_types Mesh -@group(2) @binding(0) +#ifdef MESH_BINDGROUP_1 + +@group(1) @binding(0) var mesh: Mesh; -#ifdef SKINNED -@group(2) @binding(1) -var joint_matrices: SkinnedMesh; -#import bevy_pbr::skinning -#endif +#else + +@group(2) @binding(0) +var mesh: Mesh; -#ifdef MORPH_TARGETS -@group(2) @binding(2) -var morph_weights: MorphWeights; -@group(2) @binding(3) -var morph_targets: texture_3d; -#import bevy_pbr::morph #endif diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 40779b28d5c6e..4a55cce62e031 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -1,5 +1,9 @@ #define_import_path bevy_pbr::mesh_functions +#import bevy_pbr::mesh_view_bindings view +#import bevy_pbr::mesh_bindings mesh +#import bevy_pbr::mesh_types MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT + fn mesh_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; } @@ -34,7 +38,7 @@ fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { // Calculates the sign of the determinant of the 3x3 model matrix based on a // mesh flag -fn sign_determinant_model_3x3() -> f32 { +fn sign_determinant_model_3x3m() -> 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 @@ -58,6 +62,6 @@ fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> ), // NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for // situations such as negative scaling. - vertex_tangent.w * sign_determinant_model_3x3() + vertex_tangent.w * sign_determinant_model_3x3m() ); } diff --git a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl index ba0c6b2237c0a..bc3b7c6f58797 100644 --- a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl +++ b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl @@ -1,13 +1,18 @@ #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 { + // this is `clip position` when the struct is used as a vertex stage output + // and `frag coord` when used as a fragment stage input + @builtin(position) 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 4c3e20902d84d..8c922f2152e9f 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -1,11 +1,13 @@ #define_import_path bevy_pbr::mesh_view_bindings -#import bevy_pbr::mesh_view_types +#import bevy_pbr::mesh_view_types as types +#import bevy_render::view View +#import bevy_render::globals Globals @group(0) @binding(0) var view: 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,24 +29,24 @@ var directional_shadow_textures_sampler: sampler_comparison; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 @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 @group(0) @binding(9) var globals: Globals; @group(0) @binding(10) -var fog: Fog; +var fog: types::Fog; @group(0) @binding(11) var screen_space_ambient_occlusion_texture: texture_2d; diff --git a/crates/bevy_pbr/src/render/morph.wgsl b/crates/bevy_pbr/src/render/morph.wgsl index 754268431f064..ceabe789943cf 100644 --- a/crates/bevy_pbr/src/render/morph.wgsl +++ b/crates/bevy_pbr/src/render/morph.wgsl @@ -7,6 +7,27 @@ #define_import_path bevy_pbr::morph +#ifdef MORPH_TARGETS + +#import bevy_pbr::mesh_types MorphWeights + +#ifdef MESH_BINDGROUP_1 + +@group(1) @binding(2) +var morph_weights: MorphWeights; +@group(1) @binding(3) +var morph_targets: texture_3d; + +#else + +@group(2) @binding(2) +var morph_weights: MorphWeights; +@group(2) @binding(3) +var morph_targets: texture_3d; + +#endif + + // NOTE: Those are the "hardcoded" values found in `MorphAttributes` struct // in crates/bevy_render/src/mesh/morph/visitors.rs // In an ideal world, the offsets are established dynamically and passed as #defines @@ -43,3 +64,5 @@ fn morph(vertex_index: u32, component_offset: u32, weight_index: u32) -> vec3) -> f32 { // We use `textureSampleLevel` over `textureSample` because the wgpu DX12 // backend (Fxc) panics when using "gradient instructions" inside a loop. diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 5fa1e7b8ba8c8..245a822c09e89 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -1,45 +1,45 @@ -#import bevy_pbr::mesh_view_bindings -#import bevy_pbr::pbr_bindings -#import bevy_pbr::mesh_bindings - -#import bevy_pbr::utils -#import bevy_pbr::clustered_forward -#import bevy_pbr::lighting -#import bevy_pbr::pbr_ambient -#import bevy_pbr::shadows -#import bevy_pbr::fog -#import bevy_pbr::pbr_functions -#import bevy_pbr::parallax_mapping +#define_import_path bevy_pbr::fragment + +#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::prepass_utils + +#import bevy_pbr::mesh_vertex_output MeshVertexOutput +#import bevy_pbr::mesh_bindings mesh +#import bevy_pbr::mesh_view_bindings view, fog, screen_space_ambient_occlusion_texture +#import bevy_pbr::mesh_view_types FOG_MODE_OFF +#import bevy_core_pipeline::tonemapping screen_space_dither, powsafe, tone_mapping +#import bevy_pbr::parallax_mapping parallaxed_uv #import bevy_pbr::prepass_utils #ifdef SCREEN_SPACE_AMBIENT_OCCLUSION -#import bevy_pbr::gtao_utils +#import bevy_pbr::gtao_utils gtao_multibounce #endif -struct FragmentInput { +@fragment +fn fragment( + in: 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 { let is_orthographic = view.projection[3].w == 1.0; - let V = calculate_view(in.world_position, is_orthographic); + let V = pbr_functions::calculate_view(in.world_position, is_orthographic); #ifdef VERTEX_UVS var uv = in.uv; #ifdef VERTEX_TANGENTS - if ((material.flags & STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) { + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) { let N = in.world_normal; let T = in.world_tangent.xyz; let B = in.world_tangent.w * cross(N, T); // Transform V from fragment to camera in world space to tangent space. let Vt = vec3(dot(V, T), dot(V, B), dot(V, N)); uv = parallaxed_uv( - material.parallax_depth_scale, - material.max_parallax_layer_count, - material.max_relief_mapping_search_steps, + pbr_bindings::material.parallax_depth_scale, + pbr_bindings::material.max_parallax_layer_count, + pbr_bindings::material.max_relief_mapping_search_steps, uv, // Flip the direction of Vt to go toward the surface to make the // parallax mapping algorithm easier to understand and reason @@ -49,41 +49,41 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { } #endif #endif - var output_color: vec4 = material.base_color; + #ifdef VERTEX_COLORS output_color = output_color * in.color; #endif #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { - output_color = output_color * textureSampleBias(base_color_texture, base_color_sampler, uv, view.mip_bias); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { + output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, in.uv, view.mip_bias); } #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 * textureSampleBias(emissive_texture, emissive_sampler, uv, view.mip_bias).rgb, 1.0); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { + emissive = vec4(emissive.rgb * textureSampleBias(pbr_bindings::emissive_texture, pbr_bindings::emissive_sampler, in.uv, view.mip_bias).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 = textureSampleBias(metallic_roughness_texture, metallic_roughness_sampler, uv, view.mip_bias); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { + let metallic_roughness = textureSampleBias(pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, in.uv, view.mip_bias); // Sampling from GLTF standard channels for now metallic = metallic * metallic_roughness.b; perceptual_roughness = perceptual_roughness * metallic_roughness.g; @@ -95,33 +95,33 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { // TODO: Split into diffuse/specular occlusion? var occlusion: vec3 = vec3(1.0); #ifdef VERTEX_UVS - if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { - occlusion = vec3(textureSampleBias(occlusion_texture, occlusion_sampler, in.uv, view.mip_bias).r); + if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { + occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, in.uv, view.mip_bias).r); } #endif #ifdef SCREEN_SPACE_AMBIENT_OCCLUSION - let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2(in.frag_coord.xy), 0i).r; + let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2(in.position.xy), 0i).r; let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb); occlusion = min(occlusion, ssao_multibounce); #endif pbr_input.occlusion = occlusion; - pbr_input.frag_coord = in.frag_coord; + pbr_input.frag_coord = in.position; pbr_input.world_position = in.world_position; - pbr_input.world_normal = prepare_world_normal( + pbr_input.world_normal = pbr_functions::prepare_world_normal( in.world_normal, - (material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, - in.is_front, + (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, + is_front, ); pbr_input.is_orthographic = is_orthographic; #ifdef LOAD_PREPASS_NORMALS - pbr_input.N = prepass_normal(in.frag_coord, 0u); + pbr_input.N = bevy_pbr::prepass_utils::prepass_normal(in.position, 0u); #else - pbr_input.N = apply_normal_mapping( - material.flags, + pbr_input.N = pbr_functions::apply_normal_mapping( + pbr_bindings::material.flags, pbr_input.world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP @@ -139,22 +139,22 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { pbr_input.flags = mesh.flags; - output_color = pbr(pbr_input); + output_color = pbr_functions::pbr(pbr_input); } else { - output_color = alpha_discard(material, output_color); + output_color = pbr_functions::alpha_discard(pbr_bindings::material, output_color); } // fog - if (fog.mode != FOG_MODE_OFF && (material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) { - output_color = apply_fog(fog, output_color, in.world_position.xyz, view.world_position.xyz); + if (fog.mode != FOG_MODE_OFF && (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) { + output_color = pbr_functions::apply_fog(fog, output_color, in.world_position.xyz, view.world_position.xyz); } #ifdef TONEMAP_IN_SHADER - output_color = tone_mapping(output_color); + output_color = tone_mapping(output_color, view.color_grading); #ifdef DEBAND_DITHER var output_rgb = output_color.rgb; output_rgb = powsafe(output_rgb, 1.0 / 2.2); - output_rgb = output_rgb + screen_space_dither(in.frag_coord.xy); + output_rgb = output_rgb + screen_space_dither(in.position.xy); // This conversion back to linear space is required because our output texture format is // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. output_rgb = powsafe(output_rgb, 2.2); @@ -162,7 +162,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #endif #endif #ifdef PREMULTIPLY_ALPHA - output_color = premultiply_alpha(material.flags, output_color); + output_color = pbr_functions::premultiply_alpha(pbr_bindings::material.flags, output_color); #endif return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_ambient.wgsl b/crates/bevy_pbr/src/render/pbr_ambient.wgsl index c2e33adda9bc8..28afd5588d3ec 100644 --- a/crates/bevy_pbr/src/render/pbr_ambient.wgsl +++ b/crates/bevy_pbr/src/render/pbr_ambient.wgsl @@ -1,4 +1,7 @@ -#define_import_path bevy_pbr::pbr_ambient +#define_import_path bevy_pbr::ambient + +#import bevy_pbr::lighting EnvBRDFApprox, F_AB +#import bevy_pbr::mesh_view_bindings lights // A precomputed `NdotV` is provided because it is computed regardless, // but `world_normal` and the view vector `V` are provided separately for more advanced uses. @@ -12,8 +15,8 @@ fn ambient_light( perceptual_roughness: f32, occlusion: vec3, ) -> vec3 { - let diffuse_ambient = EnvBRDFApprox(diffuse_color, F_AB(1.0, NdotV)) * occlusion; + let diffuse_ambient = EnvBRDFApprox(diffuse_color, F_AB(1.0, NdotV)); let specular_ambient = EnvBRDFApprox(specular_color, F_AB(perceptual_roughness, NdotV)); - return (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb; + return (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion; } diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index c5400dd20491e..e6664b21310b4 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -1,6 +1,6 @@ #define_import_path bevy_pbr::pbr_bindings -#import bevy_pbr::pbr_types +#import bevy_pbr::pbr_types StandardMaterial @group(1) @binding(0) var material: StandardMaterial; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 6f7b2a080d512..393ff2728dffa 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -4,20 +4,32 @@ #import bevy_core_pipeline::tonemapping #endif +#import bevy_pbr::pbr_types as pbr_types +#import bevy_pbr::pbr_bindings as pbr_bindings +#import bevy_pbr::mesh_view_bindings as view_bindings +#import bevy_pbr::mesh_view_types as mesh_view_types +#import bevy_pbr::lighting as lighting +#import bevy_pbr::clustered_forward as clustering +#import bevy_pbr::shadows as shadows +#import bevy_pbr::fog as fog +#import bevy_pbr::ambient as ambient #ifdef ENVIRONMENT_MAP #import bevy_pbr::environment_map #endif -fn alpha_discard(material: StandardMaterial, output_color: vec4) -> vec4 { +#import bevy_pbr::mesh_bindings mesh +#import bevy_pbr::mesh_types MESH_FLAGS_SHADOW_RECEIVER_BIT + +fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4) -> vec4 { var color = output_color; - let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE { + let alpha_mode = material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE { // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0 color.a = 1.0; } #ifdef MAY_DISCARD - else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { + else if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { if color.a >= material.alpha_cutoff { // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque color.a = 1.0; @@ -82,8 +94,8 @@ fn apply_normal_mapping( #ifdef VERTEX_UVS #ifdef STANDARDMATERIAL_NORMAL_MAP // Nt is the tangent-space normal. - var Nt = textureSampleBias(normal_map_texture, normal_map_sampler, uv, view.mip_bias).rgb; - if (standard_material_flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u { + var Nt = textureSampleBias(pbr_bindings::normal_map_texture, pbr_bindings::normal_map_sampler, uv, view_bindings::view.mip_bias).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); @@ -91,7 +103,7 @@ fn apply_normal_mapping( 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 @@ -116,16 +128,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: vec3, frag_coord: vec4, world_position: vec4, @@ -145,7 +157,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 = vec3(1.0); pbr_input.frag_coord = vec4(0.0, 0.0, 0.0, 1.0); @@ -174,7 +186,7 @@ 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; @@ -193,64 +205,65 @@ fn pbr( let R = reflect(-in.V, in.N); - let f_ab = F_AB(perceptual_roughness, NdotV); + let f_ab = lighting::F_AB(perceptual_roughness, NdotV); var direct_light: 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 (direct) 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_id = clustering::get_light_id(i); var shadow: f32 = 1.0; - if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (point_lights.data[light_id].flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal); + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (view_bindings::point_lights.data[light_id].flags & mesh_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_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); + let light_contrib = lighting::point_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); direct_light += light_contrib * shadow; } // Spot lights (direct) 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_id = clustering::get_light_id(i); + var shadow: f32 = 1.0; - if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (point_lights.data[light_id].flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_spot_shadow(light_id, in.world_position, in.world_normal); + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (view_bindings::point_lights.data[light_id].flags & mesh_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_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); + let light_contrib = lighting::spot_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); direct_light += light_contrib * shadow; } - // Directional lights (direct) - let n_directional_lights = lights.n_directional_lights; + // directional lights (direct) + let n_directional_lights = view_bindings::lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { var shadow: f32 = 1.0; - if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = fetch_directional_shadow(i, in.world_position, in.world_normal, view_z); + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z); } - var light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); + var light_contrib = lighting::directional_light(i, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color); #ifdef DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES - light_contrib = cascade_debug_visualization(light_contrib, i, view_z); + light_contrib = shadows::cascade_debug_visualization(light_contrib, i, view_z); #endif direct_light += light_contrib * shadow; } // Ambient light (indirect) - var indirect_light = ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, occlusion); + var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, occlusion); // Environment map light (indirect) #ifdef ENVIRONMENT_MAP - let environment_light = environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0); + let environment_light = bevy_pbr::environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0); indirect_light += (environment_light.diffuse * occlusion) + environment_light.specular; #endif @@ -262,7 +275,7 @@ fn pbr( output_color.a ); - output_color = cluster_debug_visualization( + output_color = clustering::cluster_debug_visualization( output_color, view_z, in.is_orthographic, @@ -275,7 +288,7 @@ fn pbr( #endif // PREPASS_FRAGMENT #ifndef PREPASS_FRAGMENT -fn apply_fog(fog_params: Fog, input_color: vec4, fragment_world_position: vec3, view_world_position: vec3) -> vec4 { +fn apply_fog(fog_params: mesh_view_types::Fog, input_color: vec4, fragment_world_position: vec3, view_world_position: vec3) -> vec4 { let view_to_world = fragment_world_position.xyz - view_world_position.xyz; // `length()` is used here instead of just `view_to_world.z` since that produces more @@ -287,9 +300,9 @@ fn apply_fog(fog_params: Fog, input_color: vec4, fragment_world_position: v var scattering = vec3(0.0); if fog_params.directional_light_color.a > 0.0 { let view_to_world_normalized = view_to_world / distance; - 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]; scattering += pow( max( dot(view_to_world_normalized, light.direction_to_light), @@ -300,14 +313,14 @@ fn apply_fog(fog_params: Fog, input_color: vec4, fragment_world_position: v } } - if fog_params.mode == FOG_MODE_LINEAR { - return linear_fog(fog_params, input_color, distance, scattering); - } else if fog_params.mode == FOG_MODE_EXPONENTIAL { - return exponential_fog(fog_params, input_color, distance, scattering); - } else if fog_params.mode == FOG_MODE_EXPONENTIAL_SQUARED { - return exponential_squared_fog(fog_params, input_color, distance, scattering); - } else if fog_params.mode == FOG_MODE_ATMOSPHERIC { - return atmospheric_fog(fog_params, input_color, distance, scattering); + if fog_params.mode == mesh_view_types::FOG_MODE_LINEAR { + return fog::linear_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL { + return fog::exponential_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL_SQUARED { + return fog::exponential_squared_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == mesh_view_types::FOG_MODE_ATMOSPHERIC { + return fog::atmospheric_fog(fog_params, input_color, distance, scattering); } else { return input_color; } @@ -324,8 +337,8 @@ fn premultiply_alpha(standard_material_flags: u32, color: vec4) -> vec4, diffuseColor: vec3 ) -> vec3 { - let light = &point_lights.data[light_id]; + let light = &view_bindings::point_lights.data[light_id]; let light_to_frag = (*light).position_radius.xyz - world_position.xyz; let distance_square = dot(light_to_frag, light_to_frag); let rangeAttenuation = getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w); @@ -244,12 +248,12 @@ fn spot_light( // reuse the point light calculations let point_light = point_light(world_position, light_id, roughness, NdotV, N, V, R, F0, f_ab, diffuseColor); - let light = &point_lights.data[light_id]; + let light = &view_bindings::point_lights.data[light_id]; // 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(max(0.0, 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; @@ -265,7 +269,7 @@ fn spot_light( } fn directional_light(light_id: u32, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, f_ab: vec2, diffuseColor: vec3) -> vec3 { - let light = &lights.directional_lights[light_id]; + let light = &view_bindings::lights.directional_lights[light_id]; let incident_light = (*light).direction_to_light.xyz; diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index 4b967f67ed997..b5103e73a9ab1 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -1,5 +1,6 @@ #import bevy_pbr::prepass_bindings #import bevy_pbr::pbr_bindings +#import bevy_pbr::pbr_types #ifdef NORMAL_PREPASS #import bevy_pbr::pbr_functions #endif // NORMAL_PREPASS @@ -35,24 +36,24 @@ const PREMULTIPLIED_ALPHA_CUTOFF = 0.05; fn prepass_alpha_discard(in: FragmentInput) { #ifdef MAY_DISCARD - var output_color: vec4 = material.base_color; + var output_color: vec4 = bevy_pbr::pbr_bindings::material.base_color; #ifdef VERTEX_UVS - if (material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { - output_color = output_color * textureSampleBias(base_color_texture, base_color_sampler, in.uv, view.mip_bias); + if (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { + output_color = output_color * textureSampleBias(bevy_pbr::pbr_bindings::base_color_texture, bevy_pbr::pbr_bindings::base_color_sampler, in.uv, bevy_pbr::prepass_bindings::view.mip_bias); } #endif // VERTEX_UVS - let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { - if output_color.a < material.alpha_cutoff { + let alpha_mode = bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { + if output_color.a < bevy_pbr::pbr_bindings::material.alpha_cutoff { discard; } - } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { + } else if (alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF { discard; } - } else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED { + } else if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED { if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) { discard; } @@ -88,15 +89,15 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #ifdef NORMAL_PREPASS // 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 { - let world_normal = prepare_world_normal( + if (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u { + let world_normal = bevy_pbr::pbr_functions::prepare_world_normal( in.world_normal, - (material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, + (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, in.is_front, ); - let normal = apply_normal_mapping( - material.flags, + let normal = bevy_pbr::pbr_functions::apply_normal_mapping( + bevy_pbr::pbr_bindings::material.flags, world_normal, #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP @@ -115,9 +116,9 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - let clip_position_t = view.unjittered_view_proj * in.world_position; + let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position; let clip_position = clip_position_t.xy / clip_position_t.w; - let previous_clip_position_t = previous_view_proj * in.previous_world_position; + let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position; let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w; // These motion vectors are used as offsets to UV positions and are stored // in the range -1,1 to allow offsetting from the one corner to the diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index e634d950d5c69..bcd87ae978e43 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -1,9 +1,12 @@ #define_import_path bevy_pbr::shadows +#import bevy_pbr::mesh_view_types POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE +#import bevy_pbr::mesh_view_bindings as view_bindings +#import bevy_pbr::utils hsv2rgb const flip_z: vec3 = vec3(1.0, 1.0, -1.0); 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 @@ -38,14 +41,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 * flip_z, depth); + return textureSampleCompare(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, frag_ls * flip_z, depth); #else - return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls * flip_z, i32(light_id), depth); + return textureSampleCompareLevel(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, frag_ls * flip_z, 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; @@ -93,16 +96,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 get_cascade_index(light_id: u32, view_z: f32) -> u32 { - let light = &lights.directional_lights[light_id]; + let light = &view_bindings::lights.directional_lights[light_id]; for (var i: u32 = 0u; i < (*light).num_cascades; i = i + 1u) { if (-view_z < (*light).cascades[i].far_bound) { @@ -113,7 +116,7 @@ fn get_cascade_index(light_id: u32, view_z: f32) -> u32 { } fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = &lights.directional_lights[light_id]; + let light = &view_bindings::lights.directional_lights[light_id]; let cascade = &(*light).cascades[cascade_index]; // The normal bias is scaled to the texel size. @@ -143,15 +146,15 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, s // 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, + view_bindings::directional_shadow_textures, + view_bindings::directional_shadow_textures_sampler, light_local, depth ); #else return textureSampleCompareLevel( - directional_shadow_textures, - directional_shadow_textures_sampler, + view_bindings::directional_shadow_textures, + view_bindings::directional_shadow_textures_sampler, light_local, i32((*light).depth_texture_base_index + cascade_index), depth @@ -160,7 +163,7 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, s } fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3, view_z: f32) -> f32 { - let light = &lights.directional_lights[light_id]; + let light = &view_bindings::lights.directional_lights[light_id]; let cascade_index = get_cascade_index(light_id, view_z); if (cascade_index >= (*light).num_cascades) { diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 8332d0161347a..ff2269893e4b6 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 SkinnedMesh + +#ifdef SKINNED + +#ifdef MESH_BINDGROUP_1 + + @group(1) @binding(1) + var joint_matrices: SkinnedMesh; + +#else + + @group(2) @binding(1) + var joint_matrices: SkinnedMesh; + +#endif + + fn skin_model( indexes: vec4, weights: vec4, @@ -14,7 +27,7 @@ fn skin_model( + weights.w * joint_matrices.data[indexes.w]; } -fn inverse_transpose_3x3(in: mat3x3) -> mat3x3 { +fn inverse_transpose_3x3m(in: mat3x3) -> mat3x3 { let x = cross(in[1], in[2]); let y = cross(in[2], in[0]); let z = cross(in[0], in[1]); @@ -31,7 +44,7 @@ fn skin_normals( normal: vec3, ) -> vec3 { return normalize( - inverse_transpose_3x3( + inverse_transpose_3x3m( mat3x3( model[0].xyz, model[1].xyz, @@ -40,3 +53,5 @@ fn skin_normals( ) * normal ); } + +#endif diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index 651a10d491b06..a37a17015e882 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 mesh +#import bevy_pbr::mesh_functions mesh_position_local_to_clip #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,7 +20,7 @@ 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; #endif diff --git a/crates/bevy_pbr/src/ssao/gtao.wgsl b/crates/bevy_pbr/src/ssao/gtao.wgsl index 18aa8d5ac8a0d..67c9307663b4b 100644 --- a/crates/bevy_pbr/src/ssao/gtao.wgsl +++ b/crates/bevy_pbr/src/ssao/gtao.wgsl @@ -5,10 +5,10 @@ // Source code heavily based on XeGTAO v1.30 from Intel // https://github.com/GameTechDev/XeGTAO/blob/0d177ce06bfa642f64d8af4de1197ad1bcb862d4/Source/Rendering/Shaders/XeGTAO.hlsli -#import bevy_pbr::gtao_utils -#import bevy_pbr::utils -#import bevy_render::view -#import bevy_render::globals +#import bevy_pbr::gtao_utils fast_acos +#import bevy_pbr::utils PI, HALF_PI +#import bevy_render::view View +#import bevy_render::globals Globals @group(0) @binding(0) var preprocessed_depth: texture_2d; @group(0) @binding(1) var normals: texture_2d; diff --git a/crates/bevy_pbr/src/ssao/gtao_utils.wgsl b/crates/bevy_pbr/src/ssao/gtao_utils.wgsl index 3ada70d749423..11233ba05226b 100644 --- a/crates/bevy_pbr/src/ssao/gtao_utils.wgsl +++ b/crates/bevy_pbr/src/ssao/gtao_utils.wgsl @@ -1,5 +1,7 @@ #define_import_path bevy_pbr::gtao_utils +#import bevy_pbr::utils PI, HALF_PI + // Approximates single-bounce ambient occlusion to multi-bounce ambient occlusion // https://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf#page=78 fn gtao_multibounce(visibility: f32, base_color: vec3) -> vec3 { diff --git a/crates/bevy_pbr/src/ssao/preprocess_depth.wgsl b/crates/bevy_pbr/src/ssao/preprocess_depth.wgsl index 7ddcbb01265bf..d977148609d53 100644 --- a/crates/bevy_pbr/src/ssao/preprocess_depth.wgsl +++ b/crates/bevy_pbr/src/ssao/preprocess_depth.wgsl @@ -5,7 +5,7 @@ // Reference: https://research.nvidia.com/sites/default/files/pubs/2012-06_Scalable-Ambient-Obscurance/McGuire12SAO.pdf, section 2.2 -#import bevy_render::view +#import bevy_render::view View @group(0) @binding(0) var input_depth: texture_depth_2d; @group(0) @binding(1) var preprocessed_depth_mip0: texture_storage_2d; diff --git a/crates/bevy_pbr/src/ssao/spatial_denoise.wgsl b/crates/bevy_pbr/src/ssao/spatial_denoise.wgsl index 64ce2f8dcb98c..22cf2e31b6cdb 100644 --- a/crates/bevy_pbr/src/ssao/spatial_denoise.wgsl +++ b/crates/bevy_pbr/src/ssao/spatial_denoise.wgsl @@ -9,7 +9,7 @@ // XeGTAO does a 3x3 filter, on two pixels at a time per compute thread, applied twice // We do a 3x3 filter, on 1 pixel per compute thread, applied once -#import bevy_render::view +#import bevy_render::view View @group(0) @binding(0) var ambient_occlusion_noisy: texture_2d; @group(0) @binding(1) var depth_differences: texture_2d; diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index d3c8b3ba47509..21988ecc94267 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -96,6 +96,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".into()); 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 02274174e6536..08ee1fa9a7344 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -57,7 +57,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" } image = { version = "0.24", default-features = false } # misc -wgpu = { version = "0.16.0" } +wgpu = { version = "0.16.0", features=["naga"] } wgpu-hal = "0.16.0" codespan-reporting = "0.11.0" naga = { version = "0.12.0", features = ["wgsl-in"] } @@ -76,6 +76,7 @@ parking_lot = "0.12.1" regex = "1.5" ddsfile = { version = "0.5.0", optional = true } ktx2 = { version = "0.3.0", optional = true } +naga_oil = "0.8" # For ktx2 supercompression flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.4.0", 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 60fc733f19aa3..73d9075372987 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,15 @@ use bevy_utils::{ tracing::{debug, error}, Entry, HashMap, HashSet, }; +use naga::valid::Capabilities; use parking_lot::Mutex; -use std::{hash::Hash, iter::FusedIterator, mem, ops::Deref}; +use std::{borrow::Cow, hash::Hash, mem, ops::Deref}; use thiserror::Error; +#[cfg(feature = "shader_format_spirv")] +use wgpu::util::make_spirv; use wgpu::{ - PipelineLayoutDescriptor, PushConstantRange, VertexBufferLayout as RawVertexBufferLayout, + Features, PipelineLayoutDescriptor, PushConstantRange, ShaderModuleDescriptor, + VertexBufferLayout as RawVertexBufferLayout, }; use crate::render_resource::resource_macros::*; @@ -123,13 +125,12 @@ struct ShaderData { 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, } #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -162,6 +163,78 @@ impl ShaderDefVal { } impl ShaderCache { + fn new(render_device: &RenderDevice) -> Self { + const CAPABILITIES: &[(Features, Capabilities)] = &[ + (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), + (Features::SHADER_F64, Capabilities::FLOAT64), + ( + Features::SHADER_PRIMITIVE_INDEX, + Capabilities::PRIMITIVE_INDEX, + ), + ( + Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ), + ( + Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + Capabilities::SAMPLER_NON_UNIFORM_INDEXING, + ), + ( + Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + Capabilities::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ), + ]; + 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(()) + } + #[allow(clippy::result_large_err)] fn get( &mut self, @@ -210,21 +283,56 @@ 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 { + #[cfg(feature = "shader_format_spirv")] + Source::SpirV(data) => make_spirv(data), + #[cfg(not(feature = "shader_format_spirv"))] + Source::SpirV(_) => { + unimplemented!( + "Enable feature \"shader_format_spirv\" to use SPIR-V shaders" + ) + } + _ => { + for import in shader.imports() { + Self::add_import_to_composer( + &mut self.composer, + &self.import_path_shaders, + &self.shaders, + import, + )?; + } + + let shader_defs = shader_defs + .into_iter() + .map(|def| match def { + ShaderDefVal::Bool(k, v) => { + (k, naga_oil::compose::ShaderDefValue::Bool(v)) + } + ShaderDefVal::Int(k, v) => { + (k, naga_oil::compose::ShaderDefValue::Int(v)) + } + ShaderDefVal::UInt(k, v) => { + (k, naga_oil::compose::ShaderDefValue::UInt(v)) + } + }) + .collect::>(); + + let naga = self.composer.make_naga_module( + naga_oil::compose::NagaModuleDescriptor { + shader_defs, + ..shader.into() + }, + )?; + + wgpu::ShaderSource::Naga(Cow::Owned(naga)) } }; + let module_descriptor = ShaderModuleDescriptor { + label: None, + source: shader_source, + }; + render_device .wgpu_device() .push_error_scope(wgpu::ErrorFilter::Validation); @@ -256,6 +364,10 @@ 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, .. }) = self.shaders.get(&handle) { + self.composer.remove_composable_module(import_path.as_str()); + } } } @@ -264,19 +376,18 @@ impl ShaderCache { fn set_shader(&mut self, handle: &Handle, shader: Shader) -> Vec { let pipelines_to_queue = self.clear(handle); - if let Some(path) = shader.import_path() { - self.import_path_shaders - .insert(path.clone(), handle.clone_weak()); - if let Some(waiting_shaders) = self.waiting_on_import.get_mut(path) { - for waiting_shader in waiting_shaders.drain(..) { - // resolve waiting shader import - let data = self.data.entry(waiting_shader.clone_weak()).or_default(); - data.resolved_imports - .insert(path.clone(), handle.clone_weak()); - // add waiting shader as dependent of this shader - let data = self.data.entry(handle.clone_weak()).or_default(); - data.dependents.insert(waiting_shader.clone_weak()); - } + let path = shader.import_path(); + self.import_path_shaders + .insert(path.clone(), handle.clone_weak()); + if let Some(waiting_shaders) = self.waiting_on_import.get_mut(path) { + for waiting_shader in waiting_shaders.drain(..) { + // resolve waiting shader import + let data = self.data.entry(waiting_shader.clone_weak()).or_default(); + data.resolved_imports + .insert(path.clone(), handle.clone_weak()); + // add waiting shader as dependent of this shader + let data = self.data.entry(handle.clone_weak()).or_default(); + data.dependents.insert(waiting_shader.clone_weak()); } } @@ -302,9 +413,7 @@ impl ShaderCache { fn remove(&mut self, handle: &Handle) -> Vec { let pipelines_to_queue = self.clear(handle); if let Some(shader) = self.shaders.remove(handle) { - if let Some(import_path) = shader.import_path() { - self.import_path_shaders.remove(import_path); - } + self.import_path_shaders.remove(shader.import_path()); } pipelines_to_queue @@ -373,9 +482,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(), new_pipelines: default(), pipelines: default(), @@ -697,11 +806,8 @@ impl PipelineCache { } // 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) => { @@ -737,101 +843,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); - } - #[cfg(feature = "shader_format_glsl")] - 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); - } - #[cfg(feature = "shader_format_spirv")] - 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); - } - }, - #[cfg(feature = "shader_format_glsl")] - AsModuleDescriptorError::WgslConversion(error) => { - error!("failed to convert shader to wgsl: \n{}", error); - } - #[cfg(feature = "shader_format_spirv")] - 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 { @@ -840,35 +851,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/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 7829224b2bb97..0c1b071f1d93d 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -2,17 +2,10 @@ use super::ShaderDefVal; use crate::define_atomic_id; use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset}; use bevy_reflect::{TypePath, TypeUuid}; -use bevy_utils::{tracing::error, BoxedFuture, HashMap}; -#[cfg(feature = "shader_format_glsl")] -use naga::back::wgsl::WriterFlags; -use naga::{valid::Capabilities, valid::ModuleInfo, Module}; -use once_cell::sync::Lazy; -use regex::Regex; -use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr}; +use bevy_utils::{tracing::error, BoxedFuture}; + +use std::{borrow::Cow, marker::Copy}; use thiserror::Error; -#[cfg(feature = "shader_format_spirv")] -use wgpu::util::make_spirv; -use wgpu::{Features, ShaderModuleDescriptor, ShaderSource}; define_atomic_id!(ShaderId); @@ -29,47 +22,106 @@ 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, TypePath)] #[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"] pub struct Shader { - source: Source, - import_path: Option, - imports: Vec, + pub path: String, + pub source: Source, + pub import_path: ShaderImport, + pub imports: Vec, + // extra imports not specified in the source string + pub additional_imports: Vec, + // any shader defs that will be included when this module is used + pub shader_defs: Vec, } impl Shader { - pub fn from_wgsl(source: impl Into>) -> Shader { + fn preprocess(source: &str, path: &str) -> (ShaderImport, Vec) { + let (import_path, imports, _) = naga_oil::compose::get_preprocessor_data(source); + + let import_path = import_path + .map(ShaderImport::Custom) + .unwrap_or_else(|| ShaderImport::AssetPath(path.to_owned())); + + let imports = imports + .into_iter() + .map(|import| { + if import.import.starts_with('\"') { + let import = import + .import + .chars() + .skip(1) + .take_while(|c| *c != '\"') + .collect(); + ShaderImport::AssetPath(import) + } else { + ShaderImport::Custom(import.import) + } + }) + .collect(); + + (import_path, imports) + } + + pub fn from_wgsl(source: impl Into>, path: impl Into) -> Shader { let source = source.into(); - let shader_imports = SHADER_IMPORT_PROCESSOR.get_imports_from_str(&source); + let path = path.into(); + let (import_path, imports) = Shader::preprocess(&source, &path); Shader { - imports: shader_imports.imports, - import_path: shader_imports.import_path, + path, + imports, + import_path, source: Source::Wgsl(source), + additional_imports: Default::default(), + shader_defs: Default::default(), } } - pub fn from_glsl(source: impl Into>, stage: naga::ShaderStage) -> Shader { + pub fn from_wgsl_with_defs( + source: impl Into>, + path: impl Into, + shader_defs: Vec, + ) -> Shader { + Self { + shader_defs, + ..Self::from_wgsl(source, path) + } + } + + pub fn from_glsl( + source: impl Into>, + stage: naga::ShaderStage, + path: impl Into, + ) -> Shader { let source = source.into(); - let shader_imports = SHADER_IMPORT_PROCESSOR.get_imports_from_str(&source); + let path = path.into(); + let (import_path, imports) = Shader::preprocess(&source, &path); Shader { - imports: shader_imports.imports, - import_path: shader_imports.import_path, + path, + imports, + import_path, source: Source::Glsl(source, stage), + additional_imports: Default::default(), + shader_defs: Default::default(), } } - pub fn from_spirv(source: impl Into>) -> Shader { + pub fn from_spirv(source: impl Into>, path: impl Into) -> Shader { + let path = path.into(); Shader { + path: path.clone(), imports: Vec::new(), - import_path: None, + import_path: ShaderImport::AssetPath(path), source: Source::SpirV(source.into()), + additional_imports: Default::default(), + shader_defs: Default::default(), } } pub fn set_import_path>(&mut self, import_path: P) { - self.import_path = Some(ShaderImport::Custom(import_path.into())); + self.import_path = ShaderImport::Custom(import_path.into()); } #[must_use] @@ -79,8 +131,8 @@ impl Shader { } #[inline] - pub fn import_path(&self) -> Option<&ShaderImport> { - self.import_path.as_ref() + pub fn import_path(&self) -> &ShaderImport { + &self.import_path } pub fn imports(&self) -> impl ExactSizeIterator { @@ -88,6 +140,51 @@ impl Shader { } } +impl<'a> From<&'a Shader> for naga_oil::compose::ComposableModuleDescriptor<'a> { + fn from(shader: &'a Shader) -> Self { + let shader_defs = shader + .shader_defs + .iter() + .map(|def| match def { + ShaderDefVal::Bool(name, b) => { + (name.clone(), naga_oil::compose::ShaderDefValue::Bool(*b)) + } + ShaderDefVal::Int(name, i) => { + (name.clone(), naga_oil::compose::ShaderDefValue::Int(*i)) + } + ShaderDefVal::UInt(name, i) => { + (name.clone(), naga_oil::compose::ShaderDefValue::UInt(*i)) + } + }) + .collect(); + + let as_name = match &shader.import_path { + ShaderImport::AssetPath(asset_path) => Some(format!("\"{asset_path}\"")), + ShaderImport::Custom(_) => None, + }; + + naga_oil::compose::ComposableModuleDescriptor { + source: shader.source.as_str(), + file_path: &shader.path, + language: (&shader.source).into(), + additional_imports: &shader.additional_imports, + shader_defs, + as_name, + } + } +} + +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, + shader_type: (&shader.source).into(), + ..Default::default() + } + } +} + #[derive(Debug, Clone)] pub enum Source { Wgsl(Cow<'static, str>), @@ -98,167 +195,38 @@ pub enum Source { // 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 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(_) => panic!("spirv 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(_) => panic!("spirv 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)?, - #[cfg(feature = "shader_format_glsl")] - ProcessedShader::Glsl(source, shader_stage) => { - let mut parser = naga::front::glsl::Frontend::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 } - #[cfg(not(feature = "shader_format_glsl"))] - ProcessedShader::Glsl(_source, _shader_stage) => { - unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders") - } - #[cfg(feature = "shader_format_spirv")] - ProcessedShader::SpirV(source) => naga::front::spv::parse_u8_slice( - source, - &naga::front::spv::Options { - adjust_coordinate_space: false, - ..naga::front::spv::Options::default() - }, - )?, - #[cfg(not(feature = "shader_format_spirv"))] - ProcessedShader::SpirV(_source) => { - unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders") - } - }; - const CAPABILITIES: &[(Features, Capabilities)] = &[ - (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), - (Features::SHADER_F64, Capabilities::FLOAT64), - ( - Features::SHADER_PRIMITIVE_INDEX, - Capabilities::PRIMITIVE_INDEX, - ), - ( - Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - ), - ( - Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - Capabilities::SAMPLER_NON_UNIFORM_INDEXING, - ), - ( - Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - Capabilities::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - ), - ]; - 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(_) => panic!("spirv 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()) - } - #[cfg(feature = "shader_format_glsl")] - 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()) - } - #[cfg(not(feature = "shader_format_glsl"))] - ProcessedShader::Glsl(_source, _stage) => { - unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders") - } - #[cfg(feature = "shader_format_spirv")] - ProcessedShader::SpirV(source) => make_spirv(source), - #[cfg(not(feature = "shader_format_spirv"))] - ProcessedShader::SpirV(_source) => { - unimplemented!() - } - }, - }) - } -} - -#[derive(Error, Debug)] -pub enum AsModuleDescriptorError { - #[error(transparent)] - ShaderReflectError(#[from] ShaderReflectError), - #[cfg(feature = "shader_format_glsl")] - #[error(transparent)] - WgslConversion(#[from] naga::back::wgsl::Error), - #[cfg(feature = "shader_format_spirv")] - #[error(transparent)] - SpirVConversion(#[from] naga::back::spv::Error), -} - -pub struct ShaderReflection { - pub module: Module, - pub module_info: ModuleInfo, -} - -impl ShaderReflection { - #[cfg(feature = "shader_format_spirv")] - 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, - ) - } - - #[cfg(feature = "shader_format_glsl")] - pub fn get_wgsl(&self) -> Result { - naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES) } } @@ -274,38 +242,48 @@ impl AssetLoader for ShaderLoader { Box::pin(async move { let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let mut shader = match ext { - "spv" => Shader::from_spirv(Vec::from(bytes)), - "wgsl" => Shader::from_wgsl(String::from_utf8(Vec::from(bytes))?), + let shader = match ext { + "spv" => { + Shader::from_spirv(Vec::from(bytes), load_context.path().to_string_lossy()) + } + "wgsl" => Shader::from_wgsl( + String::from_utf8(Vec::from(bytes))?, + load_context.path().to_string_lossy(), + ), "vert" => Shader::from_glsl( String::from_utf8(Vec::from(bytes))?, naga::ShaderStage::Vertex, + load_context.path().to_string_lossy(), ), "frag" => Shader::from_glsl( String::from_utf8(Vec::from(bytes))?, naga::ShaderStage::Fragment, + load_context.path().to_string_lossy(), ), "comp" => Shader::from_glsl( String::from_utf8(Vec::from(bytes))?, naga::ShaderStage::Compute, + load_context.path().to_string_lossy(), ), _ => panic!("unhandled extension: {ext}"), }; - let shader_imports = SHADER_IMPORT_PROCESSOR.get_imports(&shader); - if shader_imports.import_path.is_some() { - shader.import_path = shader_imports.import_path; - } else { - shader.import_path = Some(ShaderImport::AssetPath( - load_context.path().to_string_lossy().to_string(), - )); - } + // collect file dependencies + let dependencies = shader + .imports + .iter() + .flat_map(|import| { + if let ShaderImport::AssetPath(asset_path) = import { + Some(asset_path.clone()) + } else { + None + } + }) + .collect::>(); + let mut asset = LoadedAsset::new(shader); - for import in shader_imports.imports { - if let ShaderImport::AssetPath(asset_path) = import { - let path = PathBuf::from_str(&asset_path)?; - asset.add_dependency(path.into()); - } + for dependency in dependencies { + asset.add_dependency(dependency.into()); } load_context.set_default_asset(asset); @@ -318,479 +296,20 @@ 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 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), - #[error("Unknown shader def operator: '{operator}'")] - UnknownShaderDefOperator { operator: String }, - #[error("Unknown shader def: '{shader_def_name}'")] - UnknownShaderDef { shader_def_name: String }, - #[error( - "Invalid shader def comparison for '{shader_def_name}': expected {expected}, got {value}" - )] - InvalidShaderDefComparisonValue { - shader_def_name: String, - expected: String, - value: String, - }, - #[error("Invalid shader def definition for '{shader_def_name}': {value}")] - InvalidShaderDefDefinitionValue { - shader_def_name: String, - value: String, - }, -} - -pub struct ShaderImportProcessor { - import_asset_path_regex: Regex, - import_custom_path_regex: Regex, - define_import_path_regex: Regex, -} - #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum ShaderImport { AssetPath(String), Custom(String), } -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(), +impl ShaderImport { + pub fn as_str(&self) -> &str { + match self { + ShaderImport::AssetPath(s) | ShaderImport::Custom(s) => s, } } } -#[derive(Default)] -pub struct ShaderImports { - imports: Vec, - import_path: Option, -} - -impl ShaderImportProcessor { - pub fn get_imports(&self, shader: &Shader) -> ShaderImports { - 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(), - } - } - - pub fn get_imports_from_str(&self, shader: &str) -> ShaderImports { - let mut shader_imports = ShaderImports::default(); - for line in shader.lines() { - if let Some(cap) = self.import_asset_path_regex.captures(line) { - let import = cap.get(1).unwrap(); - shader_imports - .imports - .push(ShaderImport::AssetPath(import.as_str().to_string())); - } else if let Some(cap) = self.import_custom_path_regex.captures(line) { - let import = cap.get(1).unwrap(); - shader_imports - .imports - .push(ShaderImport::Custom(import.as_str().to_string())); - } else if let Some(cap) = self.define_import_path_regex.captures(line) { - let path = cap.get(1).unwrap(); - shader_imports.import_path = Some(ShaderImport::Custom(path.as_str().to_string())); - } - } - - shader_imports - } -} - -pub static SHADER_IMPORT_PROCESSOR: Lazy = - Lazy::new(ShaderImportProcessor::default); - -pub struct ShaderProcessor { - ifdef_regex: Regex, - ifndef_regex: Regex, - ifop_regex: Regex, - else_ifdef_regex: Regex, - else_regex: Regex, - endif_regex: Regex, - define_regex: Regex, - def_regex: Regex, - def_regex_delimited: 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(), - ifop_regex: Regex::new(r"^\s*#\s*if\s*([\w|\d|_]+)\s*([^\s]*)\s*([-\w|\d]+)").unwrap(), - else_ifdef_regex: Regex::new(r"^\s*#\s*else\s+ifdef\s*([\w|\d|_]+)").unwrap(), - else_regex: Regex::new(r"^\s*#\s*else").unwrap(), - endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(), - define_regex: Regex::new(r"^\s*#\s*define\s+([\w|\d|_]+)\s*([-\w|\d]+)?").unwrap(), - def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(), - def_regex_delimited: Regex::new(r"#\s*\{([\w|\d|_]+)\}").unwrap(), - } - } -} - -struct Scope { - // Is the current scope one in which we should accept new lines into the output? - accepting_lines: bool, - - // Has this scope ever accepted lines? - // Needs to be tracked for #else ifdef chains. - has_accepted_lines: bool, -} - -impl Scope { - fn new(should_lines_be_accepted: bool) -> Self { - Self { - accepting_lines: should_lines_be_accepted, - has_accepted_lines: should_lines_be_accepted, - } - } - - fn is_accepting_lines(&self) -> bool { - self.accepting_lines - } - - fn stop_accepting_lines(&mut self) { - self.accepting_lines = false; - } - - fn start_accepting_lines_if_appropriate(&mut self) { - if !self.has_accepted_lines { - self.has_accepted_lines = true; - self.accepting_lines = true; - } else { - self.accepting_lines = false; - } - } -} - -impl ShaderProcessor { - pub fn process( - &self, - shader: &Shader, - shader_defs: &[ShaderDefVal], - shaders: &HashMap, Shader>, - import_handles: &HashMap>, - ) -> Result { - let mut shader_defs_unique = - HashMap::::from_iter(shader_defs.iter().map(|v| match v { - ShaderDefVal::Bool(k, _) | ShaderDefVal::Int(k, _) | ShaderDefVal::UInt(k, _) => { - (k.clone(), v.clone()) - } - })); - self.process_inner(shader, &mut shader_defs_unique, shaders, import_handles) - } - - fn process_inner( - &self, - shader: &Shader, - shader_defs_unique: &mut HashMap, - 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) => { - return Ok(ProcessedShader::SpirV(source.clone())); - } - }; - - let mut scopes = vec![Scope::new(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(); - - let current_valid = scopes.last().unwrap().is_accepting_lines(); - let has_define = shader_defs_unique.contains_key(def.as_str()); - - scopes.push(Scope::new(current_valid && has_define)); - } else if let Some(cap) = self.ifndef_regex.captures(line) { - let def = cap.get(1).unwrap(); - - let current_valid = scopes.last().unwrap().is_accepting_lines(); - let has_define = shader_defs_unique.contains_key(def.as_str()); - - scopes.push(Scope::new(current_valid && !has_define)); - } else if let Some(cap) = self.ifop_regex.captures(line) { - let def = cap.get(1).unwrap(); - let op = cap.get(2).unwrap(); - let val = cap.get(3).unwrap(); - - fn act_on(a: T, b: T, op: &str) -> Result { - match op { - "==" => Ok(a == b), - "!=" => Ok(a != b), - ">" => Ok(a > b), - ">=" => Ok(a >= b), - "<" => Ok(a < b), - "<=" => Ok(a <= b), - _ => Err(ProcessShaderError::UnknownShaderDefOperator { - operator: op.to_string(), - }), - } - } - - let def = shader_defs_unique.get(def.as_str()).ok_or( - ProcessShaderError::UnknownShaderDef { - shader_def_name: def.as_str().to_string(), - }, - )?; - let new_scope = match def { - ShaderDefVal::Bool(name, def) => { - let val = val.as_str().parse().map_err(|_| { - ProcessShaderError::InvalidShaderDefComparisonValue { - shader_def_name: name.clone(), - value: val.as_str().to_string(), - expected: "bool".to_string(), - } - })?; - act_on(*def, val, op.as_str())? - } - ShaderDefVal::Int(name, def) => { - let val = val.as_str().parse().map_err(|_| { - ProcessShaderError::InvalidShaderDefComparisonValue { - shader_def_name: name.clone(), - value: val.as_str().to_string(), - expected: "int".to_string(), - } - })?; - act_on(*def, val, op.as_str())? - } - ShaderDefVal::UInt(name, def) => { - let val = val.as_str().parse().map_err(|_| { - ProcessShaderError::InvalidShaderDefComparisonValue { - shader_def_name: name.clone(), - value: val.as_str().to_string(), - expected: "uint".to_string(), - } - })?; - act_on(*def, val, op.as_str())? - } - }; - - let current_valid = scopes.last().unwrap().is_accepting_lines(); - - scopes.push(Scope::new(current_valid && new_scope)); - } else if let Some(cap) = self.else_ifdef_regex.captures(line) { - // When should we accept the code in an - // - // #else ifdef FOO - // - // #endif - // - // block? Conditions: - // 1. The parent scope is accepting lines. - // 2. The current scope is _not_ accepting lines. - // 3. FOO is defined. - // 4. We haven't already accepted another #ifdef (or #else ifdef) in the current scope. - - // Condition 1 - let mut parent_accepting = true; - - if scopes.len() > 1 { - parent_accepting = scopes[scopes.len() - 2].is_accepting_lines(); - } - - if let Some(current) = scopes.last_mut() { - // Condition 2 - let current_accepting = current.is_accepting_lines(); - - // Condition 3 - let def = cap.get(1).unwrap(); - let has_define = shader_defs_unique.contains_key(def.as_str()); - - if parent_accepting && !current_accepting && has_define { - // Condition 4: Enforced by [`Scope`]. - current.start_accepting_lines_if_appropriate(); - } else { - current.stop_accepting_lines(); - } - } - } else if self.else_regex.is_match(line) { - let mut parent_accepting = true; - - if scopes.len() > 1 { - parent_accepting = scopes[scopes.len() - 2].is_accepting_lines(); - } - if let Some(current) = scopes.last_mut() { - // Using #else means that we only want to accept those lines in the output - // if the stuff before #else was _not_ accepted. - // That's why we stop accepting here if we were currently accepting. - // - // Why do we care about the parent scope? - // Because if we have something like this: - // - // #ifdef NOT_DEFINED - // // Not accepting lines - // #ifdef NOT_DEFINED_EITHER - // // Not accepting lines - // #else - // // This is now accepting lines relative to NOT_DEFINED_EITHER - // - // #endif - // #endif - // - // We don't want to actually add . - - if current.is_accepting_lines() || !parent_accepting { - current.stop_accepting_lines(); - } else { - current.start_accepting_lines_if_appropriate(); - } - } - } else if self.endif_regex.is_match(line) { - scopes.pop(); - if scopes.is_empty() { - return Err(ProcessShaderError::TooManyEndIfs); - } - } else if scopes.last().unwrap().is_accepting_lines() { - 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_unique, - &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_unique, - &mut final_string, - )?; - } else if SHADER_IMPORT_PROCESSOR - .define_import_path_regex - .is_match(line) - { - // ignore import path lines - } else if let Some(cap) = self.define_regex.captures(line) { - let def = cap.get(1).unwrap(); - let name = def.as_str().to_string(); - - if let Some(val) = cap.get(2) { - if let Ok(val) = val.as_str().parse::() { - shader_defs_unique.insert(name.clone(), ShaderDefVal::UInt(name, val)); - } else if let Ok(val) = val.as_str().parse::() { - shader_defs_unique.insert(name.clone(), ShaderDefVal::Int(name, val)); - } else if let Ok(val) = val.as_str().parse::() { - shader_defs_unique.insert(name.clone(), ShaderDefVal::Bool(name, val)); - } else { - return Err(ProcessShaderError::InvalidShaderDefDefinitionValue { - shader_def_name: name, - value: val.as_str().to_string(), - }); - } - } else { - shader_defs_unique.insert(name.clone(), ShaderDefVal::Bool(name, true)); - } - } else { - let mut line_with_defs = line.to_string(); - for capture in self.def_regex.captures_iter(line) { - let def = capture.get(1).unwrap(); - if let Some(def) = shader_defs_unique.get(def.as_str()) { - line_with_defs = self - .def_regex - .replace(&line_with_defs, def.value_as_string()) - .to_string(); - } - } - for capture in self.def_regex_delimited.captures_iter(line) { - let def = capture.get(1).unwrap(); - if let Some(def) = shader_defs_unique.get(def.as_str()) { - line_with_defs = self - .def_regex_delimited - .replace(&line_with_defs, def.value_as_string()) - .to_string(); - } - } - final_string.push_str(&line_with_defs); - 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_unique: &mut HashMap, - 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_inner(imported_shader, shader_defs_unique, 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. @@ -818,1827 +337,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, ShaderDefVal, 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_ELSE_IFDEF: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#ifdef TEXTURE -// Main texture -@group(1) @binding(0) -var sprite_texture: texture_2d; -#else ifdef SECOND_TEXTURE -// Second texture -@group(1) @binding(0) -var sprite_texture: texture_2d; -#else ifdef THIRD_TEXTURE -// Third 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_ELSE_IFDEF_NO_ELSE_FALLBACK: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#ifdef TEXTURE -// Main texture -@group(1) @binding(0) -var sprite_texture: texture_2d; -#else ifdef OTHER_TEXTURE -// Other 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_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".into()], - &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_else_ifdef_ends_up_in_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_IFDEF), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_no_match_and_no_fallback_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_ELSE_IFDEF_NO_ELSE_FALLBACK), - &[], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_ends_up_in_first_clause() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -// Main texture -@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_ELSE_IFDEF), - &["TEXTURE".into()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_ends_up_in_second_clause() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -// Second texture -@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_ELSE_IFDEF), - &["SECOND_TEXTURE".into()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_ends_up_in_third_clause() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -// Third texture -@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_ELSE_IFDEF), - &["THIRD_TEXTURE".into()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_only_accepts_one_valid_else_ifdef() { - #[rustfmt::skip] - const EXPECTED: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -// Second texture -@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_ELSE_IFDEF), - &["SECOND_TEXTURE".into(), "THIRD_TEXTURE".into()], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); - } - - #[test] - fn process_shader_def_else_ifdef_complicated_nesting() { - // Test some nesting including #else ifdef statements - // 1. Enter an #else ifdef - // 2. Then enter an #else - // 3. Then enter another #else ifdef - - #[rustfmt::skip] - const WGSL_COMPLICATED_ELSE_IFDEF: &str = r" -#ifdef NOT_DEFINED -// not defined -#else ifdef IS_DEFINED -// defined 1 -#ifdef NOT_DEFINED -// not defined -#else -// should be here -#ifdef NOT_DEFINED -// not defined -#else ifdef ALSO_NOT_DEFINED -// not defined -#else ifdef IS_DEFINED -// defined 2 -#endif -#endif -#endif -"; - - #[rustfmt::skip] - const EXPECTED: &str = r" -// defined 1 -// should be here -// defined 2 -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL_COMPLICATED_ELSE_IFDEF), - &["IS_DEFINED".into()], - &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".into()], - &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".into()], - &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".into()], - &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".into(), "ATTRIBUTE".into()], - &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".into(), "IMPORT_PRESENT".into()], - &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".into()], - &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".into()], - &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); - } - - #[test] - fn process_shader_def_unknown_operator() { - #[rustfmt::skip] - const WGSL: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#if TEXTURE !! true -@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; -} -"; - - let processor = ShaderProcessor::default(); - - let result_missing = processor.process( - &Shader::from_wgsl(WGSL), - &["TEXTURE".into()], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!( - result_missing, - Err(ProcessShaderError::UnknownShaderDefOperator { - operator: "!!".to_string() - }) - ); - } - #[test] - fn process_shader_def_equal_int() { - #[rustfmt::skip] - const WGSL: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#if TEXTURE == 3 -@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; -} -"; - - #[rustfmt::skip] - const EXPECTED_EQ: &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; -} -"; - - #[rustfmt::skip] - const EXPECTED_NEQ: &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_eq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Int("TEXTURE".to_string(), 3)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); - - let result_neq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Int("TEXTURE".to_string(), 7)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); - - let result_missing = processor.process( - &Shader::from_wgsl(WGSL), - &[], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!( - result_missing, - Err(ProcessShaderError::UnknownShaderDef { - shader_def_name: "TEXTURE".to_string() - }) - ); - - let result_wrong_type = processor.process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!( - result_wrong_type, - Err(ProcessShaderError::InvalidShaderDefComparisonValue { - shader_def_name: "TEXTURE".to_string(), - expected: "bool".to_string(), - value: "3".to_string() - }) - ); - } - - #[test] - fn process_shader_def_equal_bool() { - #[rustfmt::skip] - const WGSL: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#if TEXTURE == true -@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; -} -"; - - #[rustfmt::skip] - const EXPECTED_EQ: &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; -} -"; - - #[rustfmt::skip] - const EXPECTED_NEQ: &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_eq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); - - let result_neq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Bool("TEXTURE".to_string(), false)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); - } - - #[test] - fn process_shader_def_not_equal_bool() { - #[rustfmt::skip] - const WGSL: &str = r" -struct View { - view_proj: mat4x4, - world_position: vec3, -}; -@group(0) @binding(0) -var view: View; - -#if TEXTURE != false -@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; -} -"; - - #[rustfmt::skip] - const EXPECTED_EQ: &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; -} -"; - - #[rustfmt::skip] - const EXPECTED_NEQ: &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_eq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); - - let result_neq = processor - .process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Bool("TEXTURE".to_string(), false)], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); - - let result_missing = processor.process( - &Shader::from_wgsl(WGSL), - &[], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!( - result_missing, - Err(ProcessShaderError::UnknownShaderDef { - shader_def_name: "TEXTURE".to_string() - }) - ); - - let result_wrong_type = processor.process( - &Shader::from_wgsl(WGSL), - &[ShaderDefVal::Int("TEXTURE".to_string(), 7)], - &HashMap::default(), - &HashMap::default(), - ); - assert_eq!( - result_wrong_type, - Err(ProcessShaderError::InvalidShaderDefComparisonValue { - shader_def_name: "TEXTURE".to_string(), - expected: "int".to_string(), - value: "false".to_string() - }) - ); - } - - #[test] - fn process_shader_def_replace() { - #[rustfmt::skip] - const WGSL: &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; - var a: i32 = #FIRST_VALUE; - var b: i32 = #FIRST_VALUE * #SECOND_VALUE; - var c: i32 = #MISSING_VALUE; - var d: bool = #BOOL_VALUE; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - - #[rustfmt::skip] - const EXPECTED_REPLACED: &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; - var a: i32 = 5; - var b: i32 = 5 * 3; - var c: i32 = #MISSING_VALUE; - var d: bool = true; - out.position = view.view_proj * vec4(vertex_position, 1.0); - return out; -} -"; - let processor = ShaderProcessor::default(); - let result = processor - .process( - &Shader::from_wgsl(WGSL), - &[ - ShaderDefVal::Bool("BOOL_VALUE".to_string(), true), - ShaderDefVal::Int("FIRST_VALUE".to_string(), 5), - ShaderDefVal::Int("SECOND_VALUE".to_string(), 3), - ], - &HashMap::default(), - &HashMap::default(), - ) - .unwrap(); - assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED_REPLACED); - } - - #[test] - fn process_shader_define_in_shader() { - #[rustfmt::skip] - const WGSL: &str = r" -#ifdef NOW_DEFINED -defined at start -#endif -#define NOW_DEFINED -#ifdef NOW_DEFINED -defined at end -#endif -"; - - #[rustfmt::skip] - const EXPECTED: &str = r" -defined at end -"; - 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_define_only_in_accepting_scopes() { - #[rustfmt::skip] - const WGSL: &str = r" -#define GUARD -#ifndef GUARD -#define GUARDED -#endif -#ifdef GUARDED -This should not be part of the result -#endif -"; - - #[rustfmt::skip] - const EXPECTED: &str = r" -"; - 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_define_in_shader_with_value() { - #[rustfmt::skip] - const WGSL: &str = r" -#define DEFUINT 1 -#define DEFINT -1 -#define DEFBOOL false -#if DEFUINT == 1 -uint: #DEFUINT -#endif -#if DEFINT == -1 -int: #DEFINT -#endif -#if DEFBOOL == false -bool: #DEFBOOL -#endif -"; - - #[rustfmt::skip] - const EXPECTED: &str = r" -uint: 1 -int: -1 -bool: false -"; - 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_define_across_imports() { - #[rustfmt::skip] - const FOO: &str = r" -#define IMPORTED -"; - const BAR: &str = r" -#IMPORTED -"; - #[rustfmt::skip] - const INPUT: &str = r" -#import FOO -#import BAR -"; - #[rustfmt::skip] - const EXPECTED: &str = r" - - -true -"; - let processor = ShaderProcessor::default(); - let mut shaders = HashMap::default(); - let mut import_handles = HashMap::default(); - { - let foo_handle = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 0).typed(); - shaders.insert(foo_handle.clone_weak(), Shader::from_wgsl(FOO)); - import_handles.insert( - ShaderImport::Custom("FOO".to_string()), - foo_handle.clone_weak(), - ); - } - { - let bar_handle = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1).typed(); - shaders.insert(bar_handle.clone_weak(), Shader::from_wgsl(BAR)); - import_handles.insert( - ShaderImport::Custom("BAR".to_string()), - bar_handle.clone_weak(), - ); - } - 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/lib.rs b/crates/bevy_sprite/src/lib.rs index fd63b19579cbd..545cd19d4768e 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -29,7 +29,7 @@ pub use texture_atlas::*; pub use texture_atlas_builder::*; use bevy_app::prelude::*; -use bevy_asset::{AddAsset, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped}; use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; @@ -56,9 +56,12 @@ pub enum SpriteSystem { impl Plugin for SpritePlugin { fn build(&self, app: &mut App) { - let mut shaders = app.world.resource_mut::>(); - let sprite_shader = Shader::from_wgsl(include_str!("render/sprite.wgsl")); - shaders.set_untracked(SPRITE_SHADER_HANDLE, sprite_shader); + load_internal_asset!( + app, + SPRITE_SHADER_HANDLE, + "render/sprite.wgsl", + Shader::from_wgsl + ); app.add_asset::() .register_asset_reflect::() .register_type::() diff --git a/crates/bevy_sprite/src/mesh2d/color_material.wgsl b/crates/bevy_sprite/src/mesh2d/color_material.wgsl index 28d16f148d5ad..97e5cb73f689b 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.wgsl +++ b/crates/bevy_sprite/src/mesh2d/color_material.wgsl @@ -1,5 +1,6 @@ -#import bevy_sprite::mesh2d_types -#import bevy_sprite::mesh2d_view_bindings +#import bevy_sprite::mesh2d_types Mesh2d +#import bevy_sprite::mesh2d_vertex_output MeshVertexOutput +#import bevy_sprite::mesh2d_view_bindings view #ifdef TONEMAP_IN_SHADER #import bevy_core_pipeline::tonemapping @@ -19,24 +20,19 @@ var texture: texture_2d; @group(1) @binding(2) var texture_sampler: sampler; -@group(2) @binding(0) -var mesh: Mesh2d; - -struct FragmentInput { - #import bevy_sprite::mesh2d_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + mesh: 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); } #ifdef TONEMAP_IN_SHADER - output_color = tone_mapping(output_color); + output_color = bevy_core_pipeline::tonemapping::tone_mapping(output_color, view.color_grading); #endif return output_color; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl index b2888bb055990..2b99639836d31 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl @@ -1,8 +1,7 @@ -#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_bindings mesh +#import bevy_sprite::mesh2d_vertex_output MeshVertexOutput +#import bevy_sprite::mesh2d_view_bindings view #ifdef TONEMAP_IN_SHADER #import bevy_core_pipeline::tonemapping @@ -26,30 +25,30 @@ 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) -> MeshVertexOutput { + var out: MeshVertexOutput; #ifdef VERTEX_UVS out.uv = vertex.uv; #endif #ifdef VERTEX_POSITIONS - 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_position = mesh_functions::mesh2d_position_local_to_world( + mesh.model, + vec4(vertex.position, 1.0) + ); + out.position = mesh_functions::mesh2d_position_world_to_clip(out.world_position); #endif #ifdef VERTEX_NORMALS - out.world_normal = mesh2d_normal_local_to_world(vertex.normal); + out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal); #endif #ifdef VERTEX_TANGENTS - out.world_tangent = mesh2d_tangent_local_to_world(mesh.model, vertex.tangent); + out.world_tangent = mesh_functions::mesh2d_tangent_local_to_world( + mesh.model, + vertex.tangent + ); #endif #ifdef VERTEX_COLORS @@ -58,16 +57,14 @@ fn vertex(vertex: Vertex) -> VertexOutput { return out; } -struct FragmentInput { - #import bevy_sprite::mesh2d_vertex_output -}; - @fragment -fn fragment(in: FragmentInput) -> @location(0) vec4 { +fn fragment( + in: MeshVertexOutput, +) -> @location(0) vec4 { #ifdef VERTEX_COLORS var color = in.color; #ifdef TONEMAP_IN_SHADER - color = tone_mapping(color); + color = bevy_core_pipeline::tonemapping::tone_mapping(color, view.color_grading); #endif return color; #else 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..cf8d6e2522068 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl @@ -1,5 +1,8 @@ #define_import_path bevy_sprite::mesh2d_functions +#import bevy_sprite::mesh2d_view_bindings view +#import bevy_sprite::mesh2d_bindings mesh + fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl index cd0c2e8f42f44..607cb00c5c571 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_vertex_output.wgsl @@ -1,11 +1,16 @@ #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 { + // this is `clip position` when the struct is used as a vertex stage output + // and `frag coord` when used as a fragment stage input + @builtin(position) 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 9f16794faf813..e3ac8c30da337 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl @@ -1,6 +1,7 @@ #define_import_path bevy_sprite::mesh2d_view_bindings -#import bevy_sprite::mesh2d_view_types +#import bevy_render::view View +#import bevy_render::globals Globals @group(0) @binding(0) var view: View; diff --git a/crates/bevy_sprite/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl index 99d340286d737..d7d83bfc5709b 100644 --- a/crates/bevy_sprite/src/render/sprite.wgsl +++ b/crates/bevy_sprite/src/render/sprite.wgsl @@ -2,7 +2,7 @@ #import bevy_core_pipeline::tonemapping #endif -#import bevy_render::view +#import bevy_render::view View @group(0) @binding(0) var view: View; @@ -45,7 +45,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { #endif #ifdef TONEMAP_IN_SHADER - color = tone_mapping(color); + color = bevy_core_pipeline::tonemapping::tone_mapping(color, view.color_grading); #endif return color; diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index ed7825adbbf4f..cdd4f6fd31abd 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -111,7 +111,7 @@ impl Plugin for TextPlugin { app, DEFAULT_FONT_HANDLE, "FiraMono-subset.ttf", - |bytes: &[u8]| { Font::try_from_bytes(bytes.to_vec()).unwrap() } + |bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() } ); } } diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 2eeb825da2c00..36e18f201604a 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -1,4 +1,4 @@ -#import bevy_render::view +#import bevy_render::view View const TEXTURED_QUAD: u32 = 0u; diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 942d603483936..f2fb481388dfa 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -214,14 +214,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 { @@ -241,7 +238,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; @@ -273,7 +270,7 @@ impl Plugin for ColoredMesh2dPlugin { let mut shaders = app.world.resource_mut::>(); shaders.set_untracked( COLORED_MESH2D_SHADER_HANDLE, - Shader::from_wgsl(COLORED_MESH2D_SHADER), + Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()), ); // Register our custom draw function, and add our render systems diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index a1cbde6d1825a..389427f9eed8e 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -194,6 +194,15 @@ impl SpecializedMeshPipeline for CustomPipeline { layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + + // meshes typically live in bind group 2. because we are using bindgroup 1 + // we need to add MESH_BINDGROUP_1 shader def so that the bindings are correctly + // linked in the shader + descriptor + .vertex + .shader_defs + .push("MESH_BINDGROUP_1".into()); + descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.buffers.push(VertexBufferLayout { array_stride: std::mem::size_of::() as u64,