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

sort by pipeline then mesh for non transparent passes for massively better batching #11671

Merged
merged 6 commits into from
Feb 5, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl ViewNode for MainOpaquePass3dNode {
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
// Run the opaque pass, sorted front-to-back
// Run the opaque pass, sorted by pipeline key and mesh id to greatly improve batching.
// NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")]
let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered();
Expand Down
13 changes: 7 additions & 6 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;

use std::{cmp::Reverse, ops::Range};

use bevy_asset::AssetId;
pub use camera_3d::*;
pub use main_opaque_pass_3d_node::*;
pub use main_transparent_pass_3d_node::*;
Expand All @@ -50,6 +51,7 @@ use bevy_render::{
camera::{Camera, ExtractedCamera},
color::Color,
extract_component::ExtractComponentPlugin,
mesh::Mesh,
prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{
Expand Down Expand Up @@ -182,7 +184,7 @@ impl Plugin for Core3dPlugin {
}

pub struct Opaque3d {
pub distance: f32,
pub asset_id: AssetId<Mesh>,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
Expand All @@ -191,8 +193,7 @@ pub struct Opaque3d {
}

impl PhaseItem for Opaque3d {
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
type SortKey = Reverse<FloatOrd>;
type SortKey = (usize, AssetId<Mesh>);

#[inline]
fn entity(&self) -> Entity {
Expand All @@ -201,7 +202,8 @@ impl PhaseItem for Opaque3d {

#[inline]
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to update this comment to mention bind groups. Also, was it not helpful to add bind group sorting for the prepasses?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the prepass and deferred it kept panicking on the unwrap when getting the material bindgroup.

We also may need to remove the unwrap for the opaque3d pass when getting the material bindgroup id, but I'm not sure if it makes sense that we'd be drawing things in the forward pass with no material bind group?

I'd like to remove the unwrap on getting the bind group id, but I'm not sure how the sorting would deal with a wrapped Option<NonZeroU32>.

Copy link
Contributor

@JMS55 JMS55 Feb 3, 2024

Choose a reason for hiding this comment

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

If you can't figure it out, I'd just add a TODO comment and we can do a followup PR.

For sorting, you can make a wrapper type implementing Ord, and then do sort_by(pipeline, Wrapper(maybe_bind_group), mesh).

(self.pipeline.id(), self.asset_id)
}

#[inline]
Expand All @@ -211,8 +213,7 @@ impl PhaseItem for Opaque3d {

#[inline]
fn sort(items: &mut [Self]) {
// Key negated to match reversed SortKey ordering
radsort::sort_by_key(items, |item| -item.distance);
items.sort_unstable_by_key(Self::sort_key);
}

#[inline]
Expand Down
13 changes: 7 additions & 6 deletions crates/bevy_core_pipeline/src/deferred/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ pub mod node;

use std::{cmp::Reverse, ops::Range};

use bevy_asset::AssetId;
use bevy_ecs::prelude::*;
use bevy_render::{
mesh::Mesh,
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_resource::{CachedRenderPipelineId, TextureFormat},
};
Expand All @@ -20,17 +22,16 @@ pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat:
///
/// Used to render all 3D meshes with materials that have no transparency.
pub struct Opaque3dDeferred {
pub distance: f32,
pub entity: Entity,
pub asset_id: AssetId<Mesh>,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>,
}

impl PhaseItem for Opaque3dDeferred {
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
type SortKey = Reverse<FloatOrd>;
type SortKey = (usize, AssetId<Mesh>);

#[inline]
fn entity(&self) -> Entity {
Expand All @@ -39,7 +40,8 @@ impl PhaseItem for Opaque3dDeferred {

#[inline]
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
}

#[inline]
Expand All @@ -49,8 +51,7 @@ impl PhaseItem for Opaque3dDeferred {

#[inline]
fn sort(items: &mut [Self]) {
// Key negated to match reversed SortKey ordering
radsort::sort_by_key(items, |item| -item.distance);
items.sort_unstable_by_key(Self::sort_key);
}

#[inline]
Expand Down
13 changes: 7 additions & 6 deletions crates/bevy_core_pipeline/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ pub mod node;

use std::{cmp::Reverse, ops::Range};

use bevy_asset::AssetId;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use bevy_render::{
mesh::Mesh,
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat, TextureView},
texture::ColorAttachment,
Expand Down Expand Up @@ -109,17 +111,16 @@ impl ViewPrepassTextures {
///
/// Used to render all 3D meshes with materials that have no transparency.
pub struct Opaque3dPrepass {
pub distance: f32,
pub entity: Entity,
pub asset_id: AssetId<Mesh>,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>,
}

impl PhaseItem for Opaque3dPrepass {
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
type SortKey = Reverse<FloatOrd>;
type SortKey = (usize, AssetId<Mesh>);

#[inline]
fn entity(&self) -> Entity {
Expand All @@ -128,7 +129,8 @@ impl PhaseItem for Opaque3dPrepass {

#[inline]
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
}

#[inline]
Expand All @@ -138,8 +140,7 @@ impl PhaseItem for Opaque3dPrepass {

#[inline]
fn sort(items: &mut [Self]) {
// Key negated to match reversed SortKey ordering
radsort::sort_by_key(items, |item| -item.distance);
items.sort_unstable_by_key(Self::sort_key);
}

#[inline]
Expand Down
14 changes: 10 additions & 4 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,12 +639,12 @@ pub fn queue_material_meshes<M: Material>(

mesh_instance.material_bind_group_id = material.get_bind_group_id();

let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
match material.properties.alpha_mode {
AlphaMode::Opaque => {
if material.properties.reads_view_transmission_texture {
let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
transmissive_phase.add(Transmissive3d {
entity: *visible_entity,
draw_function: draw_transmissive_pbr,
Expand All @@ -658,13 +658,16 @@ pub fn queue_material_meshes<M: Material>(
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Mask(_) => {
let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
if material.properties.reads_view_transmission_texture {
transmissive_phase.add(Transmissive3d {
entity: *visible_entity,
Expand All @@ -689,6 +692,9 @@ pub fn queue_material_meshes<M: Material>(
| AlphaMode::Premultiplied
| AlphaMode::Add
| AlphaMode::Multiply => {
let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
transparent_phase.add(Transparent3d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,6 @@ pub fn queue_prepass_material_meshes<M: Material>(
}
};

let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
match alpha_mode {
AlphaMode::Opaque => {
if deferred {
Expand All @@ -848,7 +845,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
entity: *visible_entity,
draw_function: opaque_draw_deferred,
pipeline_id,
distance,
asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1,
dynamic_offset: None,
});
Expand All @@ -857,13 +854,16 @@ pub fn queue_prepass_material_meshes<M: Material>(
entity: *visible_entity,
draw_function: opaque_draw_prepass,
pipeline_id,
distance,
asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Mask(_) => {
let distance = rangefinder
.distance_translation(&mesh_instance.transforms.transform.translation)
+ material.properties.depth_bias;
if deferred {
alpha_mask_deferred_phase
.as_mut()
Expand Down