Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack multiple meshes into vertex and index buffers. #13218

Closed
wants to merge 8 commits into from
Closed
49 changes: 43 additions & 6 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use bevy_math::FloatOrd;
use bevy_render::{
camera::{Camera, ExtractedCamera},
extract_component::ExtractComponentPlugin,
mesh::Mesh,
mesh::{Mesh, MeshSlabHash},
prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{
Expand All @@ -74,6 +74,8 @@ use bevy_render::{
};
use bevy_utils::{tracing::warn, HashMap};

use bitflags::bitflags;

use crate::{
core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode,
deferred::{
Expand Down Expand Up @@ -195,22 +197,57 @@ pub struct Opaque3d {
/// Data that must be identical in order to batch meshes together.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opaque3dBinKey {
/// Various flags, with the [`MeshSlabHash`] in the top bits.
///
/// We want this to be first to minimize IBO and VBO changes. See the
/// comments in [`MeshSlabHash`] for more details.
pub flags: MeshCompareFlags,

/// The identifier of the render pipeline.
pub pipeline: CachedRenderPipelineId,

/// The function used to draw.
pub draw_function: DrawFunctionId,

/// The mesh.
pub asset_id: AssetId<Mesh>,

/// The ID of a bind group specific to the material.
///
/// In the case of PBR, this is the `MaterialBindGroupId`.
pub material_bind_group_id: Option<BindGroupId>,

/// The lightmap, if present.
pub lightmap_image: Option<AssetId<Image>>,
/// The lightmap, if present; if not present, this is `AssetId::default()`.
pub lightmap_image: AssetId<Image>,

/// The mesh.
///
/// Although we don't have multidraw capability yet, we place this at the
/// end to maximize multidraw opportunities in the future.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does order matter here? It's not repr(c) or anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PartialOrd compares from first struct field to last struct field. So fields at the top of the struct will generally be placed together more often.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, Thanks.

pub asset_id: AssetId<Mesh>,
}

bitflags! {
/// Flags that are used as part of the decision to batch or not batch a
/// mesh.
///
/// This 8-bit flag field is concatenated with [`MeshSlabHash`] to form the
/// `flags` field of a bin key or compare data.
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct MeshCompareFlags: u32 {
/// A lightmap is present.
const HAS_LIGHTMAP = 0x0000_0001;
}
}

impl MeshCompareFlags {
/// Creates a new [`MeshCompareFlags`] for a mesh and corresponding slab
/// hash.
///
/// `has_lightmap` should be true if the mesh has a lightmap.
pub fn new(has_lightmap: bool, slab_hash: MeshSlabHash) -> MeshCompareFlags {
let mut flags = MeshCompareFlags::empty();
flags.set(MeshCompareFlags::HAS_LIGHTMAP, has_lightmap);
flags.insert(MeshCompareFlags::from_bits_retain(*slab_hash));
flags
}
}

impl PhaseItem for Opaque3d {
Expand Down
14 changes: 10 additions & 4 deletions crates/bevy_core_pipeline/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use bevy_asset::AssetId;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use bevy_render::{
mesh::Mesh,
mesh::{Mesh, MeshSlabHash},
render_phase::{
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
PhaseItemExtraIndex,
Expand Down Expand Up @@ -128,19 +128,25 @@ pub struct Opaque3dPrepass {
/// The data used to bin each opaque 3D mesh in the prepass and deferred pass.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OpaqueNoLightmap3dBinKey {
/// Various flags, with the [`MeshSlabHash`] in the top bits.
///
/// We want this to be first to minimize IBO and VBO changes. See the
/// comments in [`MeshSlabHash`] for more details.
pub slab_hash: MeshSlabHash,

/// The ID of the GPU pipeline.
pub pipeline: CachedRenderPipelineId,

/// The function used to draw the mesh.
pub draw_function: DrawFunctionId,

/// The ID of the mesh.
pub asset_id: AssetId<Mesh>,

/// The ID of a bind group specific to the material.
///
/// In the case of PBR, this is the `MaterialBindGroupId`.
pub material_bind_group_id: Option<BindGroupId>,

/// The ID of the mesh.
pub asset_id: AssetId<Mesh>,
}

impl PhaseItem for Opaque3dPrepass {
Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use crate::*;
use bevy_asset::{Asset, AssetId, AssetServer};
use bevy_core_pipeline::{
core_3d::{
AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
Transmissive3d, Transparent3d,
AlphaMask3d, Camera3d, MeshCompareFlags, Opaque3d, Opaque3dBinKey,
ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d,
},
prepass::{
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey,
Expand Down Expand Up @@ -723,7 +723,8 @@ pub fn queue_material_meshes<M: Material>(
pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id,
material_bind_group_id: material.get_bind_group_id().0,
lightmap_image,
lightmap_image: lightmap_image.unwrap_or_default(),
flags: MeshCompareFlags::new(lightmap_image.is_some(), mesh.slab_hash),
};
opaque_phase.add(bin_key, *visible_entity, mesh_instance.should_batch());
}
Expand All @@ -746,6 +747,7 @@ pub fn queue_material_meshes<M: Material>(
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id,
slab_hash: mesh.slab_hash,
material_bind_group_id: material.get_bind_group_id().0,
};
alpha_mask_phase.add(
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id,
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
},
*visible_entity,
mesh_instance.should_batch(),
Expand All @@ -873,6 +874,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id,
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
},
*visible_entity,
mesh_instance.should_batch(),
Expand All @@ -887,6 +889,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
draw_function: alpha_mask_draw_deferred,
asset_id: mesh_instance.mesh_asset_id,
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
};
alpha_mask_deferred_phase.as_mut().unwrap().add(
bin_key,
Expand All @@ -899,6 +902,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
draw_function: alpha_mask_draw_prepass,
asset_id: mesh_instance.mesh_asset_id,
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
};
alpha_mask_phase.add(
bin_key,
Expand Down
12 changes: 11 additions & 1 deletion crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT};
use bevy_ecs::prelude::*;
use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read};
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::mesh::Mesh;
use bevy_render::mesh::{Mesh, MeshSlabHash};
use bevy_render::{
camera::Camera,
diagnostic::RecordDiagnostics,
Expand Down Expand Up @@ -1762,6 +1762,7 @@ pub fn queue_shadows<M: Material>(
ShadowBinKey {
draw_function: draw_shadow_mesh,
pipeline: pipeline_id,
slab_hash: mesh.slab_hash,
asset_id: mesh_instance.mesh_asset_id,
},
entity,
Expand All @@ -1779,15 +1780,24 @@ pub struct Shadow {
pub extra_index: PhaseItemExtraIndex,
}

/// The data used to bin each mesh in the shadow map pass.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShadowBinKey {
/// Various flags, with the [`MeshSlabHash`] in the top bits.
///
/// We want this to be first to minimize IBO and VBO changes. See the
/// comments in [`MeshSlabHash`] for more details.
pub slab_hash: MeshSlabHash,

/// The identifier of the render pipeline.
pub pipeline: CachedRenderPipelineId,

/// The function used to draw.
pub draw_function: DrawFunctionId,

/// The mesh.
///
/// This is at the end to minimize binding changes.
pub asset_id: AssetId<Mesh>,
}

Expand Down
61 changes: 46 additions & 15 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use bevy_ecs::{
};
use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4};
use bevy_render::{
allocator::GpuAllocator,
batching::{
gpu_preprocessing::{
self, GpuPreprocessingSupport, IndirectParameters, IndirectParametersBuffer,
Expand Down Expand Up @@ -1285,23 +1286,30 @@ fn get_batch_indirect_parameters_index(
let mesh_instance = mesh_instances.get(&entity)?;
let mesh = meshes.get(mesh_instance.mesh_asset_id)?;

let vertex_offset = mesh.vertex_buffer.offset();

// Note that `IndirectParameters` covers both of these structures, even
// though they actually have distinct layouts. See the comment above that
// type for more information.
let indirect_parameters = match mesh.buffer_info {
GpuBufferInfo::Indexed {
count: index_count, ..
} => IndirectParameters {
vertex_or_index_count: index_count,
instance_count: 0,
first_vertex: 0,
base_vertex_or_first_instance: 0,
first_instance: instance_index,
},
ref allocation,
count: index_count,
..
} => {
let index_offset = allocation.offset();
IndirectParameters {
vertex_or_index_count: index_count,
instance_count: 0,
first_vertex_or_index: index_offset,
base_vertex_or_first_instance: vertex_offset,
first_instance: instance_index,
}
}
GpuBufferInfo::NonIndexed => IndirectParameters {
vertex_or_index_count: mesh.vertex_count,
instance_count: 0,
first_vertex: 0,
first_vertex_or_index: vertex_offset,
base_vertex_or_first_instance: instance_index,
first_instance: instance_index,
},
Expand Down Expand Up @@ -2035,6 +2043,7 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
SRes<RenderMeshInstances>,
SRes<IndirectParametersBuffer>,
SRes<PipelineCache>,
SRes<GpuAllocator>,
Option<SRes<PreprocessPipelines>>,
);
type ViewQuery = Has<PreprocessBindGroup>;
Expand All @@ -2044,7 +2053,14 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
item: &P,
has_preprocess_bind_group: ROQueryItem<Self::ViewQuery>,
_item_query: Option<()>,
(meshes, mesh_instances, indirect_parameters_buffer, pipeline_cache, preprocess_pipelines): SystemParamItem<'w, '_, Self::Param>,
(
meshes,
mesh_instances,
indirect_parameters_buffer,
pipeline_cache,
allocator,
preprocess_pipelines,
): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
// If we're using GPU preprocessing, then we're dependent on that
Expand All @@ -2061,6 +2077,7 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
let meshes = meshes.into_inner();
let mesh_instances = mesh_instances.into_inner();
let indirect_parameters_buffer = indirect_parameters_buffer.into_inner();
let allocator = allocator.into_inner();

let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(item.entity()) else {
return RenderCommandResult::Failure;
Expand All @@ -2084,21 +2101,32 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
},
};

pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
let vertex_buffer = allocator.buffer(&gpu_mesh.vertex_buffer);
let vertex_offset = gpu_mesh.vertex_buffer.offset();

pass.set_vertex_buffer(0, vertex_buffer.slice(..));

let batch_range = item.batch_range();

// Draw either directly or indirectly, as appropriate.
match &gpu_mesh.buffer_info {
GpuBufferInfo::Indexed {
buffer,
allocation,
index_format,
count,
} => {
pass.set_index_buffer(buffer.slice(..), 0, *index_format);
let index_buffer = allocator.buffer(allocation);
let index_offset = allocation.offset();

pass.set_index_buffer(index_buffer.slice(..), 0, *index_format);

match indirect_parameters {
None => {
pass.draw_indexed(0..*count, 0, batch_range.clone());
pass.draw_indexed(
index_offset..(index_offset + *count),
vertex_offset as i32,
batch_range.clone(),
);
}
Some((indirect_parameters_offset, indirect_parameters_buffer)) => pass
.draw_indexed_indirect(
Expand All @@ -2109,7 +2137,10 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
}
GpuBufferInfo::NonIndexed => match indirect_parameters {
None => {
pass.draw(0..gpu_mesh.vertex_count, batch_range.clone());
pass.draw(
vertex_offset..(vertex_offset + gpu_mesh.vertex_count),
batch_range.clone(),
);
}
Some((indirect_parameters_offset, indirect_parameters_buffer)) => {
pass.draw_indirect(indirect_parameters_buffer, indirect_parameters_offset);
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ profiling = { version = "1", features = [
async-channel = "2.2.0"
nonmax = "0.5"
smallvec = { version = "1.11", features = ["const_new"] }
offset-allocator = "0.1"
slotmap = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Omit the `glsl` feature in non-WebAssembly by default.
Expand Down
Loading