diff --git a/Cargo.toml b/Cargo.toml index b82b3729e2da6..74868e2b96e79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -371,6 +371,9 @@ bevy_light = ["bevy_internal/bevy_light"] # Provides shaders usable through asset handles. bevy_shader = ["bevy_internal/bevy_shader"] +# Provides materials. +bevy_material = ["bevy_internal/bevy_material"] + # Adds support for gizmos bevy_gizmos = ["bevy_internal/bevy_gizmos"] diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 68dccf17d2080..b5476da5f037c 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -23,6 +23,7 @@ bevy_derive = { path = "../bevy_derive", version = "0.18.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" } bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } bevy_render = { path = "../bevy_render", version = "0.18.0-dev" } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 0e5a07b870e83..0a039636eaa2c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -47,8 +47,7 @@ pub mod graph { } } -// PERF: vulkan docs recommend using 24 bit depth for better performance -pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; +pub use bevy_render::mesh::util::CORE_3D_DEPTH_FORMAT; /// True if multisampled depth textures are supported on this platform. /// @@ -847,6 +846,10 @@ pub fn prepare_core_3d_depth_textures( } } +/// A material can specify [`MaterialProperties::reads_view_transmission_texture`](`bevy_material::material::MaterialProperties`) to read from [`ViewTransmissionTexture`]. +/// +/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires +/// rendering to take place in a separate [`Transmissive3d`] pass. #[derive(Component)] pub struct ViewTransmissionTexture { pub texture: Texture, diff --git a/crates/bevy_gizmos_render/src/pipeline_3d.rs b/crates/bevy_gizmos_render/src/pipeline_3d.rs index 854c3c7b47ae8..a79f187daf24b 100644 --- a/crates/bevy_gizmos_render/src/pipeline_3d.rs +++ b/crates/bevy_gizmos_render/src/pipeline_3d.rs @@ -21,7 +21,7 @@ use bevy_ecs::{ system::{Commands, Query, Res, ResMut}, }; use bevy_image::BevyDefault as _; -use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; +use bevy_pbr::{init_mesh_pipeline, MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ @@ -56,7 +56,9 @@ impl Plugin for LineGizmo3dPlugin { ) .add_systems( RenderStartup, - init_line_gizmo_pipelines.after(init_line_gizmo_uniform_bind_group_layout), + init_line_gizmo_pipelines + .after(init_line_gizmo_uniform_bind_group_layout) + .after(init_mesh_pipeline), ) .add_systems( Render, diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 9870a2c5a4366..db2946e167e04 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -31,6 +31,7 @@ bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_render = { path = "../bevy_render", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_scene = { path = "../bevy_scene", version = "0.18.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.18.0-dev" } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 0a477401e207b..c8dc113200fdc 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -145,6 +145,7 @@ android-game-activity = ["bevy_winit/android-game-activity"] # Transmission textures in `StandardMaterial`: pbr_transmission_textures = [ + "bevy_render?/pbr_transmission_textures", "bevy_pbr?/pbr_transmission_textures", "bevy_gltf?/pbr_transmission_textures", ] @@ -154,27 +155,34 @@ pbr_clustered_decals = ["bevy_pbr?/pbr_clustered_decals"] # Light Texture support pbr_light_textures = [ + "bevy_render?/pbr_light_textures", "bevy_pbr?/pbr_clustered_decals", "bevy_pbr?/pbr_light_textures", ] # Multi-layer material textures in `StandardMaterial`: pbr_multi_layer_material_textures = [ + "bevy_render?/pbr_multi_layer_material_textures", "bevy_pbr?/pbr_multi_layer_material_textures", "bevy_gltf?/pbr_multi_layer_material_textures", ] # Anisotropy texture in `StandardMaterial`: pbr_anisotropy_texture = [ + "bevy_render?/pbr_anisotropy_texture", "bevy_pbr?/pbr_anisotropy_texture", "bevy_gltf?/pbr_anisotropy_texture", ] # Percentage-closer soft shadows -experimental_pbr_pcss = ["bevy_pbr?/experimental_pbr_pcss"] +experimental_pbr_pcss = [ + "bevy_render?/experimental_pbr_pcss", + "bevy_pbr?/experimental_pbr_pcss", +] # Specular textures in `StandardMaterial`: pbr_specular_textures = [ + "bevy_render?/pbr_specular_textures", "bevy_pbr?/pbr_specular_textures", "bevy_gltf?/pbr_specular_textures", ] @@ -227,6 +235,7 @@ bevy_window = ["dep:bevy_window", "dep:bevy_a11y", "bevy_image"] bevy_winit = ["dep:bevy_winit", "bevy_window"] bevy_camera = ["dep:bevy_camera", "bevy_mesh", "bevy_window"] bevy_scene = ["dep:bevy_scene", "bevy_asset"] +bevy_material = ["dep:bevy_material", "bevy_image", "bevy_shader"] bevy_light = ["dep:bevy_light", "bevy_camera", "bevy_gizmos?/bevy_light"] bevy_render = [ "dep:bevy_render", @@ -489,6 +498,7 @@ bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.18.0-dev" } bevy_feathers = { path = "../bevy_feathers", optional = true, version = "0.18.0-dev" } bevy_image = { path = "../bevy_image", optional = true, version = "0.18.0-dev" } bevy_shader = { path = "../bevy_shader", optional = true, version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", optional = true, version = "0.18.0-dev" } bevy_mesh = { path = "../bevy_mesh", optional = true, version = "0.18.0-dev" } bevy_camera = { path = "../bevy_camera", optional = true, version = "0.18.0-dev" } bevy_light = { path = "../bevy_light", optional = true, version = "0.18.0-dev" } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index e034b1688df7c..24a5dd8723cda 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -58,6 +58,8 @@ pub use bevy_input_focus as input_focus; pub use bevy_light as light; #[cfg(feature = "bevy_log")] pub use bevy_log as log; +#[cfg(feature = "bevy_material")] +pub use bevy_material as material; pub use bevy_math as math; #[cfg(feature = "bevy_mesh")] pub use bevy_mesh as mesh; diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index ea4a306d0a6d6..193245b7afcf3 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -33,6 +33,10 @@ pub use crate::camera::prelude::*; #[cfg(feature = "bevy_shader")] pub use crate::shader::prelude::*; +#[doc(hidden)] +#[cfg(feature = "bevy_material")] +pub use crate::material::prelude::*; + pub use bevy_derive::{bevy_main, Deref, DerefMut}; #[doc(hidden)] diff --git a/crates/bevy_material/Cargo.toml b/crates/bevy_material/Cargo.toml new file mode 100644 index 0000000000000..e8a35b480f4c2 --- /dev/null +++ b/crates/bevy_material/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "bevy_material" +version = "0.18.0-dev" +edition = "2024" +description = "Provides a material abstraction for Bevy Engine" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.18.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.18.0-dev" } +bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev" } +bevy_color = { path = "../bevy_color", version = "0.18.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.18.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.18.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } +bevy_material_macros = { path = "macros", version = "0.18.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } +bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } +bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } +bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" } +bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev" } + +encase = "0.12" +offset-allocator = "0.2" +tracing = { version = "0.1", default-features = false, features = ["std"] } +thiserror = { version = "2", default-features = false } +wgpu-types = { version = "26", default-features = false } +variadics_please = "1.1" +bitflags = "2" +static_assertions = "1" +nonmax = "0.5" +smallvec = { version = "1", default-features = false } + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_material/LICENSE-APACHE b/crates/bevy_material/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_material/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_material/LICENSE-MIT b/crates/bevy_material/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_material/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_material/README.md b/crates/bevy_material/README.md new file mode 100644 index 0000000000000..3c6bf8dc82bed --- /dev/null +++ b/crates/bevy_material/README.md @@ -0,0 +1,7 @@ +# Bevy Material + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy_log.svg)](https://crates.io/crates/bevy_log) +[![Downloads](https://img.shields.io/crates/d/bevy_log.svg)](https://crates.io/crates/bevy_log) +[![Docs](https://docs.rs/bevy_log/badge.svg)](https://docs.rs/bevy_log/latest/bevy_log/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_material/macros/Cargo.toml b/crates/bevy_material/macros/Cargo.toml new file mode 100644 index 0000000000000..765a2c6652937 --- /dev/null +++ b/crates/bevy_material/macros/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bevy_material_macros" +version = "0.18.0-dev" +edition = "2024" +description = "Derive implementations for bevy_material" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[lib] +proc-macro = true + +[dependencies] +bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.18.0-dev" } + +syn = { version = "2.0", features = ["full"] } +proc-macro2 = "1.0" +quote = "1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_material/macros/LICENSE-APACHE b/crates/bevy_material/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_material/macros/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_material/macros/LICENSE-MIT b/crates/bevy_material/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_material/macros/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_material/macros/src/lib.rs b/crates/bevy_material/macros/src/lib.rs new file mode 100644 index 0000000000000..f8e86280d3075 --- /dev/null +++ b/crates/bevy_material/macros/src/lib.rs @@ -0,0 +1,37 @@ +#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +use bevy_macro_utils::{derive_label, BevyManifest}; +use proc_macro::TokenStream; +use quote::format_ident; +use syn::{parse_macro_input, DeriveInput}; + +pub(crate) fn bevy_material_path() -> syn::Path { + BevyManifest::shared(|manifest| manifest.get_path("bevy_material")) +} + +#[proc_macro_derive(ShaderLabel)] +pub fn derive_shader_label(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let mut trait_path = bevy_material_path(); + trait_path + .segments + .push(format_ident!("render_phase").into()); + trait_path + .segments + .push(format_ident!("ShaderLabel").into()); + derive_label(input, "ShaderLabel", &trait_path) +} + +#[proc_macro_derive(DrawFunctionLabel)] +pub fn derive_draw_function_label(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let mut trait_path = bevy_material_path(); + trait_path + .segments + .push(format_ident!("render_phase").into()); + trait_path + .segments + .push(format_ident!("DrawFunctionLabel").into()); + derive_label(input, "DrawFunctionLabel", &trait_path) +} diff --git a/crates/bevy_render/src/alpha.rs b/crates/bevy_material/src/alpha.rs similarity index 97% rename from crates/bevy_render/src/alpha.rs rename to crates/bevy_material/src/alpha.rs index dd748811193d4..1fe587ac7f506 100644 --- a/crates/bevy_render/src/alpha.rs +++ b/crates/bevy_material/src/alpha.rs @@ -1,3 +1,5 @@ +//! Allows configuring a material's transparency behavior. + use bevy_reflect::{std_traits::ReflectDefault, Reflect}; // TODO: add discussion about performance. diff --git a/crates/bevy_material/src/lib.rs b/crates/bevy_material/src/lib.rs new file mode 100644 index 0000000000000..54ade1c6df893 --- /dev/null +++ b/crates/bevy_material/src/lib.rs @@ -0,0 +1,19 @@ +//! Provides a material abstraction for bevy +#![allow(missing_docs, reason = "Needs docs")] + +extern crate alloc; + +pub mod alpha; +pub mod material; +pub mod opaque; +pub mod render; +pub mod render_phase; +pub mod render_resource; + +/// The material prelude. +/// +/// This includes the most common types in this crate, re-exported for your convenience. +pub mod prelude { + #[doc(hidden)] + pub use crate::alpha::AlphaMode; +} diff --git a/crates/bevy_material/src/material.rs b/crates/bevy_material/src/material.rs new file mode 100644 index 0000000000000..3ef5cdfcbe133 --- /dev/null +++ b/crates/bevy_material/src/material.rs @@ -0,0 +1,202 @@ +use crate::alpha::AlphaMode; +use crate::opaque::OpaqueRendererMethod; +use crate::render::MeshPipeline; +use crate::render::MeshPipelineKey; +use crate::render_phase::{ + DrawFunctionId, DrawFunctionLabel, InternedDrawFunctionLabel, InternedShaderLabel, ShaderLabel, +}; +use crate::render_resource::{ + BindGroupLayoutDescriptor, RenderPipelineDescriptor, SpecializedMeshPipelineError, +}; +use crate::*; +use alloc::sync::Arc; +use bevy_asset::Handle; +use bevy_ecs::resource::Resource; +use bevy_mesh::MeshVertexBufferLayoutRef; +use bevy_platform::hash::FixedHasher; +use bevy_shader::Shader; +use core::any::{Any, TypeId}; +use core::hash::Hash; +use core::hash::{BuildHasher, Hasher}; +use smallvec::SmallVec; + +pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; + +/// Render pipeline data for a given material. +#[derive(Resource, Clone)] +pub struct MaterialPipeline { + pub mesh_pipeline: MeshPipeline, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ErasedMaterialPipelineKey { + pub mesh_key: MeshPipelineKey, + pub material_key: ErasedMaterialKey, + pub type_id: TypeId, +} + +#[derive(Debug)] +pub struct ErasedMaterialKey { + type_id: TypeId, + hash: u64, + value: Box, + vtable: Arc, +} + +#[derive(Debug)] +pub struct ErasedMaterialKeyVTable { + clone_fn: fn(&dyn Any) -> Box, + partial_eq_fn: fn(&dyn Any, &dyn Any) -> bool, +} + +impl ErasedMaterialKey { + pub fn new(material_key: T) -> Self + where + T: Clone + Hash + PartialEq + Send + Sync + 'static, + { + let type_id = TypeId::of::(); + let hash = FixedHasher::hash_one(&FixedHasher, &material_key); + + fn clone(any: &dyn Any) -> Box { + Box::new(any.downcast_ref::().unwrap().clone()) + } + fn partial_eq(a: &dyn Any, b: &dyn Any) -> bool { + a.downcast_ref::().unwrap() == b.downcast_ref::().unwrap() + } + + Self { + type_id, + hash, + value: Box::new(material_key), + vtable: Arc::new(ErasedMaterialKeyVTable { + clone_fn: clone::, + partial_eq_fn: partial_eq::, + }), + } + } + + pub fn to_key(&self) -> T { + debug_assert_eq!(self.type_id, TypeId::of::()); + self.value.downcast_ref::().unwrap().clone() + } +} + +impl PartialEq for ErasedMaterialKey { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id + && (self.vtable.partial_eq_fn)(self.value.as_ref(), other.value.as_ref()) + } +} + +impl Eq for ErasedMaterialKey {} + +impl Clone for ErasedMaterialKey { + fn clone(&self) -> Self { + Self { + type_id: self.type_id, + hash: self.hash, + value: (self.vtable.clone_fn)(self.value.as_ref()), + vtable: self.vtable.clone(), + } + } +} + +impl Hash for ErasedMaterialKey { + fn hash(&self, state: &mut H) { + self.type_id.hash(state); + self.hash.hash(state); + } +} + +impl Default for ErasedMaterialKey { + fn default() -> Self { + Self::new(()) + } +} + +/// Common material properties, calculated for a specific material instance. +#[derive(Default)] +pub struct MaterialProperties { + /// Is this material should be rendered by the deferred renderer when. + /// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`] + pub render_method: OpaqueRendererMethod, + /// The [`AlphaMode`] of this material. + pub alpha_mode: AlphaMode, + /// The bits in the [`MeshPipelineKey`] for this material. + /// + /// These are precalculated so that we can just "or" them together. + pub mesh_pipeline_key_bits: MeshPipelineKey, + /// Add a bias to the view depth of the mesh which can be used to force a specific render order + /// for meshes with equal depth, to avoid z-fighting. + /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. + pub depth_bias: f32, + /// Whether the material would like to read from a view transmission texture + /// + /// This allows taking color output from the opaque 3d pass as an input, (for screen-space transmission) but requires + /// rendering to take place in a separate transmissive 3d pass. + pub reads_view_transmission_texture: bool, + pub render_phase_type: RenderPhaseType, + pub material_layout: Option, + /// Backing array is a size of 4 because the `StandardMaterial` needs 4 draw functions by default + pub draw_functions: SmallVec<[(InternedDrawFunctionLabel, DrawFunctionId); 4]>, + /// Backing array is a size of 3 because the `StandardMaterial` has 3 custom shaders (`frag`, `prepass_frag`, `deferred_frag`) which is the + /// most common use case + pub shaders: SmallVec<[(InternedShaderLabel, Handle); 3]>, + /// Whether this material *actually* uses bindless resources, taking the + /// platform support (or lack thereof) of bindless resources into account. + pub bindless: bool, + pub specialize: Option< + fn( + &MaterialPipeline, + &mut RenderPipelineDescriptor, + &MeshVertexBufferLayoutRef, + ErasedMaterialPipelineKey, + ) -> Result<(), SpecializedMeshPipelineError>, + >, + /// The key for this material, typically a bitfield of flags that are used to modify + /// the pipeline descriptor used for this material. + pub material_key: ErasedMaterialKey, + /// Whether shadows are enabled for this material + pub shadows_enabled: bool, + /// Whether prepass is enabled for this material + pub prepass_enabled: bool, +} + +impl MaterialProperties { + pub fn get_shader(&self, label: impl ShaderLabel) -> Option> { + self.shaders + .iter() + .find(|(inner_label, _)| inner_label == &label.intern()) + .map(|(_, shader)| shader) + .cloned() + } + + pub fn add_shader(&mut self, label: impl ShaderLabel, shader: Handle) { + self.shaders.push((label.intern(), shader)); + } + + pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option { + self.draw_functions + .iter() + .find(|(inner_label, _)| inner_label == &label.intern()) + .map(|(_, shader)| shader) + .cloned() + } + + pub fn add_draw_function( + &mut self, + label: impl DrawFunctionLabel, + draw_function: DrawFunctionId, + ) { + self.draw_functions.push((label.intern(), draw_function)); + } +} + +#[derive(Clone, Copy, Default)] +pub enum RenderPhaseType { + #[default] + Opaque, + AlphaMask, + Transmissive, + Transparent, +} diff --git a/crates/bevy_material/src/opaque.rs b/crates/bevy_material/src/opaque.rs new file mode 100644 index 0000000000000..3df7f0c0e4e60 --- /dev/null +++ b/crates/bevy_material/src/opaque.rs @@ -0,0 +1,28 @@ +use bevy_reflect::prelude::{Reflect, ReflectDefault}; + +/// Render method used for opaque materials. +/// +/// The forward rendering main pass draws each mesh entity and shades it according to its +/// corresponding material and the lights that affect it. Some render features like Screen Space +/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like +/// prepasses over all mesh entities to populate depth and normal textures. This means that when +/// using render features that require running prepasses, multiple passes over all visible geometry +/// are required. This can be slow if there is a lot of geometry that cannot be batched into few +/// draws. +/// +/// Deferred rendering runs a prepass to gather not only geometric information like depth and +/// normals, but also all the material properties like base color, emissive color, reflectance, +/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is +/// then a fullscreen pass that reads data from these textures and executes shading. This allows +/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier +/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices. +/// +/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used. +#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)] +#[reflect(Default, Clone, PartialEq)] +pub enum OpaqueRendererMethod { + #[default] + Forward, + Deferred, + Auto, +} diff --git a/crates/bevy_material/src/render/mesh.rs b/crates/bevy_material/src/render/mesh.rs new file mode 100644 index 0000000000000..00bb125fefeba --- /dev/null +++ b/crates/bevy_material/src/render/mesh.rs @@ -0,0 +1,228 @@ +use crate::{ + render::{ + MeshLayouts, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, + }, + render_resource::*, +}; +use bevy_asset::Handle; +use bevy_ecs::resource::Resource; +use bevy_mesh::BaseMeshPipelineKey; +use bevy_shader::Shader; + +use static_assertions::const_assert_eq; + +/// How many textures are allowed in the view bind group layout (`@group(0)`) before +/// broader compatibility with WebGL and WebGPU is at risk, due to the minimum guaranteed +/// values for `MAX_TEXTURE_IMAGE_UNITS` (in WebGL) and `maxSampledTexturesPerShaderStage` (in WebGPU), +/// currently both at 16. +/// +/// We use 10 here because it still leaves us, in a worst case scenario, with 6 textures for the other bind groups. +/// +/// See: +#[cfg(debug_assertions)] +pub const MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES: usize = 10; + +/// All data needed to construct a pipeline for rendering 3D meshes. +#[derive(Resource, Clone)] +pub struct MeshPipeline { + /// A reference to all the mesh pipeline view layouts. + pub view_layouts: MeshPipelineViewLayouts, + pub clustered_forward_buffer_binding_type: BufferBindingType, + pub mesh_layouts: MeshLayouts, + /// The shader asset handle. + pub shader: Handle, + /// `MeshUniform`s are stored in arrays in buffers. If storage buffers are available, they + /// are used and this will be `None`, otherwise uniform buffers will be used with batches + /// of this many `MeshUniform`s, stored at dynamic offsets within the uniform buffer. + /// Use code like this in custom shaders: + /// ```wgsl + /// ##ifdef PER_OBJECT_BUFFER_BATCH_SIZE + /// @group(1) @binding(0) var mesh: array; + /// ##else + /// @group(1) @binding(0) var mesh: array; + /// ##endif // PER_OBJECT_BUFFER_BATCH_SIZE + /// ``` + pub per_object_buffer_batch_size: Option, + + /// Whether binding arrays (a.k.a. bindless textures) are usable on the + /// current render device. + /// + /// This affects whether reflection probes can be used. + pub binding_arrays_are_usable: bool, + + /// Whether clustered decals are usable on the current render device. + pub clustered_decals_are_usable: bool, + + /// Whether skins will use uniform buffers on account of storage buffers + /// being unavailable on this platform. + pub skins_use_uniform_buffers: bool, +} + +impl MeshPipeline { + pub fn get_view_layout( + &self, + layout_key: MeshPipelineViewLayoutKey, + ) -> &MeshPipelineViewLayout { + self.view_layouts.get_view_layout(layout_key) + } +} + +bitflags::bitflags! { + #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] + #[repr(transparent)] + // NOTE: Apparently quadro drivers support up to 64x MSAA. + /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. + pub struct MeshPipelineKey: u64 { + // Nothing + const NONE = 0; + + // Inherited bits + const MORPH_TARGETS = BaseMeshPipelineKey::MORPH_TARGETS.bits(); + + // Flag bits + const HDR = 1 << 0; + const TONEMAP_IN_SHADER = 1 << 1; + const DEBAND_DITHER = 1 << 2; + const DEPTH_PREPASS = 1 << 3; + const NORMAL_PREPASS = 1 << 4; + const DEFERRED_PREPASS = 1 << 5; + const MOTION_VECTOR_PREPASS = 1 << 6; + const MAY_DISCARD = 1 << 7; // Guards shader codepaths that may discard, allowing early depth tests in most cases + // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test + const ENVIRONMENT_MAP = 1 << 8; + const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9; + const UNCLIPPED_DEPTH_ORTHO = 1 << 10; // Disables depth clipping for use with directional light shadow views + // Emulated via fragment shader depth on hardware that doesn't support it natively + // See: https://www.w3.org/TR/webgpu/#depth-clipping and https://therealmjp.github.io/posts/shadow-maps/#disabling-z-clipping + const TEMPORAL_JITTER = 1 << 11; + const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12; + const LIGHTMAPPED = 1 << 13; + const LIGHTMAP_BICUBIC_SAMPLING = 1 << 14; + const IRRADIANCE_VOLUME = 1 << 15; + const VISIBILITY_RANGE_DITHER = 1 << 16; + const SCREEN_SPACE_REFLECTIONS = 1 << 17; + const HAS_PREVIOUS_SKIN = 1 << 18; + const HAS_PREVIOUS_MORPH = 1 << 19; + const OIT_ENABLED = 1 << 20; + const DISTANCE_FOG = 1 << 21; + const LAST_FLAG = Self::DISTANCE_FOG.bits(); + + // Bitfields + const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; + const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state + const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask + const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; // ← As blend states is on 3 bits, it can range from 0 to 7 + const BLEND_MULTIPLY = 2 << Self::BLEND_SHIFT_BITS; // ← See `BLEND_MASK_BITS` for the number of bits available + const BLEND_ALPHA = 3 << Self::BLEND_SHIFT_BITS; // + const BLEND_ALPHA_TO_COVERAGE = 4 << Self::BLEND_SHIFT_BITS; // ← We still have room for three more values without adding more bits + const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; + const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; + const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; + const SHADOW_FILTER_METHOD_GAUSSIAN = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; + const SHADOW_FILTER_METHOD_TEMPORAL = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; + const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS; + const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS; + const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS; + const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS; + const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS = Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW = 0 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM = 1 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH = 2 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA = 3 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const ALL_RESERVED_BITS = + Self::BLEND_RESERVED_BITS.bits() | + Self::MSAA_RESERVED_BITS.bits() | + Self::TONEMAP_METHOD_RESERVED_BITS.bits() | + Self::SHADOW_FILTER_METHOD_RESERVED_BITS.bits() | + Self::VIEW_PROJECTION_RESERVED_BITS.bits() | + Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS.bits(); + } +} + +impl MeshPipelineKey { + const MSAA_MASK_BITS: u64 = 0b111; + const MSAA_SHIFT_BITS: u64 = Self::LAST_FLAG.bits().trailing_zeros() as u64 + 1; + + const BLEND_MASK_BITS: u64 = 0b111; + const BLEND_SHIFT_BITS: u64 = Self::MSAA_MASK_BITS.count_ones() as u64 + Self::MSAA_SHIFT_BITS; + + const TONEMAP_METHOD_MASK_BITS: u64 = 0b111; + const TONEMAP_METHOD_SHIFT_BITS: u64 = + Self::BLEND_MASK_BITS.count_ones() as u64 + Self::BLEND_SHIFT_BITS; + + const SHADOW_FILTER_METHOD_MASK_BITS: u64 = 0b11; + const SHADOW_FILTER_METHOD_SHIFT_BITS: u64 = + Self::TONEMAP_METHOD_MASK_BITS.count_ones() as u64 + Self::TONEMAP_METHOD_SHIFT_BITS; + + const VIEW_PROJECTION_MASK_BITS: u64 = 0b11; + const VIEW_PROJECTION_SHIFT_BITS: u64 = Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones() + as u64 + + Self::SHADOW_FILTER_METHOD_SHIFT_BITS; + + const SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS: u64 = 0b11; + const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u64 = + Self::VIEW_PROJECTION_MASK_BITS.count_ones() as u64 + Self::VIEW_PROJECTION_SHIFT_BITS; + + pub fn from_msaa_samples(msaa_samples: u32) -> Self { + let msaa_bits = + (msaa_samples.trailing_zeros() as u64 & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; + Self::from_bits_retain(msaa_bits) + } + + pub fn from_hdr(hdr: bool) -> Self { + if hdr { + MeshPipelineKey::HDR + } else { + MeshPipelineKey::NONE + } + } + + pub fn msaa_samples(&self) -> u32 { + 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + } + + pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { + let primitive_topology_bits = ((primitive_topology as u64) + & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS) + << BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + Self::from_bits_retain(primitive_topology_bits) + } + + pub fn primitive_topology(&self) -> PrimitiveTopology { + let primitive_topology_bits = (self.bits() + >> BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS) + & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS; + match primitive_topology_bits { + x if x == PrimitiveTopology::PointList as u64 => PrimitiveTopology::PointList, + x if x == PrimitiveTopology::LineList as u64 => PrimitiveTopology::LineList, + x if x == PrimitiveTopology::LineStrip as u64 => PrimitiveTopology::LineStrip, + x if x == PrimitiveTopology::TriangleList as u64 => PrimitiveTopology::TriangleList, + x if x == PrimitiveTopology::TriangleStrip as u64 => PrimitiveTopology::TriangleStrip, + _ => PrimitiveTopology::default(), + } + } +} + +// Ensure that we didn't overflow the number of bits available in `MeshPipelineKey`. +const_assert_eq!( + (((MeshPipelineKey::LAST_FLAG.bits() << 1) - 1) | MeshPipelineKey::ALL_RESERVED_BITS.bits()) + & BaseMeshPipelineKey::all().bits(), + 0 +); + +// Ensure that the reserved bits don't overlap with the topology bits +const_assert_eq!( + (BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS + << BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS) + & MeshPipelineKey::ALL_RESERVED_BITS.bits(), + 0 +); diff --git a/crates/bevy_material/src/render/mesh_bindings.rs b/crates/bevy_material/src/render/mesh_bindings.rs new file mode 100644 index 0000000000000..b7dea36dbab11 --- /dev/null +++ b/crates/bevy_material/src/render/mesh_bindings.rs @@ -0,0 +1,34 @@ +use crate::render_resource::BindGroupLayoutDescriptor; + +/// All possible [`BindGroupLayoutDescriptor`]s in bevy's default mesh shader (`mesh.wgsl`). +#[derive(Clone)] +pub struct MeshLayouts { + /// The mesh model uniform (transform) and nothing else. + pub model_only: BindGroupLayoutDescriptor, + + /// Includes the lightmap texture and uniform. + pub lightmapped: BindGroupLayoutDescriptor, + + /// Also includes the uniform for skinning + pub skinned: BindGroupLayoutDescriptor, + + /// Like [`MeshLayouts::skinned`], but includes slots for the previous + /// frame's joint matrices, so that we can compute motion vectors. + pub skinned_motion: BindGroupLayoutDescriptor, + + /// Also includes the uniform and [`MorphAttributes`](`bevy_mesh::morph::MorphAttributes`) for morph targets. + pub morphed: BindGroupLayoutDescriptor, + + /// Like [`MeshLayouts::morphed`], but includes a slot for the previous + /// frame's morph weights, so that we can compute motion vectors. + pub morphed_motion: BindGroupLayoutDescriptor, + + /// Also includes both uniforms for skinning and morph targets, also the + /// morph target [`MorphAttributes`](`bevy_mesh::morph::MorphAttributes`) binding. + pub morphed_skinned: BindGroupLayoutDescriptor, + + /// Like [`MeshLayouts::morphed_skinned`], but includes slots for the + /// previous frame's joint matrices and morph weights, so that we can + /// compute motion vectors. + pub morphed_skinned_motion: BindGroupLayoutDescriptor, +} diff --git a/crates/bevy_material/src/render/mesh_view_bindings.rs b/crates/bevy_material/src/render/mesh_view_bindings.rs new file mode 100644 index 0000000000000..a1c1d39e2ee7d --- /dev/null +++ b/crates/bevy_material/src/render/mesh_view_bindings.rs @@ -0,0 +1,133 @@ +use crate::{render::MeshPipelineKey, render_resource::*}; +use alloc::sync::Arc; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::resource::Resource; + +#[cfg(debug_assertions)] +use {crate::render::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::warn}; + +#[derive(Clone)] +pub struct MeshPipelineViewLayout { + pub main_layout: BindGroupLayoutDescriptor, + pub binding_array_layout: BindGroupLayoutDescriptor, + pub empty_layout: BindGroupLayoutDescriptor, + + #[cfg(debug_assertions)] + pub texture_count: usize, +} + +bitflags::bitflags! { + /// A key that uniquely identifies a [`MeshPipelineViewLayout`]. + /// + /// Used to generate all possible layouts for the mesh pipeline in [`generate_view_layouts`], + /// so special care must be taken to not add too many flags, as the number of possible layouts + /// will grow exponentially. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + #[repr(transparent)] + pub struct MeshPipelineViewLayoutKey: u32 { + const MULTISAMPLED = 1 << 0; + const DEPTH_PREPASS = 1 << 1; + const NORMAL_PREPASS = 1 << 2; + const MOTION_VECTOR_PREPASS = 1 << 3; + const DEFERRED_PREPASS = 1 << 4; + const OIT_ENABLED = 1 << 5; + } +} + +impl MeshPipelineViewLayoutKey { + // The number of possible layouts + pub const COUNT: usize = Self::all().bits() as usize + 1; + + /// Builds a unique label for each layout based on the flags + pub fn label(&self) -> String { + use MeshPipelineViewLayoutKey as Key; + + format!( + "mesh_view_layout{}{}{}{}{}{}", + if self.contains(Key::MULTISAMPLED) { + "_multisampled" + } else { + Default::default() + }, + if self.contains(Key::DEPTH_PREPASS) { + "_depth" + } else { + Default::default() + }, + if self.contains(Key::NORMAL_PREPASS) { + "_normal" + } else { + Default::default() + }, + if self.contains(Key::MOTION_VECTOR_PREPASS) { + "_motion" + } else { + Default::default() + }, + if self.contains(Key::DEFERRED_PREPASS) { + "_deferred" + } else { + Default::default() + }, + if self.contains(Key::OIT_ENABLED) { + "_oit" + } else { + Default::default() + }, + ) + } +} + +impl From for MeshPipelineViewLayoutKey { + fn from(value: MeshPipelineKey) -> Self { + let mut result = MeshPipelineViewLayoutKey::empty(); + + if value.msaa_samples() > 1 { + result |= MeshPipelineViewLayoutKey::MULTISAMPLED; + } + if value.contains(MeshPipelineKey::DEPTH_PREPASS) { + result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; + } + if value.contains(MeshPipelineKey::NORMAL_PREPASS) { + result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; + } + if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; + } + if value.contains(MeshPipelineKey::DEFERRED_PREPASS) { + result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; + } + if value.contains(MeshPipelineKey::OIT_ENABLED) { + result |= MeshPipelineViewLayoutKey::OIT_ENABLED; + } + + result + } +} + +/// Stores the view layouts for every combination of pipeline keys. +/// +/// This is wrapped in an [`Arc`] so that it can be efficiently cloned and +/// placed inside specializable pipeline types. +#[derive(Resource, Clone, Deref, DerefMut)] +pub struct MeshPipelineViewLayouts( + pub Arc<[MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT]>, +); + +impl MeshPipelineViewLayouts { + pub fn get_view_layout( + &self, + layout_key: MeshPipelineViewLayoutKey, + ) -> &MeshPipelineViewLayout { + let index = layout_key.bits() as usize; + let layout = &self[index]; + + #[cfg(debug_assertions)] + if layout.texture_count > MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES { + // Issue our own warning here because Naga's error message is a bit cryptic in this situation + once!(warn!("Too many textures in mesh pipeline view layout, this might cause us to hit `wgpu::Limits::max_sampled_textures_per_shader_stage` in some environments.")); + } + + layout + } +} diff --git a/crates/bevy_material/src/render/mod.rs b/crates/bevy_material/src/render/mod.rs new file mode 100644 index 0000000000000..3c38edf2ee82a --- /dev/null +++ b/crates/bevy_material/src/render/mod.rs @@ -0,0 +1,7 @@ +mod mesh; +mod mesh_bindings; +mod mesh_view_bindings; + +pub use mesh::*; +pub use mesh_bindings::*; +pub use mesh_view_bindings::*; diff --git a/crates/bevy_material/src/render_phase/draw.rs b/crates/bevy_material/src/render_phase/draw.rs new file mode 100644 index 0000000000000..a5e2c9bc15bb6 --- /dev/null +++ b/crates/bevy_material/src/render_phase/draw.rs @@ -0,0 +1,6 @@ +use core::{fmt::Debug, hash::Hash}; + +// TODO: make this generic? +/// An identifier for a draw functions stored. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct DrawFunctionId(pub u32); diff --git a/crates/bevy_material/src/render_phase/mod.rs b/crates/bevy_material/src/render_phase/mod.rs new file mode 100644 index 0000000000000..90bd29908c921 --- /dev/null +++ b/crates/bevy_material/src/render_phase/mod.rs @@ -0,0 +1,31 @@ +use bevy_ecs::define_label; +use bevy_ecs::intern::Interned; +pub use bevy_material_macros::ShaderLabel; + +define_label!( + #[diagnostic::on_unimplemented( + note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`" + )] + /// Labels used to uniquely identify types of material shaders + ShaderLabel, + SHADER_LABEL_INTERNER +); + +/// A shorthand for `Interned`. +pub type InternedShaderLabel = Interned; + +pub use bevy_material_macros::DrawFunctionLabel; + +define_label!( + #[diagnostic::on_unimplemented( + note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`" + )] + /// Labels used to uniquely identify types of material shaders + DrawFunctionLabel, + DRAW_FUNCTION_LABEL_INTERNER +); + +pub type InternedDrawFunctionLabel = Interned; + +mod draw; +pub use draw::*; diff --git a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs b/crates/bevy_material/src/render_resource/bind_group_layout_entries.rs similarity index 99% rename from crates/bevy_render/src/render_resource/bind_group_layout_entries.rs rename to crates/bevy_material/src/render_resource/bind_group_layout_entries.rs index 17630b7dae2d7..ca6cd9392ad6a 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs +++ b/crates/bevy_material/src/render_resource/bind_group_layout_entries.rs @@ -1,6 +1,6 @@ use core::num::NonZero; use variadics_please::all_tuples_with_size; -use wgpu::{BindGroupLayoutEntry, BindingType, ShaderStages}; +use wgpu_types::{BindGroupLayoutEntry, BindingType, ShaderStages}; /// Helper for constructing bind group layouts. /// @@ -364,12 +364,12 @@ impl core::ops::Deref for DynamicBindGroupLayoutEntries { } pub mod binding_types { - use crate::render_resource::{ - BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension, - }; use core::num::NonZero; use encase::ShaderType; - use wgpu::{StorageTextureAccess, TextureFormat}; + use wgpu_types::{ + BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension, + }; + use wgpu_types::{StorageTextureAccess, TextureFormat}; use super::*; diff --git a/crates/bevy_material/src/render_resource/mod.rs b/crates/bevy_material/src/render_resource/mod.rs new file mode 100644 index 0000000000000..c256f1671b984 --- /dev/null +++ b/crates/bevy_material/src/render_resource/mod.rs @@ -0,0 +1,27 @@ +mod bind_group_layout_entries; +mod pipeline; +mod pipeline_specializer; + +pub use bind_group_layout_entries::*; +pub use pipeline::*; +pub use pipeline_specializer::*; + +// TODO: decide where re-exports should go +pub use wgpu_types::{ + AccelerationStructureFlags, AccelerationStructureGeometryFlags, + AccelerationStructureUpdateMode, AdapterInfo as WgpuAdapterInfo, AddressMode, AstcBlock, + AstcChannel, BindGroupLayoutEntry, BindingType, BlasGeometrySizeDescriptors, + BlasTriangleGeometrySizeDescriptor, BlendComponent, BlendFactor, BlendOperation, BlendState, + BufferAddress, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, + ColorWrites, CommandEncoderDescriptor, CompareFunction, CreateBlasDescriptor, + CreateTlasDescriptor, DepthBiasState, DepthStencilState, DownlevelFlags, Extent3d, Face, + Features as WgpuFeatures, FilterMode, FrontFace, ImageSubresourceRange, IndexFormat, + Limits as WgpuLimits, LoadOp, MultisampleState, Operations, Origin3d, PollType, PolygonMode, + PrimitiveState, PrimitiveTopology, PushConstantRange, SamplerBindingType, + SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor, ShaderStages, + StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, StoreOp, + TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, + TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, + TextureSampleType, TextureUsages, TextureViewDescriptor, TextureViewDimension, VertexAttribute, + VertexFormat, VertexStepMode, COPY_BUFFER_ALIGNMENT, +}; diff --git a/crates/bevy_material/src/render_resource/pipeline.rs b/crates/bevy_material/src/render_resource/pipeline.rs new file mode 100644 index 0000000000000..330720713b10b --- /dev/null +++ b/crates/bevy_material/src/render_resource/pipeline.rs @@ -0,0 +1,121 @@ +use alloc::borrow::Cow; +use bevy_asset::Handle; +use bevy_mesh::VertexBufferLayout; +use bevy_shader::{Shader, ShaderDefVal}; +use core::iter; +use thiserror::Error; +use wgpu_types::{ + BindGroupLayoutEntry, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, + PushConstantRange, +}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct BindGroupLayoutDescriptor { + /// Debug label of the bind group layout descriptor. This will show up in graphics debuggers for easy identification. + pub label: Cow<'static, str>, + pub entries: Vec, +} + +impl BindGroupLayoutDescriptor { + pub fn new(label: impl Into>, entries: &[BindGroupLayoutEntry]) -> Self { + Self { + label: label.into(), + entries: entries.into(), + } + } +} + +/// Describes a render (graphics) pipeline. +#[derive(Clone, Debug, PartialEq, Default)] +pub struct RenderPipelineDescriptor { + /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. + pub label: Option>, + /// The layout of bind groups for this pipeline. + pub layout: Vec, + /// The push constant ranges for this pipeline. + /// Supply an empty vector if the pipeline doesn't use push constants. + pub push_constant_ranges: Vec, + /// The compiled vertex stage, its entry point, and the input buffers layout. + pub vertex: VertexState, + /// The properties of the pipeline at the primitive assembly and rasterization level. + pub primitive: PrimitiveState, + /// The effect of draw calls on the depth and stencil aspects of the output target, if any. + pub depth_stencil: Option, + /// The multi-sampling properties of the pipeline. + pub multisample: MultisampleState, + /// The compiled fragment stage, its entry point, and the color targets. + pub fragment: Option, + /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. + /// If this is false, reading from workgroup variables before writing to them will result in garbage values. + pub zero_initialize_workgroup_memory: bool, +} + +#[derive(Copy, Clone, Debug, Error)] +#[error("RenderPipelineDescriptor has no FragmentState configured")] +pub struct NoFragmentStateError; + +impl RenderPipelineDescriptor { + pub fn fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> { + self.fragment.as_mut().ok_or(NoFragmentStateError) + } + + pub fn set_layout(&mut self, index: usize, layout: BindGroupLayoutDescriptor) { + filling_set_at(&mut self.layout, index, bevy_utils::default(), layout); + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct VertexState { + /// The compiled shader module for this stage. + pub shader: Handle, + pub shader_defs: Vec, + /// The name of the entry point in the compiled shader, or `None` if the default entry point + /// is used. + pub entry_point: Option>, + /// The format of any vertex buffers used with this pipeline. + pub buffers: Vec, +} + +/// Describes the fragment process in a render pipeline. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct FragmentState { + /// The compiled shader module for this stage. + pub shader: Handle, + pub shader_defs: Vec, + /// The name of the entry point in the compiled shader, or `None` if the default entry point + /// is used. + pub entry_point: Option>, + /// The color state of the render targets. + pub targets: Vec>, +} + +impl FragmentState { + pub fn set_target(&mut self, index: usize, target: ColorTargetState) { + filling_set_at(&mut self.targets, index, None, Some(target)); + } +} + +/// Describes a compute pipeline. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct ComputePipelineDescriptor { + pub label: Option>, + pub layout: Vec, + pub push_constant_ranges: Vec, + /// The compiled shader module for this stage. + pub shader: Handle, + pub shader_defs: Vec, + /// The name of the entry point in the compiled shader, or `None` if the default entry point + /// is used. + pub entry_point: Option>, + /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. + /// If this is false, reading from workgroup variables before writing to them will result in garbage values. + pub zero_initialize_workgroup_memory: bool, +} + +// utility function to set a value at the specified index, extending with +// a filler value if the index is out of bounds. +fn filling_set_at(vec: &mut Vec, index: usize, filler: T, value: T) { + let num_to_fill = (index + 1).saturating_sub(vec.len()); + vec.extend(iter::repeat_n(filler, num_to_fill)); + vec[index] = value; +} diff --git a/crates/bevy_material/src/render_resource/pipeline_specializer.rs b/crates/bevy_material/src/render_resource/pipeline_specializer.rs new file mode 100644 index 0000000000000..2889316242b49 --- /dev/null +++ b/crates/bevy_material/src/render_resource/pipeline_specializer.rs @@ -0,0 +1,10 @@ +use bevy_mesh::MissingVertexAttributeError; +use core::fmt::Debug; +use thiserror::Error; +use tracing::error; + +#[derive(Error, Debug)] +pub enum SpecializedMeshPipelineError { + #[error(transparent)] + MissingVertexAttribute(#[from] MissingVertexAttributeError), +} diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 365911015d52d..e3da8c9ed42ce 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -48,6 +48,7 @@ bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev", features = [ "bevy_mikktspace", ] } bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_render = { path = "../bevy_render", version = "0.18.0-dev", features = [ diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index bbce8dc909fa3..6fc09bba57932 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -4,7 +4,9 @@ use crate::{ ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; -use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset}; +use crate::{ + init_mesh_pipeline, DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset, +}; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ @@ -106,7 +108,10 @@ impl Plugin for DeferredPbrLightingPlugin { render_app .init_resource::>() - .add_systems(RenderStartup, init_deferred_lighting_layout) + .add_systems( + RenderStartup, + init_deferred_lighting_layout.after(init_mesh_pipeline), + ) .add_systems( Render, (prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),), diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 6eaa1f99ddf30..073e40cb4f79e 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -151,8 +151,8 @@ fn shader_ref(path: PathBuf) -> ShaderRef { ShaderRef::Path(AssetPath::from_path_buf(path).with_source("embedded")) } -pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 18; -pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 19; +pub use bevy_render::mesh::util::TONEMAPPING_LUT_SAMPLER_BINDING_INDEX; +pub use bevy_render::mesh::util::TONEMAPPING_LUT_TEXTURE_BINDING_INDEX; /// Sets up the entire PBR infrastructure of bevy. pub struct PbrPlugin { diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 417f22083ead6..f6672b64fe2fb 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -155,10 +155,7 @@ use crate::{ use super::LightProbeComponent; -/// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can -/// overflow the number of texture bindings when deferred rendering is in use -/// (see issue #11885). -pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "wasm32")); +pub use bevy_render::mesh::util::IRRADIANCE_VOLUMES_ARE_USABLE; /// All the bind group entries necessary for PBR shaders to access the /// irradiance volumes exposed to a view. diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index a05d3ebd12db7..123f49df92831 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -32,156 +32,23 @@ //! [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi use bevy_app::{App, Plugin}; -use bevy_asset::{AssetId, Handle}; -use bevy_camera::visibility::ViewVisibility; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - component::Component, - entity::Entity, - lifecycle::RemovedComponents, - query::{Changed, Or}, - reflect::ReflectComponent, - resource::Resource, schedule::IntoScheduleConfigs, - system::{Commands, Query, Res, ResMut}, + system::{Commands, Res}, }; -use bevy_image::Image; -use bevy_math::{uvec2, vec4, Rect, UVec2}; -use bevy_platform::collections::HashSet; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{ - render_asset::RenderAssets, - render_resource::{Sampler, TextureView, WgpuSampler, WgpuTextureView}, - renderer::RenderAdapter, - sync_world::MainEntity, - texture::{FallbackImage, GpuImage}, - Extract, ExtractSchedule, RenderApp, RenderStartup, -}; -use bevy_render::{renderer::RenderDevice, sync_world::MainEntityHashMap}; +use bevy_render::renderer::RenderDevice; +use bevy_render::{renderer::RenderAdapter, ExtractSchedule, RenderApp, RenderStartup}; use bevy_shader::load_shader_library; use bevy_utils::default; use fixedbitset::FixedBitSet; -use nonmax::{NonMaxU16, NonMaxU32}; -use tracing::error; -use crate::{binding_arrays_are_usable, MeshExtractionSystems}; +pub use bevy_render::lightmap::*; -/// The number of lightmaps that we store in a single slab, if bindless textures -/// are in use. -/// -/// If bindless textures aren't in use, then only a single lightmap can be bound -/// at a time. -pub const LIGHTMAPS_PER_SLAB: usize = 4; +use crate::{binding_arrays_are_usable, MeshExtractionSystems}; /// A plugin that provides an implementation of lightmaps. pub struct LightmapPlugin; -/// A component that applies baked indirect diffuse global illumination from a -/// lightmap. -/// -/// When assigned to an entity that contains a [`Mesh3d`](bevy_mesh::Mesh3d) and a -/// [`MeshMaterial3d`](crate::StandardMaterial), if the mesh -/// has a second UV layer ([`ATTRIBUTE_UV_1`](bevy_mesh::Mesh::ATTRIBUTE_UV_1)), -/// then the lightmap will render using those UVs. -#[derive(Component, Clone, Reflect)] -#[reflect(Component, Default, Clone)] -pub struct Lightmap { - /// The lightmap texture. - pub image: Handle, - - /// The rectangle within the lightmap texture that the UVs are relative to. - /// - /// The top left coordinate is the `min` part of the rect, and the bottom - /// right coordinate is the `max` part of the rect. The rect ranges from (0, - /// 0) to (1, 1). - /// - /// This field allows lightmaps for a variety of meshes to be packed into a - /// single atlas. - pub uv_rect: Rect, - - /// Whether bicubic sampling should be used for sampling this lightmap. - /// - /// Bicubic sampling is higher quality, but slower, and may lead to light leaks. - /// - /// If true, the lightmap texture's sampler must be set to [`bevy_image::ImageSampler::linear`]. - pub bicubic_sampling: bool, -} - -/// Lightmap data stored in the render world. -/// -/// There is one of these per visible lightmapped mesh instance. -#[derive(Debug)] -pub(crate) struct RenderLightmap { - /// The rectangle within the lightmap texture that the UVs are relative to. - /// - /// The top left coordinate is the `min` part of the rect, and the bottom - /// right coordinate is the `max` part of the rect. The rect ranges from (0, - /// 0) to (1, 1). - pub(crate) uv_rect: Rect, - - /// The index of the slab (i.e. binding array) in which the lightmap is - /// located. - pub(crate) slab_index: LightmapSlabIndex, - - /// The index of the slot (i.e. element within the binding array) in which - /// the lightmap is located. - /// - /// If bindless lightmaps aren't in use, this will be 0. - pub(crate) slot_index: LightmapSlotIndex, - - // Whether or not bicubic sampling should be used for this lightmap. - pub(crate) bicubic_sampling: bool, -} - -/// Stores data for all lightmaps in the render world. -/// -/// This is cleared and repopulated each frame during the `extract_lightmaps` -/// system. -#[derive(Resource)] -pub struct RenderLightmaps { - /// The mapping from every lightmapped entity to its lightmap info. - /// - /// Entities without lightmaps, or for which the mesh or lightmap isn't - /// loaded, won't have entries in this table. - pub(crate) render_lightmaps: MainEntityHashMap, - - /// The slabs (binding arrays) containing the lightmaps. - pub(crate) slabs: Vec, - - free_slabs: FixedBitSet, - - pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>, - - /// Whether bindless textures are supported on this platform. - pub(crate) bindless_supported: bool, -} - -/// A binding array that contains lightmaps. -/// -/// This will have a single binding if bindless lightmaps aren't in use. -pub struct LightmapSlab { - /// The GPU images in this slab. - lightmaps: Vec, - free_slots_bitmask: u32, -} - -struct AllocatedLightmap { - gpu_image: GpuImage, - // This will only be present if the lightmap is allocated but not loaded. - asset_id: Option>, -} - -/// The index of the slab (binding array) in which a lightmap is located. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)] -#[repr(transparent)] -pub struct LightmapSlabIndex(pub(crate) NonMaxU32); - -/// The index of the slot (element within the binding array) in the slab in -/// which a lightmap is located. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)] -#[repr(transparent)] -pub struct LightmapSlotIndex(pub(crate) NonMaxU16); - impl Plugin for LightmapPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "lightmap.wgsl"); @@ -198,140 +65,6 @@ impl Plugin for LightmapPlugin { } } -/// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`] -/// resource. -fn extract_lightmaps( - render_lightmaps: ResMut, - changed_lightmaps_query: Extract< - Query< - (Entity, &ViewVisibility, &Lightmap), - Or<(Changed, Changed)>, - >, - >, - mut removed_lightmaps_query: Extract>, - images: Res>, - fallback_images: Res, -) { - let render_lightmaps = render_lightmaps.into_inner(); - - // Loop over each entity. - for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() { - if render_lightmaps - .render_lightmaps - .contains_key(&MainEntity::from(entity)) - { - continue; - } - - // Only process visible entities. - if !view_visibility.get() { - continue; - } - - let (slab_index, slot_index) = - render_lightmaps.allocate(&fallback_images, lightmap.image.id()); - render_lightmaps.render_lightmaps.insert( - entity.into(), - RenderLightmap::new( - lightmap.uv_rect, - slab_index, - slot_index, - lightmap.bicubic_sampling, - ), - ); - - render_lightmaps - .pending_lightmaps - .insert((slab_index, slot_index)); - } - - for entity in removed_lightmaps_query.read() { - if changed_lightmaps_query.contains(entity) { - continue; - } - - let Some(RenderLightmap { - slab_index, - slot_index, - .. - }) = render_lightmaps - .render_lightmaps - .remove(&MainEntity::from(entity)) - else { - continue; - }; - - render_lightmaps.remove(&fallback_images, slab_index, slot_index); - render_lightmaps - .pending_lightmaps - .remove(&(slab_index, slot_index)); - } - - render_lightmaps - .pending_lightmaps - .retain(|&(slab_index, slot_index)| { - let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps - [usize::from(slot_index)] - .asset_id - else { - error!( - "Allocated lightmap should have been removed from `pending_lightmaps` by now" - ); - return false; - }; - - let Some(gpu_image) = images.get(asset_id) else { - return true; - }; - render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone()); - false - }); -} - -impl RenderLightmap { - /// Creates a new lightmap from a texture, a UV rect, and a slab and slot - /// index pair. - fn new( - uv_rect: Rect, - slab_index: LightmapSlabIndex, - slot_index: LightmapSlotIndex, - bicubic_sampling: bool, - ) -> Self { - Self { - uv_rect, - slab_index, - slot_index, - bicubic_sampling, - } - } -} - -/// Packs the lightmap UV rect into 64 bits (4 16-bit unsigned integers). -pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option) -> UVec2 { - match maybe_rect { - Some(rect) => { - let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0) - .round() - .as_uvec4(); - uvec2( - rect_uvec4.x | (rect_uvec4.y << 16), - rect_uvec4.z | (rect_uvec4.w << 16), - ) - } - None => UVec2::ZERO, - } -} - -impl Default for Lightmap { - fn default() -> Self { - Self { - image: Default::default(), - uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0), - bicubic_sampling: false, - } - } -} - pub fn init_render_lightmaps( mut commands: Commands, render_device: Res, @@ -347,177 +80,3 @@ pub fn init_render_lightmaps( bindless_supported, }); } - -impl RenderLightmaps { - /// Creates a new slab, appends it to the end of the list, and returns its - /// slab index. - fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex { - let slab_index = LightmapSlabIndex::from(self.slabs.len()); - self.free_slabs.grow_and_insert(slab_index.into()); - self.slabs - .push(LightmapSlab::new(fallback_images, self.bindless_supported)); - slab_index - } - - fn allocate( - &mut self, - fallback_images: &FallbackImage, - image_id: AssetId, - ) -> (LightmapSlabIndex, LightmapSlotIndex) { - let slab_index = match self.free_slabs.minimum() { - None => self.create_slab(fallback_images), - Some(slab_index) => slab_index.into(), - }; - - let slab = &mut self.slabs[usize::from(slab_index)]; - let slot_index = slab.allocate(image_id); - if slab.is_full() { - self.free_slabs.remove(slab_index.into()); - } - - (slab_index, slot_index) - } - - fn remove( - &mut self, - fallback_images: &FallbackImage, - slab_index: LightmapSlabIndex, - slot_index: LightmapSlotIndex, - ) { - let slab = &mut self.slabs[usize::from(slab_index)]; - slab.remove(fallback_images, slot_index); - - if !slab.is_full() { - self.free_slabs.grow_and_insert(slab_index.into()); - } - } -} - -impl LightmapSlab { - fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab { - let count = if bindless_supported { - LIGHTMAPS_PER_SLAB - } else { - 1 - }; - - LightmapSlab { - lightmaps: (0..count) - .map(|_| AllocatedLightmap { - gpu_image: fallback_images.d2.clone(), - asset_id: None, - }) - .collect(), - free_slots_bitmask: (1 << count) - 1, - } - } - - fn is_full(&self) -> bool { - self.free_slots_bitmask == 0 - } - - fn allocate(&mut self, image_id: AssetId) -> LightmapSlotIndex { - assert!( - !self.is_full(), - "Attempting to allocate on a full lightmap slab" - ); - let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros()); - self.free_slots_bitmask &= !(1 << u32::from(index)); - self.lightmaps[usize::from(index)].asset_id = Some(image_id); - index - } - - fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) { - self.lightmaps[usize::from(index)] = AllocatedLightmap { - gpu_image, - asset_id: None, - } - } - - fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) { - self.lightmaps[usize::from(index)] = AllocatedLightmap { - gpu_image: fallback_images.d2.clone(), - asset_id: None, - }; - self.free_slots_bitmask |= 1 << u32::from(index); - } - - /// Returns the texture views and samplers for the lightmaps in this slab, - /// ready to be placed into a bind group. - /// - /// This is used when constructing bind groups in bindless mode. Before - /// returning, this function pads out the arrays with fallback images in - /// order to fulfill requirements of platforms that require full binding - /// arrays (e.g. DX12). - pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) { - ( - self.lightmaps - .iter() - .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view) - .collect(), - self.lightmaps - .iter() - .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler) - .collect(), - ) - } - - /// Returns the texture view and sampler corresponding to the first - /// lightmap, which must exist. - /// - /// This is used when constructing bind groups in non-bindless mode. - pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) { - ( - &self.lightmaps[0].gpu_image.texture_view, - &self.lightmaps[0].gpu_image.sampler, - ) - } -} - -impl From for LightmapSlabIndex { - fn from(value: u32) -> Self { - Self(NonMaxU32::new(value).unwrap()) - } -} - -impl From for LightmapSlabIndex { - fn from(value: usize) -> Self { - Self::from(value as u32) - } -} - -impl From for LightmapSlotIndex { - fn from(value: u32) -> Self { - Self(NonMaxU16::new(value as u16).unwrap()) - } -} - -impl From for LightmapSlotIndex { - fn from(value: usize) -> Self { - Self::from(value as u32) - } -} - -impl From for usize { - fn from(value: LightmapSlabIndex) -> Self { - value.0.get() as usize - } -} - -impl From for usize { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() as usize - } -} - -impl From for u16 { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() - } -} - -impl From for u32 { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() as u32 - } -} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 306b867a01acd..2903aaeb84de0 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -26,6 +26,7 @@ use bevy_ecs::{ SystemParamItem, }, }; +use bevy_material::{prelude::*, render_phase::*}; use bevy_mesh::{ mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef, }; @@ -55,15 +56,14 @@ use bevy_render::{ }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; -use bevy_shader::{Shader, ShaderDefVal}; +use bevy_shader::ShaderDefVal; use bevy_utils::Parallel; -use core::any::{Any, TypeId}; -use core::hash::{BuildHasher, Hasher}; +use core::any::TypeId; use core::{hash::Hash, marker::PhantomData}; use smallvec::SmallVec; use tracing::error; -pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; +pub use bevy_material::material::*; /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level @@ -72,6 +72,8 @@ pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; /// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders. /// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details. /// +/// Doc refs [`MaterialPipeline`], [`MaterialProperties`] +/// /// # Example /// /// Here is a simple [`Material`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available, @@ -297,7 +299,10 @@ impl Plugin for MaterialsPlugin { .add_render_command::() .add_render_command::() .add_render_command::() - .add_systems(RenderStartup, init_material_pipeline) + .add_systems( + RenderStartup, + init_material_pipeline.after(init_mesh_pipeline), + ) .add_systems( Render, ( @@ -435,19 +440,6 @@ pub struct MaterialPipelineKey { pub bind_group_data: M::Data, } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct ErasedMaterialPipelineKey { - pub mesh_key: MeshPipelineKey, - pub material_key: ErasedMaterialKey, - pub type_id: TypeId, -} - -/// Render pipeline data for a given [`Material`]. -#[derive(Resource, Clone)] -pub struct MaterialPipeline { - pub mesh_pipeline: MeshPipeline, -} - pub struct MaterialPipelineSpecializer { pub(crate) pipeline: MaterialPipeline, pub(crate) properties: Arc, @@ -1153,6 +1145,8 @@ pub fn specialize_material_meshes( /// For each view, iterates over all the meshes visible from that view and adds /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. +/// +/// Uses data from [`RenderMeshQueueData`] in order to place entities that contain meshes in the right batch. pub fn queue_material_meshes( render_materials: Res>, render_mesh_instances: Res, @@ -1334,32 +1328,7 @@ impl DefaultOpaqueRendererMethod { } } -/// Render method used for opaque materials. -/// -/// The forward rendering main pass draws each mesh entity and shades it according to its -/// corresponding material and the lights that affect it. Some render features like Screen Space -/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like -/// prepasses over all mesh entities to populate depth and normal textures. This means that when -/// using render features that require running prepasses, multiple passes over all visible geometry -/// are required. This can be slow if there is a lot of geometry that cannot be batched into few -/// draws. -/// -/// Deferred rendering runs a prepass to gather not only geometric information like depth and -/// normals, but also all the material properties like base color, emissive color, reflectance, -/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is -/// then a fullscreen pass that reads data from these textures and executes shading. This allows -/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier -/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices. -/// -/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used. -#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)] -#[reflect(Default, Clone, PartialEq)] -pub enum OpaqueRendererMethod { - #[default] - Forward, - Deferred, - Auto, -} +pub use bevy_material::opaque::OpaqueRendererMethod; #[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct MaterialVertexShader; @@ -1400,172 +1369,7 @@ pub struct DeferredDrawFunction; #[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct ShadowsDrawFunction; -#[derive(Debug)] -pub struct ErasedMaterialKey { - type_id: TypeId, - hash: u64, - value: Box, - vtable: Arc, -} - -#[derive(Debug)] -pub struct ErasedMaterialKeyVTable { - clone_fn: fn(&dyn Any) -> Box, - partial_eq_fn: fn(&dyn Any, &dyn Any) -> bool, -} - -impl ErasedMaterialKey { - pub fn new(material_key: T) -> Self - where - T: Clone + Hash + PartialEq + Send + Sync + 'static, - { - let type_id = TypeId::of::(); - let hash = FixedHasher::hash_one(&FixedHasher, &material_key); - - fn clone(any: &dyn Any) -> Box { - Box::new(any.downcast_ref::().unwrap().clone()) - } - fn partial_eq(a: &dyn Any, b: &dyn Any) -> bool { - a.downcast_ref::().unwrap() == b.downcast_ref::().unwrap() - } - - Self { - type_id, - hash, - value: Box::new(material_key), - vtable: Arc::new(ErasedMaterialKeyVTable { - clone_fn: clone::, - partial_eq_fn: partial_eq::, - }), - } - } - - pub fn to_key(&self) -> T { - debug_assert_eq!(self.type_id, TypeId::of::()); - self.value.downcast_ref::().unwrap().clone() - } -} - -impl PartialEq for ErasedMaterialKey { - fn eq(&self, other: &Self) -> bool { - self.type_id == other.type_id - && (self.vtable.partial_eq_fn)(self.value.as_ref(), other.value.as_ref()) - } -} - -impl Eq for ErasedMaterialKey {} - -impl Clone for ErasedMaterialKey { - fn clone(&self) -> Self { - Self { - type_id: self.type_id, - hash: self.hash, - value: (self.vtable.clone_fn)(self.value.as_ref()), - vtable: self.vtable.clone(), - } - } -} - -impl Hash for ErasedMaterialKey { - fn hash(&self, state: &mut H) { - self.type_id.hash(state); - self.hash.hash(state); - } -} - -impl Default for ErasedMaterialKey { - fn default() -> Self { - Self::new(()) - } -} - -/// Common [`Material`] properties, calculated for a specific material instance. -#[derive(Default)] -pub struct MaterialProperties { - /// Is this material should be rendered by the deferred renderer when. - /// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`] - pub render_method: OpaqueRendererMethod, - /// The [`AlphaMode`] of this material. - pub alpha_mode: AlphaMode, - /// The bits in the [`MeshPipelineKey`] for this material. - /// - /// These are precalculated so that we can just "or" them together in - /// [`queue_material_meshes`]. - pub mesh_pipeline_key_bits: MeshPipelineKey, - /// Add a bias to the view depth of the mesh which can be used to force a specific render order - /// for meshes with equal depth, to avoid z-fighting. - /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. - pub depth_bias: f32, - /// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture). - /// - /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires - /// rendering to take place in a separate [`Transmissive3d`] pass. - pub reads_view_transmission_texture: bool, - pub render_phase_type: RenderPhaseType, - pub material_layout: Option, - /// Backing array is a size of 4 because the `StandardMaterial` needs 4 draw functions by default - pub draw_functions: SmallVec<[(InternedDrawFunctionLabel, DrawFunctionId); 4]>, - /// Backing array is a size of 3 because the `StandardMaterial` has 3 custom shaders (`frag`, `prepass_frag`, `deferred_frag`) which is the - /// most common use case - pub shaders: SmallVec<[(InternedShaderLabel, Handle); 3]>, - /// Whether this material *actually* uses bindless resources, taking the - /// platform support (or lack thereof) of bindless resources into account. - pub bindless: bool, - pub specialize: Option< - fn( - &MaterialPipeline, - &mut RenderPipelineDescriptor, - &MeshVertexBufferLayoutRef, - ErasedMaterialPipelineKey, - ) -> Result<(), SpecializedMeshPipelineError>, - >, - /// The key for this material, typically a bitfield of flags that are used to modify - /// the pipeline descriptor used for this material. - pub material_key: ErasedMaterialKey, - /// Whether shadows are enabled for this material - pub shadows_enabled: bool, - /// Whether prepass is enabled for this material - pub prepass_enabled: bool, -} - -impl MaterialProperties { - pub fn get_shader(&self, label: impl ShaderLabel) -> Option> { - self.shaders - .iter() - .find(|(inner_label, _)| inner_label == &label.intern()) - .map(|(_, shader)| shader) - .cloned() - } - - pub fn add_shader(&mut self, label: impl ShaderLabel, shader: Handle) { - self.shaders.push((label.intern(), shader)); - } - - pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option { - self.draw_functions - .iter() - .find(|(inner_label, _)| inner_label == &label.intern()) - .map(|(_, shader)| shader) - .cloned() - } - - pub fn add_draw_function( - &mut self, - label: impl DrawFunctionLabel, - draw_function: DrawFunctionId, - ) { - self.draw_functions.push((label.intern(), draw_function)); - } -} - -#[derive(Clone, Copy, Default)] -pub enum RenderPhaseType { - #[default] - Opaque, - AlphaMask, - Transmissive, - Transparent, -} +pub use bevy_material::material::MaterialProperties; /// A resource that maps each untyped material ID to its binding. /// diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index d987725bfda72..93bf7442aa2d6 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ system::{Commands, Res}, }; use bevy_platform::collections::{HashMap, HashSet}; -use bevy_reflect::{prelude::ReflectDefault, Reflect}; +pub use bevy_render::mesh::material_bind_group::*; use bevy_render::render_resource::{BindlessSlabResourceLimit, PipelineCache}; use bevy_render::{ render_resource::{ @@ -253,43 +253,6 @@ enum BindingResourceArray<'a> { Samplers(Vec<&'a WgpuSampler>), } -/// The location of a material (either bindless or non-bindless) within the -/// slabs. -#[derive(Clone, Copy, Debug, Default, Reflect)] -#[reflect(Clone, Default)] -pub struct MaterialBindingId { - /// The index of the bind group (slab) where the GPU data is located. - pub group: MaterialBindGroupIndex, - /// The slot within that bind group. - /// - /// Non-bindless materials will always have a slot of 0. - pub slot: MaterialBindGroupSlot, -} - -/// The index of each material bind group. -/// -/// In bindless mode, each bind group contains multiple materials. In -/// non-bindless mode, each bind group contains only one material. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect, Deref, DerefMut)] -#[reflect(Default, Clone, PartialEq, Hash)] -pub struct MaterialBindGroupIndex(pub u32); - -impl From for MaterialBindGroupIndex { - fn from(value: u32) -> Self { - MaterialBindGroupIndex(value) - } -} - -/// The index of the slot containing material data within each material bind -/// group. -/// -/// In bindless mode, this slot is needed to locate the material data in each -/// bind group, since multiple materials are packed into a single slab. In -/// non-bindless mode, this slot is always 0. -#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect, Deref, DerefMut)] -#[reflect(Default, Clone, PartialEq)] -pub struct MaterialBindGroupSlot(pub u32); - /// The CPU/GPU synchronization state of a buffer that we maintain. /// /// Currently, the only buffer that we maintain is the @@ -398,18 +361,6 @@ where /// the size is. const DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE: u64 = 16; -impl From for MaterialBindGroupSlot { - fn from(value: u32) -> Self { - MaterialBindGroupSlot(value) - } -} - -impl From for u32 { - fn from(value: MaterialBindGroupSlot) -> Self { - value.0 - } -} - impl<'a> From<&'a OwnedBindingResource> for BindingResourceId { fn from(value: &'a OwnedBindingResource) -> Self { match *value { diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 2d19f863efc7e..7d8af4a9ee208 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -27,6 +27,11 @@ pub enum UvChannel { /// . /// /// May be created directly from a [`Color`] or an [`Image`]. +/// +/// When a [`Lightmap`] is assigned to an entity that contains a [`Mesh3d`](bevy_mesh::Mesh3d) and a +/// [`MeshMaterial3d`](crate::StandardMaterial), if the mesh +/// has a second UV layer ([`ATTRIBUTE_UV_1`](bevy_mesh::Mesh::ATTRIBUTE_UV_1)), +/// then the lightmap will render using those UVs. #[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] #[bind_group_data(StandardMaterialKey)] #[data(0, StandardMaterialUniform, binding_array(10))] diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 28a9718d180cb..72adb5879dee2 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -2,13 +2,13 @@ mod prepass_bindings; use crate::{ alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, - collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags, - setup_morph_and_skinning_defs, skin, DeferredDrawFunction, DeferredFragmentShader, - DeferredVertexShader, DrawMesh, EntitySpecializationTicks, ErasedMaterialPipelineKey, - MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey, - OpaqueRendererMethod, PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader, - PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, - RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, + collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags, skin, + DeferredDrawFunction, DeferredFragmentShader, DeferredVertexShader, DrawMesh, + EntitySpecializationTicks, ErasedMaterialPipelineKey, MaterialPipeline, MaterialProperties, + MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, + PrepassDrawFunction, PrepassFragmentShader, PrepassVertexShader, RenderLightmaps, + RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, + SetMaterialBindGroup, SetMeshBindGroup, ShadowView, }; use bevy_app::{App, Plugin, PreUpdate}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; @@ -21,13 +21,13 @@ use bevy_ecs::{ SystemParamItem, }, }; -use bevy_math::{Affine3A, Mat4, Vec4}; +use bevy_math::{Mat4, Vec4}; use bevy_mesh::{Mesh, Mesh3d, MeshVertexBufferLayoutRef}; use bevy_render::{ alpha::AlphaMode, batching::gpu_preprocessing::GpuPreprocessingSupport, globals::{GlobalsBuffer, GlobalsUniform}, - mesh::{allocator::MeshAllocator, RenderMesh}, + mesh::{allocator::MeshAllocator, pipeline::setup_morph_and_skinning_defs, RenderMesh}, render_asset::{prepare_assets, RenderAssets}, render_phase::*, render_resource::{binding_types::uniform_buffer, *}, @@ -204,8 +204,7 @@ pub fn update_previous_view_data( } } -#[derive(Component, PartialEq, Default)] -pub struct PreviousGlobalTransform(pub Affine3A); +pub use bevy_render::mesh::render::PreviousGlobalTransform; #[cfg(not(feature = "meshlet"))] type PreviousMeshFilter = With; diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3bebf27276402..be41bb1c45d51 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,4 +1,4 @@ -use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; +use crate::{render::mesh_bindings::MeshLayoutsBuilders as _, skin::skin_uniforms_from_world}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; use bevy_camera::{ primitives::Aabb, @@ -6,7 +6,7 @@ use bevy_camera::{ Camera, Camera3d, Projection, }; use bevy_core_pipeline::{ - core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, + core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d}, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, oit::{prepare_oit_buffers, OrderIndependentTransparencySettingsOffset}, prepass::MotionVectorPrepass, @@ -18,51 +18,46 @@ use bevy_ecs::{ query::{QueryData, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; +use bevy_image::TextureFormatPixelInfo; use bevy_light::{ EnvironmentMapLight, IrradianceVolume, NotShadowCaster, NotShadowReceiver, ShadowFilteringMethod, TransmittedShadowReceiver, }; -use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; -use bevy_mesh::{ - skinning::SkinnedMesh, BaseMeshPipelineKey, Mesh, Mesh3d, MeshTag, MeshVertexBufferLayoutRef, - VertexAttributeDescriptor, -}; +use bevy_math::{Affine3, UVec2, Vec3, Vec4}; +use bevy_mesh::{skinning::SkinnedMesh, Mesh, Mesh3d, MeshTag}; use bevy_platform::collections::{hash_map::Entry, HashMap}; +pub use bevy_render::mesh::render::*; use bevy_render::{ batching::{ gpu_preprocessing::{ self, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, - IndirectParametersCpuMetadata, IndirectParametersIndexed, IndirectParametersNonIndexed, - InstanceInputUniformBuffer, UntypedPhaseIndirectParametersBuffers, + IndirectParametersIndexed, IndirectParametersNonIndexed, InstanceInputUniformBuffer, }, - no_gpu_preprocessing, GetBatchData, GetFullBatchData, NoAutomaticBatching, + no_gpu_preprocessing, NoAutomaticBatching, }, - mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo}, + mesh::{allocator::MeshAllocator, pipeline::is_skinned, RenderMesh, RenderMeshBufferInfo}, render_asset::RenderAssets, render_phase::{ - BinnedRenderPhasePlugin, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, + BinnedRenderPhasePlugin, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, }, render_resource::*, renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::MainEntityHashSet, - texture::{DefaultImageSampler, GpuImage}, + texture::GpuImage, view::{ - self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewTarget, - ViewUniformOffset, + self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewUniformOffset, }, Extract, }; -use bevy_shader::{load_shader_library, Shader, ShaderDefVal, ShaderSettings}; +use bevy_shader::{load_shader_library, ShaderDefVal, ShaderSettings}; use bevy_transform::components::GlobalTransform; use bevy_utils::{default, Parallel, TypeIdMap}; use core::any::TypeId; use core::mem::size_of; use material_bind_groups::MaterialBindingId; -use tracing::{error, warn}; +use tracing::warn; -use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; use crate::{ render::{ morph::{ @@ -84,10 +79,10 @@ use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; use bevy_render::RenderSystems::PrepareAssets; +pub use bevy_material::render::*; use bytemuck::{Pod, Zeroable}; -use nonmax::{NonMaxU16, NonMaxU32}; +use nonmax::NonMaxU32; use smallvec::{smallvec, SmallVec}; -use static_assertions::const_assert_eq; /// Provides support for rendering 3D meshes. pub struct MeshRenderPlugin { @@ -110,17 +105,6 @@ impl MeshRenderPlugin { } } -/// How many textures are allowed in the view bind group layout (`@group(0)`) before -/// broader compatibility with WebGL and WebGPU is at risk, due to the minimum guaranteed -/// values for `MAX_TEXTURE_IMAGE_UNITS` (in WebGL) and `maxSampledTexturesPerShaderStage` (in WebGPU), -/// currently both at 16. -/// -/// We use 10 here because it still leaves us, in a worst case scenario, with 6 textures for the other bind groups. -/// -/// See: -#[cfg(debug_assertions)] -pub const MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES: usize = 10; - impl Plugin for MeshRenderPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "forward_io.wgsl"); @@ -211,7 +195,7 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::() + .add_systems(RenderStartup, skin_uniforms_from_world) .add_systems( Render, check_views_need_specialization.in_set(PrepareAssets), @@ -281,8 +265,11 @@ impl Plugin for MeshRenderPlugin { } render_app - .init_resource::() - .init_resource::(); + .add_systems(RenderStartup, init_mesh_pipeline_view_layouts) + .add_systems( + RenderStartup, + init_mesh_pipeline.after(init_mesh_pipeline_view_layouts), + ); } // Load the mesh_bindings shader module here as it depends on runtime information about @@ -429,120 +416,6 @@ pub fn check_views_need_specialization( } } -#[derive(Component)] -pub struct MeshTransforms { - pub world_from_local: Affine3, - pub previous_world_from_local: Affine3, - pub flags: u32, -} - -#[derive(ShaderType, Clone)] -pub struct MeshUniform { - // Affine 4x3 matrices transposed to 3x4 - pub world_from_local: [Vec4; 3], - pub previous_world_from_local: [Vec4; 3], - // 3x3 matrix packed in mat2x4 and f32 as: - // [0].xyz, [1].x, - // [1].yz, [2].xy - // [2].z - pub local_from_world_transpose_a: [Vec4; 2], - pub local_from_world_transpose_b: f32, - pub flags: u32, - // Four 16-bit unsigned normalized UV values packed into a `UVec2`: - // - // <--- MSB LSB ---> - // +---- min v ----+ +---- min u ----+ - // lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu, - // +---- max v ----+ +---- max u ----+ - // lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU, - // - // (MSB: most significant bit; LSB: least significant bit.) - pub lightmap_uv_rect: UVec2, - /// The index of this mesh's first vertex in the vertex buffer. - /// - /// Multiple meshes can be packed into a single vertex buffer (see - /// [`MeshAllocator`]). This value stores the offset of the first vertex in - /// this mesh in that buffer. - pub first_vertex_index: u32, - /// The current skin index, or `u32::MAX` if there's no skin. - pub current_skin_index: u32, - /// The material and lightmap indices, packed into 32 bits. - /// - /// Low 16 bits: index of the material inside the bind group data. - /// High 16 bits: index of the lightmap in the binding array. - pub material_and_lightmap_bind_group_slot: u32, - /// User supplied tag to identify this mesh instance. - pub tag: u32, - /// Padding. - pub pad: u32, -} - -/// Information that has to be transferred from CPU to GPU in order to produce -/// the full [`MeshUniform`]. -/// -/// This is essentially a subset of the fields in [`MeshUniform`] above. -#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default, Debug)] -#[repr(C)] -pub struct MeshInputUniform { - /// Affine 4x3 matrix transposed to 3x4. - pub world_from_local: [Vec4; 3], - /// Four 16-bit unsigned normalized UV values packed into a `UVec2`: - /// - /// ```text - /// <--- MSB LSB ---> - /// +---- min v ----+ +---- min u ----+ - /// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu, - /// +---- max v ----+ +---- max u ----+ - /// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU, - /// - /// (MSB: most significant bit; LSB: least significant bit.) - /// ``` - pub lightmap_uv_rect: UVec2, - /// Various [`MeshFlags`]. - pub flags: u32, - /// The index of this mesh's [`MeshInputUniform`] in the previous frame's - /// buffer, if applicable. - /// - /// This is used for TAA. If not present, this will be `u32::MAX`. - pub previous_input_index: u32, - /// The index of this mesh's first vertex in the vertex buffer. - /// - /// Multiple meshes can be packed into a single vertex buffer (see - /// [`MeshAllocator`]). This value stores the offset of the first vertex in - /// this mesh in that buffer. - pub first_vertex_index: u32, - /// The index of this mesh's first index in the index buffer, if any. - /// - /// Multiple meshes can be packed into a single index buffer (see - /// [`MeshAllocator`]). This value stores the offset of the first index in - /// this mesh in that buffer. - /// - /// If this mesh isn't indexed, this value is ignored. - pub first_index_index: u32, - /// For an indexed mesh, the number of indices that make it up; for a - /// non-indexed mesh, the number of vertices in it. - pub index_count: u32, - /// The current skin index, or `u32::MAX` if there's no skin. - pub current_skin_index: u32, - /// The material and lightmap indices, packed into 32 bits. - /// - /// Low 16 bits: index of the material inside the bind group data. - /// High 16 bits: index of the lightmap in the binding array. - pub material_and_lightmap_bind_group_slot: u32, - /// The number of the frame on which this [`MeshInputUniform`] was built. - /// - /// This is used to validate the previous transform and skin. If this - /// [`MeshInputUniform`] wasn't updated on this frame, then we know that - /// neither this mesh's transform nor that of its joints have been updated - /// on this frame, and therefore the transforms of both this mesh and its - /// joints must be identical to those for the previous frame. - pub timestamp: u32, - /// User supplied tag to identify this mesh instance. - pub tag: u32, - /// Padding. - pub pad: u32, -} - /// Information about each mesh instance needed to cull it on GPU. /// /// This consists of its axis-aligned bounding box (AABB). @@ -566,172 +439,6 @@ pub struct MeshCullingData { #[derive(Resource, Deref, DerefMut)] pub struct MeshCullingDataBuffer(RawBufferVec); -impl MeshUniform { - pub fn new( - mesh_transforms: &MeshTransforms, - first_vertex_index: u32, - material_bind_group_slot: MaterialBindGroupSlot, - maybe_lightmap: Option<(LightmapSlotIndex, Rect)>, - current_skin_index: Option, - tag: Option, - ) -> Self { - let (local_from_world_transpose_a, local_from_world_transpose_b) = - mesh_transforms.world_from_local.inverse_transpose_3x3(); - let lightmap_bind_group_slot = match maybe_lightmap { - None => u16::MAX, - Some((slot_index, _)) => slot_index.into(), - }; - - Self { - world_from_local: mesh_transforms.world_from_local.to_transpose(), - previous_world_from_local: mesh_transforms.previous_world_from_local.to_transpose(), - lightmap_uv_rect: pack_lightmap_uv_rect(maybe_lightmap.map(|(_, uv_rect)| uv_rect)), - local_from_world_transpose_a, - local_from_world_transpose_b, - flags: mesh_transforms.flags, - first_vertex_index, - current_skin_index: current_skin_index.unwrap_or(u32::MAX), - material_and_lightmap_bind_group_slot: u32::from(material_bind_group_slot) - | ((lightmap_bind_group_slot as u32) << 16), - tag: tag.unwrap_or(0), - pad: 0, - } - } -} - -// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_types.wgsl! -bitflags::bitflags! { - /// Various flags and tightly-packed values on a mesh. - /// - /// Flags grow from the top bit down; other values grow from the bottom bit - /// up. - #[repr(transparent)] - pub struct MeshFlags: u32 { - /// Bitmask for the 16-bit index into the LOD array. - /// - /// This will be `u16::MAX` if this mesh has no LOD. - const LOD_INDEX_MASK = (1 << 16) - 1; - /// Disables frustum culling for this mesh. - /// - /// This corresponds to the - /// [`bevy_render::view::visibility::NoFrustumCulling`] component. - const NO_FRUSTUM_CULLING = 1 << 28; - const SHADOW_RECEIVER = 1 << 29; - const TRANSMITTED_SHADOW_RECEIVER = 1 << 30; - // Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive, - // then the flag should be set, else it should not be set. - const SIGN_DETERMINANT_MODEL_3X3 = 1 << 31; - const NONE = 0; - const UNINITIALIZED = 0xFFFFFFFF; - } -} - -impl MeshFlags { - fn from_components( - transform: &GlobalTransform, - lod_index: Option, - no_frustum_culling: bool, - not_shadow_receiver: bool, - transmitted_receiver: bool, - ) -> MeshFlags { - let mut mesh_flags = if not_shadow_receiver { - MeshFlags::empty() - } else { - MeshFlags::SHADOW_RECEIVER - }; - if no_frustum_culling { - mesh_flags |= MeshFlags::NO_FRUSTUM_CULLING; - } - if transmitted_receiver { - mesh_flags |= MeshFlags::TRANSMITTED_SHADOW_RECEIVER; - } - if transform.affine().matrix3.determinant().is_sign_positive() { - mesh_flags |= MeshFlags::SIGN_DETERMINANT_MODEL_3X3; - } - - let lod_index_bits = match lod_index { - None => u16::MAX, - Some(lod_index) => u16::from(lod_index), - }; - mesh_flags |= - MeshFlags::from_bits_retain((lod_index_bits as u32) << MeshFlags::LOD_INDEX_SHIFT); - - mesh_flags - } - - /// The first bit of the LOD index. - pub const LOD_INDEX_SHIFT: u32 = 0; -} - -bitflags::bitflags! { - /// Various useful flags for [`RenderMeshInstance`]s. - #[derive(Clone, Copy)] - pub struct RenderMeshInstanceFlags: u8 { - /// The mesh casts shadows. - const SHADOW_CASTER = 1 << 0; - /// The mesh can participate in automatic batching. - const AUTOMATIC_BATCHING = 1 << 1; - /// The mesh had a transform last frame and so is eligible for motion - /// vector computation. - const HAS_PREVIOUS_TRANSFORM = 1 << 2; - /// The mesh had a skin last frame and so that skin should be taken into - /// account for motion vector computation. - const HAS_PREVIOUS_SKIN = 1 << 3; - /// The mesh had morph targets last frame and so they should be taken - /// into account for motion vector computation. - const HAS_PREVIOUS_MORPH = 1 << 4; - } -} - -/// CPU data that the render world keeps for each entity, when *not* using GPU -/// mesh uniform building. -#[derive(Deref, DerefMut)] -pub struct RenderMeshInstanceCpu { - /// Data shared between both the CPU mesh uniform building and the GPU mesh - /// uniform building paths. - #[deref] - pub shared: RenderMeshInstanceShared, - /// The transform of the mesh. - /// - /// This will be written into the [`MeshUniform`] at the appropriate time. - pub transforms: MeshTransforms, -} - -/// CPU data that the render world needs to keep for each entity that contains a -/// mesh when using GPU mesh uniform building. -#[derive(Deref, DerefMut)] -pub struct RenderMeshInstanceGpu { - /// Data shared between both the CPU mesh uniform building and the GPU mesh - /// uniform building paths. - #[deref] - pub shared: RenderMeshInstanceShared, - /// The translation of the mesh. - /// - /// This is the only part of the transform that we have to keep on CPU (for - /// distance sorting). - pub translation: Vec3, - /// The index of the [`MeshInputUniform`] in the buffer. - pub current_uniform_index: NonMaxU32, -} - -/// CPU data that the render world needs to keep about each entity that contains -/// a mesh. -pub struct RenderMeshInstanceShared { - /// The [`AssetId`] of the mesh. - pub mesh_asset_id: AssetId, - /// A slot for the material bind group index. - pub material_bindings_index: MaterialBindingId, - /// Various flags. - pub flags: RenderMeshInstanceFlags, - /// Index of the slab that the lightmap resides in, if a lightmap is - /// present. - pub lightmap_slab_index: Option, - /// User supplied tag to identify this mesh instance. - pub tag: u32, - /// Render layers that this mesh instance belongs to. - pub render_layers: Option, -} - /// Information that is gathered during the parallel portion of mesh extraction /// when GPU mesh uniform building is enabled. /// @@ -812,186 +519,6 @@ pub struct RenderMeshInstanceGpuQueues(Parallel); #[derive(Resource, Default, Deref, DerefMut)] pub struct MeshesToReextractNextFrame(MainEntityHashSet); -impl RenderMeshInstanceShared { - /// A gpu builder will provide the mesh instance id - /// during [`RenderMeshInstanceGpuBuilder::update`]. - fn for_gpu_building( - previous_transform: Option<&PreviousGlobalTransform>, - mesh: &Mesh3d, - tag: Option<&MeshTag>, - not_shadow_caster: bool, - no_automatic_batching: bool, - render_layers: Option<&RenderLayers>, - ) -> Self { - Self::for_cpu_building( - previous_transform, - mesh, - tag, - default(), - not_shadow_caster, - no_automatic_batching, - render_layers, - ) - } - - /// The cpu builder does not have an equivalent [`RenderMeshInstanceGpuBuilder::update`]. - fn for_cpu_building( - previous_transform: Option<&PreviousGlobalTransform>, - mesh: &Mesh3d, - tag: Option<&MeshTag>, - material_bindings_index: MaterialBindingId, - not_shadow_caster: bool, - no_automatic_batching: bool, - render_layers: Option<&RenderLayers>, - ) -> Self { - let mut mesh_instance_flags = RenderMeshInstanceFlags::empty(); - mesh_instance_flags.set(RenderMeshInstanceFlags::SHADOW_CASTER, !not_shadow_caster); - mesh_instance_flags.set( - RenderMeshInstanceFlags::AUTOMATIC_BATCHING, - !no_automatic_batching, - ); - mesh_instance_flags.set( - RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM, - previous_transform.is_some(), - ); - - RenderMeshInstanceShared { - mesh_asset_id: mesh.id(), - flags: mesh_instance_flags, - material_bindings_index, - lightmap_slab_index: None, - tag: tag.map_or(0, |i| **i), - render_layers: render_layers.cloned(), - } - } - - /// Returns true if this entity is eligible to participate in automatic - /// batching. - #[inline] - pub fn should_batch(&self) -> bool { - self.flags - .contains(RenderMeshInstanceFlags::AUTOMATIC_BATCHING) - } -} - -/// Information that the render world keeps about each entity that contains a -/// mesh. -/// -/// The set of information needed is different depending on whether CPU or GPU -/// [`MeshUniform`] building is in use. -#[derive(Resource)] -pub enum RenderMeshInstances { - /// Information needed when using CPU mesh instance data building. - CpuBuilding(RenderMeshInstancesCpu), - /// Information needed when using GPU mesh instance data building. - GpuBuilding(RenderMeshInstancesGpu), -} - -/// Information that the render world keeps about each entity that contains a -/// mesh, when using CPU mesh instance data building. -#[derive(Default, Deref, DerefMut)] -pub struct RenderMeshInstancesCpu(MainEntityHashMap); - -/// Information that the render world keeps about each entity that contains a -/// mesh, when using GPU mesh instance data building. -#[derive(Default, Deref, DerefMut)] -pub struct RenderMeshInstancesGpu(MainEntityHashMap); - -impl RenderMeshInstances { - /// Creates a new [`RenderMeshInstances`] instance. - fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances { - if use_gpu_instance_buffer_builder { - RenderMeshInstances::GpuBuilding(RenderMeshInstancesGpu::default()) - } else { - RenderMeshInstances::CpuBuilding(RenderMeshInstancesCpu::default()) - } - } - - /// Returns the ID of the mesh asset attached to the given entity, if any. - pub fn mesh_asset_id(&self, entity: MainEntity) -> Option> { - match *self { - RenderMeshInstances::CpuBuilding(ref instances) => instances.mesh_asset_id(entity), - RenderMeshInstances::GpuBuilding(ref instances) => instances.mesh_asset_id(entity), - } - } - - /// Constructs [`RenderMeshQueueData`] for the given entity, if it has a - /// mesh attached. - pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { - match *self { - RenderMeshInstances::CpuBuilding(ref instances) => { - instances.render_mesh_queue_data(entity) - } - RenderMeshInstances::GpuBuilding(ref instances) => { - instances.render_mesh_queue_data(entity) - } - } - } - - /// Inserts the given flags into the CPU or GPU render mesh instance data - /// for the given mesh as appropriate. - fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { - match *self { - RenderMeshInstances::CpuBuilding(ref mut instances) => { - instances.insert_mesh_instance_flags(entity, flags); - } - RenderMeshInstances::GpuBuilding(ref mut instances) => { - instances.insert_mesh_instance_flags(entity, flags); - } - } - } -} - -impl RenderMeshInstancesCpu { - fn mesh_asset_id(&self, entity: MainEntity) -> Option> { - self.get(&entity) - .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) - } - - fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { - self.get(&entity) - .map(|render_mesh_instance| RenderMeshQueueData { - shared: &render_mesh_instance.shared, - translation: render_mesh_instance.transforms.world_from_local.translation, - current_uniform_index: InputUniformIndex::default(), - }) - } - - /// Inserts the given flags into the render mesh instance data for the given - /// mesh. - fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { - if let Some(instance) = self.get_mut(&entity) { - instance.flags.insert(flags); - } - } -} - -impl RenderMeshInstancesGpu { - fn mesh_asset_id(&self, entity: MainEntity) -> Option> { - self.get(&entity) - .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) - } - - fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { - self.get(&entity) - .map(|render_mesh_instance| RenderMeshQueueData { - shared: &render_mesh_instance.shared, - translation: render_mesh_instance.translation, - current_uniform_index: InputUniformIndex( - render_mesh_instance.current_uniform_index.into(), - ), - }) - } - - /// Inserts the given flags into the render mesh instance data for the given - /// mesh. - fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { - if let Some(instance) = self.get_mut(&entity) { - instance.flags.insert(flags); - } - } -} - impl RenderMeshInstanceGpuQueue { /// Clears out a [`RenderMeshInstanceGpuQueue`], creating or recreating it /// as necessary. @@ -1092,6 +619,8 @@ impl RenderMeshInstanceGpuQueue { impl RenderMeshInstanceGpuBuilder { /// Flushes this mesh instance to the [`RenderMeshInstanceGpu`] and /// [`MeshInputUniform`] tables, replacing the existing entry if applicable. + /// + /// Provides the mesh instance id for [`RenderMeshInstanceShared::for_gpu_building`] fn update( mut self, entity: MainEntity, @@ -1225,21 +754,6 @@ impl RenderMeshInstanceGpuBuilder { } } -/// Removes a [`MeshInputUniform`] corresponding to an entity that became -/// invisible from the buffer. -fn remove_mesh_input_uniform( - entity: MainEntity, - render_mesh_instances: &mut MainEntityHashMap, - current_input_buffer: &mut InstanceInputUniformBuffer, -) -> Option { - // Remove the uniform data. - let removed_render_mesh_instance = render_mesh_instances.remove(&entity)?; - - let removed_uniform_index = removed_render_mesh_instance.current_uniform_index.get(); - current_input_buffer.remove(removed_uniform_index); - Some(removed_uniform_index) -} - impl MeshCullingData { /// Returns a new [`MeshCullingData`] initialized with the given AABB. /// @@ -1279,20 +793,6 @@ impl Default for MeshCullingDataBuffer { } } -/// Data that [`crate::material::queue_material_meshes`] and similar systems -/// need in order to place entities that contain meshes in the right batch. -#[derive(Deref)] -pub struct RenderMeshQueueData<'a> { - /// General information about the mesh instance. - #[deref] - pub shared: &'a RenderMeshInstanceShared, - /// The translation of the mesh instance. - pub translation: Vec3, - /// The index of the [`MeshInputUniform`] in the GPU buffer for this mesh - /// instance. - pub current_uniform_index: InputUniformIndex, -} - /// A [`SystemSet`] that encompasses both [`extract_meshes_for_cpu_building`] /// and [`extract_meshes_for_gpu_building`]. #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] @@ -1759,83 +1259,38 @@ pub fn collect_meshes_for_gpu_building( previous_input_buffer.ensure_nonempty(); } -/// All data needed to construct a pipeline for rendering 3D meshes. -#[derive(Resource, Clone)] -pub struct MeshPipeline { - /// A reference to all the mesh pipeline view layouts. - pub view_layouts: MeshPipelineViewLayouts, - pub clustered_forward_buffer_binding_type: BufferBindingType, - pub mesh_layouts: MeshLayouts, - /// The shader asset handle. - pub shader: Handle, - /// `MeshUniform`s are stored in arrays in buffers. If storage buffers are available, they - /// are used and this will be `None`, otherwise uniform buffers will be used with batches - /// of this many `MeshUniform`s, stored at dynamic offsets within the uniform buffer. - /// Use code like this in custom shaders: - /// ```wgsl - /// ##ifdef PER_OBJECT_BUFFER_BATCH_SIZE - /// @group(1) @binding(0) var mesh: array; - /// ##else - /// @group(1) @binding(0) var mesh: array; - /// ##endif // PER_OBJECT_BUFFER_BATCH_SIZE - /// ``` - pub per_object_buffer_batch_size: Option, - - /// Whether binding arrays (a.k.a. bindless textures) are usable on the - /// current render device. - /// - /// This affects whether reflection probes can be used. - pub binding_arrays_are_usable: bool, - - /// Whether clustered decals are usable on the current render device. - pub clustered_decals_are_usable: bool, - - /// Whether skins will use uniform buffers on account of storage buffers - /// being unavailable on this platform. - pub skins_use_uniform_buffers: bool, -} - -impl FromWorld for MeshPipeline { - fn from_world(world: &mut World) -> Self { - let shader = load_embedded_asset!(world, "mesh.wgsl"); - let mut system_state: SystemState<( - Res, - Res, - Res, - )> = SystemState::new(world); - let (render_device, render_adapter, view_layouts) = system_state.get_mut(world); - - let clustered_forward_buffer_binding_type = render_device - .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); - - MeshPipeline { - view_layouts: view_layouts.clone(), - clustered_forward_buffer_binding_type, - mesh_layouts: MeshLayouts::new(&render_device, &render_adapter), - shader, - per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( - &render_device.limits(), - ), - binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), - clustered_decals_are_usable: decal::clustered::clustered_decals_are_usable( - &render_device, - &render_adapter, - ), - skins_use_uniform_buffers: skins_use_uniform_buffers(&render_device.limits()), - } - } -} +pub fn init_mesh_pipeline(world: &mut World) { + let shader = load_embedded_asset!(world, "mesh.wgsl"); + let mut system_state: SystemState<( + Res, + Res, + Res, + )> = SystemState::new(world); + let (render_device, render_adapter, view_layouts) = system_state.get_mut(world); + + let clustered_forward_buffer_binding_type = + render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); + + let res = MeshPipeline { + view_layouts: view_layouts.clone(), + clustered_forward_buffer_binding_type, + mesh_layouts: MeshLayouts::new(&render_device, &render_adapter), + shader, + per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( + &render_device.limits(), + ), + binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), + clustered_decals_are_usable: decal::clustered::clustered_decals_are_usable( + &render_device, + &render_adapter, + ), + skins_use_uniform_buffers: skins_use_uniform_buffers(&render_device.limits()), + }; -impl MeshPipeline { - pub fn get_view_layout( - &self, - layout_key: MeshPipelineViewLayoutKey, - ) -> &MeshPipelineViewLayout { - self.view_layouts.get_view_layout(layout_key) - } + world.insert_resource(res); } -/// A 1x1x1 'all 1.0' texture to use as a dummy texture in place of optional [`crate::pbr_material::StandardMaterial`] textures +/// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional [`crate::pbr_material::StandardMaterial`] textures pub fn build_dummy_white_gpu_image( render_device: Res, default_sampler: Res, @@ -1890,763 +1345,6 @@ pub fn get_image_texture<'a>( } } -impl GetBatchData for MeshPipeline { - type Param = ( - SRes, - SRes, - SRes>, - SRes, - SRes, - ); - // The material bind group ID, the mesh ID, and the lightmap ID, - // respectively. - type CompareData = ( - MaterialBindGroupIndex, - AssetId, - Option, - ); - - type BufferData = MeshUniform; - - fn get_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< - Self::Param, - >, - (_entity, main_entity): (Entity, MainEntity), - ) -> Option<(Self::BufferData, Option)> { - let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { - error!( - "`get_batch_data` should never be called in GPU mesh uniform \ - building mode" - ); - return None; - }; - let mesh_instance = mesh_instances.get(&main_entity)?; - let first_vertex_index = - match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { - Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, - None => 0, - }; - let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); - - let current_skin_index = skin_uniforms.skin_index(main_entity); - let material_bind_group_index = mesh_instance.material_bindings_index; - - Some(( - MeshUniform::new( - &mesh_instance.transforms, - first_vertex_index, - material_bind_group_index.slot, - maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), - current_skin_index, - Some(mesh_instance.tag), - ), - mesh_instance.should_batch().then_some(( - material_bind_group_index.group, - mesh_instance.mesh_asset_id, - maybe_lightmap.map(|lightmap| lightmap.slab_index), - )), - )) - } -} - -impl GetFullBatchData for MeshPipeline { - type BufferInputData = MeshInputUniform; - - fn get_index_and_compare_data( - (mesh_instances, lightmaps, _, _, _): &SystemParamItem, - main_entity: MainEntity, - ) -> Option<(NonMaxU32, Option)> { - // This should only be called during GPU building. - let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { - error!( - "`get_index_and_compare_data` should never be called in CPU mesh uniform building \ - mode" - ); - return None; - }; - - let mesh_instance = mesh_instances.get(&main_entity)?; - let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); - - Some(( - mesh_instance.current_uniform_index, - mesh_instance.should_batch().then_some(( - mesh_instance.material_bindings_index.group, - mesh_instance.mesh_asset_id, - maybe_lightmap.map(|lightmap| lightmap.slab_index), - )), - )) - } - - fn get_binned_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< - Self::Param, - >, - main_entity: MainEntity, - ) -> Option { - let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { - error!( - "`get_binned_batch_data` should never be called in GPU mesh uniform building mode" - ); - return None; - }; - let mesh_instance = mesh_instances.get(&main_entity)?; - let first_vertex_index = - match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { - Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, - None => 0, - }; - let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); - - let current_skin_index = skin_uniforms.skin_index(main_entity); - - Some(MeshUniform::new( - &mesh_instance.transforms, - first_vertex_index, - mesh_instance.material_bindings_index.slot, - maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), - current_skin_index, - Some(mesh_instance.tag), - )) - } - - fn get_binned_index( - (mesh_instances, _, _, _, _): &SystemParamItem, - main_entity: MainEntity, - ) -> Option { - // This should only be called during GPU building. - let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { - error!( - "`get_binned_index` should never be called in CPU mesh uniform \ - building mode" - ); - return None; - }; - - mesh_instances - .get(&main_entity) - .map(|entity| entity.current_uniform_index) - } - - fn write_batch_indirect_parameters_metadata( - indexed: bool, - base_output_index: u32, - batch_set_index: Option, - phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, - indirect_parameters_offset: u32, - ) { - let indirect_parameters = IndirectParametersCpuMetadata { - base_output_index, - batch_set_index: match batch_set_index { - Some(batch_set_index) => u32::from(batch_set_index), - None => !0, - }, - }; - - if indexed { - phase_indirect_parameters_buffers - .indexed - .set(indirect_parameters_offset, indirect_parameters); - } else { - phase_indirect_parameters_buffers - .non_indexed - .set(indirect_parameters_offset, indirect_parameters); - } - } -} - -bitflags::bitflags! { - #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] - #[repr(transparent)] - // NOTE: Apparently quadro drivers support up to 64x MSAA. - /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. - pub struct MeshPipelineKey: u64 { - // Nothing - const NONE = 0; - - // Inherited bits - const MORPH_TARGETS = BaseMeshPipelineKey::MORPH_TARGETS.bits(); - - // Flag bits - const HDR = 1 << 0; - const TONEMAP_IN_SHADER = 1 << 1; - const DEBAND_DITHER = 1 << 2; - const DEPTH_PREPASS = 1 << 3; - const NORMAL_PREPASS = 1 << 4; - const DEFERRED_PREPASS = 1 << 5; - const MOTION_VECTOR_PREPASS = 1 << 6; - const MAY_DISCARD = 1 << 7; // Guards shader codepaths that may discard, allowing early depth tests in most cases - // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test - const ENVIRONMENT_MAP = 1 << 8; - const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9; - const UNCLIPPED_DEPTH_ORTHO = 1 << 10; // Disables depth clipping for use with directional light shadow views - // Emulated via fragment shader depth on hardware that doesn't support it natively - // See: https://www.w3.org/TR/webgpu/#depth-clipping and https://therealmjp.github.io/posts/shadow-maps/#disabling-z-clipping - const TEMPORAL_JITTER = 1 << 11; - const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12; - const LIGHTMAPPED = 1 << 13; - const LIGHTMAP_BICUBIC_SAMPLING = 1 << 14; - const IRRADIANCE_VOLUME = 1 << 15; - const VISIBILITY_RANGE_DITHER = 1 << 16; - const SCREEN_SPACE_REFLECTIONS = 1 << 17; - const HAS_PREVIOUS_SKIN = 1 << 18; - const HAS_PREVIOUS_MORPH = 1 << 19; - const OIT_ENABLED = 1 << 20; - const DISTANCE_FOG = 1 << 21; - const LAST_FLAG = Self::DISTANCE_FOG.bits(); - - // Bitfields - const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; - const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state - const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask - const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; // ← As blend states is on 3 bits, it can range from 0 to 7 - const BLEND_MULTIPLY = 2 << Self::BLEND_SHIFT_BITS; // ← See `BLEND_MASK_BITS` for the number of bits available - const BLEND_ALPHA = 3 << Self::BLEND_SHIFT_BITS; // - const BLEND_ALPHA_TO_COVERAGE = 4 << Self::BLEND_SHIFT_BITS; // ← We still have room for three more values without adding more bits - const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_GAUSSIAN = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_TEMPORAL = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS = Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW = 0 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM = 1 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH = 2 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA = 3 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; - const ALL_RESERVED_BITS = - Self::BLEND_RESERVED_BITS.bits() | - Self::MSAA_RESERVED_BITS.bits() | - Self::TONEMAP_METHOD_RESERVED_BITS.bits() | - Self::SHADOW_FILTER_METHOD_RESERVED_BITS.bits() | - Self::VIEW_PROJECTION_RESERVED_BITS.bits() | - Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS.bits(); - } -} - -impl MeshPipelineKey { - const MSAA_MASK_BITS: u64 = 0b111; - const MSAA_SHIFT_BITS: u64 = Self::LAST_FLAG.bits().trailing_zeros() as u64 + 1; - - const BLEND_MASK_BITS: u64 = 0b111; - const BLEND_SHIFT_BITS: u64 = Self::MSAA_MASK_BITS.count_ones() as u64 + Self::MSAA_SHIFT_BITS; - - const TONEMAP_METHOD_MASK_BITS: u64 = 0b111; - const TONEMAP_METHOD_SHIFT_BITS: u64 = - Self::BLEND_MASK_BITS.count_ones() as u64 + Self::BLEND_SHIFT_BITS; - - const SHADOW_FILTER_METHOD_MASK_BITS: u64 = 0b11; - const SHADOW_FILTER_METHOD_SHIFT_BITS: u64 = - Self::TONEMAP_METHOD_MASK_BITS.count_ones() as u64 + Self::TONEMAP_METHOD_SHIFT_BITS; - - const VIEW_PROJECTION_MASK_BITS: u64 = 0b11; - const VIEW_PROJECTION_SHIFT_BITS: u64 = Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones() - as u64 - + Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - - const SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS: u64 = 0b11; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u64 = - Self::VIEW_PROJECTION_MASK_BITS.count_ones() as u64 + Self::VIEW_PROJECTION_SHIFT_BITS; - - pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = - (msaa_samples.trailing_zeros() as u64 & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - Self::from_bits_retain(msaa_bits) - } - - pub fn from_hdr(hdr: bool) -> Self { - if hdr { - MeshPipelineKey::HDR - } else { - MeshPipelineKey::NONE - } - } - - pub fn msaa_samples(&self) -> u32 { - 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) - } - - pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { - let primitive_topology_bits = ((primitive_topology as u64) - & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS) - << BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - Self::from_bits_retain(primitive_topology_bits) - } - - pub fn primitive_topology(&self) -> PrimitiveTopology { - let primitive_topology_bits = (self.bits() - >> BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS) - & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS; - match primitive_topology_bits { - x if x == PrimitiveTopology::PointList as u64 => PrimitiveTopology::PointList, - x if x == PrimitiveTopology::LineList as u64 => PrimitiveTopology::LineList, - x if x == PrimitiveTopology::LineStrip as u64 => PrimitiveTopology::LineStrip, - x if x == PrimitiveTopology::TriangleList as u64 => PrimitiveTopology::TriangleList, - x if x == PrimitiveTopology::TriangleStrip as u64 => PrimitiveTopology::TriangleStrip, - _ => PrimitiveTopology::default(), - } - } -} - -// Ensure that we didn't overflow the number of bits available in `MeshPipelineKey`. -const_assert_eq!( - (((MeshPipelineKey::LAST_FLAG.bits() << 1) - 1) | MeshPipelineKey::ALL_RESERVED_BITS.bits()) - & BaseMeshPipelineKey::all().bits(), - 0 -); - -// Ensure that the reserved bits don't overlap with the topology bits -const_assert_eq!( - (BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS - << BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS) - & MeshPipelineKey::ALL_RESERVED_BITS.bits(), - 0 -); - -fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool { - layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX) - && layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) -} -pub fn setup_morph_and_skinning_defs( - mesh_layouts: &MeshLayouts, - layout: &MeshVertexBufferLayoutRef, - offset: u32, - key: &MeshPipelineKey, - shader_defs: &mut Vec, - vertex_attributes: &mut Vec, - skins_use_uniform_buffers: bool, -) -> BindGroupLayoutDescriptor { - let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); - let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED); - let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS); - - if skins_use_uniform_buffers { - shader_defs.push("SKINS_USE_UNIFORM_BUFFERS".into()); - } - - let mut add_skin_data = || { - shader_defs.push("SKINNED".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); - vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1)); - }; - - match ( - is_skinned(layout), - is_morphed, - is_lightmapped, - motion_vector_prepass, - ) { - (true, false, _, true) => { - add_skin_data(); - mesh_layouts.skinned_motion.clone() - } - (true, false, _, false) => { - add_skin_data(); - mesh_layouts.skinned.clone() - } - (true, true, _, true) => { - add_skin_data(); - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed_skinned_motion.clone() - } - (true, true, _, false) => { - add_skin_data(); - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed_skinned.clone() - } - (false, true, _, true) => { - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed_motion.clone() - } - (false, true, _, false) => { - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed.clone() - } - (false, false, true, _) => mesh_layouts.lightmapped.clone(), - (false, false, false, _) => mesh_layouts.model_only.clone(), - } -} - -impl SpecializedMeshPipeline for MeshPipeline { - type Key = MeshPipelineKey; - - fn specialize( - &self, - key: Self::Key, - layout: &MeshVertexBufferLayoutRef, - ) -> Result { - let mut shader_defs = Vec::new(); - let mut vertex_attributes = Vec::new(); - - // Let the shader code know that it's running in a mesh pipeline. - shader_defs.push("MESH_PIPELINE".into()); - - shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); - - if layout.0.contains(Mesh::ATTRIBUTE_POSITION) { - shader_defs.push("VERTEX_POSITIONS".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); - } - - if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) { - shader_defs.push("VERTEX_NORMALS".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); - } - - if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { - shader_defs.push("VERTEX_UVS".into()); - shader_defs.push("VERTEX_UVS_A".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); - } - - if layout.0.contains(Mesh::ATTRIBUTE_UV_1) { - shader_defs.push("VERTEX_UVS".into()); - shader_defs.push("VERTEX_UVS_B".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3)); - } - - if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) { - shader_defs.push("VERTEX_TANGENTS".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4)); - } - - if layout.0.contains(Mesh::ATTRIBUTE_COLOR) { - shader_defs.push("VERTEX_COLORS".into()); - vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5)); - } - - if cfg!(feature = "pbr_transmission_textures") { - shader_defs.push("PBR_TRANSMISSION_TEXTURES_SUPPORTED".into()); - } - if cfg!(feature = "pbr_multi_layer_material_textures") { - shader_defs.push("PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED".into()); - } - if cfg!(feature = "pbr_anisotropy_texture") { - shader_defs.push("PBR_ANISOTROPY_TEXTURE_SUPPORTED".into()); - } - if cfg!(feature = "pbr_specular_textures") { - shader_defs.push("PBR_SPECULAR_TEXTURES_SUPPORTED".into()); - } - - let bind_group_layout = self.get_view_layout(key.into()); - let mut bind_group_layout = vec![ - bind_group_layout.main_layout.clone(), - bind_group_layout.binding_array_layout.clone(), - ]; - - if key.msaa_samples() > 1 { - shader_defs.push("MULTISAMPLED".into()); - }; - - bind_group_layout.push(setup_morph_and_skinning_defs( - &self.mesh_layouts, - layout, - 6, - &key, - &mut shader_defs, - &mut vertex_attributes, - self.skins_use_uniform_buffers, - )); - - if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { - shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); - } - - let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; - - let (label, blend, depth_write_enabled); - let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS); - let (mut is_opaque, mut alpha_to_coverage_enabled) = (false, false); - if key.contains(MeshPipelineKey::OIT_ENABLED) && pass == MeshPipelineKey::BLEND_ALPHA { - label = "oit_mesh_pipeline".into(); - // TODO tail blending would need alpha blending - blend = None; - shader_defs.push("OIT_ENABLED".into()); - // TODO it should be possible to use this to combine MSAA and OIT - // alpha_to_coverage_enabled = true; - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_ALPHA { - label = "alpha_blend_mesh_pipeline".into(); - blend = Some(BlendState::ALPHA_BLENDING); - // For the transparent pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { - label = "premultiplied_alpha_mesh_pipeline".into(); - blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING); - shader_defs.push("PREMULTIPLY_ALPHA".into()); - shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); - // For the transparent pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_MULTIPLY { - label = "multiply_mesh_pipeline".into(); - blend = Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::Dst, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent::OVER, - }); - shader_defs.push("PREMULTIPLY_ALPHA".into()); - shader_defs.push("BLEND_MULTIPLY".into()); - // For the multiply pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE { - label = "alpha_to_coverage_mesh_pipeline".into(); - // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases - blend = None; - // For the opaque and alpha mask passes, fragments that are closer will replace - // the current fragment value in the output and the depth is written to the - // depth buffer - depth_write_enabled = true; - is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); - alpha_to_coverage_enabled = true; - shader_defs.push("ALPHA_TO_COVERAGE".into()); - } else { - label = "opaque_mesh_pipeline".into(); - // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases - blend = None; - // For the opaque and alpha mask passes, fragments that are closer will replace - // the current fragment value in the output and the depth is written to the - // depth buffer - depth_write_enabled = true; - is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); - } - - if key.contains(MeshPipelineKey::NORMAL_PREPASS) { - shader_defs.push("NORMAL_PREPASS".into()); - } - - if key.contains(MeshPipelineKey::DEPTH_PREPASS) { - shader_defs.push("DEPTH_PREPASS".into()); - } - - if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { - shader_defs.push("MOTION_VECTOR_PREPASS".into()); - } - - if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { - shader_defs.push("HAS_PREVIOUS_SKIN".into()); - } - - if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { - shader_defs.push("HAS_PREVIOUS_MORPH".into()); - } - - if key.contains(MeshPipelineKey::DEFERRED_PREPASS) { - shader_defs.push("DEFERRED_PREPASS".into()); - } - - if key.contains(MeshPipelineKey::NORMAL_PREPASS) && key.msaa_samples() == 1 && is_opaque { - shader_defs.push("LOAD_PREPASS_NORMALS".into()); - } - - let view_projection = key.intersection(MeshPipelineKey::VIEW_PROJECTION_RESERVED_BITS); - if view_projection == MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD { - shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into()); - } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE { - shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into()); - } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC { - shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into()); - } - - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - shader_defs.push("WEBGL2".into()); - - #[cfg(feature = "experimental_pbr_pcss")] - shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into()); - - if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { - shader_defs.push("TONEMAP_IN_SHADER".into()); - shader_defs.push(ShaderDefVal::UInt( - "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), - TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, - )); - shader_defs.push(ShaderDefVal::UInt( - "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), - TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, - )); - - let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); - - if method == MeshPipelineKey::TONEMAP_METHOD_NONE { - shader_defs.push("TONEMAP_METHOD_NONE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD { - shader_defs.push("TONEMAP_METHOD_REINHARD".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE { - shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED { - shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX { - shader_defs.push("TONEMAP_METHOD_AGX".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM { - shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC { - shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE { - shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); - } - - // Debanding is tied to tonemapping in the shader, cannot run without it. - if key.contains(MeshPipelineKey::DEBAND_DITHER) { - shader_defs.push("DEBAND_DITHER".into()); - } - } - - if key.contains(MeshPipelineKey::MAY_DISCARD) { - shader_defs.push("MAY_DISCARD".into()); - } - - if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { - shader_defs.push("ENVIRONMENT_MAP".into()); - } - - if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) && IRRADIANCE_VOLUMES_ARE_USABLE { - shader_defs.push("IRRADIANCE_VOLUME".into()); - } - - if key.contains(MeshPipelineKey::LIGHTMAPPED) { - shader_defs.push("LIGHTMAP".into()); - } - if key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) { - shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into()); - } - - if key.contains(MeshPipelineKey::TEMPORAL_JITTER) { - shader_defs.push("TEMPORAL_JITTER".into()); - } - - let shadow_filter_method = - key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS); - if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 { - shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN { - shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL { - shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into()); - } - - let blur_quality = - key.intersection(MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS); - - shader_defs.push(ShaderDefVal::Int( - "SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS".into(), - match blur_quality { - MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW => 4, - MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM => 8, - MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH => 16, - MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA => 32, - _ => unreachable!(), // Not possible, since the mask is 2 bits, and we've covered all 4 cases - }, - )); - - if key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) { - shader_defs.push("VISIBILITY_RANGE_DITHER".into()); - } - - if key.contains(MeshPipelineKey::DISTANCE_FOG) { - shader_defs.push("DISTANCE_FOG".into()); - } - - if self.binding_arrays_are_usable { - shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); - shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); - } - - if IRRADIANCE_VOLUMES_ARE_USABLE { - shader_defs.push("IRRADIANCE_VOLUMES_ARE_USABLE".into()); - } - - if self.clustered_decals_are_usable { - shader_defs.push("CLUSTERED_DECALS_ARE_USABLE".into()); - if cfg!(feature = "pbr_light_textures") { - shader_defs.push("LIGHT_TEXTURES".into()); - } - } - - let format = if key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - - // This is defined here so that custom shaders that use something other than - // the mesh binding from bevy_pbr::mesh_bindings can easily make use of this - // in their own shaders. - if let Some(per_object_buffer_batch_size) = self.per_object_buffer_batch_size { - shader_defs.push(ShaderDefVal::UInt( - "PER_OBJECT_BUFFER_BATCH_SIZE".into(), - per_object_buffer_batch_size, - )); - } - - Ok(RenderPipelineDescriptor { - vertex: VertexState { - shader: self.shader.clone(), - shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout], - ..default() - }, - fragment: Some(FragmentState { - shader: self.shader.clone(), - shader_defs, - targets: vec![Some(ColorTargetState { - format, - blend, - write_mask: ColorWrites::ALL, - })], - ..default() - }), - layout: bind_group_layout, - primitive: PrimitiveState { - cull_mode: Some(Face::Back), - unclipped_depth: false, - topology: key.primitive_topology(), - ..default() - }, - depth_stencil: Some(DepthStencilState { - format: CORE_3D_DEPTH_FORMAT, - depth_write_enabled, - depth_compare: CompareFunction::GreaterEqual, - stencil: StencilState { - front: StencilFaceState::IGNORE, - back: StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: DepthBiasState { - constant: 0, - slope_scale: 0.0, - clamp: 0.0, - }, - }), - multisample: MultisampleState { - count: key.msaa_samples(), - mask: !0, - alpha_to_coverage_enabled, - }, - label: Some(label), - ..default() - }) - } -} - /// The bind groups for meshes currently loaded. /// /// If GPU mesh preprocessing isn't in use, these are global to the scene. If diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 0708bc08daa04..44211ca242f7f 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -1,5 +1,6 @@ //! Bind group layout related definitions for the mesh pipeline. +pub use bevy_material::render::MeshLayouts; use bevy_math::Mat4; use bevy_mesh::morph::MAX_MORPH_WEIGHTS; use bevy_render::{ @@ -160,48 +161,94 @@ mod entry { } } -/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`). -#[derive(Clone)] -pub struct MeshLayouts { - /// The mesh model uniform (transform) and nothing else. - pub model_only: BindGroupLayoutDescriptor, +pub trait MeshLayoutsBuilders { + fn new(render_device: &RenderDevice, render_adapter: &RenderAdapter) -> Self; - /// Includes the lightmap texture and uniform. - pub lightmapped: BindGroupLayoutDescriptor, - - /// Also includes the uniform for skinning - pub skinned: BindGroupLayoutDescriptor, - - /// Like [`MeshLayouts::skinned`], but includes slots for the previous - /// frame's joint matrices, so that we can compute motion vectors. - pub skinned_motion: BindGroupLayoutDescriptor, - - /// Also includes the uniform and [`MorphAttributes`] for morph targets. - /// - /// [`MorphAttributes`]: bevy_mesh::morph::MorphAttributes - pub morphed: BindGroupLayoutDescriptor, - - /// Like [`MeshLayouts::morphed`], but includes a slot for the previous - /// frame's morph weights, so that we can compute motion vectors. - pub morphed_motion: BindGroupLayoutDescriptor, - - /// Also includes both uniforms for skinning and morph targets, also the - /// morph target [`MorphAttributes`] binding. - /// - /// [`MorphAttributes`]: bevy_mesh::morph::MorphAttributes - pub morphed_skinned: BindGroupLayoutDescriptor, + fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn skinned_motion_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn morphed_motion_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn morphed_skinned_motion_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + fn lightmapped_layout( + render_device: &RenderDevice, + render_adapter: &RenderAdapter, + ) -> BindGroupLayoutDescriptor; - /// Like [`MeshLayouts::morphed_skinned`], but includes slots for the - /// previous frame's joint matrices and morph weights, so that we can - /// compute motion vectors. - pub morphed_skinned_motion: BindGroupLayoutDescriptor, + fn model_only( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + ) -> BindGroup; + fn lightmapped( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + lightmap_slab: &LightmapSlab, + bindless_lightmaps: bool, + ) -> BindGroup; + fn skinned( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + ) -> BindGroup; + fn skinned_motion( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + prev_skin: &Buffer, + ) -> BindGroup; + fn morphed( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_weights: &Buffer, + targets: &TextureView, + ) -> BindGroup; + fn morphed_motion( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_weights: &Buffer, + targets: &TextureView, + prev_weights: &Buffer, + ) -> BindGroup; + fn morphed_skinned( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + current_weights: &Buffer, + targets: &TextureView, + ) -> BindGroup; + fn morphed_skinned_motion( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + current_weights: &Buffer, + targets: &TextureView, + prev_skin: &Buffer, + prev_weights: &Buffer, + ) -> BindGroup; } -impl MeshLayouts { +impl MeshLayoutsBuilders for MeshLayouts { /// Prepare the layouts used by the default bevy [`Mesh`]. /// /// [`Mesh`]: bevy_mesh::Mesh - pub fn new(render_device: &RenderDevice, render_adapter: &RenderAdapter) -> Self { + fn new(render_device: &RenderDevice, render_adapter: &RenderAdapter) -> Self { MeshLayouts { model_only: Self::model_only_layout(render_device), lightmapped: Self::lightmapped_layout(render_device, render_adapter), @@ -369,7 +416,7 @@ impl MeshLayouts { // ---------- BindGroup methods ---------- - pub fn model_only( + fn model_only( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -382,7 +429,7 @@ impl MeshLayouts { ) } - pub fn lightmapped( + fn lightmapped( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -416,7 +463,7 @@ impl MeshLayouts { } /// Creates the bind group for skinned meshes with no morph targets. - pub fn skinned( + fn skinned( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -440,7 +487,7 @@ impl MeshLayouts { /// `prev_skin` is the buffer for the previous frame. The latter is used for /// motion vector computation. If there is no such applicable buffer, /// `current_skin` and `prev_skin` will reference the same buffer. - pub fn skinned_motion( + fn skinned_motion( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -460,7 +507,7 @@ impl MeshLayouts { } /// Creates the bind group for meshes with no skins but morph targets. - pub fn morphed( + fn morphed( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -486,7 +533,7 @@ impl MeshLayouts { /// `prev_weights` is the buffer for the previous frame. The latter is used /// for motion vector computation. If there is no such applicable buffer, /// `current_weights` and `prev_weights` will reference the same buffer. - pub fn morphed_motion( + fn morphed_motion( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -508,7 +555,7 @@ impl MeshLayouts { } /// Creates the bind group for meshes with skins and morph targets. - pub fn morphed_skinned( + fn morphed_skinned( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -536,7 +583,7 @@ impl MeshLayouts { /// [`MeshLayouts::morphed_motion`] above for more information about the /// `current_skin`, `prev_skin`, `current_weights`, and `prev_weights` /// buffers. - pub fn morphed_skinned_motion( + fn morphed_skinned_motion( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 3550e4c8eb0d4..0ebc937afeeaf 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -7,17 +7,18 @@ use bevy_core_pipeline::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, }, }; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, query::Has, - resource::Resource, system::{Commands, Query, Res}, - world::{FromWorld, World}, + world::World, }; use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, IrradianceVolume}; +use bevy_material::render::{ + MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, +}; use bevy_math::Vec4; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, @@ -45,7 +46,7 @@ use crate::{ }, prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, - MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, + MeshPipeline, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; @@ -53,141 +54,37 @@ use crate::{ #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] use bevy_render::render_resource::binding_types::texture_cube; -#[cfg(debug_assertions)] -use {crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::warn}; - -#[derive(Clone)] -pub struct MeshPipelineViewLayout { - pub main_layout: BindGroupLayoutDescriptor, - pub binding_array_layout: BindGroupLayoutDescriptor, - pub empty_layout: BindGroupLayoutDescriptor, +pub fn mesh_pipeline_view_layout_key_from_msaa(value: Msaa) -> MeshPipelineViewLayoutKey { + let mut result = MeshPipelineViewLayoutKey::empty(); - #[cfg(debug_assertions)] - pub texture_count: usize, -} - -bitflags::bitflags! { - /// A key that uniquely identifies a [`MeshPipelineViewLayout`]. - /// - /// Used to generate all possible layouts for the mesh pipeline in [`generate_view_layouts`], - /// so special care must be taken to not add too many flags, as the number of possible layouts - /// will grow exponentially. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - #[repr(transparent)] - pub struct MeshPipelineViewLayoutKey: u32 { - const MULTISAMPLED = 1 << 0; - const DEPTH_PREPASS = 1 << 1; - const NORMAL_PREPASS = 1 << 2; - const MOTION_VECTOR_PREPASS = 1 << 3; - const DEFERRED_PREPASS = 1 << 4; - const OIT_ENABLED = 1 << 5; + if value.samples() > 1 { + result |= MeshPipelineViewLayoutKey::MULTISAMPLED; } -} -impl MeshPipelineViewLayoutKey { - // The number of possible layouts - pub const COUNT: usize = Self::all().bits() as usize + 1; - - /// Builds a unique label for each layout based on the flags - pub fn label(&self) -> String { - use MeshPipelineViewLayoutKey as Key; - - format!( - "mesh_view_layout{}{}{}{}{}{}", - if self.contains(Key::MULTISAMPLED) { - "_multisampled" - } else { - Default::default() - }, - if self.contains(Key::DEPTH_PREPASS) { - "_depth" - } else { - Default::default() - }, - if self.contains(Key::NORMAL_PREPASS) { - "_normal" - } else { - Default::default() - }, - if self.contains(Key::MOTION_VECTOR_PREPASS) { - "_motion" - } else { - Default::default() - }, - if self.contains(Key::DEFERRED_PREPASS) { - "_deferred" - } else { - Default::default() - }, - if self.contains(Key::OIT_ENABLED) { - "_oit" - } else { - Default::default() - }, - ) - } + result } -impl From for MeshPipelineViewLayoutKey { - fn from(value: MeshPipelineKey) -> Self { - let mut result = MeshPipelineViewLayoutKey::empty(); +pub fn mesh_pipeline_view_layout_key_from_view_prepass_textures( + value: Option<&ViewPrepassTextures>, +) -> MeshPipelineViewLayoutKey { + let mut result = MeshPipelineViewLayoutKey::empty(); - if value.msaa_samples() > 1 { - result |= MeshPipelineViewLayoutKey::MULTISAMPLED; - } - if value.contains(MeshPipelineKey::DEPTH_PREPASS) { + if let Some(prepass_textures) = value { + if prepass_textures.depth.is_some() { result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; } - if value.contains(MeshPipelineKey::NORMAL_PREPASS) { + if prepass_textures.normal.is_some() { result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; } - if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + if prepass_textures.motion_vectors.is_some() { result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; } - if value.contains(MeshPipelineKey::DEFERRED_PREPASS) { + if prepass_textures.deferred.is_some() { result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; } - if value.contains(MeshPipelineKey::OIT_ENABLED) { - result |= MeshPipelineViewLayoutKey::OIT_ENABLED; - } - - result } -} - -impl From for MeshPipelineViewLayoutKey { - fn from(value: Msaa) -> Self { - let mut result = MeshPipelineViewLayoutKey::empty(); - - if value.samples() > 1 { - result |= MeshPipelineViewLayoutKey::MULTISAMPLED; - } - - result - } -} - -impl From> for MeshPipelineViewLayoutKey { - fn from(value: Option<&ViewPrepassTextures>) -> Self { - let mut result = MeshPipelineViewLayoutKey::empty(); - if let Some(prepass_textures) = value { - if prepass_textures.depth.is_some() { - result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; - } - if prepass_textures.normal.is_some() { - result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; - } - if prepass_textures.motion_vectors.is_some() { - result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; - } - if prepass_textures.deferred.is_some() { - result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; - } - } - - result - } + result } pub(crate) fn buffer_layout( @@ -416,76 +313,49 @@ fn layout_entries( [entries.to_vec(), binding_array_entries.to_vec()] } -/// Stores the view layouts for every combination of pipeline keys. -/// -/// This is wrapped in an [`Arc`] so that it can be efficiently cloned and -/// placed inside specializable pipeline types. -#[derive(Resource, Clone, Deref, DerefMut)] -pub struct MeshPipelineViewLayouts( - pub Arc<[MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT]>, -); - -impl FromWorld for MeshPipelineViewLayouts { - fn from_world(world: &mut World) -> Self { - // Generates all possible view layouts for the mesh pipeline, based on all combinations of - // [`MeshPipelineViewLayoutKey`] flags. - - let render_device = world.resource::(); - let render_adapter = world.resource::(); - - let clustered_forward_buffer_binding_type = render_device - .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); - let visibility_ranges_buffer_binding_type = render_device - .get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); - - Self(Arc::new(array::from_fn(|i| { - let key = MeshPipelineViewLayoutKey::from_bits_truncate(i as u32); - let entries = layout_entries( - clustered_forward_buffer_binding_type, - visibility_ranges_buffer_binding_type, - key, - render_device, - render_adapter, - ); - #[cfg(debug_assertions)] - let texture_count: usize = entries - .iter() - .flat_map(|e| { - e.iter() - .filter(|entry| matches!(entry.ty, BindingType::Texture { .. })) - }) - .count(); - - MeshPipelineViewLayout { - main_layout: BindGroupLayoutDescriptor::new(key.label(), &entries[0]), - binding_array_layout: BindGroupLayoutDescriptor::new( - format!("{}_binding_array", key.label()), - &entries[1], - ), - empty_layout: BindGroupLayoutDescriptor::new(format!("{}_empty", key.label()), &[]), - #[cfg(debug_assertions)] - texture_count, - } - }))) - } -} +pub fn init_mesh_pipeline_view_layouts(world: &mut World) { + // Generates all possible view layouts for the mesh pipeline, based on all combinations of + // [`MeshPipelineViewLayoutKey`] flags. + + let render_device = world.resource::(); + let render_adapter = world.resource::(); -impl MeshPipelineViewLayouts { - pub fn get_view_layout( - &self, - layout_key: MeshPipelineViewLayoutKey, - ) -> &MeshPipelineViewLayout { - let index = layout_key.bits() as usize; - let layout = &self[index]; + let clustered_forward_buffer_binding_type = + render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); + let visibility_ranges_buffer_binding_type = + render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); + let res = MeshPipelineViewLayouts(Arc::new(array::from_fn(|i| { + let key = MeshPipelineViewLayoutKey::from_bits_truncate(i as u32); + let entries = layout_entries( + clustered_forward_buffer_binding_type, + visibility_ranges_buffer_binding_type, + key, + render_device, + render_adapter, + ); #[cfg(debug_assertions)] - if layout.texture_count > MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES { - // Issue our own warning here because Naga's error message is a bit cryptic in this situation - once!(warn!("Too many textures in mesh pipeline view layout, this might cause us to hit `wgpu::Limits::max_sampled_textures_per_shader_stage` in some environments.")); + let texture_count: usize = entries + .iter() + .flat_map(|e| { + e.iter() + .filter(|entry| matches!(entry.ty, BindingType::Texture { .. })) + }) + .count(); + + MeshPipelineViewLayout { + main_layout: BindGroupLayoutDescriptor::new(key.label(), &entries[0]), + binding_array_layout: BindGroupLayoutDescriptor::new( + format!("{}_binding_array", key.label()), + &entries[1], + ), + empty_layout: BindGroupLayoutDescriptor::new(format!("{}_empty", key.label()), &[]), + #[cfg(debug_assertions)] + texture_count, } + }))); - layout - } + world.insert_resource(res); } /// Generates all possible view layouts for the mesh pipeline, based on all combinations of @@ -617,8 +487,8 @@ pub fn prepare_mesh_view_bind_groups( .map(|t| &t.screen_space_ambient_occlusion_texture.default_view) .unwrap_or(&fallback_ssao); - let mut layout_key = MeshPipelineViewLayoutKey::from(*msaa) - | MeshPipelineViewLayoutKey::from(prepass_textures); + let mut layout_key = mesh_pipeline_view_layout_key_from_msaa(*msaa) + | mesh_pipeline_view_layout_key_from_view_prepass_textures(prepass_textures); if has_oit { layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED; } diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 75c424311a056..93e86d3124de7 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -7,7 +7,8 @@ use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}; use bevy_platform::collections::hash_map::Entry; -use bevy_render::render_resource::{Buffer, BufferDescriptor}; +pub use bevy_render::mesh::skin::*; +use bevy_render::render_resource::BufferDescriptor; use bevy_render::settings::WgpuLimits; use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; use bevy_render::{ @@ -17,176 +18,44 @@ use bevy_render::{ Extract, }; use bevy_transform::prelude::GlobalTransform; -use offset_allocator::{Allocation, Allocator}; -use smallvec::SmallVec; +use offset_allocator::Allocator; use tracing::error; -/// Maximum number of joints supported for skinned meshes. -/// -/// It is used to allocate buffers. -/// The correctness of the value depends on the GPU/platform. -/// The current value is chosen because it is guaranteed to work everywhere. -/// To allow for bigger values, a check must be made for the limits -/// of the GPU at runtime, which would mean not using consts anymore. -pub const MAX_JOINTS: usize = 256; - -/// The total number of joints we support. -/// -/// This is 256 GiB worth of joint matrices, which we will never hit under any -/// reasonable circumstances. -const MAX_TOTAL_JOINTS: u32 = 1024 * 1024 * 1024; - -/// The number of joints that we allocate at a time. -/// -/// Some hardware requires that uniforms be allocated on 256-byte boundaries, so -/// we need to allocate 4 64-byte matrices at a time to satisfy alignment -/// requirements. -const JOINTS_PER_ALLOCATION_UNIT: u32 = (256 / size_of::()) as u32; - -/// The maximum ratio of the number of entities whose transforms changed to the -/// total number of joints before we re-extract all joints. -/// -/// We use this as a heuristic to decide whether it's worth switching over to -/// fine-grained detection to determine which skins need extraction. If the -/// number of changed entities is over this threshold, we skip change detection -/// and simply re-extract the transforms of all joints. -const JOINT_EXTRACTION_THRESHOLD_FACTOR: f64 = 0.25; - -/// The location of the first joint matrix in the skin uniform buffer. -#[derive(Clone, Copy)] -pub struct SkinByteOffset { - /// The byte offset of the first joint matrix. - pub byte_offset: u32, -} - -impl SkinByteOffset { - /// Index to be in address space based on the size of a skin uniform. - const fn from_index(index: usize) -> Self { - SkinByteOffset { - byte_offset: (index * size_of::()) as u32, - } - } - - /// Returns this skin index in elements (not bytes). - /// - /// Each element is a 4x4 matrix. - pub fn index(&self) -> u32 { - self.byte_offset / size_of::() as u32 - } -} - -/// The GPU buffers containing joint matrices for all skinned meshes. -/// -/// This is double-buffered: we store the joint matrices of each mesh for the -/// previous frame in addition to those of each mesh for the current frame. This -/// is for motion vector calculation. Every frame, we swap buffers and overwrite -/// the joint matrix buffer from two frames ago with the data for the current -/// frame. -/// -/// Notes on implementation: see comment on top of the `extract_skins` system. -#[derive(Resource)] -pub struct SkinUniforms { - /// The CPU-side buffer that stores the joint matrices for skinned meshes in - /// the current frame. - pub current_staging_buffer: Vec, - /// The GPU-side buffer that stores the joint matrices for skinned meshes in - /// the current frame. - pub current_buffer: Buffer, - /// The GPU-side buffer that stores the joint matrices for skinned meshes in - /// the previous frame. - pub prev_buffer: Buffer, - /// The offset allocator that manages the placement of the joints within the - /// [`Self::current_buffer`]. - allocator: Allocator, - /// Allocation information that we keep about each skin. - skin_uniform_info: MainEntityHashMap, - /// Maps each joint entity to the skins it's associated with. - /// - /// We use this in conjunction with change detection to only update the - /// skins that need updating each frame. - /// - /// Note that conceptually this is a hash map of sets, but we use a - /// [`SmallVec`] to avoid allocations for the vast majority of the cases in - /// which each bone belongs to exactly one skin. - joint_to_skins: MainEntityHashMap>, - /// The total number of joints in the scene. - /// - /// We use this as part of our heuristic to decide whether to use - /// fine-grained change detection. - total_joints: usize, -} - -impl FromWorld for SkinUniforms { - fn from_world(world: &mut World) -> Self { - let device = world.resource::(); - let buffer_usages = (if skins_use_uniform_buffers(&device.limits()) { - BufferUsages::UNIFORM - } else { - BufferUsages::STORAGE - }) | BufferUsages::COPY_DST; - - // Create the current and previous buffer with the minimum sizes. - // - // These will be swapped every frame. - let current_buffer = device.create_buffer(&BufferDescriptor { - label: Some("skin uniform buffer"), - size: MAX_JOINTS as u64 * size_of::() as u64, - usage: buffer_usages, - mapped_at_creation: false, - }); - let prev_buffer = device.create_buffer(&BufferDescriptor { - label: Some("skin uniform buffer"), - size: MAX_JOINTS as u64 * size_of::() as u64, - usage: buffer_usages, - mapped_at_creation: false, - }); - - Self { - current_staging_buffer: vec![], - current_buffer, - prev_buffer, - allocator: Allocator::new(MAX_TOTAL_JOINTS), - skin_uniform_info: MainEntityHashMap::default(), - joint_to_skins: MainEntityHashMap::default(), - total_joints: 0, - } - } -} - -impl SkinUniforms { - /// Returns the current offset in joints of the skin in the buffer. - pub fn skin_index(&self, skin: MainEntity) -> Option { - self.skin_uniform_info - .get(&skin) - .map(SkinUniformInfo::offset) - } +pub fn skin_uniforms_from_world(world: &mut World) { + let device = world.resource::(); + let buffer_usages = (if skins_use_uniform_buffers(&device.limits()) { + BufferUsages::UNIFORM + } else { + BufferUsages::STORAGE + }) | BufferUsages::COPY_DST; - /// Returns the current offset in bytes of the skin in the buffer. - pub fn skin_byte_offset(&self, skin: MainEntity) -> Option { - self.skin_uniform_info.get(&skin).map(|skin_uniform_info| { - SkinByteOffset::from_index(skin_uniform_info.offset() as usize) - }) - } - - /// Returns an iterator over all skins in the scene. - pub fn all_skins(&self) -> impl Iterator { - self.skin_uniform_info.keys() - } -} - -/// Allocation information about each skin. -struct SkinUniformInfo { - /// The allocation of the joints within the [`SkinUniforms::current_buffer`]. - allocation: Allocation, - /// The entities that comprise the joints. - joints: Vec, -} + // Create the current and previous buffer with the minimum sizes. + // + // These will be swapped every frame. + let current_buffer = device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + size: MAX_JOINTS as u64 * size_of::() as u64, + usage: buffer_usages, + mapped_at_creation: false, + }); + let prev_buffer = device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + size: MAX_JOINTS as u64 * size_of::() as u64, + usage: buffer_usages, + mapped_at_creation: false, + }); + + let res = SkinUniforms { + current_staging_buffer: vec![], + current_buffer, + prev_buffer, + allocator: Allocator::new(MAX_TOTAL_JOINTS), + skin_uniform_info: MainEntityHashMap::default(), + joint_to_skins: MainEntityHashMap::default(), + total_joints: 0, + }; -impl SkinUniformInfo { - /// The offset in joints within the [`SkinUniforms::current_staging_buffer`]. - fn offset(&self) -> u32 { - self.allocation.offset * JOINTS_PER_ALLOCATION_UNIT - } + world.insert_resource(res); } /// Returns true if skinning must use uniforms (and dynamic offsets) because diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index b752b3daf9214..de2f9a99348a0 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -47,7 +47,8 @@ use bevy_utils::{once, prelude::default}; use tracing::info; use crate::{ - binding_arrays_are_usable, graph::NodePbr, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, + binding_arrays_are_usable, graph::NodePbr, init_mesh_pipeline_view_layouts, + mesh_pipeline_view_layout_key_from_msaa, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, }; @@ -196,7 +197,7 @@ impl Plugin for ScreenSpaceReflectionsPlugin { .add_systems( RenderStartup, ( - init_screen_space_reflections_pipeline, + init_screen_space_reflections_pipeline.after(init_mesh_pipeline_view_layouts), add_screen_space_reflections_render_graph_edges, ), ) @@ -438,7 +439,7 @@ pub fn prepare_ssr_pipelines( { // SSR is only supported in the deferred pipeline, which has no MSAA // support. Thus we can assume MSAA is off. - let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(Msaa::Off) + let mut mesh_pipeline_view_key = mesh_pipeline_view_layout_key_from_msaa(Msaa::Off) | MeshPipelineViewLayoutKey::DEPTH_PREPASS | MeshPipelineViewLayoutKey::DEFERRED_PREPASS; mesh_pipeline_view_key.set( diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 57cbd7bb1722c..69c0f4325077c 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -50,7 +50,10 @@ use bevy_render::{ }; use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer}; -use crate::{graph::NodePbr, volumetric_fog::render::init_volumetric_fog_pipeline}; +use crate::{ + graph::NodePbr, init_mesh_pipeline_view_layouts, + volumetric_fog::render::init_volumetric_fog_pipeline, +}; pub mod render; @@ -84,7 +87,10 @@ impl Plugin for VolumetricFogPlugin { }) .init_resource::>() .init_resource::() - .add_systems(RenderStartup, init_volumetric_fog_pipeline) + .add_systems( + RenderStartup, + init_volumetric_fog_pipeline.after(init_mesh_pipeline_view_layouts), + ) .add_systems(ExtractSchedule, render::extract_volumetric_fog) .add_systems( Render, diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index f33210cba2f02..b1b60f32511a8 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -50,9 +50,9 @@ use bevy_utils::prelude::default; use bitflags::bitflags; use crate::{ - MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, - ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, - ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, + mesh_pipeline_view_layout_key_from_msaa, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, + MeshViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, + ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; use super::FogAssets; @@ -647,7 +647,7 @@ pub fn prepare_volumetric_fog_pipelines( ) in view_targets.iter() { // Create a mesh pipeline view layout key corresponding to the view. - let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(*msaa); + let mut mesh_pipeline_view_key = mesh_pipeline_view_layout_key_from_msaa(*msaa); mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::NORMAL_PREPASS, normal_prepass); mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::DEPTH_PREPASS, depth_prepass); mesh_pipeline_view_key.set( diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index ff732d7887548..0d196679d0bda 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,7 +1,7 @@ use crate::{ - DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances, - SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache, - ViewSpecializationTicks, + init_mesh_pipeline, DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, + RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, + ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; use bevy_asset::{ @@ -141,7 +141,10 @@ impl Plugin for WireframePlugin { Node3d::PostProcessing, ), ) - .add_systems(RenderStartup, init_wireframe_3d_pipeline) + .add_systems( + RenderStartup, + init_wireframe_3d_pipeline.after(init_mesh_pipeline), + ) .add_systems( ExtractSchedule, ( diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 7b4fa6259f64b..0300b61acb3c9 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -37,6 +37,13 @@ statically-linked-dxc = ["wgpu/static-dxc"] # Forces the wgpu instance to be initialized using the raw Vulkan HAL, enabling additional configuration raw_vulkan_init = ["wgpu/vulkan"] +pbr_light_textures = [] +pbr_transmission_textures = [] +pbr_multi_layer_material_textures = [] +pbr_anisotropy_texture = [] +pbr_specular_textures = [] +experimental_pbr_pcss = [] + trace = ["profiling"] tracing-tracy = ["dep:tracy-client"] ci_limits = [] @@ -61,8 +68,10 @@ bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.18.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.18.0-dev" } bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } -bevy_render_macros = { path = "macros", version = "0.18.0-dev" } +bevy_material_macros = { path = "../bevy_material/macros", version = "0.18.0-dev" } +bevy_render_macros = { path = "../bevy_render/macros", version = "0.18.0-dev" } bevy_time = { path = "../bevy_time", version = "0.18.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" } bevy_window = { path = "../bevy_window", version = "0.18.0-dev" } diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index a30cce08f59d1..78b255e2a6eee 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -120,29 +120,3 @@ pub fn derive_specialize(input: TokenStream) -> TokenStream { pub fn derive_specializer_key(input: TokenStream) -> TokenStream { specializer::impl_specializer_key(input) } - -#[proc_macro_derive(ShaderLabel)] -pub fn derive_shader_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_render_path(); - trait_path - .segments - .push(format_ident!("render_phase").into()); - trait_path - .segments - .push(format_ident!("ShaderLabel").into()); - derive_label(input, "ShaderLabel", &trait_path) -} - -#[proc_macro_derive(DrawFunctionLabel)] -pub fn derive_draw_function_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_render_path(); - trait_path - .segments - .push(format_ident!("render_phase").into()); - trait_path - .segments - .push(format_ident!("DrawFunctionLabel").into()); - derive_label(input, "DrawFunctionLabel", &trait_path) -} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 92336e21e7b89..955f24b26e123 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -35,7 +35,6 @@ extern crate core; // Required to make proc macros work in bevy itself. extern crate self as bevy_render; -pub mod alpha; pub mod batching; pub mod camera; pub mod diagnostic; @@ -48,6 +47,7 @@ pub mod extract_resource; pub mod globals; pub mod gpu_component_array_buffer; pub mod gpu_readback; +pub mod lightmap; pub mod mesh; #[cfg(not(target_arch = "wasm32"))] pub mod pipelined_rendering; @@ -74,6 +74,11 @@ pub mod prelude { }; } +#[doc(hidden)] +pub mod alpha { + pub use bevy_material::alpha::AlphaMode; +} + pub use extract_param::Extract; use crate::{ diff --git a/crates/bevy_render/src/lightmap/mod.rs b/crates/bevy_render/src/lightmap/mod.rs new file mode 100644 index 0000000000000..fb8bad53218cf --- /dev/null +++ b/crates/bevy_render/src/lightmap/mod.rs @@ -0,0 +1,443 @@ +use bevy_asset::{AssetId, Handle}; +use bevy_camera::visibility::ViewVisibility; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Component, + entity::Entity, + lifecycle::RemovedComponents, + query::{Changed, Or}, + reflect::ReflectComponent, + resource::Resource, + system::{Query, Res, ResMut}, +}; +use bevy_image::Image; +use bevy_math::{uvec2, vec4, Rect, UVec2}; +use bevy_platform::collections::HashSet; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::sync_world::MainEntityHashMap; +use bevy_render::{ + render_asset::RenderAssets, + render_resource::{Sampler, TextureView, WgpuSampler, WgpuTextureView}, + sync_world::MainEntity, + texture::{FallbackImage, GpuImage}, + Extract, +}; +use fixedbitset::FixedBitSet; +use nonmax::{NonMaxU16, NonMaxU32}; +use tracing::error; + +/// The number of lightmaps that we store in a single slab, if bindless textures +/// are in use. +/// +/// If bindless textures aren't in use, then only a single lightmap can be bound +/// at a time. +pub const LIGHTMAPS_PER_SLAB: usize = 4; + +/// A component that applies baked indirect diffuse global illumination from a +/// lightmap. +#[derive(Component, Clone, Reflect)] +#[reflect(Component, Default, Clone)] +pub struct Lightmap { + /// The lightmap texture. + pub image: Handle, + + /// The rectangle within the lightmap texture that the UVs are relative to. + /// + /// The top left coordinate is the `min` part of the rect, and the bottom + /// right coordinate is the `max` part of the rect. The rect ranges from (0, + /// 0) to (1, 1). + /// + /// This field allows lightmaps for a variety of meshes to be packed into a + /// single atlas. + pub uv_rect: Rect, + + /// Whether bicubic sampling should be used for sampling this lightmap. + /// + /// Bicubic sampling is higher quality, but slower, and may lead to light leaks. + /// + /// If true, the lightmap texture's sampler must be set to [`bevy_image::ImageSampler::linear`]. + pub bicubic_sampling: bool, +} + +/// Lightmap data stored in the render world. +/// +/// There is one of these per visible lightmapped mesh instance. +#[derive(Debug)] +pub struct RenderLightmap { + /// The rectangle within the lightmap texture that the UVs are relative to. + /// + /// The top left coordinate is the `min` part of the rect, and the bottom + /// right coordinate is the `max` part of the rect. The rect ranges from (0, + /// 0) to (1, 1). + pub uv_rect: Rect, + + /// The index of the slab (i.e. binding array) in which the lightmap is + /// located. + pub slab_index: LightmapSlabIndex, + + /// The index of the slot (i.e. element within the binding array) in which + /// the lightmap is located. + /// + /// If bindless lightmaps aren't in use, this will be 0. + pub slot_index: LightmapSlotIndex, + + // Whether or not bicubic sampling should be used for this lightmap. + pub bicubic_sampling: bool, +} + +/// Stores data for all lightmaps in the render world. +/// +/// This is cleared and repopulated each frame during the `extract_lightmaps` +/// system. +#[derive(Resource)] +pub struct RenderLightmaps { + /// The mapping from every lightmapped entity to its lightmap info. + /// + /// Entities without lightmaps, or for which the mesh or lightmap isn't + /// loaded, won't have entries in this table. + pub render_lightmaps: MainEntityHashMap, + + /// The slabs (binding arrays) containing the lightmaps. + pub slabs: Vec, + + pub free_slabs: FixedBitSet, + + pub pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>, + + /// Whether bindless textures are supported on this platform. + pub bindless_supported: bool, +} + +/// A binding array that contains lightmaps. +/// +/// This will have a single binding if bindless lightmaps aren't in use. +pub struct LightmapSlab { + /// The GPU images in this slab. + lightmaps: Vec, + free_slots_bitmask: u32, +} + +struct AllocatedLightmap { + gpu_image: GpuImage, + // This will only be present if the lightmap is allocated but not loaded. + asset_id: Option>, +} + +/// The index of the slab (binding array) in which a lightmap is located. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)] +#[repr(transparent)] +pub struct LightmapSlabIndex(pub NonMaxU32); + +/// The index of the slot (element within the binding array) in the slab in +/// which a lightmap is located. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)] +#[repr(transparent)] +pub struct LightmapSlotIndex(pub NonMaxU16); + +/// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`] +/// resource. +pub fn extract_lightmaps( + render_lightmaps: ResMut, + changed_lightmaps_query: Extract< + Query< + (Entity, &ViewVisibility, &Lightmap), + Or<(Changed, Changed)>, + >, + >, + mut removed_lightmaps_query: Extract>, + images: Res>, + fallback_images: Res, +) { + let render_lightmaps = render_lightmaps.into_inner(); + + // Loop over each entity. + for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() { + if render_lightmaps + .render_lightmaps + .contains_key(&MainEntity::from(entity)) + { + continue; + } + + // Only process visible entities. + if !view_visibility.get() { + continue; + } + + let (slab_index, slot_index) = + render_lightmaps.allocate(&fallback_images, lightmap.image.id()); + render_lightmaps.render_lightmaps.insert( + entity.into(), + RenderLightmap::new( + lightmap.uv_rect, + slab_index, + slot_index, + lightmap.bicubic_sampling, + ), + ); + + render_lightmaps + .pending_lightmaps + .insert((slab_index, slot_index)); + } + + for entity in removed_lightmaps_query.read() { + if changed_lightmaps_query.contains(entity) { + continue; + } + + let Some(RenderLightmap { + slab_index, + slot_index, + .. + }) = render_lightmaps + .render_lightmaps + .remove(&MainEntity::from(entity)) + else { + continue; + }; + + render_lightmaps.remove(&fallback_images, slab_index, slot_index); + render_lightmaps + .pending_lightmaps + .remove(&(slab_index, slot_index)); + } + + render_lightmaps + .pending_lightmaps + .retain(|&(slab_index, slot_index)| { + let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps + [usize::from(slot_index)] + .asset_id + else { + error!( + "Allocated lightmap should have been removed from `pending_lightmaps` by now" + ); + return false; + }; + + let Some(gpu_image) = images.get(asset_id) else { + return true; + }; + render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone()); + false + }); +} + +impl RenderLightmap { + /// Creates a new lightmap from a texture, a UV rect, and a slab and slot + /// index pair. + fn new( + uv_rect: Rect, + slab_index: LightmapSlabIndex, + slot_index: LightmapSlotIndex, + bicubic_sampling: bool, + ) -> Self { + Self { + uv_rect, + slab_index, + slot_index, + bicubic_sampling, + } + } +} + +/// Packs the lightmap UV rect into 64 bits (4 16-bit unsigned integers). +pub fn pack_lightmap_uv_rect(maybe_rect: Option) -> UVec2 { + match maybe_rect { + Some(rect) => { + let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0) + .round() + .as_uvec4(); + uvec2( + rect_uvec4.x | (rect_uvec4.y << 16), + rect_uvec4.z | (rect_uvec4.w << 16), + ) + } + None => UVec2::ZERO, + } +} + +impl Default for Lightmap { + fn default() -> Self { + Self { + image: Default::default(), + uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0), + bicubic_sampling: false, + } + } +} + +impl RenderLightmaps { + /// Creates a new slab, appends it to the end of the list, and returns its + /// slab index. + fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex { + let slab_index = LightmapSlabIndex::from(self.slabs.len()); + self.free_slabs.grow_and_insert(slab_index.into()); + self.slabs + .push(LightmapSlab::new(fallback_images, self.bindless_supported)); + slab_index + } + + fn allocate( + &mut self, + fallback_images: &FallbackImage, + image_id: AssetId, + ) -> (LightmapSlabIndex, LightmapSlotIndex) { + let slab_index = match self.free_slabs.minimum() { + None => self.create_slab(fallback_images), + Some(slab_index) => slab_index.into(), + }; + + let slab = &mut self.slabs[usize::from(slab_index)]; + let slot_index = slab.allocate(image_id); + if slab.is_full() { + self.free_slabs.remove(slab_index.into()); + } + + (slab_index, slot_index) + } + + fn remove( + &mut self, + fallback_images: &FallbackImage, + slab_index: LightmapSlabIndex, + slot_index: LightmapSlotIndex, + ) { + let slab = &mut self.slabs[usize::from(slab_index)]; + slab.remove(fallback_images, slot_index); + + if !slab.is_full() { + self.free_slabs.grow_and_insert(slab_index.into()); + } + } +} + +impl LightmapSlab { + fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab { + let count = if bindless_supported { + LIGHTMAPS_PER_SLAB + } else { + 1 + }; + + LightmapSlab { + lightmaps: (0..count) + .map(|_| AllocatedLightmap { + gpu_image: fallback_images.d2.clone(), + asset_id: None, + }) + .collect(), + free_slots_bitmask: (1 << count) - 1, + } + } + + fn is_full(&self) -> bool { + self.free_slots_bitmask == 0 + } + + fn allocate(&mut self, image_id: AssetId) -> LightmapSlotIndex { + assert!( + !self.is_full(), + "Attempting to allocate on a full lightmap slab" + ); + let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros()); + self.free_slots_bitmask &= !(1 << u32::from(index)); + self.lightmaps[usize::from(index)].asset_id = Some(image_id); + index + } + + fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) { + self.lightmaps[usize::from(index)] = AllocatedLightmap { + gpu_image, + asset_id: None, + } + } + + fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) { + self.lightmaps[usize::from(index)] = AllocatedLightmap { + gpu_image: fallback_images.d2.clone(), + asset_id: None, + }; + self.free_slots_bitmask |= 1 << u32::from(index); + } + + /// Returns the texture views and samplers for the lightmaps in this slab, + /// ready to be placed into a bind group. + /// + /// This is used when constructing bind groups in bindless mode. Before + /// returning, this function pads out the arrays with fallback images in + /// order to fulfill requirements of platforms that require full binding + /// arrays (e.g. DX12). + pub fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) { + ( + self.lightmaps + .iter() + .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view) + .collect(), + self.lightmaps + .iter() + .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler) + .collect(), + ) + } + + /// Returns the texture view and sampler corresponding to the first + /// lightmap, which must exist. + /// + /// This is used when constructing bind groups in non-bindless mode. + pub fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) { + ( + &self.lightmaps[0].gpu_image.texture_view, + &self.lightmaps[0].gpu_image.sampler, + ) + } +} + +impl From for LightmapSlabIndex { + fn from(value: u32) -> Self { + Self(NonMaxU32::new(value).unwrap()) + } +} + +impl From for LightmapSlabIndex { + fn from(value: usize) -> Self { + Self::from(value as u32) + } +} + +impl From for LightmapSlotIndex { + fn from(value: u32) -> Self { + Self(NonMaxU16::new(value as u16).unwrap()) + } +} + +impl From for LightmapSlotIndex { + fn from(value: usize) -> Self { + Self::from(value as u32) + } +} + +impl From for usize { + fn from(value: LightmapSlabIndex) -> Self { + value.0.get() as usize + } +} + +impl From for usize { + fn from(value: LightmapSlotIndex) -> Self { + value.0.get() as usize + } +} + +impl From for u16 { + fn from(value: LightmapSlotIndex) -> Self { + value.0.get() + } +} + +impl From for u32 { + fn from(value: LightmapSlotIndex) -> Self { + value.0.get() as u32 + } +} diff --git a/crates/bevy_render/src/mesh/material_bind_group.rs b/crates/bevy_render/src/mesh/material_bind_group.rs new file mode 100644 index 0000000000000..2101fcd43f468 --- /dev/null +++ b/crates/bevy_render/src/mesh/material_bind_group.rs @@ -0,0 +1,52 @@ +use bevy_derive::{Deref, DerefMut}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use core::hash::Hash; + +/// The location of a material (either bindless or non-bindless) within the +/// slabs. +#[derive(Clone, Copy, Debug, Default, Reflect)] +#[reflect(Clone, Default)] +pub struct MaterialBindingId { + /// The index of the bind group (slab) where the GPU data is located. + pub group: MaterialBindGroupIndex, + /// The slot within that bind group. + /// + /// Non-bindless materials will always have a slot of 0. + pub slot: MaterialBindGroupSlot, +} + +/// The index of each material bind group. +/// +/// In bindless mode, each bind group contains multiple materials. In +/// non-bindless mode, each bind group contains only one material. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect, Deref, DerefMut)] +#[reflect(Default, Clone, PartialEq, Hash)] +pub struct MaterialBindGroupIndex(pub u32); + +impl From for MaterialBindGroupIndex { + fn from(value: u32) -> Self { + MaterialBindGroupIndex(value) + } +} + +/// The index of the slot containing material data within each material bind +/// group. +/// +/// In bindless mode, this slot is needed to locate the material data in each +/// bind group, since multiple materials are packed into a single slab. In +/// non-bindless mode, this slot is always 0. +#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect, Deref, DerefMut)] +#[reflect(Default, Clone, PartialEq)] +pub struct MaterialBindGroupSlot(pub u32); + +impl From for MaterialBindGroupSlot { + fn from(value: u32) -> Self { + MaterialBindGroupSlot(value) + } +} + +impl From for u32 { + fn from(value: MaterialBindGroupSlot) -> Self { + value.0 + } +} diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 2c2f84a0a9e66..5439e7a88631a 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,4 +1,9 @@ pub mod allocator; +pub mod material_bind_group; +pub mod pipeline; +pub mod render; +pub mod skin; +pub mod util; use crate::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, texture::GpuImage, diff --git a/crates/bevy_render/src/mesh/pipeline.rs b/crates/bevy_render/src/mesh/pipeline.rs new file mode 100644 index 0000000000000..d3ef46dd28be6 --- /dev/null +++ b/crates/bevy_render/src/mesh/pipeline.rs @@ -0,0 +1,636 @@ +use bevy_asset::AssetId; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, SystemParamItem}, +}; +use bevy_image::BevyDefault; +use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef, VertexAttributeDescriptor}; +use bevy_render::{ + batching::{ + gpu_preprocessing::{IndirectParametersCpuMetadata, UntypedPhaseIndirectParametersBuffers}, + GetBatchData, GetFullBatchData, + }, + mesh::{allocator::MeshAllocator, RenderMesh}, + render_asset::RenderAssets, + render_resource::*, + view::ViewTarget, +}; +use bevy_shader::ShaderDefVal; +use bevy_utils::default; +use tracing::error; + +use bevy_render::sync_world::MainEntity; + +pub use bevy_material::render::*; +use nonmax::NonMaxU32; + +use crate::{ + lightmap::{LightmapSlabIndex, RenderLightmaps}, + mesh::{ + material_bind_group::MaterialBindGroupIndex, + render::{MeshInputUniform, MeshUniform, RenderMeshInstances}, + skin::SkinUniforms, + util::{ + CORE_3D_DEPTH_FORMAT, IRRADIANCE_VOLUMES_ARE_USABLE, + TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, + }, + }, +}; + +impl GetBatchData for MeshPipeline { + type Param = ( + SRes, + SRes, + SRes>, + SRes, + SRes, + ); + // The material bind group ID, the mesh ID, and the lightmap ID, + // respectively. + type CompareData = ( + MaterialBindGroupIndex, + AssetId, + Option, + ); + + type BufferData = MeshUniform; + + fn get_batch_data( + (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< + Self::Param, + >, + (_entity, main_entity): (Entity, MainEntity), + ) -> Option<(Self::BufferData, Option)> { + let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { + error!( + "`get_batch_data` should never be called in GPU mesh uniform \ + building mode" + ); + return None; + }; + let mesh_instance = mesh_instances.get(&main_entity)?; + let first_vertex_index = + match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { + Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, + None => 0, + }; + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); + + let current_skin_index = skin_uniforms.skin_index(main_entity); + let material_bind_group_index = mesh_instance.material_bindings_index; + + Some(( + MeshUniform::new( + &mesh_instance.transforms, + first_vertex_index, + material_bind_group_index.slot, + maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), + current_skin_index, + Some(mesh_instance.tag), + ), + mesh_instance.should_batch().then_some(( + material_bind_group_index.group, + mesh_instance.mesh_asset_id, + maybe_lightmap.map(|lightmap| lightmap.slab_index), + )), + )) + } +} + +impl GetFullBatchData for MeshPipeline { + type BufferInputData = MeshInputUniform; + + fn get_index_and_compare_data( + (mesh_instances, lightmaps, _, _, _): &SystemParamItem, + main_entity: MainEntity, + ) -> Option<(NonMaxU32, Option)> { + // This should only be called during GPU building. + let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { + error!( + "`get_index_and_compare_data` should never be called in CPU mesh uniform building \ + mode" + ); + return None; + }; + + let mesh_instance = mesh_instances.get(&main_entity)?; + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); + + Some(( + mesh_instance.current_uniform_index, + mesh_instance.should_batch().then_some(( + mesh_instance.material_bindings_index.group, + mesh_instance.mesh_asset_id, + maybe_lightmap.map(|lightmap| lightmap.slab_index), + )), + )) + } + + fn get_binned_batch_data( + (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< + Self::Param, + >, + main_entity: MainEntity, + ) -> Option { + let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { + error!( + "`get_binned_batch_data` should never be called in GPU mesh uniform building mode" + ); + return None; + }; + let mesh_instance = mesh_instances.get(&main_entity)?; + let first_vertex_index = + match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { + Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, + None => 0, + }; + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); + + let current_skin_index = skin_uniforms.skin_index(main_entity); + + Some(MeshUniform::new( + &mesh_instance.transforms, + first_vertex_index, + mesh_instance.material_bindings_index.slot, + maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), + current_skin_index, + Some(mesh_instance.tag), + )) + } + + fn get_binned_index( + (mesh_instances, _, _, _, _): &SystemParamItem, + main_entity: MainEntity, + ) -> Option { + // This should only be called during GPU building. + let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { + error!( + "`get_binned_index` should never be called in CPU mesh uniform \ + building mode" + ); + return None; + }; + + mesh_instances + .get(&main_entity) + .map(|entity| entity.current_uniform_index) + } + + fn write_batch_indirect_parameters_metadata( + indexed: bool, + base_output_index: u32, + batch_set_index: Option, + phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, + indirect_parameters_offset: u32, + ) { + let indirect_parameters = IndirectParametersCpuMetadata { + base_output_index, + batch_set_index: match batch_set_index { + Some(batch_set_index) => u32::from(batch_set_index), + None => !0, + }, + }; + + if indexed { + phase_indirect_parameters_buffers + .indexed + .set(indirect_parameters_offset, indirect_parameters); + } else { + phase_indirect_parameters_buffers + .non_indexed + .set(indirect_parameters_offset, indirect_parameters); + } + } +} + +impl SpecializedMeshPipeline for MeshPipeline { + type Key = MeshPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut shader_defs = Vec::new(); + let mut vertex_attributes = Vec::new(); + + // Let the shader code know that it's running in a mesh pipeline. + shader_defs.push("MESH_PIPELINE".into()); + + shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); + + if layout.0.contains(Mesh::ATTRIBUTE_POSITION) { + shader_defs.push("VERTEX_POSITIONS".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); + } + + if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) { + shader_defs.push("VERTEX_NORMALS".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); + } + + if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { + shader_defs.push("VERTEX_UVS".into()); + shader_defs.push("VERTEX_UVS_A".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); + } + + if layout.0.contains(Mesh::ATTRIBUTE_UV_1) { + shader_defs.push("VERTEX_UVS".into()); + shader_defs.push("VERTEX_UVS_B".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3)); + } + + if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) { + shader_defs.push("VERTEX_TANGENTS".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4)); + } + + if layout.0.contains(Mesh::ATTRIBUTE_COLOR) { + shader_defs.push("VERTEX_COLORS".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5)); + } + + if cfg!(feature = "pbr_transmission_textures") { + shader_defs.push("PBR_TRANSMISSION_TEXTURES_SUPPORTED".into()); + } + if cfg!(feature = "pbr_multi_layer_material_textures") { + shader_defs.push("PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED".into()); + } + if cfg!(feature = "pbr_anisotropy_texture") { + shader_defs.push("PBR_ANISOTROPY_TEXTURE_SUPPORTED".into()); + } + if cfg!(feature = "pbr_specular_textures") { + shader_defs.push("PBR_SPECULAR_TEXTURES_SUPPORTED".into()); + } + + let bind_group_layout = self.get_view_layout(key.into()); + let mut bind_group_layout = vec![ + bind_group_layout.main_layout.clone(), + bind_group_layout.binding_array_layout.clone(), + ]; + + if key.msaa_samples() > 1 { + shader_defs.push("MULTISAMPLED".into()); + }; + + bind_group_layout.push(setup_morph_and_skinning_defs( + &self.mesh_layouts, + layout, + 6, + &key, + &mut shader_defs, + &mut vertex_attributes, + self.skins_use_uniform_buffers, + )); + + if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { + shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); + } + + let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; + + let (label, blend, depth_write_enabled); + let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS); + let (mut is_opaque, mut alpha_to_coverage_enabled) = (false, false); + if key.contains(MeshPipelineKey::OIT_ENABLED) && pass == MeshPipelineKey::BLEND_ALPHA { + label = "oit_mesh_pipeline".into(); + // TODO tail blending would need alpha blending + blend = None; + shader_defs.push("OIT_ENABLED".into()); + // TODO it should be possible to use this to combine MSAA and OIT + // alpha_to_coverage_enabled = true; + depth_write_enabled = false; + } else if pass == MeshPipelineKey::BLEND_ALPHA { + label = "alpha_blend_mesh_pipeline".into(); + blend = Some(BlendState::ALPHA_BLENDING); + // For the transparent pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + depth_write_enabled = false; + } else if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { + label = "premultiplied_alpha_mesh_pipeline".into(); + blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING); + shader_defs.push("PREMULTIPLY_ALPHA".into()); + shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); + // For the transparent pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + depth_write_enabled = false; + } else if pass == MeshPipelineKey::BLEND_MULTIPLY { + label = "multiply_mesh_pipeline".into(); + blend = Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }); + shader_defs.push("PREMULTIPLY_ALPHA".into()); + shader_defs.push("BLEND_MULTIPLY".into()); + // For the multiply pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + depth_write_enabled = false; + } else if pass == MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE { + label = "alpha_to_coverage_mesh_pipeline".into(); + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases + blend = None; + // For the opaque and alpha mask passes, fragments that are closer will replace + // the current fragment value in the output and the depth is written to the + // depth buffer + depth_write_enabled = true; + is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); + alpha_to_coverage_enabled = true; + shader_defs.push("ALPHA_TO_COVERAGE".into()); + } else { + label = "opaque_mesh_pipeline".into(); + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases + blend = None; + // For the opaque and alpha mask passes, fragments that are closer will replace + // the current fragment value in the output and the depth is written to the + // depth buffer + depth_write_enabled = true; + is_opaque = !key.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); + } + + if key.contains(MeshPipelineKey::NORMAL_PREPASS) { + shader_defs.push("NORMAL_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + shader_defs.push("DEPTH_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + shader_defs.push("MOTION_VECTOR_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { + shader_defs.push("HAS_PREVIOUS_SKIN".into()); + } + + if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { + shader_defs.push("HAS_PREVIOUS_MORPH".into()); + } + + if key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + shader_defs.push("DEFERRED_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::NORMAL_PREPASS) && key.msaa_samples() == 1 && is_opaque { + shader_defs.push("LOAD_PREPASS_NORMALS".into()); + } + + let view_projection = key.intersection(MeshPipelineKey::VIEW_PROJECTION_RESERVED_BITS); + if view_projection == MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD { + shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into()); + } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE { + shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into()); + } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC { + shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into()); + } + + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + shader_defs.push("WEBGL2".into()); + + #[cfg(feature = "experimental_pbr_pcss")] + shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into()); + + if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { + shader_defs.push("TONEMAP_IN_SHADER".into()); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, + )); + + let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); + + if method == MeshPipelineKey::TONEMAP_METHOD_NONE { + shader_defs.push("TONEMAP_METHOD_NONE".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD { + shader_defs.push("TONEMAP_METHOD_REINHARD".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE { + shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED { + shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX { + shader_defs.push("TONEMAP_METHOD_AGX".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM { + shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC { + shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE { + shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); + } + + // Debanding is tied to tonemapping in the shader, cannot run without it. + if key.contains(MeshPipelineKey::DEBAND_DITHER) { + shader_defs.push("DEBAND_DITHER".into()); + } + } + + if key.contains(MeshPipelineKey::MAY_DISCARD) { + shader_defs.push("MAY_DISCARD".into()); + } + + if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { + shader_defs.push("ENVIRONMENT_MAP".into()); + } + + if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) && IRRADIANCE_VOLUMES_ARE_USABLE { + shader_defs.push("IRRADIANCE_VOLUME".into()); + } + + if key.contains(MeshPipelineKey::LIGHTMAPPED) { + shader_defs.push("LIGHTMAP".into()); + } + if key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) { + shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into()); + } + + if key.contains(MeshPipelineKey::TEMPORAL_JITTER) { + shader_defs.push("TEMPORAL_JITTER".into()); + } + + let shadow_filter_method = + key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS); + if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 { + shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into()); + } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN { + shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into()); + } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL { + shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into()); + } + + let blur_quality = + key.intersection(MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS); + + shader_defs.push(ShaderDefVal::Int( + "SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS".into(), + match blur_quality { + MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW => 4, + MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM => 8, + MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH => 16, + MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA => 32, + _ => unreachable!(), // Not possible, since the mask is 2 bits, and we've covered all 4 cases + }, + )); + + if key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) { + shader_defs.push("VISIBILITY_RANGE_DITHER".into()); + } + + if key.contains(MeshPipelineKey::DISTANCE_FOG) { + shader_defs.push("DISTANCE_FOG".into()); + } + + if self.binding_arrays_are_usable { + shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); + shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); + } + + if IRRADIANCE_VOLUMES_ARE_USABLE { + shader_defs.push("IRRADIANCE_VOLUMES_ARE_USABLE".into()); + } + + if self.clustered_decals_are_usable { + shader_defs.push("CLUSTERED_DECALS_ARE_USABLE".into()); + if cfg!(feature = "pbr_light_textures") { + shader_defs.push("LIGHT_TEXTURES".into()); + } + } + + let format = if key.contains(MeshPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + // This is defined here so that custom shaders that use something other than + // the mesh binding from bevy_pbr::mesh_bindings can easily make use of this + // in their own shaders. + if let Some(per_object_buffer_batch_size) = self.per_object_buffer_batch_size { + shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + per_object_buffer_batch_size, + )); + } + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: self.shader.clone(), + shader_defs: shader_defs.clone(), + buffers: vec![vertex_buffer_layout], + ..default() + }, + fragment: Some(FragmentState { + shader: self.shader.clone(), + shader_defs, + targets: vec![Some(ColorTargetState { + format, + blend, + write_mask: ColorWrites::ALL, + })], + ..default() + }), + layout: bind_group_layout, + primitive: PrimitiveState { + cull_mode: Some(Face::Back), + unclipped_depth: false, + topology: key.primitive_topology(), + ..default() + }, + depth_stencil: Some(DepthStencilState { + format: CORE_3D_DEPTH_FORMAT, + depth_write_enabled, + depth_compare: CompareFunction::GreaterEqual, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled, + }, + label: Some(label), + ..default() + }) + } +} + +pub fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool { + layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX) + && layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) +} + +pub fn setup_morph_and_skinning_defs( + mesh_layouts: &MeshLayouts, + layout: &MeshVertexBufferLayoutRef, + offset: u32, + key: &MeshPipelineKey, + shader_defs: &mut Vec, + vertex_attributes: &mut Vec, + skins_use_uniform_buffers: bool, +) -> BindGroupLayoutDescriptor { + let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); + let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED); + let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS); + + if skins_use_uniform_buffers { + shader_defs.push("SKINS_USE_UNIFORM_BUFFERS".into()); + } + + let mut add_skin_data = || { + shader_defs.push("SKINNED".into()); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1)); + }; + + match ( + is_skinned(layout), + is_morphed, + is_lightmapped, + motion_vector_prepass, + ) { + (true, false, _, true) => { + add_skin_data(); + mesh_layouts.skinned_motion.clone() + } + (true, false, _, false) => { + add_skin_data(); + mesh_layouts.skinned.clone() + } + (true, true, _, true) => { + add_skin_data(); + shader_defs.push("MORPH_TARGETS".into()); + mesh_layouts.morphed_skinned_motion.clone() + } + (true, true, _, false) => { + add_skin_data(); + shader_defs.push("MORPH_TARGETS".into()); + mesh_layouts.morphed_skinned.clone() + } + (false, true, _, true) => { + shader_defs.push("MORPH_TARGETS".into()); + mesh_layouts.morphed_motion.clone() + } + (false, true, _, false) => { + shader_defs.push("MORPH_TARGETS".into()); + mesh_layouts.morphed.clone() + } + (false, false, true, _) => mesh_layouts.lightmapped.clone(), + (false, false, false, _) => mesh_layouts.model_only.clone(), + } +} diff --git a/crates/bevy_render/src/mesh/render.rs b/crates/bevy_render/src/mesh/render.rs new file mode 100644 index 0000000000000..64444efce30a2 --- /dev/null +++ b/crates/bevy_render/src/mesh/render.rs @@ -0,0 +1,516 @@ +use bevy_asset::AssetId; +use bevy_camera::visibility::RenderLayers; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::prelude::*; +use bevy_encase_derive::ShaderType; +use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; +use bevy_mesh::{Mesh, Mesh3d, MeshTag}; +use bevy_render::batching::gpu_preprocessing::InstanceInputUniformBuffer; +use bevy_transform::components::GlobalTransform; +use bevy_utils::default; +use glam::Affine3A; + +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; + +use bytemuck::{Pod, Zeroable}; +use nonmax::{NonMaxU16, NonMaxU32}; + +use crate::{ + lightmap::{pack_lightmap_uv_rect, LightmapSlabIndex, LightmapSlotIndex}, + mesh::material_bind_group::{MaterialBindGroupSlot, MaterialBindingId}, + render_phase::InputUniformIndex, +}; + +#[derive(Component)] +pub struct MeshTransforms { + pub world_from_local: Affine3, + pub previous_world_from_local: Affine3, + pub flags: u32, +} + +#[derive(ShaderType, Clone)] +pub struct MeshUniform { + // Affine 4x3 matrices transposed to 3x4 + pub world_from_local: [Vec4; 3], + pub previous_world_from_local: [Vec4; 3], + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + pub local_from_world_transpose_a: [Vec4; 2], + pub local_from_world_transpose_b: f32, + pub flags: u32, + // Four 16-bit unsigned normalized UV values packed into a `UVec2`: + // + // <--- MSB LSB ---> + // +---- min v ----+ +---- min u ----+ + // lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu, + // +---- max v ----+ +---- max u ----+ + // lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU, + // + // (MSB: most significant bit; LSB: least significant bit.) + pub lightmap_uv_rect: UVec2, + /// The index of this mesh's first vertex in the vertex buffer. + /// + /// Multiple meshes can be packed into a single vertex buffer (see + /// [`MeshAllocator`](`crate::mesh::allocator::MeshAllocator`) ). This value stores the offset of the first vertex in + /// this mesh in that buffer. + pub first_vertex_index: u32, + /// The current skin index, or `u32::MAX` if there's no skin. + pub current_skin_index: u32, + /// The material and lightmap indices, packed into 32 bits. + /// + /// Low 16 bits: index of the material inside the bind group data. + /// High 16 bits: index of the lightmap in the binding array. + pub material_and_lightmap_bind_group_slot: u32, + /// User supplied tag to identify this mesh instance. + pub tag: u32, + /// Padding. + pub pad: u32, +} + +/// Information that has to be transferred from CPU to GPU in order to produce +/// the full [`MeshUniform`]. +/// +/// This is essentially a subset of the fields in [`MeshUniform`] above. +#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default, Debug)] +#[repr(C)] +pub struct MeshInputUniform { + /// Affine 4x3 matrix transposed to 3x4. + pub world_from_local: [Vec4; 3], + /// Four 16-bit unsigned normalized UV values packed into a `UVec2`: + /// + /// ```text + /// <--- MSB LSB ---> + /// +---- min v ----+ +---- min u ----+ + /// lightmap_uv_rect.x: vvvvvvvv vvvvvvvv uuuuuuuu uuuuuuuu, + /// +---- max v ----+ +---- max u ----+ + /// lightmap_uv_rect.y: VVVVVVVV VVVVVVVV UUUUUUUU UUUUUUUU, + /// + /// (MSB: most significant bit; LSB: least significant bit.) + /// ``` + pub lightmap_uv_rect: UVec2, + /// Various [`MeshFlags`]. + pub flags: u32, + /// The index of this mesh's [`MeshInputUniform`] in the previous frame's + /// buffer, if applicable. + /// + /// This is used for TAA. If not present, this will be `u32::MAX`. + pub previous_input_index: u32, + /// The index of this mesh's first vertex in the vertex buffer. + /// + /// Multiple meshes can be packed into a single vertex buffer (see + /// [`MeshAllocator`](`crate::mesh::allocator::MeshAllocator`) ). This value stores the offset of the first vertex in + /// this mesh in that buffer. + pub first_vertex_index: u32, + /// The index of this mesh's first index in the index buffer, if any. + /// + /// Multiple meshes can be packed into a single index buffer (see + /// [`MeshAllocator`](`crate::mesh::allocator::MeshAllocator`) ). This value stores the offset of the first index in + /// this mesh in that buffer. + /// + /// If this mesh isn't indexed, this value is ignored. + pub first_index_index: u32, + /// For an indexed mesh, the number of indices that make it up; for a + /// non-indexed mesh, the number of vertices in it. + pub index_count: u32, + /// The current skin index, or `u32::MAX` if there's no skin. + pub current_skin_index: u32, + /// The material and lightmap indices, packed into 32 bits. + /// + /// Low 16 bits: index of the material inside the bind group data. + /// High 16 bits: index of the lightmap in the binding array. + pub material_and_lightmap_bind_group_slot: u32, + /// The number of the frame on which this [`MeshInputUniform`] was built. + /// + /// This is used to validate the previous transform and skin. If this + /// [`MeshInputUniform`] wasn't updated on this frame, then we know that + /// neither this mesh's transform nor that of its joints have been updated + /// on this frame, and therefore the transforms of both this mesh and its + /// joints must be identical to those for the previous frame. + pub timestamp: u32, + /// User supplied tag to identify this mesh instance. + pub tag: u32, + /// Padding. + pub pad: u32, +} + +impl MeshUniform { + pub fn new( + mesh_transforms: &MeshTransforms, + first_vertex_index: u32, + material_bind_group_slot: MaterialBindGroupSlot, + maybe_lightmap: Option<(LightmapSlotIndex, Rect)>, + current_skin_index: Option, + tag: Option, + ) -> Self { + let (local_from_world_transpose_a, local_from_world_transpose_b) = + mesh_transforms.world_from_local.inverse_transpose_3x3(); + let lightmap_bind_group_slot = match maybe_lightmap { + None => u16::MAX, + Some((slot_index, _)) => slot_index.into(), + }; + + Self { + world_from_local: mesh_transforms.world_from_local.to_transpose(), + previous_world_from_local: mesh_transforms.previous_world_from_local.to_transpose(), + lightmap_uv_rect: pack_lightmap_uv_rect(maybe_lightmap.map(|(_, uv_rect)| uv_rect)), + local_from_world_transpose_a, + local_from_world_transpose_b, + flags: mesh_transforms.flags, + first_vertex_index, + current_skin_index: current_skin_index.unwrap_or(u32::MAX), + material_and_lightmap_bind_group_slot: u32::from(material_bind_group_slot) + | ((lightmap_bind_group_slot as u32) << 16), + tag: tag.unwrap_or(0), + pad: 0, + } + } +} + +// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_types.wgsl! +bitflags::bitflags! { + /// Various flags and tightly-packed values on a mesh. + /// + /// Flags grow from the top bit down; other values grow from the bottom bit + /// up. + #[repr(transparent)] + pub struct MeshFlags: u32 { + /// Bitmask for the 16-bit index into the LOD array. + /// + /// This will be `u16::MAX` if this mesh has no LOD. + const LOD_INDEX_MASK = (1 << 16) - 1; + /// Disables frustum culling for this mesh. + /// + /// This corresponds to the + /// [`bevy_render::view::visibility::NoFrustumCulling`] component. + const NO_FRUSTUM_CULLING = 1 << 28; + const SHADOW_RECEIVER = 1 << 29; + const TRANSMITTED_SHADOW_RECEIVER = 1 << 30; + // Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive, + // then the flag should be set, else it should not be set. + const SIGN_DETERMINANT_MODEL_3X3 = 1 << 31; + const NONE = 0; + const UNINITIALIZED = 0xFFFFFFFF; + } +} + +impl MeshFlags { + pub fn from_components( + transform: &GlobalTransform, + lod_index: Option, + no_frustum_culling: bool, + not_shadow_receiver: bool, + transmitted_receiver: bool, + ) -> MeshFlags { + let mut mesh_flags = if not_shadow_receiver { + MeshFlags::empty() + } else { + MeshFlags::SHADOW_RECEIVER + }; + if no_frustum_culling { + mesh_flags |= MeshFlags::NO_FRUSTUM_CULLING; + } + if transmitted_receiver { + mesh_flags |= MeshFlags::TRANSMITTED_SHADOW_RECEIVER; + } + if transform.affine().matrix3.determinant().is_sign_positive() { + mesh_flags |= MeshFlags::SIGN_DETERMINANT_MODEL_3X3; + } + + let lod_index_bits = match lod_index { + None => u16::MAX, + Some(lod_index) => u16::from(lod_index), + }; + mesh_flags |= + MeshFlags::from_bits_retain((lod_index_bits as u32) << MeshFlags::LOD_INDEX_SHIFT); + + mesh_flags + } + + /// The first bit of the LOD index. + pub const LOD_INDEX_SHIFT: u32 = 0; +} + +bitflags::bitflags! { + /// Various useful flags for [`RenderMeshInstance`]s. + #[derive(Clone, Copy)] + pub struct RenderMeshInstanceFlags: u8 { + /// The mesh casts shadows. + const SHADOW_CASTER = 1 << 0; + /// The mesh can participate in automatic batching. + const AUTOMATIC_BATCHING = 1 << 1; + /// The mesh had a transform last frame and so is eligible for motion + /// vector computation. + const HAS_PREVIOUS_TRANSFORM = 1 << 2; + /// The mesh had a skin last frame and so that skin should be taken into + /// account for motion vector computation. + const HAS_PREVIOUS_SKIN = 1 << 3; + /// The mesh had morph targets last frame and so they should be taken + /// into account for motion vector computation. + const HAS_PREVIOUS_MORPH = 1 << 4; + } +} + +/// CPU data that the render world keeps for each entity, when *not* using GPU +/// mesh uniform building. +#[derive(Deref, DerefMut)] +pub struct RenderMeshInstanceCpu { + /// Data shared between both the CPU mesh uniform building and the GPU mesh + /// uniform building paths. + #[deref] + pub shared: RenderMeshInstanceShared, + /// The transform of the mesh. + /// + /// This will be written into the [`MeshUniform`] at the appropriate time. + pub transforms: MeshTransforms, +} + +/// CPU data that the render world needs to keep for each entity that contains a +/// mesh when using GPU mesh uniform building. +#[derive(Deref, DerefMut)] +pub struct RenderMeshInstanceGpu { + /// Data shared between both the CPU mesh uniform building and the GPU mesh + /// uniform building paths. + #[deref] + pub shared: RenderMeshInstanceShared, + /// The translation of the mesh. + /// + /// This is the only part of the transform that we have to keep on CPU (for + /// distance sorting). + pub translation: Vec3, + /// The index of the [`MeshInputUniform`] in the buffer. + pub current_uniform_index: NonMaxU32, +} + +#[derive(Component, PartialEq, Default)] +pub struct PreviousGlobalTransform(pub Affine3A); + +/// CPU data that the render world needs to keep about each entity that contains +/// a mesh. +pub struct RenderMeshInstanceShared { + /// The [`AssetId`] of the mesh. + pub mesh_asset_id: AssetId, + /// A slot for the material bind group index. + pub material_bindings_index: MaterialBindingId, + /// Various flags. + pub flags: RenderMeshInstanceFlags, + /// Index of the slab that the lightmap resides in, if a lightmap is + /// present. + pub lightmap_slab_index: Option, + /// User supplied tag to identify this mesh instance. + pub tag: u32, + /// Render layers that this mesh instance belongs to. + pub render_layers: Option, +} + +impl RenderMeshInstanceShared { + /// A gpu builder will provide the mesh instance id + pub fn for_gpu_building( + previous_transform: Option<&PreviousGlobalTransform>, + mesh: &Mesh3d, + tag: Option<&MeshTag>, + not_shadow_caster: bool, + no_automatic_batching: bool, + render_layers: Option<&RenderLayers>, + ) -> Self { + Self::for_cpu_building( + previous_transform, + mesh, + tag, + default(), + not_shadow_caster, + no_automatic_batching, + render_layers, + ) + } + + /// The cpu builder does not have an equivalent ? + pub fn for_cpu_building( + previous_transform: Option<&PreviousGlobalTransform>, + mesh: &Mesh3d, + tag: Option<&MeshTag>, + material_bindings_index: MaterialBindingId, + not_shadow_caster: bool, + no_automatic_batching: bool, + render_layers: Option<&RenderLayers>, + ) -> Self { + let mut mesh_instance_flags = RenderMeshInstanceFlags::empty(); + mesh_instance_flags.set(RenderMeshInstanceFlags::SHADOW_CASTER, !not_shadow_caster); + mesh_instance_flags.set( + RenderMeshInstanceFlags::AUTOMATIC_BATCHING, + !no_automatic_batching, + ); + mesh_instance_flags.set( + RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM, + previous_transform.is_some(), + ); + + RenderMeshInstanceShared { + mesh_asset_id: mesh.id(), + flags: mesh_instance_flags, + material_bindings_index, + lightmap_slab_index: None, + tag: tag.map_or(0, |i| **i), + render_layers: render_layers.cloned(), + } + } + + /// Returns true if this entity is eligible to participate in automatic + /// batching. + #[inline] + pub fn should_batch(&self) -> bool { + self.flags + .contains(RenderMeshInstanceFlags::AUTOMATIC_BATCHING) + } +} + +/// Information that the render world keeps about each entity that contains a +/// mesh. +/// +/// The set of information needed is different depending on whether CPU or GPU +/// [`MeshUniform`] building is in use. +#[derive(Resource)] +pub enum RenderMeshInstances { + /// Information needed when using CPU mesh instance data building. + CpuBuilding(RenderMeshInstancesCpu), + /// Information needed when using GPU mesh instance data building. + GpuBuilding(RenderMeshInstancesGpu), +} + +/// Information that the render world keeps about each entity that contains a +/// mesh, when using CPU mesh instance data building. +#[derive(Default, Deref, DerefMut)] +pub struct RenderMeshInstancesCpu(MainEntityHashMap); + +/// Information that the render world keeps about each entity that contains a +/// mesh, when using GPU mesh instance data building. +#[derive(Default, Deref, DerefMut)] +pub struct RenderMeshInstancesGpu(MainEntityHashMap); + +impl RenderMeshInstances { + /// Creates a new [`RenderMeshInstances`] instance. + pub fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances { + if use_gpu_instance_buffer_builder { + RenderMeshInstances::GpuBuilding(RenderMeshInstancesGpu::default()) + } else { + RenderMeshInstances::CpuBuilding(RenderMeshInstancesCpu::default()) + } + } + + /// Returns the ID of the mesh asset attached to the given entity, if any. + pub fn mesh_asset_id(&self, entity: MainEntity) -> Option> { + match *self { + RenderMeshInstances::CpuBuilding(ref instances) => instances.mesh_asset_id(entity), + RenderMeshInstances::GpuBuilding(ref instances) => instances.mesh_asset_id(entity), + } + } + + /// Constructs [`RenderMeshQueueData`] for the given entity, if it has a + /// mesh attached. + pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { + match *self { + RenderMeshInstances::CpuBuilding(ref instances) => { + instances.render_mesh_queue_data(entity) + } + RenderMeshInstances::GpuBuilding(ref instances) => { + instances.render_mesh_queue_data(entity) + } + } + } + + /// Inserts the given flags into the CPU or GPU render mesh instance data + /// for the given mesh as appropriate. + pub fn insert_mesh_instance_flags( + &mut self, + entity: MainEntity, + flags: RenderMeshInstanceFlags, + ) { + match *self { + RenderMeshInstances::CpuBuilding(ref mut instances) => { + instances.insert_mesh_instance_flags(entity, flags); + } + RenderMeshInstances::GpuBuilding(ref mut instances) => { + instances.insert_mesh_instance_flags(entity, flags); + } + } + } +} + +impl RenderMeshInstancesCpu { + fn mesh_asset_id(&self, entity: MainEntity) -> Option> { + self.get(&entity) + .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) + } + + pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { + self.get(&entity) + .map(|render_mesh_instance| RenderMeshQueueData { + shared: &render_mesh_instance.shared, + translation: render_mesh_instance.transforms.world_from_local.translation, + current_uniform_index: InputUniformIndex::default(), + }) + } + + /// Inserts the given flags into the render mesh instance data for the given + /// mesh. + fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { + if let Some(instance) = self.get_mut(&entity) { + instance.flags.insert(flags); + } + } +} + +impl RenderMeshInstancesGpu { + fn mesh_asset_id(&self, entity: MainEntity) -> Option> { + self.get(&entity) + .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) + } + + fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { + self.get(&entity) + .map(|render_mesh_instance| RenderMeshQueueData { + shared: &render_mesh_instance.shared, + translation: render_mesh_instance.translation, + current_uniform_index: InputUniformIndex( + render_mesh_instance.current_uniform_index.into(), + ), + }) + } + + /// Inserts the given flags into the render mesh instance data for the given + /// mesh. + fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { + if let Some(instance) = self.get_mut(&entity) { + instance.flags.insert(flags); + } + } +} + +/// Data that systems need in order to place entities that contain meshes in the right batch. +#[derive(Deref)] +pub struct RenderMeshQueueData<'a> { + /// General information about the mesh instance. + #[deref] + pub shared: &'a RenderMeshInstanceShared, + /// The translation of the mesh instance. + pub translation: Vec3, + /// The index of the [`MeshInputUniform`] in the GPU buffer for this mesh + /// instance. + pub current_uniform_index: InputUniformIndex, +} + +/// Removes a [`MeshInputUniform`] corresponding to an entity that became +/// invisible from the buffer. +pub fn remove_mesh_input_uniform( + entity: MainEntity, + render_mesh_instances: &mut MainEntityHashMap, + current_input_buffer: &mut InstanceInputUniformBuffer, +) -> Option { + // Remove the uniform data. + let removed_render_mesh_instance = render_mesh_instances.remove(&entity)?; + + let removed_uniform_index = removed_render_mesh_instance.current_uniform_index.get(); + current_input_buffer.remove(removed_uniform_index); + Some(removed_uniform_index) +} diff --git a/crates/bevy_render/src/mesh/skin.rs b/crates/bevy_render/src/mesh/skin.rs new file mode 100644 index 0000000000000..e6ca697a78675 --- /dev/null +++ b/crates/bevy_render/src/mesh/skin.rs @@ -0,0 +1,139 @@ +use core::mem::size_of; + +use bevy_ecs::prelude::*; +use bevy_math::Mat4; +use bevy_render::render_resource::Buffer; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; +use offset_allocator::{Allocation, Allocator}; +use smallvec::SmallVec; + +/// Maximum number of joints supported for skinned meshes. +/// +/// It is used to allocate buffers. +/// The correctness of the value depends on the GPU/platform. +/// The current value is chosen because it is guaranteed to work everywhere. +/// To allow for bigger values, a check must be made for the limits +/// of the GPU at runtime, which would mean not using consts anymore. +pub const MAX_JOINTS: usize = 256; + +/// The total number of joints we support. +/// +/// This is 256 GiB worth of joint matrices, which we will never hit under any +/// reasonable circumstances. +pub const MAX_TOTAL_JOINTS: u32 = 1024 * 1024 * 1024; + +/// The number of joints that we allocate at a time. +/// +/// Some hardware requires that uniforms be allocated on 256-byte boundaries, so +/// we need to allocate 4 64-byte matrices at a time to satisfy alignment +/// requirements. +pub const JOINTS_PER_ALLOCATION_UNIT: u32 = (256 / size_of::()) as u32; + +/// The maximum ratio of the number of entities whose transforms changed to the +/// total number of joints before we re-extract all joints. +/// +/// We use this as a heuristic to decide whether it's worth switching over to +/// fine-grained detection to determine which skins need extraction. If the +/// number of changed entities is over this threshold, we skip change detection +/// and simply re-extract the transforms of all joints. +pub const JOINT_EXTRACTION_THRESHOLD_FACTOR: f64 = 0.25; + +/// The location of the first joint matrix in the skin uniform buffer. +#[derive(Clone, Copy)] +pub struct SkinByteOffset { + /// The byte offset of the first joint matrix. + pub byte_offset: u32, +} + +impl SkinByteOffset { + /// Index to be in address space based on the size of a skin uniform. + const fn from_index(index: usize) -> Self { + SkinByteOffset { + byte_offset: (index * size_of::()) as u32, + } + } + + /// Returns this skin index in elements (not bytes). + /// + /// Each element is a 4x4 matrix. + pub fn index(&self) -> u32 { + self.byte_offset / size_of::() as u32 + } +} + +/// The GPU buffers containing joint matrices for all skinned meshes. +/// +/// This is double-buffered: we store the joint matrices of each mesh for the +/// previous frame in addition to those of each mesh for the current frame. This +/// is for motion vector calculation. Every frame, we swap buffers and overwrite +/// the joint matrix buffer from two frames ago with the data for the current +/// frame. +/// +/// Notes on implementation: see comment on top of the `extract_skins` system. +#[derive(Resource)] +pub struct SkinUniforms { + /// The CPU-side buffer that stores the joint matrices for skinned meshes in + /// the current frame. + pub current_staging_buffer: Vec, + /// The GPU-side buffer that stores the joint matrices for skinned meshes in + /// the current frame. + pub current_buffer: Buffer, + /// The GPU-side buffer that stores the joint matrices for skinned meshes in + /// the previous frame. + pub prev_buffer: Buffer, + /// The offset allocator that manages the placement of the joints within the + /// [`Self::current_buffer`]. + pub allocator: Allocator, + /// Allocation information that we keep about each skin. + pub skin_uniform_info: MainEntityHashMap, + /// Maps each joint entity to the skins it's associated with. + /// + /// We use this in conjunction with change detection to only update the + /// skins that need updating each frame. + /// + /// Note that conceptually this is a hash map of sets, but we use a + /// [`SmallVec`] to avoid allocations for the vast majority of the cases in + /// which each bone belongs to exactly one skin. + pub joint_to_skins: MainEntityHashMap>, + /// The total number of joints in the scene. + /// + /// We use this as part of our heuristic to decide whether to use + /// fine-grained change detection. + pub total_joints: usize, +} + +impl SkinUniforms { + /// Returns the current offset in joints of the skin in the buffer. + pub fn skin_index(&self, skin: MainEntity) -> Option { + self.skin_uniform_info + .get(&skin) + .map(SkinUniformInfo::offset) + } + + /// Returns the current offset in bytes of the skin in the buffer. + pub fn skin_byte_offset(&self, skin: MainEntity) -> Option { + self.skin_uniform_info.get(&skin).map(|skin_uniform_info| { + SkinByteOffset::from_index(skin_uniform_info.offset() as usize) + }) + } + + /// Returns an iterator over all skins in the scene. + pub fn all_skins(&self) -> impl Iterator { + self.skin_uniform_info.keys() + } +} + +/// Allocation information about each skin. +pub struct SkinUniformInfo { + /// The allocation of the joints within the [`SkinUniforms::current_buffer`]. + pub allocation: Allocation, + /// The entities that comprise the joints. + pub joints: Vec, +} + +impl SkinUniformInfo { + /// The offset in joints within the [`SkinUniforms::current_staging_buffer`]. + pub fn offset(&self) -> u32 { + self.allocation.offset * JOINTS_PER_ALLOCATION_UNIT + } +} diff --git a/crates/bevy_render/src/mesh/util.rs b/crates/bevy_render/src/mesh/util.rs new file mode 100644 index 0000000000000..904faa1d96cd5 --- /dev/null +++ b/crates/bevy_render/src/mesh/util.rs @@ -0,0 +1,12 @@ +use wgpu::TextureFormat; + +// PERF: vulkan docs recommend using 24 bit depth for better performance +pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; + +/// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can +/// overflow the number of texture bindings when deferred rendering is in use +/// (see issue #11885). +pub const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "wasm32")); + +pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 18; +pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 19; diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 39c6a074e6f57..a1bc10bf3252e 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -7,14 +7,17 @@ use bevy_ecs::{ system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState}, world::World, }; +pub use bevy_material::render_phase::DrawFunctionId; use bevy_utils::TypeIdMap; -use core::{any::TypeId, fmt::Debug, hash::Hash}; +use core::{any::TypeId, fmt::Debug}; use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; use thiserror::Error; use variadics_please::all_tuples; /// A draw function used to draw [`PhaseItem`]s. /// +/// Identified by a [`DrawFunctionId`], stored in [`DrawFunctions`] +/// /// The draw function can retrieve and query the required ECS data from the render world. /// /// This trait can either be implemented directly or implicitly composed out of multiple modular @@ -49,11 +52,6 @@ pub enum DrawError { ViewEntityNotFound, } -// TODO: make this generic? -/// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct DrawFunctionId(u32); - /// Stores all [`Draw`] functions for the [`PhaseItem`] type. /// /// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. @@ -110,7 +108,7 @@ impl DrawFunctionsInternal

