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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ use bevy_math::FloatOrd;
use bevy_render::{
camera::{Camera, ExtractedCamera},
extract_component::ExtractComponentPlugin,
mesh::{Mesh, MeshSlabHash},
prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{
Expand All @@ -94,6 +95,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 @@ -223,25 +226,57 @@ pub struct Opaque3d {
/// Data that must be identical in order to batch phase items 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 asset that this phase item is associated with.
///
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
/// the ID of another type of asset.
pub asset_id: UntypedAssetId,

/// 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: UntypedAssetId,
}

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
13 changes: 10 additions & 3 deletions crates/bevy_core_pipeline/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use bevy_ecs::prelude::*;
use bevy_math::Mat4;
use bevy_reflect::Reflect;
use bevy_render::{
mesh::{Mesh, MeshSlabHash},
render_phase::{
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
PhaseItemExtraIndex,
Expand Down Expand Up @@ -149,19 +150,25 @@ pub struct Opaque3dPrepass {
/// The data used to bin each opaque 3D object 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 asset.
pub asset_id: UntypedAssetId,

/// 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: UntypedAssetId,
}

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 @@ -765,7 +765,8 @@ pub fn queue_material_meshes<M: Material>(
pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id.into(),
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,
Expand All @@ -791,6 +792,7 @@ pub fn queue_material_meshes<M: Material>(
let bin_key = OpaqueNoLightmap3dBinKey {
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
slab_hash: mesh.slab_hash,
asset_id: mesh_instance.mesh_asset_id.into(),
material_bind_group_id: material.get_bind_group_id().0,
};
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.into(),
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
},
*visible_entity,
BinnedRenderPhaseType::mesh(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.into(),
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
},
*visible_entity,
BinnedRenderPhaseType::mesh(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.into(),
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.into(),
material_bind_group_id: material.get_bind_group_id().0,
slab_hash: mesh.slab_hash,
};
alpha_mask_phase.add(
bin_key,
Expand Down
13 changes: 12 additions & 1 deletion crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::prelude::*;
use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read};
use bevy_math::{Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::mesh::{Mesh, MeshSlabHash};
use bevy_render::{
diagnostic::RecordDiagnostics,
mesh::GpuMesh,
Expand Down Expand Up @@ -1285,6 +1286,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.into(),
},
entity,
Expand All @@ -1305,12 +1307,21 @@ pub struct Shadow {
/// Data used to bin each object in the shadow map phase.
#[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.
/// The object.
pub asset_id: UntypedAssetId,
}
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 @@ -15,6 +15,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 @@ -1349,23 +1350,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 @@ -2242,6 +2250,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 @@ -2251,7 +2260,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 @@ -2268,6 +2284,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 @@ -2291,21 +2308,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 @@ -2316,7 +2344,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
Loading