bevy_pbr/meshlet/
mod.rs

1//! Render high-poly 3d meshes using an efficient GPU-driven method. See [`MeshletPlugin`] and [`MeshletMesh`] for details.
2
3mod asset;
4#[cfg(feature = "meshlet_processor")]
5mod from_mesh;
6mod instance_manager;
7mod material_pipeline_prepare;
8mod material_shade_nodes;
9mod meshlet_mesh_manager;
10mod persistent_buffer;
11mod persistent_buffer_impls;
12mod pipelines;
13mod resource_manager;
14mod visibility_buffer_raster_node;
15
16pub mod graph {
17    use bevy_render::render_graph::RenderLabel;
18
19    #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
20    pub enum NodeMeshlet {
21        VisibilityBufferRasterPass,
22        Prepass,
23        DeferredPrepass,
24        MainOpaquePass,
25    }
26}
27
28pub(crate) use self::{
29    instance_manager::{queue_material_meshlet_meshes, InstanceManager},
30    material_pipeline_prepare::{
31        prepare_material_meshlet_meshes_main_opaque_pass, prepare_material_meshlet_meshes_prepass,
32    },
33};
34
35pub use self::asset::{
36    MeshletMesh, MeshletMeshLoader, MeshletMeshSaver, MESHLET_MESH_ASSET_VERSION,
37};
38#[cfg(feature = "meshlet_processor")]
39pub use self::from_mesh::{
40    MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
41};
42use self::{
43    graph::NodeMeshlet,
44    instance_manager::extract_meshlet_mesh_entities,
45    material_pipeline_prepare::{
46        MeshletViewMaterialsDeferredGBufferPrepass, MeshletViewMaterialsMainOpaquePass,
47        MeshletViewMaterialsPrepass,
48    },
49    material_shade_nodes::{
50        MeshletDeferredGBufferPrepassNode, MeshletMainOpaquePass3dNode, MeshletPrepassNode,
51    },
52    meshlet_mesh_manager::perform_pending_meshlet_mesh_writes,
53    pipelines::*,
54    resource_manager::{
55        prepare_meshlet_per_frame_resources, prepare_meshlet_view_bind_groups, ResourceManager,
56    },
57    visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode,
58};
59use crate::{
60    graph::NodePbr, meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager,
61    PreviousGlobalTransform,
62};
63use bevy_app::{App, Plugin};
64use bevy_asset::{embedded_asset, AssetApp, AssetId, Handle};
65use bevy_camera::visibility::{self, Visibility, VisibilityClass};
66use bevy_core_pipeline::{
67    core_3d::graph::{Core3d, Node3d},
68    prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass},
69};
70use bevy_derive::{Deref, DerefMut};
71use bevy_ecs::{
72    component::Component,
73    entity::Entity,
74    query::Has,
75    reflect::ReflectComponent,
76    schedule::IntoScheduleConfigs,
77    system::{Commands, Query, Res},
78};
79use bevy_reflect::{std_traits::ReflectDefault, Reflect};
80use bevy_render::{
81    render_graph::{RenderGraphExt, ViewNodeRunner},
82    renderer::RenderDevice,
83    settings::WgpuFeatures,
84    view::{prepare_view_targets, Msaa},
85    ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
86};
87use bevy_shader::load_shader_library;
88use bevy_transform::components::Transform;
89use derive_more::From;
90use tracing::error;
91
92/// Provides a plugin for rendering large amounts of high-poly 3d meshes using an efficient GPU-driven method. See also [`MeshletMesh`].
93///
94/// Rendering dense scenes made of high-poly meshes with thousands or millions of triangles is extremely expensive in Bevy's standard renderer.
95/// Once meshes are pre-processed into a [`MeshletMesh`], this plugin can render these kinds of scenes very efficiently.
96///
97/// In comparison to Bevy's standard renderer:
98/// * Much more efficient culling. Meshlets can be culled individually, instead of all or nothing culling for entire meshes at a time.
99///   Additionally, occlusion culling can eliminate meshlets that would cause overdraw.
100/// * Much more efficient batching. All geometry can be rasterized in a single draw.
101/// * Scales better with large amounts of dense geometry and overdraw. Bevy's standard renderer will bottleneck sooner.
102/// * Near-seamless level of detail (LOD).
103/// * Much greater base overhead. Rendering will be slower and use more memory than Bevy's standard renderer
104///   with small amounts of geometry and overdraw.
105/// * Requires preprocessing meshes. See [`MeshletMesh`] for details.
106/// * Limitations on the kinds of materials you can use. See [`MeshletMesh`] for details.
107///
108/// This plugin requires a fairly recent GPU that supports [`WgpuFeatures::TEXTURE_INT64_ATOMIC`].
109///
110/// This plugin currently works only on the Vulkan and Metal backends.
111///
112/// This plugin is not compatible with [`Msaa`]. Any camera rendering a [`MeshletMesh`] must have
113/// [`Msaa`] set to [`Msaa::Off`].
114///
115/// Mixing forward+prepass and deferred rendering for opaque materials is not currently supported when using this plugin.
116/// You must use one or the other by setting [`crate::DefaultOpaqueRendererMethod`].
117/// Do not override [`crate::Material::opaque_render_method`] for any material when using this plugin.
118///
119/// ![A render of the Stanford dragon as a `MeshletMesh`](https://raw.githubusercontent.com/bevyengine/bevy/main/crates/bevy_pbr/src/meshlet/meshlet_preview.png)
120pub struct MeshletPlugin {
121    /// The maximum amount of clusters that can be processed at once,
122    /// used to control the size of a pre-allocated GPU buffer.
123    ///
124    /// If this number is too low, you'll see rendering artifacts like missing or blinking meshes.
125    ///
126    /// Each cluster slot costs 4 bytes of VRAM.
127    ///
128    /// Must not be greater than 2^25.
129    pub cluster_buffer_slots: u32,
130}
131
132impl MeshletPlugin {
133    /// [`WgpuFeatures`] required for this plugin to function.
134    pub fn required_wgpu_features() -> WgpuFeatures {
135        WgpuFeatures::TEXTURE_INT64_ATOMIC
136            | WgpuFeatures::TEXTURE_ATOMIC
137            | WgpuFeatures::SHADER_INT64
138            | WgpuFeatures::SUBGROUP
139            | WgpuFeatures::DEPTH_CLIP_CONTROL
140            | WgpuFeatures::PUSH_CONSTANTS
141    }
142}
143
144impl Plugin for MeshletPlugin {
145    fn build(&self, app: &mut App) {
146        #[cfg(target_endian = "big")]
147        compile_error!("MeshletPlugin is only supported on little-endian processors.");
148
149        if self.cluster_buffer_slots > 2_u32.pow(25) {
150            error!("MeshletPlugin::cluster_buffer_slots must not be greater than 2^25.");
151            std::process::exit(1);
152        }
153
154        load_shader_library!(app, "meshlet_bindings.wgsl");
155        load_shader_library!(app, "visibility_buffer_resolve.wgsl");
156        load_shader_library!(app, "meshlet_cull_shared.wgsl");
157        embedded_asset!(app, "clear_visibility_buffer.wgsl");
158        embedded_asset!(app, "cull_instances.wgsl");
159        embedded_asset!(app, "cull_bvh.wgsl");
160        embedded_asset!(app, "cull_clusters.wgsl");
161        embedded_asset!(app, "visibility_buffer_software_raster.wgsl");
162        embedded_asset!(app, "visibility_buffer_hardware_raster.wgsl");
163        embedded_asset!(app, "meshlet_mesh_material.wgsl");
164        embedded_asset!(app, "resolve_render_targets.wgsl");
165        embedded_asset!(app, "remap_1d_to_2d_dispatch.wgsl");
166        embedded_asset!(app, "fill_counts.wgsl");
167
168        app.init_asset::<MeshletMesh>()
169            .register_asset_loader(MeshletMeshLoader);
170
171        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
172            return;
173        };
174
175        // Create a variable here so we can move-capture it.
176        let cluster_buffer_slots = self.cluster_buffer_slots;
177        let init_resource_manager_system =
178            move |mut commands: Commands, render_device: Res<RenderDevice>| {
179                commands
180                    .insert_resource(ResourceManager::new(cluster_buffer_slots, &render_device));
181            };
182
183        render_app
184            .add_render_graph_node::<MeshletVisibilityBufferRasterPassNode>(
185                Core3d,
186                NodeMeshlet::VisibilityBufferRasterPass,
187            )
188            .add_render_graph_node::<ViewNodeRunner<MeshletPrepassNode>>(
189                Core3d,
190                NodeMeshlet::Prepass,
191            )
192            .add_render_graph_node::<ViewNodeRunner<MeshletDeferredGBufferPrepassNode>>(
193                Core3d,
194                NodeMeshlet::DeferredPrepass,
195            )
196            .add_render_graph_node::<ViewNodeRunner<MeshletMainOpaquePass3dNode>>(
197                Core3d,
198                NodeMeshlet::MainOpaquePass,
199            )
200            .add_render_graph_edges(
201                Core3d,
202                (
203                    NodeMeshlet::VisibilityBufferRasterPass,
204                    NodePbr::EarlyShadowPass,
205                    //
206                    NodeMeshlet::Prepass,
207                    //
208                    NodeMeshlet::DeferredPrepass,
209                    Node3d::EndPrepasses,
210                    //
211                    Node3d::StartMainPass,
212                    NodeMeshlet::MainOpaquePass,
213                    Node3d::MainOpaquePass,
214                    Node3d::EndMainPass,
215                ),
216            )
217            .insert_resource(InstanceManager::new())
218            .add_systems(
219                RenderStartup,
220                (
221                    check_meshlet_features,
222                    (
223                        (init_resource_manager_system, init_meshlet_pipelines).chain(),
224                        init_meshlet_mesh_manager,
225                    ),
226                )
227                    .chain(),
228            )
229            .add_systems(ExtractSchedule, extract_meshlet_mesh_entities)
230            .add_systems(
231                Render,
232                (
233                    perform_pending_meshlet_mesh_writes.in_set(RenderSystems::PrepareAssets),
234                    configure_meshlet_views
235                        .after(prepare_view_targets)
236                        .in_set(RenderSystems::ManageViews),
237                    prepare_meshlet_per_frame_resources.in_set(RenderSystems::PrepareResources),
238                    prepare_meshlet_view_bind_groups.in_set(RenderSystems::PrepareBindGroups),
239                    queue_material_meshlet_meshes.in_set(RenderSystems::QueueMeshes),
240                    prepare_material_meshlet_meshes_main_opaque_pass
241                        .in_set(RenderSystems::QueueMeshes)
242                        .before(queue_material_meshlet_meshes),
243                ),
244            );
245    }
246}
247
248fn check_meshlet_features(render_device: Res<RenderDevice>) {
249    let features = render_device.features();
250    if !features.contains(MeshletPlugin::required_wgpu_features()) {
251        error!(
252            "MeshletPlugin can't be used. GPU lacks support for required features: {:?}.",
253            MeshletPlugin::required_wgpu_features().difference(features)
254        );
255        std::process::exit(1);
256    }
257}
258
259/// The meshlet mesh equivalent of [`bevy_mesh::Mesh3d`].
260#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
261#[reflect(Component, Default, Clone, PartialEq)]
262#[require(Transform, PreviousGlobalTransform, Visibility, VisibilityClass)]
263#[component(on_add = visibility::add_visibility_class::<MeshletMesh3d>)]
264pub struct MeshletMesh3d(pub Handle<MeshletMesh>);
265
266impl From<MeshletMesh3d> for AssetId<MeshletMesh> {
267    fn from(mesh: MeshletMesh3d) -> Self {
268        mesh.id()
269    }
270}
271
272impl From<&MeshletMesh3d> for AssetId<MeshletMesh> {
273    fn from(mesh: &MeshletMesh3d) -> Self {
274        mesh.id()
275    }
276}
277
278fn configure_meshlet_views(
279    mut views_3d: Query<(
280        Entity,
281        &Msaa,
282        Has<NormalPrepass>,
283        Has<MotionVectorPrepass>,
284        Has<DeferredPrepass>,
285    )>,
286    mut commands: Commands,
287) {
288    for (entity, msaa, normal_prepass, motion_vector_prepass, deferred_prepass) in &mut views_3d {
289        if *msaa != Msaa::Off {
290            error!("MeshletPlugin can't be used with MSAA. Add Msaa::Off to your camera to use this plugin.");
291            std::process::exit(1);
292        }
293
294        if !(normal_prepass || motion_vector_prepass || deferred_prepass) {
295            commands
296                .entity(entity)
297                .insert(MeshletViewMaterialsMainOpaquePass::default());
298        } else {
299            // TODO: Should we add both Prepass and DeferredGBufferPrepass materials here, and in other systems/nodes?
300            commands.entity(entity).insert((
301                MeshletViewMaterialsMainOpaquePass::default(),
302                MeshletViewMaterialsPrepass::default(),
303                MeshletViewMaterialsDeferredGBufferPrepass::default(),
304            ));
305        }
306    }
307}