From 10f5c9206847ae01b8dc833c2680562e7bd46664 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Tue, 27 Jun 2023 01:29:22 +0100 Subject: [PATCH] improve shader import model (#5703) # Objective operate on naga IR directly to improve handling of shader modules. - give codespan reporting into imported modules - allow glsl to be used from wgsl and vice-versa the ultimate objective is to make it possible to - provide user hooks for core shader functions (to modify light behaviour within the standard pbr pipeline, for example) - make automatic binding slot allocation possible but ... since this is already big, adds some value and (i think) is at feature parity with the existing code, i wanted to push this now. ## Solution i made a crate called naga_oil (https://github.com/robtfm/naga_oil - unpublished for now, could be part of bevy) which manages modules by - building each module independantly to naga IR - creating "header" files for each supported language, which are used to build dependent modules/shaders - make final shaders by combining the shader IR with the IR for imported modules then integrated this into bevy, replacing some of the existing shader processing stuff. also reworked examples to reflect this. ## Migration Guide shaders that don't use `#import` directives should work without changes. the most notable user-facing difference is that imported functions/variables/etc need to be qualified at point of use, and there's no "leakage" of visible stuff into your shader scope from the imports of your imports, so if you used things imported by your imports, you now need to import them directly and qualify them. the current strategy of including/'spreading' `mesh_vertex_output` directly into a struct doesn't work any more, so these need to be modified as per the examples (e.g. color_material.wgsl, or many others). mesh data is assumed to be in bindgroup 2 by default, if mesh data is bound into bindgroup 1 instead then the shader def `MESH_BINDGROUP_1` needs to be added to the pipeline shader_defs. --- assets/shaders/animate_shader.wgsl | 10 +- assets/shaders/array_texture.wgsl | 57 +- assets/shaders/cubemap_unlit.wgsl | 6 +- assets/shaders/custom_gltf_2d.wgsl | 6 +- assets/shaders/custom_material.frag | 4 + assets/shaders/custom_material.wgsl | 6 +- .../custom_material_screenspace_texture.wgsl | 10 +- assets/shaders/custom_vertex_attribute.wgsl | 12 +- assets/shaders/instancing.wgsl | 15 +- assets/shaders/line_material.wgsl | 4 +- assets/shaders/post_processing.wgsl | 2 +- assets/shaders/shader_defs.wgsl | 4 +- assets/shaders/show_prepass.wgsl | 12 +- assets/shaders/texture_binding_array.wgsl | 8 +- assets/shaders/tonemapping_test_patterns.wgsl | 15 +- crates/bevy_asset/src/assets.rs | 77 +- crates/bevy_asset/src/debug_asset_server.rs | 2 +- crates/bevy_core_pipeline/src/blit/blit.wgsl | 2 +- crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl | 2 +- .../bevy_core_pipeline/src/skybox/skybox.wgsl | 2 +- .../src/tonemapping/tonemapping.wgsl | 10 +- .../src/tonemapping/tonemapping_shared.wgsl | 34 +- crates/bevy_gizmos/src/lines.wgsl | 11 +- crates/bevy_gizmos/src/pipeline_3d.rs | 14 +- crates/bevy_pbr/Cargo.toml | 1 + .../src/environment_map/environment_map.wgsl | 7 +- crates/bevy_pbr/src/prepass/mod.rs | 14 +- crates/bevy_pbr/src/prepass/prepass.wgsl | 21 +- .../src/prepass/prepass_bindings.wgsl | 20 +- .../bevy_pbr/src/prepass/prepass_utils.wgsl | 14 +- .../src/render/clustered_forward.wgsl | 27 +- crates/bevy_pbr/src/render/fog.wgsl | 3 + crates/bevy_pbr/src/render/mesh.rs | 21 +- crates/bevy_pbr/src/render/mesh.wgsl | 51 +- crates/bevy_pbr/src/render/mesh_bindings.wgsl | 21 +- .../bevy_pbr/src/render/mesh_functions.wgsl | 8 +- .../src/render/mesh_vertex_output.wgsl | 27 +- .../src/render/mesh_view_bindings.wgsl | 20 +- crates/bevy_pbr/src/render/morph.wgsl | 23 + .../bevy_pbr/src/render/parallax_mapping.wgsl | 2 + crates/bevy_pbr/src/render/pbr.wgsl | 112 +- crates/bevy_pbr/src/render/pbr_ambient.wgsl | 9 +- crates/bevy_pbr/src/render/pbr_bindings.wgsl | 2 +- crates/bevy_pbr/src/render/pbr_functions.wgsl | 117 +- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 12 +- crates/bevy_pbr/src/render/pbr_prepass.wgsl | 31 +- crates/bevy_pbr/src/render/shadows.wgsl | 31 +- crates/bevy_pbr/src/render/skinning.wgsl | 27 +- crates/bevy_pbr/src/render/wireframe.wgsl | 16 +- crates/bevy_pbr/src/ssao/gtao.wgsl | 8 +- crates/bevy_pbr/src/ssao/gtao_utils.wgsl | 2 + .../bevy_pbr/src/ssao/preprocess_depth.wgsl | 2 +- crates/bevy_pbr/src/ssao/spatial_denoise.wgsl | 2 +- crates/bevy_pbr/src/wireframe.rs | 4 + crates/bevy_render/Cargo.toml | 3 +- .../src/render_resource/pipeline_cache.rs | 313 +- .../bevy_render/src/render_resource/shader.rs | 2661 ++--------------- crates/bevy_sprite/src/lib.rs | 11 +- .../src/mesh2d/color_material.wgsl | 22 +- crates/bevy_sprite/src/mesh2d/mesh2d.wgsl | 43 +- .../src/mesh2d/mesh2d_bindings.wgsl | 2 +- .../src/mesh2d/mesh2d_functions.wgsl | 3 + .../src/mesh2d/mesh2d_vertex_output.wgsl | 23 +- .../src/mesh2d/mesh2d_view_bindings.wgsl | 3 +- crates/bevy_sprite/src/render/sprite.wgsl | 4 +- crates/bevy_text/src/lib.rs | 2 +- crates/bevy_ui/src/render/ui.wgsl | 2 +- examples/2d/mesh2d_manual.rs | 13 +- examples/shader/shader_instancing.rs | 9 + 69 files changed, 950 insertions(+), 3144 deletions(-) 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,