
# Objective Prevents duplicate implementation between IntoSystemConfigs and IntoSystemSetConfigs using a generic, adds a NodeType trait for more config flexibility (opening the door to implement https://github.com/bevyengine/bevy/issues/14195?). ## Solution Followed writeup by @ItsDoot: https://hackmd.io/@doot/rJeefFHc1x Removes IntoSystemConfigs and IntoSystemSetConfigs, instead using IntoNodeConfigs with generics. ## Testing Pending --- ## Showcase N/A ## Migration Guide SystemSetConfigs -> NodeConfigs<InternedSystemSet> SystemConfigs -> NodeConfigs<ScheduleSystem> IntoSystemSetConfigs -> IntoNodeConfigs<InternedSystemSet, M> IntoSystemConfigs -> IntoNodeConfigs<ScheduleSystem, M> --------- Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
340 lines
12 KiB
Rust
340 lines
12 KiB
Rust
//! Render high-poly 3d meshes using an efficient GPU-driven method. See [`MeshletPlugin`] and [`MeshletMesh`] for details.
|
|
|
|
mod asset;
|
|
#[cfg(feature = "meshlet_processor")]
|
|
mod from_mesh;
|
|
mod instance_manager;
|
|
mod material_pipeline_prepare;
|
|
mod material_shade_nodes;
|
|
mod meshlet_mesh_manager;
|
|
mod persistent_buffer;
|
|
mod persistent_buffer_impls;
|
|
mod pipelines;
|
|
mod resource_manager;
|
|
mod visibility_buffer_raster_node;
|
|
|
|
pub mod graph {
|
|
use bevy_render::render_graph::RenderLabel;
|
|
|
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
|
|
pub enum NodeMeshlet {
|
|
VisibilityBufferRasterPass,
|
|
Prepass,
|
|
DeferredPrepass,
|
|
MainOpaquePass,
|
|
}
|
|
}
|
|
|
|
pub(crate) use self::{
|
|
instance_manager::{queue_material_meshlet_meshes, InstanceManager},
|
|
material_pipeline_prepare::{
|
|
prepare_material_meshlet_meshes_main_opaque_pass, prepare_material_meshlet_meshes_prepass,
|
|
},
|
|
};
|
|
|
|
pub use self::asset::{
|
|
MeshletMesh, MeshletMeshLoader, MeshletMeshSaver, MESHLET_MESH_ASSET_VERSION,
|
|
};
|
|
#[cfg(feature = "meshlet_processor")]
|
|
pub use self::from_mesh::{
|
|
MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
|
|
};
|
|
use self::{
|
|
graph::NodeMeshlet,
|
|
instance_manager::extract_meshlet_mesh_entities,
|
|
material_pipeline_prepare::{
|
|
MeshletViewMaterialsDeferredGBufferPrepass, MeshletViewMaterialsMainOpaquePass,
|
|
MeshletViewMaterialsPrepass,
|
|
},
|
|
material_shade_nodes::{
|
|
MeshletDeferredGBufferPrepassNode, MeshletMainOpaquePass3dNode, MeshletPrepassNode,
|
|
},
|
|
meshlet_mesh_manager::{perform_pending_meshlet_mesh_writes, MeshletMeshManager},
|
|
pipelines::*,
|
|
resource_manager::{
|
|
prepare_meshlet_per_frame_resources, prepare_meshlet_view_bind_groups, ResourceManager,
|
|
},
|
|
visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode,
|
|
};
|
|
use crate::{graph::NodePbr, PreviousGlobalTransform};
|
|
use bevy_app::{App, Plugin};
|
|
use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetId, Handle};
|
|
use bevy_core_pipeline::{
|
|
core_3d::graph::{Core3d, Node3d},
|
|
prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass},
|
|
};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
component::Component,
|
|
entity::Entity,
|
|
query::Has,
|
|
reflect::ReflectComponent,
|
|
schedule::IntoScheduleConfigs,
|
|
system::{Commands, Query},
|
|
};
|
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
use bevy_render::{
|
|
render_graph::{RenderGraphApp, ViewNodeRunner},
|
|
render_resource::Shader,
|
|
renderer::RenderDevice,
|
|
settings::WgpuFeatures,
|
|
view::{self, prepare_view_targets, Msaa, Visibility, VisibilityClass},
|
|
ExtractSchedule, Render, RenderApp, RenderSet,
|
|
};
|
|
use bevy_transform::components::Transform;
|
|
use derive_more::From;
|
|
use tracing::error;
|
|
|
|
const MESHLET_BINDINGS_SHADER_HANDLE: Handle<Shader> =
|
|
weak_handle!("d90ac78c-500f-48aa-b488-cc98eb3f6314");
|
|
const MESHLET_MESH_MATERIAL_SHADER_HANDLE: Handle<Shader> =
|
|
weak_handle!("db8d9001-6ca7-4d00-968a-d5f5b96b89c3");
|
|
|
|
/// Provides a plugin for rendering large amounts of high-poly 3d meshes using an efficient GPU-driven method. See also [`MeshletMesh`].
|
|
///
|
|
/// Rendering dense scenes made of high-poly meshes with thousands or millions of triangles is extremely expensive in Bevy's standard renderer.
|
|
/// Once meshes are pre-processed into a [`MeshletMesh`], this plugin can render these kinds of scenes very efficiently.
|
|
///
|
|
/// In comparison to Bevy's standard renderer:
|
|
/// * Much more efficient culling. Meshlets can be culled individually, instead of all or nothing culling for entire meshes at a time.
|
|
/// Additionally, occlusion culling can eliminate meshlets that would cause overdraw.
|
|
/// * Much more efficient batching. All geometry can be rasterized in a single draw.
|
|
/// * Scales better with large amounts of dense geometry and overdraw. Bevy's standard renderer will bottleneck sooner.
|
|
/// * Near-seamless level of detail (LOD).
|
|
/// * Much greater base overhead. Rendering will be slower and use more memory than Bevy's standard renderer
|
|
/// with small amounts of geometry and overdraw.
|
|
/// * Requires preprocessing meshes. See [`MeshletMesh`] for details.
|
|
/// * Limitations on the kinds of materials you can use. See [`MeshletMesh`] for details.
|
|
///
|
|
/// This plugin requires a fairly recent GPU that supports [`WgpuFeatures::TEXTURE_INT64_ATOMIC`].
|
|
///
|
|
/// This plugin currently works only on the Vulkan and Metal backends.
|
|
///
|
|
/// This plugin is not compatible with [`Msaa`]. Any camera rendering a [`MeshletMesh`] must have
|
|
/// [`Msaa`] set to [`Msaa::Off`].
|
|
///
|
|
/// Mixing forward+prepass and deferred rendering for opaque materials is not currently supported when using this plugin.
|
|
/// You must use one or the other by setting [`crate::DefaultOpaqueRendererMethod`].
|
|
/// Do not override [`crate::Material::opaque_render_method`] for any material when using this plugin.
|
|
///
|
|
/// 
|
|
pub struct MeshletPlugin {
|
|
/// The maximum amount of clusters that can be processed at once,
|
|
/// used to control the size of a pre-allocated GPU buffer.
|
|
///
|
|
/// If this number is too low, you'll see rendering artifacts like missing or blinking meshes.
|
|
///
|
|
/// Each cluster slot costs 4 bytes of VRAM.
|
|
///
|
|
/// Must not be greater than 2^25.
|
|
pub cluster_buffer_slots: u32,
|
|
}
|
|
|
|
impl MeshletPlugin {
|
|
/// [`WgpuFeatures`] required for this plugin to function.
|
|
pub fn required_wgpu_features() -> WgpuFeatures {
|
|
WgpuFeatures::TEXTURE_INT64_ATOMIC
|
|
| WgpuFeatures::TEXTURE_ATOMIC
|
|
| WgpuFeatures::SHADER_INT64
|
|
| WgpuFeatures::SUBGROUP
|
|
| WgpuFeatures::DEPTH_CLIP_CONTROL
|
|
| WgpuFeatures::PUSH_CONSTANTS
|
|
}
|
|
}
|
|
|
|
impl Plugin for MeshletPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
#[cfg(target_endian = "big")]
|
|
compile_error!("MeshletPlugin is only supported on little-endian processors.");
|
|
|
|
if self.cluster_buffer_slots > 2_u32.pow(25) {
|
|
error!("MeshletPlugin::cluster_buffer_slots must not be greater than 2^25.");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_CLEAR_VISIBILITY_BUFFER_SHADER_HANDLE,
|
|
"clear_visibility_buffer.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_BINDINGS_SHADER_HANDLE,
|
|
"meshlet_bindings.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
super::MESHLET_VISIBILITY_BUFFER_RESOLVE_SHADER_HANDLE,
|
|
"visibility_buffer_resolve.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_FILL_CLUSTER_BUFFERS_SHADER_HANDLE,
|
|
"fill_cluster_buffers.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_CULLING_SHADER_HANDLE,
|
|
"cull_clusters.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_VISIBILITY_BUFFER_SOFTWARE_RASTER_SHADER_HANDLE,
|
|
"visibility_buffer_software_raster.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE,
|
|
"visibility_buffer_hardware_raster.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_MESH_MATERIAL_SHADER_HANDLE,
|
|
"meshlet_mesh_material.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_RESOLVE_RENDER_TARGETS_SHADER_HANDLE,
|
|
"resolve_render_targets.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESHLET_REMAP_1D_TO_2D_DISPATCH_SHADER_HANDLE,
|
|
"remap_1d_to_2d_dispatch.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
|
|
app.init_asset::<MeshletMesh>()
|
|
.register_asset_loader(MeshletMeshLoader);
|
|
}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
|
return;
|
|
};
|
|
|
|
let render_device = render_app.world().resource::<RenderDevice>().clone();
|
|
let features = render_device.features();
|
|
if !features.contains(Self::required_wgpu_features()) {
|
|
error!(
|
|
"MeshletPlugin can't be used. GPU lacks support for required features: {:?}.",
|
|
Self::required_wgpu_features().difference(features)
|
|
);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
render_app
|
|
.add_render_graph_node::<MeshletVisibilityBufferRasterPassNode>(
|
|
Core3d,
|
|
NodeMeshlet::VisibilityBufferRasterPass,
|
|
)
|
|
.add_render_graph_node::<ViewNodeRunner<MeshletPrepassNode>>(
|
|
Core3d,
|
|
NodeMeshlet::Prepass,
|
|
)
|
|
.add_render_graph_node::<ViewNodeRunner<MeshletDeferredGBufferPrepassNode>>(
|
|
Core3d,
|
|
NodeMeshlet::DeferredPrepass,
|
|
)
|
|
.add_render_graph_node::<ViewNodeRunner<MeshletMainOpaquePass3dNode>>(
|
|
Core3d,
|
|
NodeMeshlet::MainOpaquePass,
|
|
)
|
|
.add_render_graph_edges(
|
|
Core3d,
|
|
(
|
|
NodeMeshlet::VisibilityBufferRasterPass,
|
|
NodePbr::EarlyShadowPass,
|
|
//
|
|
NodeMeshlet::Prepass,
|
|
//
|
|
NodeMeshlet::DeferredPrepass,
|
|
Node3d::EndPrepasses,
|
|
//
|
|
Node3d::StartMainPass,
|
|
NodeMeshlet::MainOpaquePass,
|
|
Node3d::MainOpaquePass,
|
|
Node3d::EndMainPass,
|
|
),
|
|
)
|
|
.init_resource::<MeshletMeshManager>()
|
|
.insert_resource(InstanceManager::new())
|
|
.insert_resource(ResourceManager::new(
|
|
self.cluster_buffer_slots,
|
|
&render_device,
|
|
))
|
|
.init_resource::<MeshletPipelines>()
|
|
.add_systems(ExtractSchedule, extract_meshlet_mesh_entities)
|
|
.add_systems(
|
|
Render,
|
|
(
|
|
perform_pending_meshlet_mesh_writes.in_set(RenderSet::PrepareAssets),
|
|
configure_meshlet_views
|
|
.after(prepare_view_targets)
|
|
.in_set(RenderSet::ManageViews),
|
|
prepare_meshlet_per_frame_resources.in_set(RenderSet::PrepareResources),
|
|
prepare_meshlet_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`].
|
|
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
|
#[reflect(Component, Default)]
|
|
#[require(Transform, PreviousGlobalTransform, Visibility, VisibilityClass)]
|
|
#[component(on_add = view::add_visibility_class::<MeshletMesh3d>)]
|
|
pub struct MeshletMesh3d(pub Handle<MeshletMesh>);
|
|
|
|
impl From<MeshletMesh3d> for AssetId<MeshletMesh> {
|
|
fn from(mesh: MeshletMesh3d) -> Self {
|
|
mesh.id()
|
|
}
|
|
}
|
|
|
|
impl From<&MeshletMesh3d> for AssetId<MeshletMesh> {
|
|
fn from(mesh: &MeshletMesh3d) -> Self {
|
|
mesh.id()
|
|
}
|
|
}
|
|
|
|
fn configure_meshlet_views(
|
|
mut views_3d: Query<(
|
|
Entity,
|
|
&Msaa,
|
|
Has<NormalPrepass>,
|
|
Has<MotionVectorPrepass>,
|
|
Has<DeferredPrepass>,
|
|
)>,
|
|
mut commands: Commands,
|
|
) {
|
|
for (entity, msaa, normal_prepass, motion_vector_prepass, deferred_prepass) in &mut views_3d {
|
|
if *msaa != Msaa::Off {
|
|
error!("MeshletPlugin can't be used with MSAA. Add Msaa::Off to your camera to use this plugin.");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
if !(normal_prepass || motion_vector_prepass || deferred_prepass) {
|
|
commands
|
|
.entity(entity)
|
|
.insert(MeshletViewMaterialsMainOpaquePass::default());
|
|
} else {
|
|
// TODO: Should we add both Prepass and DeferredGBufferPrepass materials here, and in other systems/nodes?
|
|
commands.entity(entity).insert((
|
|
MeshletViewMaterialsMainOpaquePass::default(),
|
|
MeshletViewMaterialsPrepass::default(),
|
|
MeshletViewMaterialsDeferredGBufferPrepass::default(),
|
|
));
|
|
}
|
|
}
|
|
}
|