diff --git a/Cargo.toml b/Cargo.toml index dbf0401117928..e663eaa38703d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,6 +152,7 @@ default = [ "bevy_camera", "bevy_light", "bevy_shader", + "bevy_material", "bevy_sprite", "bevy_sprite_picking_backend", "bevy_sprite_render", @@ -279,6 +280,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 rendering 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_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index dee095185d44b..e3606c04df0bf 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -36,6 +36,7 @@ use core::ops::Range; use bevy_asset::UntypedAssetId; use bevy_camera::{Camera, Camera2d}; use bevy_image::ToExtents; +use bevy_material::render_phase::DrawFunctionId; use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, @@ -58,7 +59,7 @@ use bevy_render::{ extract_component::ExtractComponentPlugin, render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ - sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, + sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 0e5a07b870e83..e21c98609c321 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -47,9 +47,6 @@ pub mod graph { } } -// PERF: vulkan docs recommend using 24 bit depth for better performance -pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; - /// True if multisampled depth textures are supported on this platform. /// /// In theory, Naga supports depth textures on WebGL 2. In practice, it doesn't, @@ -73,13 +70,9 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true; use core::ops::Range; use bevy_camera::{Camera, Camera3d, Camera3dDepthLoadOp}; +use bevy_material::render_phase::DrawFunctionId; use bevy_render::{ - batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, - camera::CameraRenderGraph, - experimental::occlusion_culling::OcclusionCulling, - mesh::allocator::SlabId, - render_phase::PhaseItemBatchSetKey, - view::{prepare_view_targets, NoIndirectDrawing, RetainedViewEntity}, + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::CameraRenderGraph, experimental::occlusion_culling::OcclusionCulling, mesh::allocator::SlabId, render_phase::PhaseItemBatchSetKey, render_resource::CORE_3D_DEPTH_FORMAT, view::{prepare_view_targets, NoIndirectDrawing, RetainedViewEntity} }; pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; @@ -97,7 +90,7 @@ use bevy_render::{ prelude::Msaa, render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ - sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, + sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index b9f5169b48f32..668bac74cb1de 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -5,10 +5,11 @@ use core::ops::Range; use crate::prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey}; use bevy_ecs::prelude::*; +use bevy_material::render_phase::DrawFunctionId; use bevy_render::sync_world::MainEntity; use bevy_render::{ render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem, + BinnedPhaseItem, CachedRenderPipelinePhaseItem, PhaseItem, PhaseItemExtraIndex, }, render_resource::{CachedRenderPipelineId, TextureFormat}, diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 89b09c88b3f09..83dfa707ab138 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -32,6 +32,7 @@ use core::ops::Range; use crate::deferred::{DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT}; use bevy_asset::UntypedAssetId; use bevy_ecs::prelude::*; +use bevy_material::render_phase::DrawFunctionId; use bevy_math::Mat4; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::mesh::allocator::SlabId; @@ -39,7 +40,7 @@ use bevy_render::render_phase::PhaseItemBatchSetKey; use bevy_render::sync_world::MainEntity; use bevy_render::{ render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem, + BinnedPhaseItem, CachedRenderPipelinePhaseItem, PhaseItem, PhaseItemExtraIndex, }, render_resource::{ diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index 88b2bf9f6e43c..632fc603dd776 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -33,7 +33,7 @@ use bevy_utils::default; use prepass::SkyboxPrepassPipeline; use crate::{ - core_3d::CORE_3D_DEPTH_FORMAT, prepass::PreviousViewUniforms, + prepass::PreviousViewUniforms, skybox::prepass::init_skybox_prepass_pipeline, }; diff --git a/crates/bevy_core_pipeline/src/skybox/prepass.rs b/crates/bevy_core_pipeline/src/skybox/prepass.rs index 8eb51c10e2067..fc8fa6c687e1e 100644 --- a/crates/bevy_core_pipeline/src/skybox/prepass.rs +++ b/crates/bevy_core_pipeline/src/skybox/prepass.rs @@ -13,7 +13,7 @@ use bevy_render::{ binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedRenderPipelineId, CompareFunction, DepthStencilState, FragmentState, MultisampleState, PipelineCache, RenderPipelineDescriptor, ShaderStages, - SpecializedRenderPipeline, SpecializedRenderPipelines, + SpecializedRenderPipeline, SpecializedRenderPipelines, CORE_3D_DEPTH_FORMAT, }, renderer::RenderDevice, view::{Msaa, ViewUniform, ViewUniforms}, @@ -22,7 +22,6 @@ use bevy_shader::Shader; use bevy_utils::prelude::default; use crate::{ - core_3d::CORE_3D_DEPTH_FORMAT, prepass::{ prepass_target_descriptors, MotionVectorPrepass, NormalPrepass, PreviousViewData, PreviousViewUniforms, diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index 5e2e0301dbe2e..9f7c094f3940e 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -23,6 +23,7 @@ bevy_light = { path = "../bevy_light", version = "0.18.0-dev" } bevy_color = { path = "../bevy_color", 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_material = { path = "../bevy_material", 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_asset = { path = "../bevy_asset", version = "0.18.0-dev" } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 1fd1c92293020..c27bece8e9dea 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -8,7 +8,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_camera::visibility::RenderLayers; use bevy_core_pipeline::{ - core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}, + core_3d::{Transparent3d}, oit::OrderIndependentTransparencySettings, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, }; @@ -21,7 +21,8 @@ use bevy_ecs::{ system::{Commands, Query, Res, ResMut}, }; use bevy_image::BevyDefault as _; -use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; +use bevy_material::render::{MeshPipeline, MeshPipelineKey}; +use bevy_pbr::{SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ 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_gltf/src/loader/gltf_ext/material.rs b/crates/bevy_gltf/src/loader/gltf_ext/material.rs index 9d8b7c5745910..c0d4ef99ed43c 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/material.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/material.rs @@ -1,6 +1,6 @@ +use bevy_material::alpha::AlphaMode; use bevy_math::Affine2; use bevy_pbr::UvChannel; -use bevy_render::alpha::AlphaMode; use gltf::{json::texture::Info, Material}; diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index d5a6134542c3f..811f6a8fe597f 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -38,9 +38,9 @@ use bevy_mesh::{ }; #[cfg(feature = "pbr_transmission_textures")] use bevy_pbr::UvChannel; -use bevy_pbr::{MeshMaterial3d, StandardMaterial, MAX_JOINTS}; +use bevy_pbr::{MeshMaterial3d, StandardMaterial}; use bevy_platform::collections::{HashMap, HashSet}; -use bevy_render::render_resource::Face; +use bevy_render::{mesh::skin::MAX_JOINTS, render_resource::Face}; use bevy_scene::Scene; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::IoTaskPool; diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 3a43a38147c50..158a6d16acd6e 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -224,6 +224,11 @@ 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_render = [ "dep:bevy_render", @@ -485,6 +490,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 d4a9b06965caa..d9246433e86a9 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -54,6 +54,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..d1ae5c8f0816b --- /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().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..1479ad1dc67d2 --- /dev/null +++ b/crates/bevy_material/src/lib.rs @@ -0,0 +1,19 @@ +//! Provides a material abstraction for bevy +#![allow(missing_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..0e655983a17b3 --- /dev/null +++ b/crates/bevy_material/src/material.rs @@ -0,0 +1,205 @@ +use crate::render::{MeshPipeline, MeshPipelineKey}; +use crate::render_phase::{DrawFunctionId, DrawFunctionLabel, InternedShaderLabel, ShaderLabel}; +use crate::render_resource::{BindGroupLayoutDescriptor, RenderPipelineDescriptor, SpecializedMeshPipelineError}; +use crate::{alpha::AlphaMode, render_phase::InternedDrawFunctionLabel}; +// use crate::material_bind_groups::{ +// FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId, +// }; +use crate::opaque::OpaqueRendererMethod; +use crate::*; +use alloc::sync::Arc; +use bevy_asset::Handle; +use bevy_ecs::{ + prelude::*, +}; +use bevy_mesh::{ + MeshVertexBufferLayoutRef, +}; +use bevy_platform::hash::FixedHasher; +use bevy_shader::{Shader}; +use core::any::{Any, TypeId}; +use core::hash::{BuildHasher, Hasher}; +use core::{hash::Hash, marker::PhantomData}; +use smallvec::SmallVec; +use tracing::error; + +#[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, +} + + +#[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, +} 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..0a2a4a6f344e8 --- /dev/null +++ b/crates/bevy_material/src/render/mesh.rs @@ -0,0 +1,269 @@ +// use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetId, Handle}; +use bevy_camera::{ + primitives::Aabb, + visibility::{NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange}, + Camera, Camera3d, Projection, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + prelude::*, + query::{QueryData, ROQueryItem}, + system::{lifetimeless::*, SystemParamItem, SystemState}, +}; +use bevy_image::{BevyDefault, Image, ImageSampler, 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_platform::collections::{hash_map::Entry, HashMap}; +use crate::{render::{MeshLayouts, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}, render_resource::*}; +use bevy_shader::{load_shader_library, Shader, 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 self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; +use crate::{ + render::{ + // morph::{ + // extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, + // MorphUniforms, + // }, + // skin::no_automatic_skin_batching, + }, + *, +}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; + +use nonmax::{NonMaxU16, NonMaxU32}; +use smallvec::{smallvec, SmallVec}; +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, + // This dummy white texture is to be used in place of optional StandardMaterial textures + pub dummy_white_gpu_image: Handle, + 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: &MeshPipeline, + 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..cf8bdc729ce39 --- /dev/null +++ b/crates/bevy_material/src/render/mesh_bindings.rs @@ -0,0 +1,40 @@ +use crate::render_resource::BindGroupLayoutDescriptor; + + +/// 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, + + /// 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, + + /// 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..10c4430d906cc --- /dev/null +++ b/crates/bevy_material/src/render/mesh_view_bindings.rs @@ -0,0 +1,167 @@ +use alloc::sync::Arc; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::Has, + resource::Resource, + system::{Commands, Query, Res}, + world::{FromWorld, World}, +}; +use bevy_image::BevyDefault as _; +// use bevy_light::{EnvironmentMapLight, IrradianceVolume}; +use bevy_math::Vec4; +use crate::{ + render::MeshPipelineKey, render_resource::{binding_types::*, *} +}; +use core::{array, num::NonZero}; + +// use crate::{ +// decal::{ +// self, +// clustered::{ +// DecalsBuffer, RenderClusteredDecals, RenderViewClusteredDecalBindGroupEntries, +// }, +// }, +// environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, +// irradiance_volume::{ +// self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, +// }, +// prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, +// GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, +// MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, +// ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, +// ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, +// }; + +use crate::render_resource::BindGroupLayoutDescriptor; + +#[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..aca5f339a9382 --- /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`] function stored in [`DrawFunctions`]. +#[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..b6fff075a4621 --- /dev/null +++ b/crates/bevy_material/src/render_resource/mod.rs @@ -0,0 +1,29 @@ +mod bind_group_layout_entries; +mod pipeline; +// mod pipeline_cache; +mod pipeline_specializer; + +pub use bind_group_layout_entries::*; +pub use pipeline::*; +// pub use pipeline_cache::*; +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..5095bb7a9af0c --- /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: Option>, + pub entries: Vec, +} + +impl BindGroupLayoutDescriptor { + pub fn new(label: impl Into>, entries: &[BindGroupLayoutEntry]) -> Self { + Self { + label: Some(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 51fbd25000469..e25984d0a1426 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -45,6 +45,7 @@ bevy_light = { path = "../bevy_light", version = "0.18.0-dev" } bevy_image = { path = "../bevy_image", version = "0.18.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } 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" } diff --git a/crates/bevy_pbr/src/cluster.rs b/crates/bevy_pbr/src/cluster.rs index ca7db2358735f..d53af8381b327 100644 --- a/crates/bevy_pbr/src/cluster.rs +++ b/crates/bevy_pbr/src/cluster.rs @@ -3,6 +3,7 @@ use core::num::NonZero; use bevy_camera::Camera; use bevy_ecs::{entity::EntityHashMap, prelude::*}; use bevy_light::cluster::{ClusterableObjectCounts, Clusters, GlobalClusterSettings}; +use bevy_material::render::MeshPipeline; use bevy_math::{uvec4, UVec3, UVec4, Vec4}; use bevy_render::{ render_resource::{ @@ -14,7 +15,6 @@ use bevy_render::{ }; use tracing::warn; -use crate::MeshPipeline; // NOTE: this must be kept in sync with the same constants in // `mesh_view_types.wgsl`. diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index 65ef0a44663ac..8908dd5f0fade 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -7,11 +7,11 @@ use bevy_asset::{Asset, Assets, Handle}; use bevy_ecs::{ component::Component, lifecycle::HookContext, resource::Resource, world::DeferredWorld, }; +use bevy_material::alpha::AlphaMode; use bevy_math::{prelude::Rectangle, Quat, Vec2, Vec3}; use bevy_mesh::{Mesh, Mesh3d, MeshBuilder, MeshVertexBufferLayoutRef, Meshable}; use bevy_reflect::{Reflect, TypePath}; use bevy_render::{ - alpha::AlphaMode, render_asset::RenderAssets, render_resource::{ AsBindGroup, AsBindGroupShaderType, CompareFunction, RenderPipelineDescriptor, ShaderType, diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index bbce8dc909fa3..f39677ea49a9b 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -1,10 +1,9 @@ use crate::{ - graph::NodePbr, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, + graph::NodePbr, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, - TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; -use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset}; +use crate::{DistanceFog, ViewFogUniformOffset, ViewLightsUniformOffset}; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ @@ -18,6 +17,7 @@ use bevy_core_pipeline::{ use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, IrradianceVolume, ShadowFilteringMethod}; +use bevy_material::render::{MeshPipeline, MeshPipelineKey}; use bevy_render::RenderStartup; use bevy_render::{ diagnostic::RecordDiagnostics, diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 585039263100b..9a8f828726a78 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -2,11 +2,11 @@ use alloc::borrow::Cow; use bevy_asset::Asset; use bevy_ecs::system::SystemParamItem; +use bevy_material::{alpha::AlphaMode, material::MaterialPipeline, render::{MeshPipeline, MeshPipelineKey}}; use bevy_mesh::MeshVertexBufferLayoutRef; use bevy_platform::{collections::HashSet, hash::FixedHasher}; use bevy_reflect::{impl_type_path, Reflect}; use bevy_render::{ - alpha::AlphaMode, render_resource::{ AsBindGroup, AsBindGroupError, BindGroupLayout, BindGroupLayoutEntry, BindlessDescriptor, BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor, @@ -16,7 +16,7 @@ use bevy_render::{ }; use bevy_shader::ShaderRef; -use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey}; +use crate::{Material, MaterialPipelineKey}; pub struct MaterialExtensionPipeline { pub mesh_pipeline: MeshPipeline, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a0bf22ba22c23..2cbc230182c8a 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -131,8 +131,8 @@ use bevy_ecs::prelude::*; #[cfg(feature = "bluenoise_texture")] use bevy_image::{CompressedImageFormats, ImageType}; use bevy_image::{Image, ImageSampler}; +use bevy_material::alpha::AlphaMode; use bevy_render::{ - alpha::AlphaMode, camera::sort_cameras, extract_resource::ExtractResourcePlugin, render_graph::RenderGraph, @@ -150,9 +150,6 @@ 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; - /// Sets up the entire PBR infrastructure of bevy. pub struct PbrPlugin { /// Controls if the prepass is enabled for the [`StandardMaterial`]. diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 417f22083ead6..1bad021d825ef 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -155,11 +155,6 @@ 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")); - /// All the bind group entries necessary for PBR shaders to access the /// irradiance volumes exposed to a view. pub(crate) enum RenderViewIrradianceVolumeBindGroupEntries<'a> { diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index f4adb94738c48..16e62c0bd0e67 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -50,12 +50,7 @@ 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, + mesh::lightmap::{LightmapSlabIndex, LightmapSlotIndex, RenderLightmap, RenderLightmapsU}, 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_shader::load_shader_library; @@ -107,53 +102,11 @@ pub struct Lightmap { 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. +/// Corresponds to `RenderLightmapsU` #[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, - +pub struct RenderLightmapsL { /// 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, + pub slabs: Vec, } /// A binding array that contains lightmaps. @@ -167,21 +120,8 @@ pub struct LightmapSlab { 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"); @@ -201,7 +141,8 @@ impl Plugin for LightmapPlugin { /// Extracts all lightmaps from the scene and populates the [`RenderLightmaps`] /// resource. fn extract_lightmaps( - render_lightmaps: ResMut, + render_lightmaps_u: ResMut, + mut render_lightmaps_l: ResMut, changed_lightmaps_query: Extract< Query< (Entity, &ViewVisibility, &Lightmap), @@ -212,11 +153,12 @@ fn extract_lightmaps( images: Res>, fallback_images: Res, ) { - let render_lightmaps = render_lightmaps.into_inner(); + let render_lightmaps_u2 = render_lightmaps_u.into_inner(); + let render_lightmaps_l2 = render_lightmaps_l.into_inner(); // Loop over each entity. for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() { - if render_lightmaps + if render_lightmaps_u2 .render_lightmaps .contains_key(&MainEntity::from(entity)) { @@ -229,8 +171,8 @@ fn extract_lightmaps( } let (slab_index, slot_index) = - render_lightmaps.allocate(&fallback_images, lightmap.image.id()); - render_lightmaps.render_lightmaps.insert( + render_lightmaps_l2.allocate(render_lightmaps_u2, &fallback_images, lightmap.image.id()); + render_lightmaps_u2.render_lightmaps.insert( entity.into(), RenderLightmap::new( lightmap.uv_rect, @@ -240,7 +182,7 @@ fn extract_lightmaps( ), ); - render_lightmaps + render_lightmaps_u2 .pending_lightmaps .insert((slab_index, slot_index)); } @@ -254,74 +196,41 @@ fn extract_lightmaps( slab_index, slot_index, .. - }) = render_lightmaps + }) = render_lightmaps_u2 .render_lightmaps .remove(&MainEntity::from(entity)) else { continue; }; - render_lightmaps.remove(&fallback_images, slab_index, slot_index); - render_lightmaps + // render_lightmaps_l.remove(render_lightmaps_u2, &fallback_images, slab_index, slot_index); + render_lightmaps_u2 .pending_lightmaps .remove(&(slab_index, slot_index)); } - render_lightmaps + render_lightmaps_u2 .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 + return false; + // let asset_id = render_lightmaps_u2.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_l.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 { @@ -339,7 +248,7 @@ pub fn init_render_lightmaps( ) { let bindless_supported = binding_arrays_are_usable(&render_device, &render_adapter); - commands.insert_resource(RenderLightmaps { + commands.insert_resource(RenderLightmapsU { render_lightmaps: default(), slabs: vec![], free_slabs: FixedBitSet::new(), @@ -348,51 +257,6 @@ pub fn init_render_lightmaps( }); } -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(slot_index.into()); - } - } -} - impl LightmapSlab { fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab { let count = if bindless_supported { @@ -405,7 +269,6 @@ impl LightmapSlab { lightmaps: (0..count) .map(|_| AllocatedLightmap { gpu_image: fallback_images.d2.clone(), - asset_id: None, }) .collect(), free_slots_bitmask: (1 << count) - 1, @@ -419,21 +282,19 @@ impl LightmapSlab { fn allocate(&mut self, image_id: AssetId) -> LightmapSlotIndex { 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); + // 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); } @@ -470,50 +331,49 @@ impl LightmapSlab { } } -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 RenderLightmapsL { + /// Creates a new slab, appends it to the end of the list, and returns its + /// slab index. + fn create_slab(&mut self, other: &mut RenderLightmapsU, fallback_images: &FallbackImage) -> LightmapSlabIndex { + let slab_index = LightmapSlabIndex::from(self.slabs.len()); + other.free_slabs.grow_and_insert(slab_index.into()); + self.slabs + .push(LightmapSlab::new(fallback_images, other.bindless_supported)); + slab_index } -} -impl From for LightmapSlotIndex { - fn from(value: usize) -> Self { - Self::from(value as u32) - } -} + fn allocate( + &mut self, + other: &mut RenderLightmapsU, + fallback_images: &FallbackImage, + image_id: AssetId, + ) -> (LightmapSlabIndex, LightmapSlotIndex) { + let slab_index = match other.free_slabs.minimum() { + None => self.create_slab(other, fallback_images), + Some(slab_index) => slab_index.into(), + }; -impl From for usize { - fn from(value: LightmapSlabIndex) -> Self { - value.0.get() as usize - } -} + let slab = &mut self.slabs[usize::from(slab_index)]; + let slot_index = slab.allocate(image_id); + if slab.is_full() { + other.free_slabs.remove(slab_index.into()); + } -impl From for usize { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() as usize + (slab_index, slot_index) } -} -impl From for u16 { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() - } -} + fn remove( + &mut self, + other: &mut RenderLightmapsU, + fallback_images: &FallbackImage, + slab_index: LightmapSlabIndex, + slot_index: LightmapSlotIndex, + ) { + let slab = &mut self.slabs[usize::from(slab_index)]; + slab.remove(fallback_images, slot_index); -impl From for u32 { - fn from(value: LightmapSlotIndex) -> Self { - value.0.get() as u32 + if !slab.is_full() { + other.free_slabs.grow_and_insert(slot_index.into()); + } } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index eb01811e0ec7f..0d8b89c4ffa09 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,5 +1,5 @@ use crate::material_bind_groups::{ - FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId, + FallbackBindlessResources, MaterialBindGroupAllocator, }; use crate::*; use alloc::sync::Arc; @@ -26,6 +26,12 @@ use bevy_ecs::{ SystemParamItem, }, }; +use bevy_material::material::{ErasedMaterialKey, ErasedMaterialPipelineKey, MaterialPipeline, MaterialProperties, RenderPhaseType}; +use bevy_material::render::{MeshPipeline, MeshPipelineKey}; +use bevy_material::{ + prelude::*, + render_phase::*, +}; use bevy_mesh::{ mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef, }; @@ -38,6 +44,9 @@ use bevy_render::camera::extract_cameras; use bevy_render::erased_render_asset::{ ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError, }; +use bevy_render::mesh::lightmap::RenderLightmapsU; +use bevy_render::mesh::material_bind_group::MaterialBindingId; +use bevy_render::mesh::mesh::RenderMeshInstanceFlags; use bevy_render::render_asset::{prepare_assets, RenderAssets}; use bevy_render::renderer::RenderQueue; use bevy_render::RenderStartup; @@ -432,19 +441,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, @@ -912,7 +908,7 @@ pub fn specialize_material_meshes( render_materials: Res>, render_mesh_instances: Res, render_material_instances: Res, - render_lightmaps: Res, + render_lightmaps: Res, render_visibility_ranges: Res, ( opaque_render_phases, @@ -1241,32 +1237,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; @@ -1307,173 +1278,6 @@ 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, -} - /// A resource that maps each untyped material ID to its binding. /// /// This duplicates information in `RenderAssets`, but it doesn't have the diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index e38439734ef8e..0cc0f365a15a5 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_render::render_resource::{BindlessSlabResourceLimit, PipelineCache}; +use bevy_render::{mesh::material_bind_group::{MaterialBindGroupIndex, MaterialBindGroupSlot, MaterialBindingId}, render_resource::{BindlessSlabResourceLimit, PipelineCache}}; use bevy_render::{ render_resource::{ BindGroup, BindGroupEntry, BindGroupLayoutDescriptor, BindingNumber, BindingResource, @@ -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..241c4d65064f2 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -1,5 +1,6 @@ use bevy_asset::Asset; use bevy_color::{Alpha, ColorToComponents}; +use bevy_material::material::MaterialPipeline; use bevy_math::{Affine2, Affine3, Mat2, Mat3, Vec2, Vec3, Vec4}; use bevy_mesh::MeshVertexBufferLayoutRef; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 317e6a6b06165..3be54d1b3d4b4 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -3,17 +3,16 @@ 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, Material, - MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey, + skin, DeferredDrawFunction, DeferredFragmentShader, + DeferredVertexShader, DrawMesh, EntitySpecializationTicks, Material, OpaqueRendererMethod, PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader, - PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, - RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, + PrepassVertexShader, RenderMaterialInstances, + RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, }; use bevy_app::{App, Plugin, PreUpdate}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::{Camera, Camera3d}; -use bevy_core_pipeline::{core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prepass::*}; +use bevy_core_pipeline::{deferred::*, prepass::*}; use bevy_ecs::{ prelude::*, system::{ @@ -21,13 +20,13 @@ use bevy_ecs::{ SystemParamItem, }, }; +use bevy_material::{alpha::AlphaMode, material::{ErasedMaterialPipelineKey, MaterialPipeline, MaterialProperties, RenderPhaseType}, render::{MeshLayouts, MeshPipeline, MeshPipelineKey}}; use bevy_math::{Affine3A, 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, lightmap::RenderLightmapsU, mesh::{PreviousGlobalTransform, RenderMeshInstanceFlags}, RenderMesh}, render_asset::{prepare_assets, RenderAssets}, render_phase::*, render_resource::{binding_types::uniform_buffer, *}, @@ -215,9 +214,6 @@ pub fn update_previous_view_data( } } -#[derive(Component, PartialEq, Default)] -pub struct PreviousGlobalTransform(pub Affine3A); - #[cfg(not(feature = "meshlet"))] type PreviousMeshFilter = With; #[cfg(feature = "meshlet")] @@ -821,7 +817,7 @@ pub fn specialize_prepass_material_meshes( render_materials: Res>, render_mesh_instances: Res, render_material_instances: Res, - render_lightmaps: Res, + render_lightmaps: Res, render_visibility_ranges: Res, view_key_cache: Res, views: Query<( diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.rs b/crates/bevy_pbr/src/prepass/prepass_bindings.rs index 3c66625ed8254..49ba36da93f16 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.rs +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.rs @@ -1,4 +1,5 @@ use bevy_core_pipeline::prepass::ViewPrepassTextures; +use bevy_material::render::MeshPipelineViewLayoutKey; use bevy_render::render_resource::{ binding_types::{ texture_2d, texture_2d_multisampled, texture_depth_2d, texture_depth_2d_multisampled, @@ -8,8 +9,6 @@ use bevy_render::render_resource::{ }; use bevy_utils::default; -use crate::MeshPipelineViewLayoutKey; - pub fn get_bind_group_layout_entries( layout_key: MeshPipelineViewLayoutKey, ) -> [Option; 4] { diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 04abbaca71d25..1af4e4835c9b2 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -34,22 +34,14 @@ use bevy_render::{ IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem, PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers, UntypedPhaseIndirectParametersBuffers, - }, - diagnostic::RecordDiagnostics, - experimental::occlusion_culling::OcclusionCulling, - render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt}, - render_resource::{ + }, diagnostic::RecordDiagnostics, experimental::occlusion_culling::OcclusionCulling, mesh::mesh::{MeshInputUniform, MeshUniform}, render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt}, render_resource::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindingResource, Buffer, BufferBinding, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, DynamicBindGroupLayoutEntries, PipelineCache, PushConstantRange, RawBufferVec, ShaderStages, ShaderType, SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType, UninitBufferVec, - }, - renderer::{RenderContext, RenderDevice, RenderQueue}, - settings::WgpuFeatures, - view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, - Render, RenderApp, RenderSystems, + }, renderer::{RenderContext, RenderDevice, RenderQueue}, settings::WgpuFeatures, view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, Render, RenderApp, RenderSystems }; use bevy_shader::Shader; use bevy_utils::{default, TypeIdMap}; @@ -58,7 +50,7 @@ use smallvec::{smallvec, SmallVec}; use tracing::warn; use crate::{ - graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform, + graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, }; use super::{ShadowView, ViewLightEntities}; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 8bce0f4556bb6..2da700367394b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -10,7 +10,6 @@ use bevy_camera::visibility::{ }; use bevy_camera::Camera3d; use bevy_color::ColorToComponents; -use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::system::SystemChangeTick; @@ -28,6 +27,9 @@ use bevy_light::{ Cascades, DirectionalLight, DirectionalLightShadowMap, NotShadowCaster, PointLight, PointLightShadowMap, ShadowFilteringMethod, SpotLight, VolumetricLight, }; +use bevy_material::material::ErasedMaterialPipelineKey; +use bevy_material::render::MeshPipelineKey; +use bevy_material::render_phase::DrawFunctionId; use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_platform::collections::{HashMap, HashSet}; use bevy_platform::hash::FixedHasher; @@ -35,6 +37,8 @@ use bevy_render::erased_render_asset::ErasedRenderAssets; use bevy_render::experimental::occlusion_culling::{ OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, }; +use bevy_render::mesh::lightmap::RenderLightmapsU; +use bevy_render::mesh::mesh::RenderMeshInstanceFlags; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, @@ -1755,7 +1759,7 @@ pub fn specialize_shadows( shadow_render_phases: Res>, mut pipelines: ResMut>, pipeline_cache: Res, - render_lightmaps: Res, + render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1ef120403e711..9dab94625a03b 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::MeshLayoutsBuilder, skin::skinUniforms_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, @@ -23,6 +23,7 @@ use bevy_light::{ EnvironmentMapLight, IrradianceVolume, NotShadowCaster, NotShadowReceiver, ShadowFilteringMethod, TransmittedShadowReceiver, }; +use bevy_material::render::{MeshLayouts, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}; use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; use bevy_mesh::{ skinning::SkinnedMesh, BaseMeshPipelineKey, Mesh, Mesh3d, MeshTag, MeshVertexBufferLayoutRef, @@ -38,7 +39,7 @@ use bevy_render::{ }, no_gpu_preprocessing, GetBatchData, GetFullBatchData, NoAutomaticBatching, }, - mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo}, + mesh::{allocator::MeshAllocator, lightmap::{pack_lightmap_uv_rect, LightmapSlabIndex, RenderLightmapsU}, material_bind_group::MaterialBindingId, mesh::{MeshFlags, MeshInputUniform, MeshTransforms, MeshUniform, PreviousGlobalTransform, RenderMeshInstanceCpu, RenderMeshInstanceFlags, RenderMeshInstanceGpu, RenderMeshInstanceShared}, skin::SkinUniforms, RenderMesh, RenderMeshBufferInfo}, render_asset::RenderAssets, render_phase::{ BinnedRenderPhasePlugin, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, @@ -59,10 +60,8 @@ 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 self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; use crate::{ render::{ morph::{ @@ -87,7 +86,6 @@ use bevy_render::RenderSystems::PrepareAssets; use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; -use static_assertions::const_assert_eq; /// Provides support for rendering 3D meshes. pub struct MeshRenderPlugin { @@ -110,17 +108,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 +198,7 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::() + .add_systems(RenderStartup, skinUniforms_from_world) .add_systems( Render, check_views_need_specialization.in_set(PrepareAssets), @@ -280,8 +267,8 @@ impl Plugin for MeshRenderPlugin { } render_app - .init_resource::() - .init_resource::(); + .add_systems(RenderStartup, meshPipelineViewLayouts_from_world) + .add_systems(RenderStartup, meshPipeline_from_world); } // Load the mesh_bindings shader module here as it depends on runtime information about @@ -428,119 +415,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. /// @@ -565,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. /// @@ -811,68 +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. /// @@ -1100,7 +746,7 @@ impl RenderMeshInstanceGpuBuilder { mesh_allocator: &MeshAllocator, mesh_material_ids: &RenderMaterialInstances, render_material_bindings: &RenderMaterialBindings, - render_lightmaps: &RenderLightmaps, + render_lightmaps: &RenderLightmapsU, skin_uniforms: &SkinUniforms, timestamp: FrameCount, meshes_to_reextract_next_frame: &mut MeshesToReextractNextFrame, @@ -1659,7 +1305,7 @@ pub fn collect_meshes_for_gpu_building( mesh_allocator: Res, mesh_material_ids: Res, render_material_bindings: Res, - render_lightmaps: Res, + render_lightmaps: Res, skin_uniforms: Res, frame_count: Res, mut meshes_to_reextract_next_frame: ResMut, @@ -1758,46 +1404,8 @@ 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, - // This dummy white texture is to be used in place of optional StandardMaterial textures - pub dummy_white_gpu_image: GpuImage, - 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 { +// impl FromWorld for MeshPipeline { + pub fn meshPipeline_from_world(world: &mut World) { let shader = load_embedded_asset!(world, "mesh.wgsl"); let mut system_state: SystemState<( Res, @@ -1812,46 +1420,48 @@ impl FromWorld for MeshPipeline { let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); + todo!("Build 1x1 image"); // A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures - let dummy_white_gpu_image = { - let image = Image::default(); - let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = match image.sampler { - ImageSampler::Default => (**default_sampler).clone(), - ImageSampler::Descriptor(ref descriptor) => { - render_device.create_sampler(&descriptor.as_wgpu()) - } - }; - - if let Ok(format_size) = image.texture_descriptor.format.pixel_size() { - render_queue.write_texture( - texture.as_image_copy(), - image.data.as_ref().expect("Image was created without data"), - TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(image.width() * format_size as u32), - rows_per_image: None, - }, - image.texture_descriptor.size, - ); - } - - let texture_view = texture.create_view(&TextureViewDescriptor::default()); - GpuImage { - texture, - texture_view, - texture_format: image.texture_descriptor.format, - sampler, - size: image.texture_descriptor.size, - mip_level_count: image.texture_descriptor.mip_level_count, - } - }; - - MeshPipeline { + // let dummy_white_gpu_image = { + // let image = Image::default(); + // let texture = render_device.create_texture(&image.texture_descriptor); + // let sampler = match image.sampler { + // ImageSampler::Default => (**default_sampler).clone(), + // ImageSampler::Descriptor(ref descriptor) => { + // render_device.create_sampler(&descriptor.as_wgpu()) + // } + // }; + + // if let Ok(format_size) = image.texture_descriptor.format.pixel_size() { + // render_queue.write_texture( + // texture.as_image_copy(), + // image.data.as_ref().expect("Image was created without data"), + // TexelCopyBufferLayout { + // offset: 0, + // bytes_per_row: Some(image.width() * format_size as u32), + // rows_per_image: None, + // }, + // image.texture_descriptor.size, + // ); + // } + + // let texture_view = texture.create_view(&TextureViewDescriptor::default()); + // GpuImage { + // texture, + // texture_view, + // texture_format: image.texture_descriptor.format, + // sampler, + // size: image.texture_descriptor.size, + // mip_level_count: image.texture_descriptor.mip_level_count, + // } + // }; + let dummy_white_gpu_image : Handle = todo!(); + + let res = MeshPipeline { view_layouts: view_layouts.clone(), clustered_forward_buffer_binding_type, dummy_white_gpu_image, - mesh_layouts: MeshLayouts::new(&render_device, &render_adapter), + mesh_layouts: MeshLayoutsBuilder::new(&render_device, &render_adapter), shader, per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), @@ -1860,13 +1470,15 @@ impl FromWorld for MeshPipeline { &render_adapter, ), skins_use_uniform_buffers: skins_use_uniform_buffers(&render_device), - } + }; + + world.insert_resource(res); } -} +// } -impl MeshPipeline { +// impl MeshPipeline { pub fn get_image_texture<'a>( - &'a self, + sel: &'a MeshPipeline, gpu_images: &'a RenderAssets, handle_option: &Option>, ) -> Option<(&'a TextureView, &'a Sampler)> { @@ -1874,777 +1486,14 @@ impl MeshPipeline { let gpu_image = gpu_images.get(handle)?; Some((&gpu_image.texture_view, &gpu_image.sampler)) } else { - Some(( - &self.dummy_white_gpu_image.texture_view, - &self.dummy_white_gpu_image.sampler, - )) - } - } - - pub fn get_view_layout( - &self, - layout_key: MeshPipelineViewLayoutKey, - ) -> &MeshPipelineViewLayout { - self.view_layouts.get_view_layout(layout_key) - } -} - -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()); + todo!("Figure this out"); + // Some(( + // &sel.dummy_white_gpu_image.texture_view, + // &sel.dummy_white_gpu_image.sampler, + // )) } - - 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. /// @@ -2731,7 +1580,8 @@ pub fn prepare_mesh_bind_groups( >, skins_uniform: Res, weights_uniform: Res, - mut render_lightmaps: ResMut, + mut render_lightmaps_u: ResMut, + mut render_lightmaps_l: ResMut, ) { // CPU mesh preprocessing path. if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer @@ -2748,7 +1598,8 @@ pub fn prepare_mesh_bind_groups( &pipeline_cache, &skins_uniform, &weights_uniform, - &mut render_lightmaps, + &mut render_lightmaps_u, + &mut render_lightmaps_l ); commands.insert_resource(MeshBindGroups::CpuPreprocessing( @@ -2779,7 +1630,8 @@ pub fn prepare_mesh_bind_groups( &pipeline_cache, &skins_uniform, &weights_uniform, - &mut render_lightmaps, + &mut render_lightmaps_u, + &mut render_lightmaps_l ); gpu_preprocessing_mesh_bind_groups.insert(*phase_type_id, mesh_phase_bind_groups); @@ -2800,7 +1652,8 @@ fn prepare_mesh_bind_groups_for_phase( pipeline_cache: &PipelineCache, skins_uniform: &SkinUniforms, weights_uniform: &MorphUniforms, - render_lightmaps: &mut RenderLightmaps, + render_lightmaps_u: &mut RenderLightmapsU, + render_lightmaps_l: &mut RenderLightmapsL, ) -> MeshPhaseBindGroups { let layouts = &mesh_pipeline.mesh_layouts; @@ -2877,8 +1730,9 @@ fn prepare_mesh_bind_groups_for_phase( } // Create lightmap bindgroups. There will be one bindgroup for each slab. - let bindless_supported = render_lightmaps.bindless_supported; - for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter_mut().enumerate() { + let bindless_supported = render_lightmaps_u.bindless_supported; + for (lightmap_slab_id, lightmap_slab_u) in render_lightmaps_u.slabs.iter_mut().enumerate() { + let lightmap_slab = render_lightmaps_l.slabs.get(lightmap_slab_id).unwrap(); groups.lightmaps.insert( LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()), layouts.lightmapped( @@ -2991,7 +1845,7 @@ impl RenderCommand

for SetMeshBindGroup { SRes, SRes, SRes, - SRes, + SRes, ); type ViewQuery = Has; type ItemQuery = (); diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index acaa31c312390..237fb068ecd1d 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -1,13 +1,13 @@ //! Bind group layout related definitions for the mesh pipeline. +use bevy_material::render::MeshLayouts; use bevy_math::Mat4; use bevy_mesh::morph::MAX_MORPH_WEIGHTS; use bevy_render::{ - render_resource::*, - renderer::{RenderAdapter, RenderDevice}, + mesh::skin::MAX_JOINTS, render_resource::*, renderer::{RenderAdapter, RenderDevice} }; -use crate::{binding_arrays_are_usable, render::skin::MAX_JOINTS, LightmapSlab}; +use crate::{binding_arrays_are_usable, LightmapSlab}; const MORPH_WEIGHT_SIZE: usize = size_of::(); @@ -26,17 +26,16 @@ mod layout_entry { use core::num::NonZeroU32; use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; - use crate::{render::skin, MeshUniform, LIGHTMAPS_PER_SLAB}; + use crate::{render::skin, LIGHTMAPS_PER_SLAB}; use bevy_render::{ - render_resource::{ + mesh::mesh::MeshUniform, render_resource::{ binding_types::{ sampler, storage_buffer_read_only_sized, texture_2d, texture_3d, uniform_buffer_sized, }, BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, SamplerBindingType, ShaderStages, TextureSampleType, - }, - renderer::RenderDevice, + }, renderer::RenderDevice }; pub(super) fn model(render_device: &RenderDevice) -> BindGroupLayoutEntryBuilder { @@ -160,48 +159,146 @@ 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 MeshLayoutsBuilder { + fn new(render_device: &RenderDevice, render_adapter: &RenderAdapter) -> Self; + + // ---------- create individual BindGroupLayouts ---------- + + fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + + /// Creates the layout for skinned meshes. + fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + + /// Creates the layout for skinned meshes with the infrastructure to compute + /// motion vectors. + fn skinned_motion_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + + /// Creates the layout for meshes with morph targets. + fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; + + /// Creates the layout for meshes with morph targets and the infrastructure + /// to compute motion vectors. + fn morphed_motion_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; - /// Includes the lightmap texture and uniform. - pub lightmapped: BindGroupLayoutDescriptor, + /// Creates the bind group layout for meshes with both skins and morph + /// targets. + fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayoutDescriptor; - /// Also includes the uniform for skinning - pub skinned: BindGroupLayoutDescriptor, + /// Creates the bind group layout for meshes with both skins and morph + /// targets, in addition to the infrastructure to compute motion vectors. + fn morphed_skinned_motion_layout(render_device: &RenderDevice) -> 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, + fn lightmapped_layout( + render_device: &RenderDevice, + render_adapter: &RenderAdapter, + ) -> BindGroupLayoutDescriptor; - /// Also includes the uniform and [`MorphAttributes`] for morph targets. + // ---------- BindGroup methods ---------- + + 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; + + /// Creates the bind group for skinned meshes with no morph targets. + fn skinned( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + ) -> BindGroup; + + /// Creates the bind group for skinned meshes with no morph targets, with + /// the infrastructure to compute motion vectors. /// - /// [`MorphAttributes`]: bevy_mesh::morph::MorphAttributes - pub morphed: BindGroupLayoutDescriptor, + /// `current_skin` is the buffer of joint matrices for this frame; + /// `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. + fn skinned_motion( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + prev_skin: &Buffer, + ) -> BindGroup; - /// 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, + /// Creates the bind group for meshes with no skins but morph targets. + fn morphed( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_weights: &Buffer, + targets: &TextureView, + ) -> BindGroup; - /// Also includes both uniforms for skinning and morph targets, also the - /// morph target [`MorphAttributes`] binding. + /// Creates the bind group for meshes with no skins but morph targets, in + /// addition to the infrastructure to compute motion vectors. /// - /// [`MorphAttributes`]: bevy_mesh::morph::MorphAttributes - pub morphed_skinned: BindGroupLayoutDescriptor, + /// `current_weights` is the buffer of morph weights for this frame; + /// `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. + fn morphed_motion( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_weights: &Buffer, + targets: &TextureView, + prev_weights: &Buffer, + ) -> BindGroup; - /// 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, + /// Creates the bind group for meshes with skins and morph targets. + fn morphed_skinned( + &self, + render_device: &RenderDevice, + pipeline_cache: &PipelineCache, + model: &BindingResource, + current_skin: &Buffer, + current_weights: &Buffer, + targets: &TextureView, + ) -> BindGroup; + + /// Creates the bind group for meshes with skins and morph targets, in + /// addition to the infrastructure to compute motion vectors. + /// + /// See the documentation for [`MeshLayouts::skinned_motion`] and + /// [`MeshLayouts::morphed_motion`] above for more information about the + /// `current_skin`, `prev_skin`, `current_weights`, and `prev_weights` + /// buffers. + 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 MeshLayoutsBuilder 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 +466,7 @@ impl MeshLayouts { // ---------- BindGroup methods ---------- - pub fn model_only( + fn model_only( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -382,7 +479,7 @@ impl MeshLayouts { ) } - pub fn lightmapped( + fn lightmapped( &self, render_device: &RenderDevice, pipeline_cache: &PipelineCache, @@ -416,7 +513,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 +537,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 +557,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 +583,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 +605,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 +633,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..40054fac82025 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -18,6 +18,7 @@ use bevy_ecs::{ }; use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, IrradianceVolume}; +use bevy_material::render::{MeshPipeline, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}; use bevy_math::Vec4; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, @@ -38,125 +39,16 @@ use crate::{ clustered::{ DecalsBuffer, RenderClusteredDecals, RenderViewClusteredDecalBindGroupEntries, }, - }, - environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, - irradiance_volume::{ - self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, - }, - prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, - GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, - MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, - ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, - ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + }, environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, irradiance_volume::{ + self, RenderViewIrradianceVolumeBindGroupEntries, + }, prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT }; #[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, - - #[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 - } -} - -impl From for MeshPipelineViewLayoutKey { - fn from(value: Msaa) -> Self { +// impl From for MeshPipelineViewLayoutKey { + pub fn meshPipelineViewLayoutKey_from_msaa(value: Msaa) -> MeshPipelineViewLayoutKey { let mut result = MeshPipelineViewLayoutKey::empty(); if value.samples() > 1 { @@ -165,10 +57,10 @@ impl From for MeshPipelineViewLayoutKey { result } -} +// } -impl From> for MeshPipelineViewLayoutKey { - fn from(value: Option<&ViewPrepassTextures>) -> Self { +// impl From> for MeshPipelineViewLayoutKey { + pub fn meshPipelineViewLayoutKey_from_viewPrepassTexture(value: Option<&ViewPrepassTextures>) -> MeshPipelineViewLayoutKey { let mut result = MeshPipelineViewLayoutKey::empty(); if let Some(prepass_textures) = value { @@ -188,7 +80,7 @@ impl From> for MeshPipelineViewLayoutKey { result } -} +// } pub(crate) fn buffer_layout( buffer_binding_type: BufferBindingType, @@ -416,17 +308,8 @@ 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 { +// impl FromWorld for MeshPipelineViewLayouts { + pub fn meshPipelineViewLayouts_from_world(world: &mut World) { // Generates all possible view layouts for the mesh pipeline, based on all combinations of // [`MeshPipelineViewLayoutKey`] flags. @@ -438,7 +321,7 @@ impl FromWorld for MeshPipelineViewLayouts { 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 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, @@ -466,27 +349,11 @@ impl FromWorld for MeshPipelineViewLayouts { #[cfg(debug_assertions)] texture_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 + world.insert_resource(res); } -} +// } /// Generates all possible view layouts for the mesh pipeline, based on all combinations of /// [`MeshPipelineViewLayoutKey`] flags. @@ -617,8 +484,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 = meshPipelineViewLayoutKey_from_msaa(*msaa) + | meshPipelineViewLayoutKey_from_viewPrepassTexture(prepass_textures); if has_oit { layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED; } diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 6a29823022b58..c7a50b1e8bd08 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -11,7 +11,6 @@ pub use fog::*; pub use gpu_preprocess::*; pub use light::*; pub use mesh::*; -pub use mesh_bindings::MeshLayouts; pub use mesh_view_bindings::*; pub use morph::*; -pub use skin::{extract_skins, prepare_skins, skins_use_uniform_buffers, SkinUniforms, MAX_JOINTS}; +pub use skin::{extract_skins, prepare_skins, skins_use_uniform_buffers}; diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 44e55094236d3..75a6d7d7531f0 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -7,6 +7,7 @@ use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}; use bevy_platform::collections::hash_map::Entry; +use bevy_render::mesh::skin::{SkinUniformInfo, SkinUniforms, JOINTS_PER_ALLOCATION_UNIT, MAX_JOINTS, MAX_TOTAL_JOINTS}; use bevy_render::render_resource::{Buffer, BufferDescriptor}; use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; use bevy_render::{ @@ -20,28 +21,6 @@ use offset_allocator::{Allocation, Allocator}; use smallvec::SmallVec; 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. /// @@ -51,72 +30,8 @@ const JOINTS_PER_ALLOCATION_UNIT: u32 = (256 / size_of::()) as u32; /// 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 { +// impl FromWorld for SkinUniforms { + pub fn skinUniforms_from_world(world: &mut World) { let device = world.resource::(); let buffer_usages = (if skins_use_uniform_buffers(device) { BufferUsages::UNIFORM @@ -140,7 +55,7 @@ impl FromWorld for SkinUniforms { mapped_at_creation: false, }); - Self { + let res = SkinUniforms { current_staging_buffer: vec![], current_buffer, prev_buffer, @@ -148,45 +63,10 @@ impl FromWorld for SkinUniforms { 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) - } - - /// 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, -} - -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 /// storage buffers aren't supported on the current platform. diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index b752b3daf9214..6eb7fc4889607 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -23,6 +23,7 @@ use bevy_ecs::{ }; use bevy_image::BevyDefault as _; use bevy_light::EnvironmentMapLight; +use bevy_material::render::{MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ diagnostic::RecordDiagnostics, @@ -47,9 +48,7 @@ use bevy_utils::{once, prelude::default}; use tracing::info; use crate::{ - binding_arrays_are_usable, graph::NodePbr, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, - MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset, - ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, + binding_arrays_are_usable, graph::NodePbr, meshPipelineViewLayoutKey_from_msaa, MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset }; /// Enables screen-space reflections for a camera. @@ -438,7 +437,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 = meshPipelineViewLayoutKey_from_msaa(Msaa::Off) | MeshPipelineViewLayoutKey::DEPTH_PREPASS | MeshPipelineViewLayoutKey::DEFERRED_PREPASS; mesh_pipeline_view_key.set( diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index f33210cba2f02..2c36064cb569b 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -19,6 +19,7 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault, Image}; use bevy_light::{FogVolume, VolumetricFog, VolumetricLight}; +use bevy_material::render::{MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}; use bevy_math::{vec4, Affine3A, Mat4, Vec3, Vec3A, Vec4}; use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef}; use bevy_render::{ @@ -50,9 +51,7 @@ use bevy_utils::prelude::default; use bitflags::bitflags; use crate::{ - MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, - ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, - ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, + meshPipelineViewLayoutKey_from_msaa, MeshViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset }; use super::FogAssets; @@ -647,7 +646,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 = meshPipelineViewLayoutKey_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..a04c56a892f6d 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,5 +1,5 @@ use crate::{ - DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances, + DrawMesh, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache, ViewSpecializationTicks, }; @@ -18,6 +18,7 @@ use bevy_ecs::{ query::QueryItem, system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, }; +use bevy_material::{render::{MeshPipeline, MeshPipelineKey}, render_phase::DrawFunctionId}; use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef}; use bevy_platform::{ collections::{HashMap, HashSet}, @@ -30,8 +31,7 @@ use bevy_render::{ diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ - allocator::{MeshAllocator, SlabId}, - RenderMesh, + allocator::{MeshAllocator, SlabId}, mesh::RenderMeshInstanceFlags, RenderMesh }, prelude::*, render_asset::{ @@ -40,7 +40,7 @@ use bevy_render::{ render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, + CachedRenderPipelinePhaseItem, DrawFunctions, PhaseItem, PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index b3daf57a84236..659b50797e008 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -60,13 +60,15 @@ 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_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" } bevy_utils = { path = "../bevy_utils", version = "0.18.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.18.0-dev" } bevy_image = { path = "../bevy_image", 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_camera = { path = "../bevy_camera", version = "0.18.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.18.0-dev" } diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 1c9dcebf3df5b..7db9a787a47c5 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/batching/mesh_batch.rs b/crates/bevy_render/src/batching/mesh_batch.rs new file mode 100644 index 0000000000000..33522e7e056fa --- /dev/null +++ b/crates/bevy_render/src/batching/mesh_batch.rs @@ -0,0 +1,185 @@ +use bevy_asset::AssetId; +use bevy_ecs::{entity::Entity, system::{lifetimeless::SRes, SystemParamItem}}; +use bevy_material::render::MeshPipeline; +use bevy_mesh::Mesh; +use bevy_utils::default; +use nonmax::NonMaxU32; +use tracing::{error, warn}; + +use crate::{ + batching::{gpu_preprocessing::{IndirectParametersCpuMetadata, UntypedPhaseIndirectParametersBuffers}, GetBatchData, GetFullBatchData}, + mesh::{allocator::MeshAllocator, lightmap::{LightmapSlabIndex, RenderLightmapsU}, material_bind_group::MaterialBindGroupIndex, mesh::{ + MeshInputUniform, + MeshUniform, + RenderMeshInstances + }, skin::SkinUniforms, RenderMesh}, + render_asset::RenderAssets, sync_world::MainEntity +}; + +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); + } + } +} + + diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 40ce7ce3b4aa3..2c550668542d9 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -3,13 +3,14 @@ use bevy_ecs::{ entity::Entity, system::{ResMut, SystemParam, SystemParamItem}, }; +use bevy_material::render_phase::DrawFunctionId; use bytemuck::Pod; use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers; use nonmax::NonMaxU32; use crate::{ render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItemExtraIndex, + BinnedPhaseItem, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, ViewBinnedRenderPhases, }, render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, @@ -18,6 +19,7 @@ use crate::{ pub mod gpu_preprocessing; pub mod no_gpu_preprocessing; +pub mod mesh_batch; /// Add this component to mesh entities to disable automatic batching #[derive(Component, Default)] diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d2790bfe760b5..40bcaec62b4f1 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; @@ -69,8 +68,8 @@ pub mod view; pub mod prelude { #[doc(hidden)] pub use crate::{ - alpha::AlphaMode, camera::NormalizedRenderTargetExt as _, texture::ManualTextureViews, - view::Msaa, ExtractSchedule, + camera::NormalizedRenderTargetExt as _, texture::ManualTextureViews, view::Msaa, + ExtractSchedule, }; } diff --git a/crates/bevy_render/src/mesh/lightmap.rs b/crates/bevy_render/src/mesh/lightmap.rs new file mode 100644 index 0000000000000..31e181bf01920 --- /dev/null +++ b/crates/bevy_render/src/mesh/lightmap.rs @@ -0,0 +1,171 @@ + +use bevy_asset::AssetId; +use bevy_derive::{Deref, DerefMut}; +use bevy_image::Image; +use fixedbitset::FixedBitSet; +use nonmax::{NonMaxU16, NonMaxU32}; +use bevy_math::{uvec2, vec4, Rect, UVec2}; +use bevy_ecs::{ + resource::Resource, +}; +use bevy_platform::collections::HashSet; + +use crate::sync_world::MainEntityHashMap; + +/// 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); + + + +/// 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(crate) 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 RenderLightmapsU { + /// 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 LightmapSlabUnloaded { + /// The GPU images in this slab. + pub lightmaps: Vec, + pub free_slots_bitmask: u32, +} + +pub struct AllocatedLightmapUnloaded { + // This will only be present if the lightmap is allocated but not loaded. + pub asset_id: AssetId, +} + +/// 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 RenderLightmap { + /// Creates a new lightmap from a texture, a UV rect, and a slab and slot + /// index pair. + pub fn new( + uv_rect: Rect, + slab_index: LightmapSlabIndex, + slot_index: LightmapSlotIndex, + bicubic_sampling: bool, + ) -> Self { + Self { + uv_rect, + slab_index, + slot_index, + bicubic_sampling, + } + } +} + +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..3e46366215650 --- /dev/null +++ b/crates/bevy_render/src/mesh/material_bind_group.rs @@ -0,0 +1,60 @@ +//! Material bind group management for bindless resources. +//! +//! In bindless mode, Bevy's renderer groups materials into bind groups. This +//! allocator manages each bind group, assigning slots to materials as +//! appropriate. + +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/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs new file mode 100644 index 0000000000000..d76b2bc284183 --- /dev/null +++ b/crates/bevy_render/src/mesh/mesh.rs @@ -0,0 +1,513 @@ +use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; +use bevy_camera::{ + primitives::Aabb, + visibility::{NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange}, + Camera, Camera3d, Projection, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_diagnostic::FrameCount; +use bevy_ecs::{ + prelude::*, + query::{QueryData, ROQueryItem}, + system::{lifetimeless::*, SystemParamItem, SystemState}, +}; +use bevy_encase_derive::ShaderType; +use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; +use bevy_material::render::{MeshLayouts, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts}; +use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; +use bevy_mesh::{ + skinning::SkinnedMesh, BaseMeshPipelineKey, Mesh, Mesh3d, MeshTag, MeshVertexBufferLayoutRef, + VertexAttributeDescriptor, +}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; +use bevy_render::{ + batching::{ + gpu_preprocessing::{ + self, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, + IndirectParametersCpuMetadata, IndirectParametersIndexed, IndirectParametersNonIndexed, + InstanceInputUniformBuffer, UntypedPhaseIndirectParametersBuffers, + }, + no_gpu_preprocessing, GetBatchData, GetFullBatchData, NoAutomaticBatching, + }, + mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo}, + render_asset::RenderAssets, + render_phase::{ + BinnedRenderPhasePlugin, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, + RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, + }, + // render_resource::*, + renderer::{RenderAdapter, RenderDevice, RenderQueue}, + sync_world::MainEntityHashSet, + texture::{DefaultImageSampler, GpuImage}, + view::{ + self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewTarget, + ViewUniformOffset, + }, + Extract, +}; +use bevy_shader::{load_shader_library, Shader, ShaderDefVal, ShaderSettings}; +use bevy_transform::components::GlobalTransform; +use bevy_utils::{default, Parallel, TypeIdMap}; +use glam::Affine3A; +use core::any::TypeId; +use core::mem::size_of; +use tracing::{error, warn}; + +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; +use bevy_render::camera::TemporalJitter; +use bevy_render::prelude::Msaa; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; +use bevy_render::view::ExtractedView; +use bevy_render::RenderSystems::PrepareAssets; + +use bytemuck::{Pod, Zeroable}; +use nonmax::{NonMaxU16, NonMaxU32}; +use smallvec::{smallvec, SmallVec}; + +use crate::{mesh::{lightmap::{pack_lightmap_uv_rect, LightmapSlabIndex, LightmapSlotIndex}, material_bind_group::{MaterialBindGroupSlot, MaterialBindingId}}, render_resource::GpuArrayBuffer}; + +#[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, +} + +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 + /// during [`RenderMeshInstanceGpuBuilder::update`]. + 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 [`RenderMeshInstanceGpuBuilder::update`]. + 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. + 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), + } + } + + /// 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) + } + + /// 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) + } + + /// 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); + } + } +} + +/// 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) +} diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index d8c7bc63e1ec4..674fe5aeadb28 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,4 +1,8 @@ pub mod allocator; +pub mod mesh; +pub mod material_bind_group; +pub mod lightmap; +pub mod skin; use crate::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_resource::TextureView, diff --git a/crates/bevy_render/src/mesh/skin.rs b/crates/bevy_render/src/mesh/skin.rs new file mode 100644 index 0000000000000..d266d0773497f --- /dev/null +++ b/crates/bevy_render/src/mesh/skin.rs @@ -0,0 +1,143 @@ +use core::mem::{self, size_of}; +use std::sync::OnceLock; + +use bevy_asset::{prelude::AssetChanged, Assets}; +use bevy_camera::visibility::ViewVisibility; +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}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; +use bevy_render::{ + batching::NoAutomaticBatching, + render_resource::BufferUsages, + renderer::{RenderDevice, RenderQueue}, + Extract, +}; +use bevy_transform::prelude::GlobalTransform; +use offset_allocator::{Allocation, Allocator}; +use smallvec::SmallVec; +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. +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 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/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 39c6a074e6f57..f3ebcadeacb9c 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -7,6 +7,7 @@ use bevy_ecs::{ system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState}, world::World, }; +use bevy_material::render_phase::DrawFunctionId; use bevy_utils::TypeIdMap; use core::{any::TypeId, fmt::Debug, hash::Hash}; use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -49,11 +50,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. diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 253d06f48521e..59e144829c2e5 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -32,6 +32,7 @@ use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::entity::EntityHash; +use bevy_material::render_phase::DrawFunctionId; use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_utils::default; pub use draw::*; @@ -61,42 +62,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}, }; use bevy_render::renderer::RenderAdapterInfo; -pub use bevy_render_macros::ShaderLabel; +pub use bevy_material_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/mesh_pipeline_specializer.rs b/crates/bevy_render/src/render_resource/mesh_pipeline_specializer.rs new file mode 100644 index 0000000000000..1ed7dcbc3ad4c --- /dev/null +++ b/crates/bevy_render/src/render_resource/mesh_pipeline_specializer.rs @@ -0,0 +1,457 @@ +use bevy_image::BevyDefault; +use bevy_material::{render::{MeshLayouts, MeshPipeline, MeshPipelineKey}, render_resource::{BindGroupLayoutDescriptor, FragmentState, RenderPipelineDescriptor, SpecializedMeshPipelineError, VertexState}}; +use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef, VertexAttributeDescriptor}; +use bevy_shader::ShaderDefVal; +use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, Face, MultisampleState, PrimitiveState, StencilFaceState, StencilState, TextureFormat}; + +use crate::{render_resource::SpecializedMeshPipeline, view::ViewTarget}; +use bevy_utils::default; +use tracing::{error, warn}; + + + +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(), + } +} + + +// 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_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 2d8ddabf2dbf6..2872644703092 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; @@ -10,6 +9,7 @@ mod gpu_array_buffer; mod pipeline; mod pipeline_cache; mod pipeline_specializer; +mod mesh_pipeline_specializer; pub mod resource_macros; mod specializer; mod storage_buffer; @@ -19,7 +19,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::*; @@ -27,6 +26,7 @@ pub use gpu_array_buffer::*; pub use pipeline::*; pub use pipeline_cache::*; pub use pipeline_specializer::*; +pub use mesh_pipeline_specializer::*; pub use specializer::*; pub use storage_buffer::*; pub use texture::*; @@ -64,6 +64,7 @@ pub use wgpu::{ VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, VertexStepMode, COPY_BUFFER_ALIGNMENT, }; +pub use bevy_material::render_resource::*; pub mod encase { pub use bevy_encase_derive::ShaderType; diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 98ab1b4d67228..8f311037ccdd6 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -87,113 +87,3 @@ impl Deref for ComputePipeline { } } -#[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: Option>, - pub entries: Vec, -} - -impl BindGroupLayoutDescriptor { - pub fn new(label: impl Into>, entries: &[BindGroupLayoutEntry]) -> Self { - Self { - label: Some(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..88abd792a2768 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -3,6 +3,7 @@ use crate::render_resource::{ RenderPipelineDescriptor, }; use bevy_ecs::resource::Resource; +use bevy_material::render_resource::SpecializedMeshPipelineError; use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout}; use bevy_platform::{ collections::{ @@ -251,9 +252,3 @@ impl SpecializedMeshPipelines { } } } - -#[derive(Error, Debug)] -pub enum SpecializedMeshPipelineError { - #[error(transparent)] - MissingVertexAttribute(#[from] MissingVertexAttributeError), -} 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_sprite_render/src/mesh2d/material.rs b/crates/bevy_sprite_render/src/mesh2d/material.rs index 079b050c0f10a..e27175121f4b8 100644 --- a/crates/bevy_sprite_render/src/mesh2d/material.rs +++ b/crates/bevy_sprite_render/src/mesh2d/material.rs @@ -20,6 +20,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; +use bevy_material::render_phase::DrawFunctionId; use bevy_math::FloatOrd; use bevy_mesh::MeshVertexBufferLayoutRef; use bevy_platform::collections::HashMap; @@ -32,7 +33,7 @@ use bevy_render::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, render_phase::{ - AddRenderCommand, BinnedRenderPhaseType, DrawFunctionId, DrawFunctions, InputUniformIndex, + AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index 98ab9c73f3fae..9196ab6c432c7 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -17,6 +17,7 @@ use bevy_ecs::{ query::QueryItem, system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, }; +use bevy_material::render_phase::DrawFunctionId; use bevy_mesh::{Mesh2d, MeshVertexBufferLayoutRef}; use bevy_platform::{ collections::{HashMap, HashSet}, @@ -39,7 +40,7 @@ use bevy_render::{ render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + CachedRenderPipelinePhaseItem, DrawFunctions, InputUniformIndex, PhaseItem, PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, 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/crates/bevy_ui_render/src/render_pass.rs b/crates/bevy_ui_render/src/render_pass.rs index 407f200e86b1c..1de23225aa825 100644 --- a/crates/bevy_ui_render/src/render_pass.rs +++ b/crates/bevy_ui_render/src/render_pass.rs @@ -7,6 +7,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, }; +use bevy_material::render_phase::DrawFunctionId; use bevy_math::FloatOrd; use bevy_render::{ camera::ExtractedCamera,