{ } } -/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock. +/// Stores all [`Draw`] functions for the [`PhaseItem`] type hidden behind a reader-writer lock. /// /// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used. #[derive(Resource)] diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 253d06f48521e..389fb5c46ab70 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -61,42 +61,17 @@ use crate::{ render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache}, Render, RenderApp, RenderSystems, }; -use bevy_ecs::intern::Interned; use bevy_ecs::{ - define_label, prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; +pub use bevy_material_macros::ShaderLabel; use bevy_render::renderer::RenderAdapterInfo; -pub use bevy_render_macros::ShaderLabel; use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex}; use smallvec::SmallVec; use tracing::warn; -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`" - )] - /// Labels used to uniquely identify types of material shaders - ShaderLabel, - SHADER_LABEL_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedShaderLabel = Interned; - -pub use bevy_render_macros::DrawFunctionLabel; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`" - )] - /// Labels used to uniquely identify types of material shaders - DrawFunctionLabel, - DRAW_FUNCTION_LABEL_INTERNER -); - -pub type InternedDrawFunctionLabel = Interned; +pub use bevy_material_macros::DrawFunctionLabel; /// Stores the rendering instructions for a single phase that uses bins in all /// views. diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 2d8ddabf2dbf6..7463015e66261 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -2,7 +2,6 @@ mod batched_uniform_buffer; mod bind_group; mod bind_group_entries; mod bind_group_layout; -mod bind_group_layout_entries; mod bindless; mod buffer; mod buffer_vec; @@ -19,7 +18,6 @@ mod uniform_buffer; pub use bind_group::*; pub use bind_group_entries::*; pub use bind_group_layout::*; -pub use bind_group_layout_entries::*; pub use bindless::*; pub use buffer::*; pub use buffer_vec::*; @@ -33,36 +31,21 @@ pub use texture::*; pub use uniform_buffer::*; // TODO: decide where re-exports should go +pub use bevy_material::render_resource::*; pub use wgpu::{ util::{ BufferInitDescriptor, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, TextureDataOrder, }, - AccelerationStructureFlags, AccelerationStructureGeometryFlags, - AccelerationStructureUpdateMode, AdapterInfo as WgpuAdapterInfo, AddressMode, AstcBlock, - AstcChannel, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutEntry, BindingResource, - BindingType, Blas, BlasBuildEntry, BlasGeometries, BlasGeometrySizeDescriptors, - BlasTriangleGeometry, BlasTriangleGeometrySizeDescriptor, BlendComponent, BlendFactor, - BlendOperation, BlendState, BufferAddress, BufferAsyncError, BufferBinding, BufferBindingType, - BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, - CommandEncoderDescriptor, CompareFunction, ComputePass, ComputePassDescriptor, - ComputePipelineDescriptor as RawComputePipelineDescriptor, CreateBlasDescriptor, - CreateTlasDescriptor, DepthBiasState, DepthStencilState, DownlevelFlags, Extent3d, Face, - Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace, - ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, MultisampleState, - Operations, Origin3d, PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, - PollType, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange, - RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, - RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler, - SamplerBindingType, SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor, - ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, - StencilOperation, StencilState, StorageTextureAccess, StoreOp, TexelCopyBufferInfo, - TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor, - TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, - TextureSampleType, TextureUsages, TextureView as WgpuTextureView, TextureViewDescriptor, - TextureViewDimension, Tlas, TlasInstance, VertexAttribute, - VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, - VertexStepMode, COPY_BUFFER_ALIGNMENT, + BindGroupDescriptor, BindGroupEntry, BindingResource, Blas, BlasBuildEntry, BlasGeometries, + BlasTriangleGeometry, BufferAsyncError, BufferBinding, CommandEncoder, ComputePass, + ComputePassDescriptor, ComputePipelineDescriptor as RawComputePipelineDescriptor, + FragmentState as RawFragmentState, MapMode, PipelineCompilationOptions, PipelineLayout, + PipelineLayoutDescriptor, RenderPassColorAttachment, RenderPassDepthStencilAttachment, + RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor, + Sampler as WgpuSampler, ShaderModule, ShaderModuleDescriptor, ShaderSource, TextureDescriptor, + TextureView as WgpuTextureView, Tlas, TlasInstance, + VertexBufferLayout as RawVertexBufferLayout, VertexState as RawVertexState, }; pub mod encase { diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index f3976f68af916..7151533a0db41 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,16 +1,6 @@ use crate::define_atomic_id; use crate::renderer::WgpuWrapper; -use alloc::borrow::Cow; -use bevy_asset::Handle; -use bevy_mesh::VertexBufferLayout; -use bevy_shader::{Shader, ShaderDefVal}; -use core::iter; use core::ops::Deref; -use thiserror::Error; -use wgpu::{ - BindGroupLayoutEntry, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, - PushConstantRange, -}; define_atomic_id!(RenderPipelineId); @@ -86,114 +76,3 @@ impl Deref for ComputePipeline { &self.value } } - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct BindGroupLayoutDescriptor { - /// Debug label of the bind group layout descriptor. This will show up in graphics debuggers for easy identification. - pub label: Cow<'static, str>, - pub entries: Vec, -} - -impl BindGroupLayoutDescriptor { - pub fn new(label: impl Into>, entries: &[BindGroupLayoutEntry]) -> Self { - Self { - label: label.into(), - entries: entries.into(), - } - } -} - -/// Describes a render (graphics) pipeline. -#[derive(Clone, Debug, PartialEq, Default)] -pub struct RenderPipelineDescriptor { - /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. - pub label: Option>, - /// The layout of bind groups for this pipeline. - pub layout: Vec, - /// The push constant ranges for this pipeline. - /// Supply an empty vector if the pipeline doesn't use push constants. - pub push_constant_ranges: Vec, - /// The compiled vertex stage, its entry point, and the input buffers layout. - pub vertex: VertexState, - /// The properties of the pipeline at the primitive assembly and rasterization level. - pub primitive: PrimitiveState, - /// The effect of draw calls on the depth and stencil aspects of the output target, if any. - pub depth_stencil: Option, - /// The multi-sampling properties of the pipeline. - pub multisample: MultisampleState, - /// The compiled fragment stage, its entry point, and the color targets. - pub fragment: Option, - /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. - /// If this is false, reading from workgroup variables before writing to them will result in garbage values. - pub zero_initialize_workgroup_memory: bool, -} - -#[derive(Copy, Clone, Debug, Error)] -#[error("RenderPipelineDescriptor has no FragmentState configured")] -pub struct NoFragmentStateError; - -impl RenderPipelineDescriptor { - pub fn fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> { - self.fragment.as_mut().ok_or(NoFragmentStateError) - } - - pub fn set_layout(&mut self, index: usize, layout: BindGroupLayoutDescriptor) { - filling_set_at(&mut self.layout, index, bevy_utils::default(), layout); - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct VertexState { - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// The format of any vertex buffers used with this pipeline. - pub buffers: Vec, -} - -/// Describes the fragment process in a render pipeline. -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct FragmentState { - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// The color state of the render targets. - pub targets: Vec>, -} - -impl FragmentState { - pub fn set_target(&mut self, index: usize, target: ColorTargetState) { - filling_set_at(&mut self.targets, index, None, Some(target)); - } -} - -/// Describes a compute pipeline. -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct ComputePipelineDescriptor { - pub label: Option>, - pub layout: Vec, - pub push_constant_ranges: Vec, - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. - /// If this is false, reading from workgroup variables before writing to them will result in garbage values. - pub zero_initialize_workgroup_memory: bool, -} - -// utility function to set a value at the specified index, extending with -// a filler value if the index is out of bounds. -fn filling_set_at(vec: &mut Vec, index: usize, filler: T, value: T) { - let num_to_fill = (index + 1).saturating_sub(vec.len()); - vec.extend(iter::repeat_n(filler, num_to_fill)); - vec[index] = value; -} diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index f2e84a2fc3575..15b5efd50b2d8 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -3,7 +3,7 @@ use crate::render_resource::{ RenderPipelineDescriptor, }; use bevy_ecs::resource::Resource; -use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout}; +use bevy_mesh::{MeshVertexBufferLayoutRef, VertexBufferLayout}; use bevy_platform::{ collections::{ hash_map::{Entry, RawEntryMut, VacantEntry}, @@ -12,8 +12,7 @@ use bevy_platform::{ hash::FixedHasher, }; use bevy_utils::default; -use core::{fmt::Debug, hash::Hash}; -use thiserror::Error; +use core::hash::Hash; use tracing::error; /// A trait that allows constructing different variants of a render pipeline from a key. @@ -252,8 +251,4 @@ impl SpecializedMeshPipelines { } } -#[derive(Error, Debug)] -pub enum SpecializedMeshPipelineError { - #[error(transparent)] - MissingVertexAttribute(#[from] MissingVertexAttributeError), -} +pub use bevy_material::render_resource::SpecializedMeshPipelineError; diff --git a/crates/bevy_sprite_render/Cargo.toml b/crates/bevy_sprite_render/Cargo.toml index c607255280cb9..d192271a41017 100644 --- a/crates/bevy_sprite_render/Cargo.toml +++ b/crates/bevy_sprite_render/Cargo.toml @@ -24,6 +24,7 @@ bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } bevy_sprite = { path = "../bevy_sprite", version = "0.18.0-dev" } bevy_text = { path = "../bevy_text", version = "0.18.0-dev", optional = true } diff --git a/crates/bevy_ui_render/Cargo.toml b/crates/bevy_ui_render/Cargo.toml index c40fbd6cb4da4..deaac109cef74 100644 --- a/crates/bevy_ui_render/Cargo.toml +++ b/crates/bevy_ui_render/Cargo.toml @@ -19,6 +19,7 @@ bevy_derive = { path = "../bevy_derive", version = "0.18.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev" } bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } +bevy_material = { path = "../bevy_material", version = "0.18.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } diff --git a/docs/cargo_features.md b/docs/cargo_features.md index eed65e7833613..0647eee72411a 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -84,6 +84,7 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio |bevy_input_focus|Enable input focus subsystem| |bevy_light|Provides light types such as point lights, directional lights, spotlights.| |bevy_log|Enable integration with `tracing` and `log`| +|bevy_material|Provides materials.| |bevy_mesh|Provides a mesh format and some primitive meshing routines.| |bevy_mikktspace|Provides vertex tangent generation for use with bevy_mesh.| |bevy_pbr|Adds PBR rendering| diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 866e96adca9da..798d40362b24e 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -13,7 +13,7 @@ use std::ops::Range; use bevy::camera::Viewport; -use bevy::pbr::SetMeshViewEmptyBindGroup; +use bevy::pbr::{init_mesh_pipeline, SetMeshViewEmptyBindGroup}; use bevy::{ camera::MainPassResolutionOverride, core_pipeline::core_3d::graph::{Core3d, Node3d}, @@ -130,7 +130,10 @@ impl Plugin for MeshStencilPhasePlugin { .init_resource::>() .add_render_command::() .init_resource::>() - .add_systems(RenderStartup, init_stencil_pipeline) + .add_systems( + RenderStartup, + init_stencil_pipeline.after(init_mesh_pipeline), + ) .add_systems(ExtractSchedule, extract_camera_phases) .add_systems( Render, diff --git a/examples/shader_advanced/custom_shader_instancing.rs b/examples/shader_advanced/custom_shader_instancing.rs index 4be7a37c2bda4..3c6d1e0bf4dab 100644 --- a/examples/shader_advanced/custom_shader_instancing.rs +++ b/examples/shader_advanced/custom_shader_instancing.rs @@ -7,7 +7,7 @@ //! implementation using bevy's low level rendering api. //! It's generally recommended to try the built-in instancing before going with this approach. -use bevy::pbr::SetMeshViewBindingArrayBindGroup; +use bevy::pbr::{init_mesh_pipeline, SetMeshViewBindingArrayBindGroup}; use bevy::{ camera::visibility::NoFrustumCulling, core_pipeline::core_3d::Transparent3d, @@ -102,7 +102,10 @@ impl Plugin for CustomMaterialPlugin { app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::>() - .add_systems(RenderStartup, init_custom_pipeline) + .add_systems( + RenderStartup, + init_custom_pipeline.after(init_mesh_pipeline), + ) .add_systems( Render, ( diff --git a/examples/shader_advanced/specialized_mesh_pipeline.rs b/examples/shader_advanced/specialized_mesh_pipeline.rs index 40602a8429274..7432cb652f6cc 100644 --- a/examples/shader_advanced/specialized_mesh_pipeline.rs +++ b/examples/shader_advanced/specialized_mesh_pipeline.rs @@ -14,8 +14,8 @@ use bevy::{ math::{vec3, vec4}, mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology}, pbr::{ - DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances, - SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup, + init_mesh_pipeline, DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, + RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup, }, prelude::*, render::{ @@ -113,7 +113,10 @@ impl Plugin for CustomRenderedMeshPipelinePlugin { .init_resource::>() // We need to use a custom draw command so we need to register it .add_render_command::() - .add_systems(RenderStartup, init_custom_mesh_pipeline) + .add_systems( + RenderStartup, + init_custom_mesh_pipeline.after(init_mesh_pipeline), + ) .add_systems( Render, queue_custom_mesh_pipeline.in_set(RenderSystems::Queue), diff --git a/release-content/migration-guides/material.md b/release-content/migration-guides/material.md new file mode 100644 index 0000000000000..245c1997ab682 --- /dev/null +++ b/release-content/migration-guides/material.md @@ -0,0 +1,39 @@ +--- +title: "`bevy_material`" +pull_requests: [21543] +--- + +Materials can now be defined without a renderer, with the new `bevy_material` crate. + +As such, the following moves have occurred: + +Move from `bevy_render` to `bevy_material`: + +- `AlphaMode` +- `ShaderLabel`, `DrawFunctionLabel`, wgpu exports from `render_resource`, `bind_group_layout_entries` +- `DrawFunctionId` +- `SpecializedMeshPipelineError` +- `BindGroupLayoutDescriptor`, `RenderPipelineDescriptor`, `NoFragmentStateError`, `VertexState`, `FragmentState`, `ComputePipelineDescriptor` + +Move from `bevy_pbr` to `bevy_material`: + +- `Opaque` +- `MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES`, `MeshPipelineKey` +- `MeshPipeline` +- `MATERIAL_BIND_GROUP_INDEX`, `ErasedMaterialPipelineKey`, `ErasedMaterialKey`, `ErasedMaterialKeyVTable`, `RenderPhaseType` +- `MeshPipelineViewLayout`, `MeshPipelineViewLayoutKey`, `MeshPipelineViewLayouts` +- `MeshLayouts` +- `MaterialProperties` and `MaterialPipeline` + +Move from `bevy_pbr` to `bevy_render`: + +- `LIGHTMAPS_PER_SLAB`, `Lightmap`, `RenderLightmap`, `RenderLightmaps`, `LightmapSlab`, `AllocatedLightmap`, `LightmapSlabIndex`, `LightmapSlotIndex` +- `MaterialBindingId`, `MaterialBindGroupIndex`, `MaterialBindGroupSlot` +- `MeshTransforms`, `MeshUniform`, `MeshInputUniform`, `MeshFlags`, `RenderMeshInstanceFlags`, `RenderMeshInstanceCpu`, `RenderMeshInstanceGpu`, `PreviousGlobalTransform`, `RenderMeshInstanceShared`, `remove_mesh_input_uniform` +- `MAX_JOINTS`, `MAX_TOTAL_JOINTS`, `JOINTS_PER_ALLOCATION_UNIT`, `JOINT_EXTRACTION_THRESHOLD_FACTOR`, `SkinByteOffset`, `SkinUniforms`, `SkinUniformInfo` +- `RenderMeshInstances`, `RenderMeshInstancesCpu`, `RenderMeshInstancesGpu`, `RenderMeshQueueData` +- `TONEMAPPING_LUT_TEXTURE_BINDING_INDEX`, `TONEMAPPING_LUT_SAMPLER_BINDING_INDEX`, `IRRADIANCE_VOLUMES_ARE_USABLE` + +`CORE_3D_DEPTH_FORMAT` from `bevy_core_pipeline` to `bevy_render` + +`Into` has been removed, use `mesh_pipeline_view_layout_key_from_msaa()` (for `Msaa`) and `mesh_pipeline_view_layout_key_from_view_prepass_textures()` (for `ViewPrepassTextures`) instead diff --git a/release-content/release-notes/material.md b/release-content/release-notes/material.md new file mode 100644 index 0000000000000..4236db07fc993 --- /dev/null +++ b/release-content/release-notes/material.md @@ -0,0 +1,7 @@ +--- +title: "`bevy_material`" +authors: ["@Zeophlite", "@atlv24"] +pull_requests: [21543] +--- + +Materials can now be defined without a renderer.