Improve performance by binning together opaque items instead of sorting them. (#12453)

Today, we sort all entities added to all phases, even the phases that
don't strictly need sorting, such as the opaque and shadow phases. This
results in a performance loss because our `PhaseItem`s are rather large
in memory, so sorting is slow. Additionally, determining the boundaries
of batches is an O(n) process.

This commit makes Bevy instead applicable place phase items into *bins*
keyed by *bin keys*, which have the invariant that everything in the
same bin is potentially batchable. This makes determining batch
boundaries O(1), because everything in the same bin can be batched.
Instead of sorting each entity, we now sort only the bin keys. This
drops the sorting time to near-zero on workloads with few bins like
`many_cubes --no-frustum-culling`. Memory usage is improved too, with
batch boundaries and dynamic indices now implicit instead of explicit.
The improved memory usage results in a significant win even on
unbatchable workloads like `many_cubes --no-frustum-culling
--vary-material-data-per-instance`, presumably due to cache effects.

Not all phases can be binned; some, such as transparent and transmissive
phases, must still be sorted. To handle this, this commit splits
`PhaseItem` into `BinnedPhaseItem` and `SortedPhaseItem`. Most of the
logic that today deals with `PhaseItem`s has been moved to
`SortedPhaseItem`. `BinnedPhaseItem` has the new logic.

Frame time results (in ms/frame) are as follows:

| Benchmark                | `binning` | `main`  | Speedup |
| ------------------------ | --------- | ------- | ------- |
| `many_cubes -nfc -vpi` | 232.179     | 312.123   | 34.43%  |
| `many_cubes -nfc`        | 25.874 | 30.117 | 16.40%  |
| `many_foxes`             | 3.276 | 3.515 | 7.30%   |

(`-nfc` is short for `--no-frustum-culling`; `-vpi` is short for
`--vary-per-instance`.)

---

## Changelog

### Changed

* Render phases have been split into binned and sorted phases. Binned
phases, such as the common opaque phase, achieve improved CPU
performance by avoiding the sorting step.

## Migration Guide

- `PhaseItem` has been split into `BinnedPhaseItem` and
`SortedPhaseItem`. If your code has custom `PhaseItem`s, you will need
to migrate them to one of these two types. `SortedPhaseItem` requires
the fewest code changes, but you may want to pick `BinnedPhaseItem` if
your phase doesn't require sorting, as that enables higher performance.

## Tracy graphs

`many-cubes --no-frustum-culling`, `main` branch:
<img width="1064" alt="Screenshot 2024-03-12 180037"
src="https://github.com/bevyengine/bevy/assets/157897/e1180ce8-8e89-46d2-85e3-f59f72109a55">

`many-cubes --no-frustum-culling`, this branch:
<img width="1064" alt="Screenshot 2024-03-12 180011"
src="https://github.com/bevyengine/bevy/assets/157897/0899f036-6075-44c5-a972-44d95895f46c">

You can see that `batch_and_prepare_binned_render_phase` is a much
smaller fraction of the time. Zooming in on that function, with yellow
being this branch and red being `main`, we see:

<img width="1064" alt="Screenshot 2024-03-12 175832"
src="https://github.com/bevyengine/bevy/assets/157897/0dfc8d3f-49f4-496e-8825-a66e64d356d0">

The binning happens in `queue_material_meshes`. Again with yellow being
this branch and red being `main`:
<img width="1064" alt="Screenshot 2024-03-12 175755"
src="https://github.com/bevyengine/bevy/assets/157897/b9b20dc1-11c8-400c-a6cc-1c2e09c1bb96">

We can see that there is a small regression in `queue_material_meshes`
performance, but it's not nearly enough to outweigh the large gains in
`batch_and_prepare_binned_render_phase`.

---------

Co-authored-by: James Liu <contact@jamessliu.com>
This commit is contained in:
Patrick Walton 2024-03-29 21:55:02 -05:00 committed by GitHub
parent df76fd4a1b
commit 4dadebd9c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1061 additions and 420 deletions

View file

@ -4,7 +4,7 @@ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
diagnostic::RecordDiagnostics, diagnostic::RecordDiagnostics,
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::RenderPhase, render_phase::SortedRenderPhase,
render_resource::RenderPassDescriptor, render_resource::RenderPassDescriptor,
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewTarget}, view::{ExtractedView, ViewTarget},
@ -16,7 +16,7 @@ pub struct MainPass2dNode {
query: QueryState< query: QueryState<
( (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static RenderPhase<Transparent2d>, &'static SortedRenderPhase<Transparent2d>,
&'static ViewTarget, &'static ViewTarget,
), ),
With<ExtractedView>, With<ExtractedView>,

View file

@ -38,7 +38,7 @@ use bevy_render::{
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{ render_phase::{
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
RenderPhase, SortedPhaseItem, SortedRenderPhase,
}, },
render_resource::CachedRenderPipelineId, render_resource::CachedRenderPipelineId,
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
@ -96,29 +96,16 @@ pub struct Transparent2d {
} }
impl PhaseItem for Transparent2d { impl PhaseItem for Transparent2d {
type SortKey = FloatOrd;
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.entity
} }
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.sort_key
}
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.draw_function
} }
#[inline]
fn sort(items: &mut [Self]) {
// radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
radsort::sort_by_key(items, |item| item.sort_key().0);
}
#[inline] #[inline]
fn batch_range(&self) -> &Range<u32> { fn batch_range(&self) -> &Range<u32> {
&self.batch_range &self.batch_range
@ -140,6 +127,21 @@ impl PhaseItem for Transparent2d {
} }
} }
impl SortedPhaseItem for Transparent2d {
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.sort_key
}
#[inline]
fn sort(items: &mut [Self]) {
// radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
radsort::sort_by_key(items, |item| item.sort_key().0);
}
}
impl CachedRenderPipelinePhaseItem for Transparent2d { impl CachedRenderPipelinePhaseItem for Transparent2d {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
@ -155,7 +157,7 @@ pub fn extract_core_2d_camera_phases(
if camera.is_active { if camera.is_active {
commands commands
.get_or_spawn(entity) .get_or_spawn(entity)
.insert(RenderPhase::<Transparent2d>::default()); .insert(SortedRenderPhase::<Transparent2d>::default());
} }
} }
} }

View file

@ -7,7 +7,7 @@ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
diagnostic::RecordDiagnostics, diagnostic::RecordDiagnostics,
render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::{RenderPhase, TrackedRenderPass}, render_phase::{BinnedRenderPhase, TrackedRenderPass},
render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp},
renderer::RenderContext, renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset},
@ -17,14 +17,16 @@ use bevy_utils::tracing::info_span;
use super::AlphaMask3d; use super::AlphaMask3d;
/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`]. /// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`]
/// [`BinnedRenderPhase`] and [`AlphaMask3d`]
/// [`bevy_render::render_phase::SortedRenderPhase`]s.
#[derive(Default)] #[derive(Default)]
pub struct MainOpaquePass3dNode; pub struct MainOpaquePass3dNode;
impl ViewNode for MainOpaquePass3dNode { impl ViewNode for MainOpaquePass3dNode {
type ViewQuery = ( type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static RenderPhase<Opaque3d>, &'static BinnedRenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>, &'static BinnedRenderPhase<AlphaMask3d>,
&'static ViewTarget, &'static ViewTarget,
&'static ViewDepthTexture, &'static ViewDepthTexture,
Option<&'static SkyboxPipelineId>, Option<&'static SkyboxPipelineId>,
@ -80,14 +82,14 @@ impl ViewNode for MainOpaquePass3dNode {
} }
// Opaque draws // Opaque draws
if !opaque_phase.items.is_empty() { if !opaque_phase.is_empty() {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered(); let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered();
opaque_phase.render(&mut render_pass, world, view_entity); opaque_phase.render(&mut render_pass, world, view_entity);
} }
// Alpha draws // Alpha draws
if !alpha_mask_phase.items.is_empty() { if !alpha_mask_phase.is_empty() {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered(); let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered();
alpha_mask_phase.render(&mut render_pass, world, view_entity); alpha_mask_phase.render(&mut render_pass, world, view_entity);

View file

@ -4,7 +4,7 @@ use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase, render_phase::SortedRenderPhase,
render_resource::{Extent3d, RenderPassDescriptor, StoreOp}, render_resource::{Extent3d, RenderPassDescriptor, StoreOp},
renderer::RenderContext, renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget}, view::{ViewDepthTexture, ViewTarget},
@ -13,7 +13,8 @@ use bevy_render::{
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
use std::ops::Range; use std::ops::Range;
/// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] [`RenderPhase`]. /// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`]
/// [`SortedRenderPhase`].
#[derive(Default)] #[derive(Default)]
pub struct MainTransmissivePass3dNode; pub struct MainTransmissivePass3dNode;
@ -21,7 +22,7 @@ impl ViewNode for MainTransmissivePass3dNode {
type ViewQuery = ( type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static Camera3d, &'static Camera3d,
&'static RenderPhase<Transmissive3d>, &'static SortedRenderPhase<Transmissive3d>,
&'static ViewTarget, &'static ViewTarget,
Option<&'static ViewTransmissionTexture>, Option<&'static ViewTransmissionTexture>,
&'static ViewDepthTexture, &'static ViewDepthTexture,

View file

@ -4,7 +4,7 @@ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
diagnostic::RecordDiagnostics, diagnostic::RecordDiagnostics,
render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase, render_phase::SortedRenderPhase,
render_resource::{RenderPassDescriptor, StoreOp}, render_resource::{RenderPassDescriptor, StoreOp},
renderer::RenderContext, renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget}, view::{ViewDepthTexture, ViewTarget},
@ -12,14 +12,15 @@ use bevy_render::{
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
/// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] [`RenderPhase`]. /// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`]
/// [`SortedRenderPhase`].
#[derive(Default)] #[derive(Default)]
pub struct MainTransparentPass3dNode; pub struct MainTransparentPass3dNode;
impl ViewNode for MainTransparentPass3dNode { impl ViewNode for MainTransparentPass3dNode {
type ViewQuery = ( type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static RenderPhase<Transparent3d>, &'static SortedRenderPhase<Transparent3d>,
&'static ViewTarget, &'static ViewTarget,
&'static ViewDepthTexture, &'static ViewDepthTexture,
); );

View file

@ -56,15 +56,15 @@ use bevy_render::{
prelude::Msaa, prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{ render_phase::{
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, sort_phase_system, BinnedPhaseItem, BinnedRenderPhase, CachedRenderPipelinePhaseItem,
RenderPhase, DrawFunctionId, DrawFunctions, PhaseItem, SortedPhaseItem, SortedRenderPhase,
}, },
render_resource::{ render_resource::{
CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, Texture, BindGroupId, CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
}, },
renderer::RenderDevice, renderer::RenderDevice,
texture::{BevyDefault, ColorAttachment, TextureCache}, texture::{BevyDefault, ColorAttachment, Image, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget}, view::{ExtractedView, ViewDepthTexture, ViewTarget},
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
}; };
@ -80,8 +80,8 @@ use crate::{
}, },
prepass::{ prepass::{
node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass,
NormalPrepass, Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBinKey, ViewPrepassTextures,
NORMAL_PREPASS_FORMAT, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
}, },
skybox::SkyboxPlugin, skybox::SkyboxPlugin,
tonemapping::TonemappingNode, tonemapping::TonemappingNode,
@ -117,14 +117,8 @@ impl Plugin for Core3dPlugin {
.add_systems( .add_systems(
Render, Render,
( (
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort), sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort), sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Opaque3dDeferred>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dDeferred>.in_set(RenderSet::PhaseSort),
prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources), prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources), prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources),
prepare_prepass_textures.in_set(RenderSet::PrepareResources), prepare_prepass_textures.in_set(RenderSet::PrepareResources),
@ -180,37 +174,49 @@ impl Plugin for Core3dPlugin {
} }
} }
/// Opaque 3D [`BinnedPhaseItem`]s.
pub struct Opaque3d { pub struct Opaque3d {
pub asset_id: AssetId<Mesh>, /// The key, which determines which can be batched.
pub pipeline: CachedRenderPipelineId, pub key: Opaque3dBinKey,
pub entity: Entity, /// An entity from which data will be fetched, including the mesh if
pub draw_function: DrawFunctionId, /// applicable.
pub representative_entity: Entity,
/// The ranges of instances.
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
/// The dynamic offset.
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for Opaque3d { /// Data that must be identical in order to batch meshes together.
type SortKey = (usize, AssetId<Mesh>); #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opaque3dBinKey {
/// 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>>,
}
impl PhaseItem for Opaque3d {
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -234,44 +240,48 @@ impl PhaseItem for Opaque3d {
} }
} }
impl BinnedPhaseItem for Opaque3d {
type BinKey = Opaque3dBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Opaque3d {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for Opaque3d { impl CachedRenderPipelinePhaseItem for Opaque3d {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline self.key.pipeline
} }
} }
pub struct AlphaMask3d { pub struct AlphaMask3d {
pub asset_id: AssetId<Mesh>, pub key: OpaqueNoLightmap3dBinKey,
pub pipeline: CachedRenderPipelineId, pub representative_entity: Entity,
pub entity: Entity,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for AlphaMask3d { impl PhaseItem for AlphaMask3d {
type SortKey = (usize, AssetId<Mesh>);
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -295,10 +305,29 @@ impl PhaseItem for AlphaMask3d {
} }
} }
impl BinnedPhaseItem for AlphaMask3d {
type BinKey = OpaqueNoLightmap3dBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Self {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for AlphaMask3d { impl CachedRenderPipelinePhaseItem for AlphaMask3d {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline self.key.pipeline
} }
} }
@ -312,9 +341,6 @@ pub struct Transmissive3d {
} }
impl PhaseItem for Transmissive3d { impl PhaseItem for Transmissive3d {
// NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort.
type SortKey = FloatOrd;
/// For now, automatic batching is disabled for transmissive items because their rendering is /// For now, automatic batching is disabled for transmissive items because their rendering is
/// split into multiple steps depending on [`Camera3d::screen_space_specular_transmission_steps`], /// split into multiple steps depending on [`Camera3d::screen_space_specular_transmission_steps`],
/// which the batching system doesn't currently know about. /// which the batching system doesn't currently know about.
@ -331,21 +357,11 @@ impl PhaseItem for Transmissive3d {
self.entity self.entity
} }
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.draw_function
} }
#[inline]
fn sort(items: &mut [Self]) {
radsort::sort_by_key(items, |item| item.distance);
}
#[inline] #[inline]
fn batch_range(&self) -> &Range<u32> { fn batch_range(&self) -> &Range<u32> {
&self.batch_range &self.batch_range
@ -367,6 +383,21 @@ impl PhaseItem for Transmissive3d {
} }
} }
impl SortedPhaseItem for Transmissive3d {
// NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort.
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn sort(items: &mut [Self]) {
radsort::sort_by_key(items, |item| item.distance);
}
}
impl CachedRenderPipelinePhaseItem for Transmissive3d { impl CachedRenderPipelinePhaseItem for Transmissive3d {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
@ -384,29 +415,16 @@ pub struct Transparent3d {
} }
impl PhaseItem for Transparent3d { impl PhaseItem for Transparent3d {
// NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort.
type SortKey = FloatOrd;
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.entity
} }
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.draw_function
} }
#[inline]
fn sort(items: &mut [Self]) {
radsort::sort_by_key(items, |item| item.distance);
}
#[inline] #[inline]
fn batch_range(&self) -> &Range<u32> { fn batch_range(&self) -> &Range<u32> {
&self.batch_range &self.batch_range
@ -428,6 +446,21 @@ impl PhaseItem for Transparent3d {
} }
} }
impl SortedPhaseItem for Transparent3d {
// NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort.
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn sort(items: &mut [Self]) {
radsort::sort_by_key(items, |item| item.distance);
}
}
impl CachedRenderPipelinePhaseItem for Transparent3d { impl CachedRenderPipelinePhaseItem for Transparent3d {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
@ -442,10 +475,10 @@ pub fn extract_core_3d_camera_phases(
for (entity, camera) in &cameras_3d { for (entity, camera) in &cameras_3d {
if camera.is_active { if camera.is_active {
commands.get_or_spawn(entity).insert(( commands.get_or_spawn(entity).insert((
RenderPhase::<Opaque3d>::default(), BinnedRenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(), BinnedRenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transmissive3d>::default(), SortedRenderPhase::<Transmissive3d>::default(),
RenderPhase::<Transparent3d>::default(), SortedRenderPhase::<Transparent3d>::default(),
)); ));
} }
} }
@ -476,15 +509,15 @@ pub fn extract_camera_prepass_phase(
if depth_prepass || normal_prepass || motion_vector_prepass { if depth_prepass || normal_prepass || motion_vector_prepass {
entity.insert(( entity.insert((
RenderPhase::<Opaque3dPrepass>::default(), BinnedRenderPhase::<Opaque3dPrepass>::default(),
RenderPhase::<AlphaMask3dPrepass>::default(), BinnedRenderPhase::<AlphaMask3dPrepass>::default(),
)); ));
} }
if deferred_prepass { if deferred_prepass {
entity.insert(( entity.insert((
RenderPhase::<Opaque3dDeferred>::default(), BinnedRenderPhase::<Opaque3dDeferred>::default(),
RenderPhase::<AlphaMask3dDeferred>::default(), BinnedRenderPhase::<AlphaMask3dDeferred>::default(),
)); ));
} }
@ -512,10 +545,10 @@ pub fn prepare_core_3d_depth_textures(
views_3d: Query< views_3d: Query<
(Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d), (Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d),
( (
With<RenderPhase<Opaque3d>>, With<BinnedRenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>, With<BinnedRenderPhase<AlphaMask3d>>,
With<RenderPhase<Transmissive3d>>, With<SortedRenderPhase<Transmissive3d>>,
With<RenderPhase<Transparent3d>>, With<SortedRenderPhase<Transparent3d>>,
), ),
>, >,
) { ) {
@ -595,12 +628,12 @@ pub fn prepare_core_3d_transmission_textures(
&ExtractedCamera, &ExtractedCamera,
&Camera3d, &Camera3d,
&ExtractedView, &ExtractedView,
&RenderPhase<Transmissive3d>, &SortedRenderPhase<Transmissive3d>,
), ),
( (
With<RenderPhase<Opaque3d>>, With<BinnedRenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>, With<BinnedRenderPhase<AlphaMask3d>>,
With<RenderPhase<Transparent3d>>, With<SortedRenderPhase<Transparent3d>>,
), ),
>, >,
) { ) {
@ -700,10 +733,10 @@ pub fn prepare_prepass_textures(
Has<DeferredPrepass>, Has<DeferredPrepass>,
), ),
Or<( Or<(
With<RenderPhase<Opaque3dPrepass>>, With<BinnedRenderPhase<Opaque3dPrepass>>,
With<RenderPhase<AlphaMask3dPrepass>>, With<BinnedRenderPhase<AlphaMask3dPrepass>>,
With<RenderPhase<Opaque3dDeferred>>, With<BinnedRenderPhase<Opaque3dDeferred>>,
With<RenderPhase<AlphaMask3dDeferred>>, With<BinnedRenderPhase<AlphaMask3dDeferred>>,
)>, )>,
>, >,
) { ) {

View file

@ -3,15 +3,15 @@ pub mod node;
use std::ops::Range; use std::ops::Range;
use bevy_asset::AssetId;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_render::{ use bevy_render::{
mesh::Mesh, render_phase::{BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_resource::{CachedRenderPipelineId, TextureFormat}, render_resource::{CachedRenderPipelineId, TextureFormat},
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
use crate::prepass::OpaqueNoLightmap3dBinKey;
pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint; pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint;
pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint; pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint;
pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth16Unorm; pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth16Unorm;
@ -21,37 +21,23 @@ pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat:
/// Sorted by pipeline, then by mesh to improve batching. /// Sorted by pipeline, then by mesh to improve batching.
/// ///
/// Used to render all 3D meshes with materials that have no transparency. /// Used to render all 3D meshes with materials that have no transparency.
#[derive(PartialEq, Eq, Hash)]
pub struct Opaque3dDeferred { pub struct Opaque3dDeferred {
pub entity: Entity, pub key: OpaqueNoLightmap3dBinKey,
pub asset_id: AssetId<Mesh>, pub representative_entity: Entity,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for Opaque3dDeferred { impl PhaseItem for Opaque3dDeferred {
type SortKey = (usize, AssetId<Mesh>);
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -75,10 +61,29 @@ impl PhaseItem for Opaque3dDeferred {
} }
} }
impl BinnedPhaseItem for Opaque3dDeferred {
type BinKey = OpaqueNoLightmap3dBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Self {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { impl CachedRenderPipelinePhaseItem for Opaque3dDeferred {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id self.key.pipeline
} }
} }
@ -88,36 +93,21 @@ impl CachedRenderPipelinePhaseItem for Opaque3dDeferred {
/// ///
/// Used to render all meshes with a material with an alpha mask. /// Used to render all meshes with a material with an alpha mask.
pub struct AlphaMask3dDeferred { pub struct AlphaMask3dDeferred {
pub asset_id: AssetId<Mesh>, pub key: OpaqueNoLightmap3dBinKey,
pub entity: Entity, pub representative_entity: Entity,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for AlphaMask3dDeferred { impl PhaseItem for AlphaMask3dDeferred {
type SortKey = (usize, AssetId<Mesh>);
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -141,9 +131,27 @@ impl PhaseItem for AlphaMask3dDeferred {
} }
} }
impl BinnedPhaseItem for AlphaMask3dDeferred {
type BinKey = OpaqueNoLightmap3dBinKey;
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Self {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred { impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id self.key.pipeline
} }
} }

View file

@ -2,12 +2,11 @@ use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryItem; use bevy_ecs::query::QueryItem;
use bevy_render::render_graph::ViewNode; use bevy_render::render_graph::ViewNode;
use bevy_render::render_phase::TrackedRenderPass; use bevy_render::render_phase::{BinnedRenderPhase, TrackedRenderPass};
use bevy_render::render_resource::{CommandEncoderDescriptor, StoreOp}; use bevy_render::render_resource::{CommandEncoderDescriptor, StoreOp};
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
render_resource::RenderPassDescriptor, render_resource::RenderPassDescriptor,
renderer::RenderContext, renderer::RenderContext,
view::ViewDepthTexture, view::ViewDepthTexture,
@ -28,8 +27,8 @@ pub struct DeferredGBufferPrepassNode;
impl ViewNode for DeferredGBufferPrepassNode { impl ViewNode for DeferredGBufferPrepassNode {
type ViewQuery = ( type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static RenderPhase<Opaque3dDeferred>, &'static BinnedRenderPhase<Opaque3dDeferred>,
&'static RenderPhase<AlphaMask3dDeferred>, &'static BinnedRenderPhase<AlphaMask3dDeferred>,
&'static ViewDepthTexture, &'static ViewDepthTexture,
&'static ViewPrepassTextures, &'static ViewPrepassTextures,
); );
@ -138,14 +137,16 @@ impl ViewNode for DeferredGBufferPrepassNode {
} }
// Opaque draws // Opaque draws
if !opaque_deferred_phase.items.is_empty() { if !opaque_deferred_phase.batchable_keys.is_empty()
|| !opaque_deferred_phase.unbatchable_keys.is_empty()
{
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _opaque_prepass_span = info_span!("opaque_deferred").entered(); let _opaque_prepass_span = info_span!("opaque_deferred").entered();
opaque_deferred_phase.render(&mut render_pass, world, view_entity); opaque_deferred_phase.render(&mut render_pass, world, view_entity);
} }
// Alpha masked draws // Alpha masked draws
if !alpha_mask_deferred_phase.items.is_empty() { if !alpha_mask_deferred_phase.is_empty() {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered(); let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered();
alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity); alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity);

View file

@ -34,8 +34,8 @@ use bevy_ecs::prelude::*;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::{ use bevy_render::{
mesh::Mesh, mesh::Mesh,
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, render_phase::{BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat, TextureView}, render_resource::{BindGroupId, CachedRenderPipelineId, Extent3d, TextureFormat, TextureView},
texture::ColorAttachment, texture::ColorAttachment,
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
@ -111,36 +111,45 @@ impl ViewPrepassTextures {
/// ///
/// Used to render all 3D meshes with materials that have no transparency. /// Used to render all 3D meshes with materials that have no transparency.
pub struct Opaque3dPrepass { pub struct Opaque3dPrepass {
pub entity: Entity, /// Information that separates items into bins.
pub asset_id: AssetId<Mesh>, pub key: OpaqueNoLightmap3dBinKey,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId, /// An entity from which Bevy fetches data common to all instances in this
/// batch, such as the mesh.
pub representative_entity: Entity,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for Opaque3dPrepass { // TODO: Try interning these.
type SortKey = (usize, AssetId<Mesh>); /// 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 {
/// 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>,
}
impl PhaseItem for Opaque3dPrepass {
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -164,10 +173,29 @@ impl PhaseItem for Opaque3dPrepass {
} }
} }
impl BinnedPhaseItem for Opaque3dPrepass {
type BinKey = OpaqueNoLightmap3dBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Opaque3dPrepass {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { impl CachedRenderPipelinePhaseItem for Opaque3dPrepass {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id self.key.pipeline
} }
} }
@ -177,36 +205,21 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass {
/// ///
/// Used to render all meshes with a material with an alpha mask. /// Used to render all meshes with a material with an alpha mask.
pub struct AlphaMask3dPrepass { pub struct AlphaMask3dPrepass {
pub asset_id: AssetId<Mesh>, pub key: OpaqueNoLightmap3dBinKey,
pub entity: Entity, pub representative_entity: Entity,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for AlphaMask3dPrepass { impl PhaseItem for AlphaMask3dPrepass {
type SortKey = (usize, AssetId<Mesh>);
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
// Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes.
(self.pipeline_id.id(), self.asset_id)
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
} }
#[inline] #[inline]
@ -230,9 +243,28 @@ impl PhaseItem for AlphaMask3dPrepass {
} }
} }
impl BinnedPhaseItem for AlphaMask3dPrepass {
type BinKey = OpaqueNoLightmap3dBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Self {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass { impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id self.key.pipeline
} }
} }

View file

@ -4,7 +4,7 @@ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
diagnostic::RecordDiagnostics, diagnostic::RecordDiagnostics,
render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::{RenderPhase, TrackedRenderPass}, render_phase::{BinnedRenderPhase, TrackedRenderPass},
render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp},
renderer::RenderContext, renderer::RenderContext,
view::ViewDepthTexture, view::ViewDepthTexture,
@ -23,8 +23,8 @@ pub struct PrepassNode;
impl ViewNode for PrepassNode { impl ViewNode for PrepassNode {
type ViewQuery = ( type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static RenderPhase<Opaque3dPrepass>, &'static BinnedRenderPhase<Opaque3dPrepass>,
&'static RenderPhase<AlphaMask3dPrepass>, &'static BinnedRenderPhase<AlphaMask3dPrepass>,
&'static ViewDepthTexture, &'static ViewDepthTexture,
&'static ViewPrepassTextures, &'static ViewPrepassTextures,
Option<&'static DeferredPrepass>, Option<&'static DeferredPrepass>,
@ -95,14 +95,16 @@ impl ViewNode for PrepassNode {
} }
// Opaque draws // Opaque draws
if !opaque_prepass_phase.items.is_empty() { if !opaque_prepass_phase.batchable_keys.is_empty()
|| !opaque_prepass_phase.unbatchable_keys.is_empty()
{
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _opaque_prepass_span = info_span!("opaque_prepass").entered(); let _opaque_prepass_span = info_span!("opaque_prepass").entered();
opaque_prepass_phase.render(&mut render_pass, world, view_entity); opaque_prepass_phase.render(&mut render_pass, world, view_entity);
} }
// Alpha masked draws // Alpha masked draws
if !alpha_mask_prepass_phase.items.is_empty() { if !alpha_mask_prepass_phase.is_empty() {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered(); let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered();
alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity); alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity);

View file

@ -17,7 +17,7 @@ use bevy_ecs::{
use bevy_math::FloatOrd; use bevy_math::FloatOrd;
use bevy_render::{ use bevy_render::{
render_asset::{prepare_assets, RenderAssets}, render_asset::{prepare_assets, RenderAssets},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase},
render_resource::*, render_resource::*,
texture::BevyDefault, texture::BevyDefault,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
@ -256,7 +256,7 @@ fn queue_line_gizmos_2d(
line_gizmo_assets: Res<RenderAssets<LineGizmo>>, line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
mut views: Query<( mut views: Query<(
&ExtractedView, &ExtractedView,
&mut RenderPhase<Transparent2d>, &mut SortedRenderPhase<Transparent2d>,
Option<&RenderLayers>, Option<&RenderLayers>,
)>, )>,
) { ) {
@ -309,7 +309,7 @@ fn queue_line_joint_gizmos_2d(
line_gizmo_assets: Res<RenderAssets<LineGizmo>>, line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
mut views: Query<( mut views: Query<(
&ExtractedView, &ExtractedView,
&mut RenderPhase<Transparent2d>, &mut SortedRenderPhase<Transparent2d>,
Option<&RenderLayers>, Option<&RenderLayers>,
)>, )>,
) { ) {

View file

@ -21,7 +21,7 @@ use bevy_ecs::{
use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup};
use bevy_render::{ use bevy_render::{
render_asset::{prepare_assets, RenderAssets}, render_asset::{prepare_assets, RenderAssets},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase},
render_resource::*, render_resource::*,
texture::BevyDefault, texture::BevyDefault,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
@ -281,7 +281,7 @@ fn queue_line_gizmos_3d(
line_gizmo_assets: Res<RenderAssets<LineGizmo>>, line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
mut views: Query<( mut views: Query<(
&ExtractedView, &ExtractedView,
&mut RenderPhase<Transparent3d>, &mut SortedRenderPhase<Transparent3d>,
Option<&RenderLayers>, Option<&RenderLayers>,
( (
Has<NormalPrepass>, Has<NormalPrepass>,
@ -364,7 +364,7 @@ fn queue_line_joint_gizmos_3d(
line_gizmo_assets: Res<RenderAssets<LineGizmo>>, line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
mut views: Query<( mut views: Query<(
&ExtractedView, &ExtractedView,
&mut RenderPhase<Transparent3d>, &mut SortedRenderPhase<Transparent3d>,
Option<&RenderLayers>, Option<&RenderLayers>,
( (
Has<NormalPrepass>, Has<NormalPrepass>,

View file

@ -94,7 +94,6 @@ use bevy_render::{
extract_resource::ExtractResourcePlugin, extract_resource::ExtractResourcePlugin,
render_asset::prepare_assets, render_asset::prepare_assets,
render_graph::RenderGraph, render_graph::RenderGraph,
render_phase::sort_phase_system,
render_resource::Shader, render_resource::Shader,
texture::Image, texture::Image,
view::VisibilitySystems, view::VisibilitySystems,
@ -375,7 +374,6 @@ impl Plugin for PbrPlugin {
prepare_lights prepare_lights
.in_set(RenderSet::ManageViews) .in_set(RenderSet::ManageViews)
.after(prepare_assets::<Image>), .after(prepare_assets::<Image>),
sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort),
prepare_clusters.in_set(RenderSet::PrepareResources), prepare_clusters.in_set(RenderSet::PrepareResources),
), ),
) )
@ -390,7 +388,7 @@ impl Plugin for PbrPlugin {
render_app.ignore_ambiguity( render_app.ignore_ambiguity(
bevy_render::Render, bevy_render::Render,
bevy_core_pipeline::core_3d::prepare_core_3d_transmission_textures, bevy_core_pipeline::core_3d::prepare_core_3d_transmission_textures,
bevy_render::batching::batch_and_prepare_render_phase::< bevy_render::batching::batch_and_prepare_sorted_render_phase::<
bevy_core_pipeline::core_3d::Transmissive3d, bevy_core_pipeline::core_3d::Transmissive3d,
MeshPipeline, MeshPipeline,
>, >,

View file

@ -7,10 +7,12 @@ use crate::*;
use bevy_asset::{Asset, AssetEvent, AssetId, AssetServer}; use bevy_asset::{Asset, AssetEvent, AssetId, AssetServer};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::{ core_3d::{
AlphaMask3d, Camera3d, Opaque3d, ScreenSpaceTransmissionQuality, Transmissive3d, AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
Transparent3d, Transmissive3d, Transparent3d,
},
prepass::{
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey,
}, },
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
tonemapping::{DebandDither, Tonemapping}, tonemapping::{DebandDither, Tonemapping},
}; };
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
@ -541,10 +543,10 @@ pub fn queue_material_meshes<M: Material>(
Option<&Camera3d>, Option<&Camera3d>,
Has<TemporalJitter>, Has<TemporalJitter>,
Option<&Projection>, Option<&Projection>,
&mut RenderPhase<Opaque3d>, &mut BinnedRenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>, &mut BinnedRenderPhase<AlphaMask3d>,
&mut RenderPhase<Transmissive3d>, &mut SortedRenderPhase<Transmissive3d>,
&mut RenderPhase<Transparent3d>, &mut SortedRenderPhase<Transparent3d>,
( (
Has<RenderViewLightProbes<EnvironmentMapLight>>, Has<RenderViewLightProbes<EnvironmentMapLight>>,
Has<RenderViewLightProbes<IrradianceVolume>>, Has<RenderViewLightProbes<IrradianceVolume>>,
@ -679,10 +681,11 @@ pub fn queue_material_meshes<M: Material>(
mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode);
if render_lightmaps let lightmap_image = render_lightmaps
.render_lightmaps .render_lightmaps
.contains_key(visible_entity) .get(visible_entity)
{ .map(|lightmap| lightmap.image);
if lightmap_image.is_some() {
mesh_key |= MeshPipelineKey::LIGHTMAPPED; mesh_key |= MeshPipelineKey::LIGHTMAPPED;
} }
@ -722,14 +725,14 @@ pub fn queue_material_meshes<M: Material>(
dynamic_offset: None, dynamic_offset: None,
}); });
} else if forward { } else if forward {
opaque_phase.add(Opaque3d { let bin_key = Opaque3dBinKey {
entity: *visible_entity,
draw_function: draw_opaque_pbr, draw_function: draw_opaque_pbr,
pipeline: pipeline_id, pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, lightmap_image,
}); };
opaque_phase.add(bin_key, *visible_entity, mesh_instance.should_batch());
} }
} }
AlphaMode::Mask(_) => { AlphaMode::Mask(_) => {
@ -746,14 +749,17 @@ pub fn queue_material_meshes<M: Material>(
dynamic_offset: None, dynamic_offset: None,
}); });
} else if forward { } else if forward {
alpha_mask_phase.add(AlphaMask3d { let bin_key = OpaqueNoLightmap3dBinKey {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr, draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id, pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, };
}); alpha_mask_phase.add(
bin_key,
*visible_entity,
mesh_instance.should_batch(),
);
} }
} }
AlphaMode::Blend AlphaMode::Blend

View file

@ -1,5 +1,6 @@
mod prepass_bindings; mod prepass_bindings;
use bevy_render::batching::{batch_and_prepare_binned_render_phase, sort_binned_render_phase};
use bevy_render::mesh::MeshVertexBufferLayoutRef; use bevy_render::mesh::MeshVertexBufferLayoutRef;
use bevy_render::render_resource::binding_types::uniform_buffer; use bevy_render::render_resource::binding_types::uniform_buffer;
pub use prepass_bindings::*; pub use prepass_bindings::*;
@ -16,7 +17,6 @@ use bevy_ecs::{
}; };
use bevy_math::{Affine3A, Mat4}; use bevy_math::{Affine3A, Mat4};
use bevy_render::{ use bevy_render::{
batching::batch_and_prepare_render_phase,
globals::{GlobalsBuffer, GlobalsUniform}, globals::{GlobalsBuffer, GlobalsUniform},
prelude::{Camera, Mesh}, prelude::{Camera, Mesh},
render_asset::RenderAssets, render_asset::RenderAssets,
@ -154,12 +154,18 @@ where
.add_systems(ExtractSchedule, extract_camera_previous_view_projection) .add_systems(ExtractSchedule, extract_camera_previous_view_projection)
.add_systems( .add_systems(
Render, Render,
(
(
sort_binned_render_phase::<Opaque3dPrepass>,
sort_binned_render_phase::<AlphaMask3dPrepass>
).in_set(RenderSet::PhaseSort),
( (
prepare_previous_view_projection_uniforms, prepare_previous_view_projection_uniforms,
batch_and_prepare_render_phase::<Opaque3dPrepass, MeshPipeline>, batch_and_prepare_binned_render_phase::<Opaque3dPrepass, MeshPipeline>,
batch_and_prepare_render_phase::<AlphaMask3dPrepass, MeshPipeline>, batch_and_prepare_binned_render_phase::<AlphaMask3dPrepass,
MeshPipeline>,
).in_set(RenderSet::PrepareResources),
) )
.in_set(RenderSet::PrepareResources),
); );
} }
@ -710,20 +716,20 @@ pub fn queue_prepass_material_meshes<M: Material>(
( (
&ExtractedView, &ExtractedView,
&VisibleEntities, &VisibleEntities,
Option<&mut RenderPhase<Opaque3dPrepass>>, Option<&mut BinnedRenderPhase<Opaque3dPrepass>>,
Option<&mut RenderPhase<AlphaMask3dPrepass>>, Option<&mut BinnedRenderPhase<AlphaMask3dPrepass>>,
Option<&mut RenderPhase<Opaque3dDeferred>>, Option<&mut BinnedRenderPhase<Opaque3dDeferred>>,
Option<&mut RenderPhase<AlphaMask3dDeferred>>, Option<&mut BinnedRenderPhase<AlphaMask3dDeferred>>,
Option<&DepthPrepass>, Option<&DepthPrepass>,
Option<&NormalPrepass>, Option<&NormalPrepass>,
Option<&MotionVectorPrepass>, Option<&MotionVectorPrepass>,
Option<&DeferredPrepass>, Option<&DeferredPrepass>,
), ),
Or<( Or<(
With<RenderPhase<Opaque3dPrepass>>, With<BinnedRenderPhase<Opaque3dPrepass>>,
With<RenderPhase<AlphaMask3dPrepass>>, With<BinnedRenderPhase<AlphaMask3dPrepass>>,
With<RenderPhase<Opaque3dDeferred>>, With<BinnedRenderPhase<Opaque3dDeferred>>,
With<RenderPhase<AlphaMask3dDeferred>>, With<BinnedRenderPhase<AlphaMask3dDeferred>>,
)>, )>,
>, >,
) where ) where
@ -848,50 +854,54 @@ pub fn queue_prepass_material_meshes<M: Material>(
match alpha_mode { match alpha_mode {
AlphaMode::Opaque => { AlphaMode::Opaque => {
if deferred { if deferred {
opaque_deferred_phase opaque_deferred_phase.as_mut().unwrap().add(
.as_mut() OpaqueNoLightmap3dBinKey {
.unwrap()
.add(Opaque3dDeferred {
entity: *visible_entity,
draw_function: opaque_draw_deferred, draw_function: opaque_draw_deferred,
pipeline_id, pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, },
}); *visible_entity,
mesh_instance.should_batch(),
);
} else if let Some(opaque_phase) = opaque_phase.as_mut() { } else if let Some(opaque_phase) = opaque_phase.as_mut() {
opaque_phase.add(Opaque3dPrepass { opaque_phase.add(
entity: *visible_entity, OpaqueNoLightmap3dBinKey {
draw_function: opaque_draw_prepass, draw_function: opaque_draw_prepass,
pipeline_id, pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, },
}); *visible_entity,
mesh_instance.should_batch(),
);
} }
} }
AlphaMode::Mask(_) => { AlphaMode::Mask(_) => {
if deferred { if deferred {
alpha_mask_deferred_phase let bin_key = OpaqueNoLightmap3dBinKey {
.as_mut() pipeline: pipeline_id,
.unwrap()
.add(AlphaMask3dDeferred {
entity: *visible_entity,
draw_function: alpha_mask_draw_deferred, draw_function: alpha_mask_draw_deferred,
pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, };
}); alpha_mask_deferred_phase.as_mut().unwrap().add(
bin_key,
*visible_entity,
mesh_instance.should_batch(),
);
} else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() {
alpha_mask_phase.add(AlphaMask3dPrepass { let bin_key = OpaqueNoLightmap3dBinKey {
entity: *visible_entity, pipeline: pipeline_id,
draw_function: alpha_mask_draw_prepass, draw_function: alpha_mask_draw_prepass,
pipeline_id,
asset_id: mesh_instance.mesh_asset_id, asset_id: mesh_instance.mesh_asset_id,
batch_range: 0..1, material_bind_group_id: material.get_bind_group_id().0,
dynamic_offset: None, };
}); alpha_mask_phase.add(
bin_key,
*visible_entity,
mesh_instance.should_batch(),
);
} }
} }
AlphaMode::Blend AlphaMode::Blend

View file

@ -1,6 +1,7 @@
use bevy_asset::AssetId;
use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}; use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*; 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_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::{ use bevy_render::{
camera::Camera, camera::Camera,
@ -684,7 +685,7 @@ pub fn prepare_lights(
mut light_meta: ResMut<LightMeta>, mut light_meta: ResMut<LightMeta>,
views: Query< views: Query<
(Entity, &ExtractedView, &ExtractedClusterConfig), (Entity, &ExtractedView, &ExtractedClusterConfig),
With<RenderPhase<Transparent3d>>, With<SortedRenderPhase<Transparent3d>>,
>, >,
ambient_light: Res<AmbientLight>, ambient_light: Res<AmbientLight>,
point_light_shadow_map: Res<PointLightShadowMap>, point_light_shadow_map: Res<PointLightShadowMap>,
@ -1057,7 +1058,7 @@ pub fn prepare_lights(
color_grading: Default::default(), color_grading: Default::default(),
}, },
*frustum, *frustum,
RenderPhase::<Shadow>::default(), BinnedRenderPhase::<Shadow>::default(),
LightEntity::Point { LightEntity::Point {
light_entity, light_entity,
face_index, face_index,
@ -1116,7 +1117,7 @@ pub fn prepare_lights(
color_grading: Default::default(), color_grading: Default::default(),
}, },
*spot_light_frustum.unwrap(), *spot_light_frustum.unwrap(),
RenderPhase::<Shadow>::default(), BinnedRenderPhase::<Shadow>::default(),
LightEntity::Spot { light_entity }, LightEntity::Spot { light_entity },
)) ))
.id(); .id();
@ -1196,7 +1197,7 @@ pub fn prepare_lights(
color_grading: Default::default(), color_grading: Default::default(),
}, },
frustum, frustum,
RenderPhase::<Shadow>::default(), BinnedRenderPhase::<Shadow>::default(),
LightEntity::Directional { LightEntity::Directional {
light_entity, light_entity,
cascade_index, cascade_index,
@ -1548,7 +1549,7 @@ pub fn prepare_clusters(
render_queue: Res<RenderQueue>, render_queue: Res<RenderQueue>,
mesh_pipeline: Res<MeshPipeline>, mesh_pipeline: Res<MeshPipeline>,
global_light_meta: Res<GlobalLightMeta>, global_light_meta: Res<GlobalLightMeta>,
views: Query<(Entity, &ExtractedClustersPointLights), With<RenderPhase<Transparent3d>>>, views: Query<(Entity, &ExtractedClustersPointLights), With<SortedRenderPhase<Transparent3d>>>,
) { ) {
let render_device = render_device.into_inner(); let render_device = render_device.into_inner();
let supports_storage_buffers = matches!( let supports_storage_buffers = matches!(
@ -1605,7 +1606,7 @@ pub fn queue_shadows<M: Material>(
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
render_lightmaps: Res<RenderLightmaps>, render_lightmaps: Res<RenderLightmaps>,
view_lights: Query<(Entity, &ViewLightEntities)>, view_lights: Query<(Entity, &ViewLightEntities)>,
mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>, mut view_light_shadow_phases: Query<(&LightEntity, &mut BinnedRenderPhase<Shadow>)>,
point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>, point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>,
directional_light_entities: Query<&CascadesVisibleEntities, With<ExtractedDirectionalLight>>, directional_light_entities: Query<&CascadesVisibleEntities, With<ExtractedDirectionalLight>>,
spot_light_entities: Query<&VisibleEntities, With<ExtractedPointLight>>, spot_light_entities: Query<&VisibleEntities, With<ExtractedPointLight>>,
@ -1708,52 +1709,48 @@ pub fn queue_shadows<M: Material>(
.material_bind_group_id .material_bind_group_id
.set(material.get_bind_group_id()); .set(material.get_bind_group_id());
shadow_phase.add(Shadow { shadow_phase.add(
ShadowBinKey {
draw_function: draw_shadow_mesh, draw_function: draw_shadow_mesh,
pipeline: pipeline_id, pipeline: pipeline_id,
asset_id: mesh_instance.mesh_asset_id,
},
entity, entity,
distance: 0.0, // TODO: sort front-to-back mesh_instance.should_batch(),
batch_range: 0..1, );
dynamic_offset: None,
});
} }
} }
} }
} }
pub struct Shadow { pub struct Shadow {
pub distance: f32, pub key: ShadowBinKey,
pub entity: Entity, pub representative_entity: Entity,
pub pipeline: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>, pub dynamic_offset: Option<NonMaxU32>,
} }
impl PhaseItem for Shadow { #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
type SortKey = usize; pub struct ShadowBinKey {
/// 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>,
}
impl PhaseItem for Shadow {
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.representative_entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.pipeline.id()
} }
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.key.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
// The shadow phase is sorted by pipeline id for performance reasons.
// Grouping all draw commands using the same pipeline together performs
// better than rebinding everything at a high rate.
radsort::sort_by_key(items, |item| item.sort_key());
} }
#[inline] #[inline]
@ -1777,16 +1774,35 @@ impl PhaseItem for Shadow {
} }
} }
impl BinnedPhaseItem for Shadow {
type BinKey = ShadowBinKey;
#[inline]
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self {
Shadow {
key,
representative_entity,
batch_range,
dynamic_offset,
}
}
}
impl CachedRenderPipelinePhaseItem for Shadow { impl CachedRenderPipelinePhaseItem for Shadow {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline self.key.pipeline
} }
} }
pub struct ShadowPassNode { pub struct ShadowPassNode {
main_view_query: QueryState<&'static ViewLightEntities>, main_view_query: QueryState<Read<ViewLightEntities>>,
view_light_query: QueryState<(&'static ShadowView, &'static RenderPhase<Shadow>)>, view_light_query: QueryState<(Read<ShadowView>, Read<BinnedRenderPhase<Shadow>>)>,
} }
impl ShadowPassNode { impl ShadowPassNode {

View file

@ -13,7 +13,8 @@ use bevy_ecs::{
use bevy_math::{Affine3, Rect, UVec2, Vec4}; use bevy_math::{Affine3, Rect, UVec2, Vec4};
use bevy_render::{ use bevy_render::{
batching::{ batching::{
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase,
sort_binned_render_phase, write_batched_instance_buffer, GetBatchData, GetBinnedBatchData,
NoAutomaticBatching, NoAutomaticBatching,
}, },
mesh::*, mesh::*,
@ -125,13 +126,24 @@ impl Plugin for MeshRenderPlugin {
Render, Render,
( (
( (
batch_and_prepare_render_phase::<Opaque3d, MeshPipeline>, sort_binned_render_phase::<Opaque3d>,
batch_and_prepare_render_phase::<Transmissive3d, MeshPipeline>, sort_binned_render_phase::<AlphaMask3d>,
batch_and_prepare_render_phase::<Transparent3d, MeshPipeline>, sort_binned_render_phase::<Shadow>,
batch_and_prepare_render_phase::<AlphaMask3d, MeshPipeline>, sort_binned_render_phase::<Opaque3dDeferred>,
batch_and_prepare_render_phase::<Shadow, MeshPipeline>, sort_binned_render_phase::<AlphaMask3dDeferred>,
batch_and_prepare_render_phase::<Opaque3dDeferred, MeshPipeline>, )
batch_and_prepare_render_phase::<AlphaMask3dDeferred, MeshPipeline>, .in_set(RenderSet::PhaseSort),
(
batch_and_prepare_binned_render_phase::<Opaque3d, MeshPipeline>,
batch_and_prepare_sorted_render_phase::<Transmissive3d, MeshPipeline>,
batch_and_prepare_sorted_render_phase::<Transparent3d, MeshPipeline>,
batch_and_prepare_binned_render_phase::<AlphaMask3d, MeshPipeline>,
batch_and_prepare_binned_render_phase::<Shadow, MeshPipeline>,
batch_and_prepare_binned_render_phase::<Opaque3dDeferred, MeshPipeline>,
batch_and_prepare_binned_render_phase::<
AlphaMask3dDeferred,
MeshPipeline,
>,
) )
.in_set(RenderSet::PrepareResources), .in_set(RenderSet::PrepareResources),
write_batched_instance_buffer::<MeshPipeline> write_batched_instance_buffer::<MeshPipeline>
@ -471,6 +483,25 @@ impl GetBatchData for MeshPipeline {
} }
} }
impl GetBinnedBatchData for MeshPipeline {
type Param = (SRes<RenderMeshInstances>, SRes<RenderLightmaps>);
type BufferData = MeshUniform;
fn get_batch_data(
(mesh_instances, lightmaps): &SystemParamItem<Self::Param>,
entity: Entity,
) -> Option<Self::BufferData> {
let mesh_instance = mesh_instances.get(&entity)?;
let maybe_lightmap = lightmaps.render_lightmaps.get(&entity);
Some(MeshUniform::new(
&mesh_instance.transforms,
maybe_lightmap.map(|lightmap| lightmap.uv_rect),
))
}
}
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)] #[repr(transparent)]

View file

@ -5,9 +5,13 @@ use bevy_ecs::{
system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem}, system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem},
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
use smallvec::{smallvec, SmallVec};
use crate::{ use crate::{
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, render_phase::{
BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem,
DrawFunctionId, SortedPhaseItem, SortedRenderPhase,
},
render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable}, render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable},
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
}; };
@ -56,6 +60,8 @@ impl<T: PartialEq> BatchMeta<T> {
/// A trait to support getting data used for batching draw commands via phase /// A trait to support getting data used for batching draw commands via phase
/// items. /// items.
pub trait GetBatchData { pub trait GetBatchData {
/// The system parameters [`GetBatchData::get_batch_data`] needs in
/// order to compute the batch data.
type Param: SystemParam + 'static; type Param: SystemParam + 'static;
/// Data used for comparison between phase items. If the pipeline id, draw /// Data used for comparison between phase items. If the pipeline id, draw
/// function id, per-instance data buffer dynamic offset and this data /// function id, per-instance data buffer dynamic offset and this data
@ -74,13 +80,35 @@ pub trait GetBatchData {
) -> Option<(Self::BufferData, Option<Self::CompareData>)>; ) -> Option<(Self::BufferData, Option<Self::CompareData>)>;
} }
/// Batch the items in a render phase. This means comparing metadata needed to draw each phase item /// When implemented on a pipeline, this trait allows the batching logic to
/// and trying to combine the draws into a batch. /// compute the per-batch data that will be uploaded to the GPU.
pub fn batch_and_prepare_render_phase<I: CachedRenderPipelinePhaseItem, F: GetBatchData>( ///
/// This includes things like the mesh transforms.
pub trait GetBinnedBatchData {
/// The system parameters [`GetBinnedBatchData::get_batch_data`] needs
/// in order to compute the batch data.
type Param: SystemParam + 'static;
/// The per-instance data to be inserted into the [`GpuArrayBuffer`]
/// containing these data for all instances.
type BufferData: GpuArrayBufferable + Sync + Send + 'static;
/// Get the per-instance data to be inserted into the [`GpuArrayBuffer`].
fn get_batch_data(
param: &SystemParamItem<Self::Param>,
entity: Entity,
) -> Option<Self::BufferData>;
}
/// Batch the items in a sorted render phase. This means comparing metadata
/// needed to draw each phase item and trying to combine the draws into a batch.
pub fn batch_and_prepare_sorted_render_phase<I, F>(
gpu_array_buffer: ResMut<GpuArrayBuffer<F::BufferData>>, gpu_array_buffer: ResMut<GpuArrayBuffer<F::BufferData>>,
mut views: Query<&mut RenderPhase<I>>, mut views: Query<&mut SortedRenderPhase<I>>,
param: StaticSystemParam<F::Param>, param: StaticSystemParam<F::Param>,
) { ) where
I: CachedRenderPipelinePhaseItem + SortedPhaseItem,
F: GetBatchData,
{
let gpu_array_buffer = gpu_array_buffer.into_inner(); let gpu_array_buffer = gpu_array_buffer.into_inner();
let system_param_item = param.into_inner(); let system_param_item = param.into_inner();
@ -115,6 +143,80 @@ pub fn batch_and_prepare_render_phase<I: CachedRenderPipelinePhaseItem, F: GetBa
} }
} }
/// Sorts a render phase that uses bins.
pub fn sort_binned_render_phase<BPI>(mut views: Query<&mut BinnedRenderPhase<BPI>>)
where
BPI: BinnedPhaseItem,
{
for mut phase in &mut views {
phase.batchable_keys.sort_unstable();
phase.unbatchable_keys.sort_unstable();
}
}
/// Creates batches for a render phase that uses bins.
pub fn batch_and_prepare_binned_render_phase<BPI, GBBD>(
gpu_array_buffer: ResMut<GpuArrayBuffer<GBBD::BufferData>>,
mut views: Query<&mut BinnedRenderPhase<BPI>>,
param: StaticSystemParam<GBBD::Param>,
) where
BPI: BinnedPhaseItem,
GBBD: GetBinnedBatchData,
{
let gpu_array_buffer = gpu_array_buffer.into_inner();
let system_param_item = param.into_inner();
for mut phase in &mut views {
let phase = &mut *phase; // Borrow checker.
// Prepare batchables.
for key in &phase.batchable_keys {
let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![];
for &entity in &phase.batchable_values[key] {
let Some(buffer_data) = GBBD::get_batch_data(&system_param_item, entity) else {
continue;
};
let instance = gpu_array_buffer.push(buffer_data);
// If the dynamic offset has changed, flush the batch.
//
// This is the only time we ever have more than one batch per
// bin. Note that dynamic offsets are only used on platforms
// with no storage buffers.
if !batch_set.last().is_some_and(|batch| {
batch.instance_range.end == instance.index
&& batch.dynamic_offset == instance.dynamic_offset
}) {
batch_set.push(BinnedRenderPhaseBatch {
representative_entity: entity,
instance_range: instance.index..instance.index,
dynamic_offset: instance.dynamic_offset,
});
}
if let Some(batch) = batch_set.last_mut() {
batch.instance_range.end = instance.index + 1;
}
}
phase.batch_sets.push(batch_set);
}
// Prepare unbatchables.
for key in &phase.unbatchable_keys {
let unbatchables = phase.unbatchable_values.get_mut(key).unwrap();
for &entity in &unbatchables.entities {
if let Some(buffer_data) = GBBD::get_batch_data(&system_param_item, entity) {
let instance = gpu_array_buffer.push(buffer_data);
unbatchables.buffer_indices.add(instance);
}
}
}
}
}
pub fn write_batched_instance_buffer<F: GetBatchData>( pub fn write_batched_instance_buffer<F: GetBatchData>(
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>, render_queue: Res<RenderQueue>,

View file

@ -110,13 +110,15 @@ pub enum RenderSet {
PrepareAssets, PrepareAssets,
/// Create any additional views such as those used for shadow mapping. /// Create any additional views such as those used for shadow mapping.
ManageViews, ManageViews,
/// Queue drawable entities as phase items in [`RenderPhase`](crate::render_phase::RenderPhase)s /// Queue drawable entities as phase items in render phases ready for
/// ready for sorting /// sorting (if necessary)
Queue, Queue,
/// A sub-set within [`Queue`](RenderSet::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::<Mesh>` is completed. /// A sub-set within [`Queue`](RenderSet::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::<Mesh>` is completed.
QueueMeshes, QueueMeshes,
// TODO: This could probably be moved in favor of a system ordering abstraction in `Render` or `Queue` // TODO: This could probably be moved in favor of a system ordering
/// Sort the [`RenderPhases`](render_phase::RenderPhase) here. // abstraction in `Render` or `Queue`
/// Sort the [`SortedRenderPhase`](render_phase::SortedRenderPhase)s and
/// [`BinKey`](render_phase::BinnedPhaseItem::BinKey)s here.
PhaseSort, PhaseSort,
/// Prepare render resources from extracted data for the GPU based on their sorted order. /// Prepare render resources from extracted data for the GPU based on their sorted order.
/// Create [`BindGroups`](render_resource::BindGroup) that depend on those data. /// Create [`BindGroups`](render_resource::BindGroup) that depend on those data.

View file

@ -39,7 +39,7 @@ pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
// TODO: make this generic? // TODO: make this generic?
/// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. /// An identifier for a [`Draw`] function stored in [`DrawFunctions`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct DrawFunctionId(u32); pub struct DrawFunctionId(u32);
/// Stores all [`Draw`] functions for the [`PhaseItem`] type. /// Stores all [`Draw`] functions for the [`PhaseItem`] type.
@ -209,6 +209,7 @@ pub trait RenderCommand<P: PhaseItem> {
} }
/// The result of a [`RenderCommand`]. /// The result of a [`RenderCommand`].
#[derive(Debug)]
pub enum RenderCommandResult { pub enum RenderCommandResult {
Success, Success,
Failure, Failure,
@ -301,7 +302,7 @@ where
/// Registers a [`RenderCommand`] as a [`Draw`] function. /// Registers a [`RenderCommand`] as a [`Draw`] function.
/// They are stored inside the [`DrawFunctions`] resource of the app. /// They are stored inside the [`DrawFunctions`] resource of the app.
pub trait AddRenderCommand { pub trait AddRenderCommand {
/// Adds the [`RenderCommand`] for the specified [`RenderPhase`](super::RenderPhase) to the app. /// Adds the [`RenderCommand`] for the specified render phase to the app.
fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>( fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
&mut self, &mut self,
) -> &mut Self ) -> &mut Self

View file

@ -1,7 +1,7 @@
//! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing //! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing
//! entities as part of separate render phases. //! entities as part of separate render phases.
//! //!
//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple [`RenderPhase`]s //! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple render phases
//! (e.g. opaque, transparent, shadow, etc). //! (e.g. opaque, transparent, shadow, etc).
//! They are used to queue entities for rendering. //! They are used to queue entities for rendering.
//! Multiple phases might be required due to different sorting/batching behaviors //! Multiple phases might be required due to different sorting/batching behaviors
@ -29,17 +29,20 @@ mod draw;
mod draw_state; mod draw_state;
mod rangefinder; mod rangefinder;
use bevy_utils::{default, hashbrown::hash_map::Entry, HashMap};
pub use draw::*; pub use draw::*;
pub use draw_state::*; pub use draw_state::*;
use encase::{internal::WriteInto, ShaderSize};
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
pub use rangefinder::*; pub use rangefinder::*;
use crate::render_resource::{CachedRenderPipelineId, PipelineCache}; use crate::render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache};
use bevy_ecs::{ use bevy_ecs::{
prelude::*, prelude::*,
system::{lifetimeless::SRes, SystemParamItem}, system::{lifetimeless::SRes, SystemParamItem},
}; };
use std::{ops::Range, slice::SliceIndex}; use smallvec::SmallVec;
use std::{hash::Hash, ops::Range, slice::SliceIndex};
/// A collection of all rendering instructions, that will be executed by the GPU, for a /// A collection of all rendering instructions, that will be executed by the GPU, for a
/// single render phase for a single view. /// single render phase for a single view.
@ -51,18 +54,351 @@ use std::{ops::Range, slice::SliceIndex};
/// the rendered texture of the previous phase (e.g. for screen-space reflections). /// the rendered texture of the previous phase (e.g. for screen-space reflections).
/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. /// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
/// The render pass might be reused for multiple phases to reduce GPU overhead. /// The render pass might be reused for multiple phases to reduce GPU overhead.
///
/// This flavor of render phase is used for phases in which the ordering is less
/// critical: for example, `Opaque3d`. It's generally faster than the
/// alternative [`SortedRenderPhase`].
#[derive(Component)] #[derive(Component)]
pub struct RenderPhase<I: PhaseItem> { pub struct BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
/// A list of `BinKey`s for batchable items.
///
/// These are accumulated in `queue_material_meshes` and then sorted in
/// `batch_and_prepare_binned_render_phase`.
pub batchable_keys: Vec<BPI::BinKey>,
/// The batchable bins themselves.
///
/// Each bin corresponds to a single batch set. For unbatchable entities,
/// prefer `unbatchable_values` instead.
pub(crate) batchable_values: HashMap<BPI::BinKey, Vec<Entity>>,
/// A list of `BinKey`s for unbatchable items.
///
/// These are accumulated in `queue_material_meshes` and then sorted in
/// `batch_and_prepare_binned_render_phase`.
pub unbatchable_keys: Vec<BPI::BinKey>,
/// The unbatchable bins.
///
/// Each entity here is rendered in a separate drawcall.
pub(crate) unbatchable_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
/// Information on each batch set.
///
/// A *batch set* is a set of entities that will be batched together unless
/// we're on a platform that doesn't support storage buffers (e.g. WebGL 2)
/// and differing dynamic uniform indices force us to break batches. On
/// platforms that support storage buffers, a batch set always consists of
/// at most one batch.
///
/// The unbatchable entities immediately follow the batches in the storage
/// buffers.
pub(crate) batch_sets: Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>,
}
/// Information about a single batch of entities rendered using binned phase
/// items.
#[derive(Debug)]
pub struct BinnedRenderPhaseBatch {
/// An entity that's *representative* of this batch.
///
/// Bevy uses this to fetch the mesh. It can be any entity in the batch.
pub representative_entity: Entity,
/// The range of instance indices in this batch.
pub instance_range: Range<u32>,
/// The dynamic offset of the batch.
///
/// Note that dynamic offsets are only used on platforms that don't support
/// storage buffers.
pub dynamic_offset: Option<NonMaxU32>,
}
/// Information about the unbatchable entities in a bin.
pub(crate) struct UnbatchableBinnedEntities {
/// The entities.
pub(crate) entities: Vec<Entity>,
/// The GPU array buffer indices of each unbatchable binned entity.
pub(crate) buffer_indices: UnbatchableBinnedEntityBufferIndex,
}
/// Stores instance indices and dynamic offsets for unbatchable entities in a
/// binned render phase.
///
/// This is conceptually `Vec<UnbatchableBinnedEntityDynamicOffset>`, but it
/// avoids the overhead of storing dynamic offsets on platforms that support
/// them. In other words, this allows a fast path that avoids allocation on
/// platforms that aren't WebGL 2.
#[derive(Default)]
pub(crate) enum UnbatchableBinnedEntityBufferIndex {
/// There are no unbatchable entities in this bin (yet).
#[default]
NoEntities,
/// The instances for all unbatchable entities in this bin are contiguous,
/// and there are no dynamic uniforms.
///
/// This is the typical case on platforms other than WebGL 2. We special
/// case this to avoid allocation on those platforms.
NoDynamicOffsets {
/// The range of indices.
instance_range: Range<u32>,
},
/// Dynamic uniforms are present for unbatchable entities in this bin.
///
/// We fall back to this on WebGL 2.
DynamicOffsets(Vec<UnbatchableBinnedEntityDynamicOffset>),
}
/// The instance index and dynamic offset (if present) for an unbatchable entity.
///
/// This is only useful on platforms that don't support storage buffers.
#[derive(Clone, Copy)]
pub(crate) struct UnbatchableBinnedEntityDynamicOffset {
/// The instance index.
instance_index: u32,
/// The dynamic offset, if present.
dynamic_offset: Option<NonMaxU32>,
}
impl<BPI> BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
/// Bins a new entity.
///
/// `batchable` specifies whether the entity can be batched with other
/// entities of the same type.
pub fn add(&mut self, key: BPI::BinKey, entity: Entity, batchable: bool) {
if batchable {
match self.batchable_values.entry(key.clone()) {
Entry::Occupied(mut entry) => entry.get_mut().push(entity),
Entry::Vacant(entry) => {
self.batchable_keys.push(key);
entry.insert(vec![entity]);
}
}
} else {
match self.unbatchable_values.entry(key.clone()) {
Entry::Occupied(mut entry) => entry.get_mut().entities.push(entity),
Entry::Vacant(entry) => {
self.unbatchable_keys.push(key);
entry.insert(UnbatchableBinnedEntities {
entities: vec![entity],
buffer_indices: default(),
});
}
}
}
}
/// Encodes the GPU commands needed to render all entities in this phase.
pub fn render<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) {
let draw_functions = world.resource::<DrawFunctions<BPI>>();
let mut draw_functions = draw_functions.write();
draw_functions.prepare(world);
// Encode draws for batchables.
debug_assert_eq!(self.batchable_keys.len(), self.batch_sets.len());
for (key, batch_set) in self.batchable_keys.iter().zip(self.batch_sets.iter()) {
for batch in batch_set {
let binned_phase_item = BPI::new(
key.clone(),
batch.representative_entity,
batch.instance_range.clone(),
batch.dynamic_offset,
);
// Fetch the draw function.
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
else {
continue;
};
draw_function.draw(world, render_pass, view, &binned_phase_item);
}
}
// Encode draws for unbatchables.
for key in &self.unbatchable_keys {
let unbatchable_entities = &self.unbatchable_values[key];
for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() {
let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
UnbatchableBinnedEntityBufferIndex::NoEntities => {
// Shouldn't happen…
continue;
}
UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { instance_range } => {
UnbatchableBinnedEntityDynamicOffset {
instance_index: instance_range.start + entity_index as u32,
dynamic_offset: None,
}
}
UnbatchableBinnedEntityBufferIndex::DynamicOffsets(ref dynamic_offsets) => {
dynamic_offsets[entity_index]
}
};
let binned_phase_item = BPI::new(
key.clone(),
entity,
unbatchable_dynamic_offset.instance_index
..(unbatchable_dynamic_offset.instance_index + 1),
unbatchable_dynamic_offset.dynamic_offset,
);
// Fetch the draw function.
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
else {
continue;
};
draw_function.draw(world, render_pass, view, &binned_phase_item);
}
}
}
pub fn is_empty(&self) -> bool {
self.batchable_keys.is_empty() && self.unbatchable_keys.is_empty()
}
}
impl<BPI> Default for BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
fn default() -> Self {
Self {
batchable_keys: vec![],
batchable_values: HashMap::default(),
unbatchable_keys: vec![],
unbatchable_values: HashMap::default(),
batch_sets: vec![],
}
}
}
impl UnbatchableBinnedEntityBufferIndex {
/// Adds a new entity to the list of unbatchable binned entities.
pub fn add<T>(&mut self, gpu_array_buffer_index: GpuArrayBufferIndex<T>)
where
T: ShaderSize + WriteInto + Clone,
{
match (&mut *self, gpu_array_buffer_index.dynamic_offset) {
(UnbatchableBinnedEntityBufferIndex::NoEntities, None) => {
// This is the first entity we've seen, and we're not on WebGL
// 2. Initialize the fast path.
*self = UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets {
instance_range: gpu_array_buffer_index.index
..(gpu_array_buffer_index.index + 1),
}
}
(UnbatchableBinnedEntityBufferIndex::NoEntities, Some(dynamic_offset)) => {
// This is the first entity we've seen, and we're on WebGL 2.
// Initialize an array.
*self = UnbatchableBinnedEntityBufferIndex::DynamicOffsets(vec![
UnbatchableBinnedEntityDynamicOffset {
instance_index: gpu_array_buffer_index.index,
dynamic_offset: Some(dynamic_offset),
},
]);
}
(
UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets {
ref mut instance_range,
},
None,
) if instance_range.end == gpu_array_buffer_index.index => {
// This is the normal case on non-WebGL 2.
instance_range.end += 1;
}
(
UnbatchableBinnedEntityBufferIndex::DynamicOffsets(ref mut offsets),
dynamic_offset,
) => {
// This is the normal case on WebGL 2.
offsets.push(UnbatchableBinnedEntityDynamicOffset {
instance_index: gpu_array_buffer_index.index,
dynamic_offset,
});
}
(
UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { instance_range },
dynamic_offset,
) => {
// We thought we were in non-WebGL 2 mode, but we got a dynamic
// offset or non-contiguous index anyway. This shouldn't happen,
// but let's go ahead and do the sensible thing anyhow: demote
// the compressed `NoDynamicOffsets` field to the full
// `DynamicOffsets` array.
let mut new_dynamic_offsets: Vec<_> = instance_range
.map(|instance_index| UnbatchableBinnedEntityDynamicOffset {
instance_index,
dynamic_offset: None,
})
.collect();
new_dynamic_offsets.push(UnbatchableBinnedEntityDynamicOffset {
instance_index: gpu_array_buffer_index.index,
dynamic_offset,
});
*self = UnbatchableBinnedEntityBufferIndex::DynamicOffsets(new_dynamic_offsets);
}
}
}
}
/// A collection of all items to be rendered that will be encoded to GPU
/// commands for a single render phase for a single view.
///
/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
/// They are used to queue entities for rendering.
/// Multiple phases might be required due to different sorting/batching behaviors
/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
/// the rendered texture of the previous phase (e.g. for screen-space reflections).
/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
/// The render pass might be reused for multiple phases to reduce GPU overhead.
///
/// This flavor of render phase is used only for meshes that need to be sorted
/// back-to-front, such as transparent meshes. For items that don't need strict
/// sorting, [`BinnedRenderPhase`] is preferred, for performance.
#[derive(Component)]
pub struct SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
pub items: Vec<I>, pub items: Vec<I>,
} }
impl<I: PhaseItem> Default for RenderPhase<I> { impl<I> Default for SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
fn default() -> Self { fn default() -> Self {
Self { items: Vec::new() } Self { items: Vec::new() }
} }
} }
impl<I: PhaseItem> RenderPhase<I> { impl<I> SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
/// Adds a [`PhaseItem`] to this render phase. /// Adds a [`PhaseItem`] to this render phase.
#[inline] #[inline]
pub fn add(&mut self, item: I) { pub fn add(&mut self, item: I) {
@ -123,22 +459,31 @@ impl<I: PhaseItem> RenderPhase<I> {
} }
/// An item (entity of the render world) which will be drawn to a texture or the screen, /// An item (entity of the render world) which will be drawn to a texture or the screen,
/// as part of a [`RenderPhase`]. /// as part of a render phase.
/// ///
/// The data required for rendering an entity is extracted from the main world in the /// The data required for rendering an entity is extracted from the main world in the
/// [`ExtractSchedule`](crate::ExtractSchedule). /// [`ExtractSchedule`](crate::ExtractSchedule).
/// Then it has to be queued up for rendering during the /// Then it has to be queued up for rendering during the
/// [`RenderSet::Queue`](crate::RenderSet::Queue), by adding a corresponding phase item to /// [`RenderSet::Queue`](crate::RenderSet::Queue), by adding a corresponding phase item to
/// a render phase. /// a render phase.
/// Afterwards it will be sorted and rendered automatically in the /// Afterwards it will be possibly sorted and rendered automatically in the
/// [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort) and /// [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort) and
/// [`RenderSet::Render`](crate::RenderSet::Render), respectively. /// [`RenderSet::Render`](crate::RenderSet::Render), respectively.
///
/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and
/// [`SortedPhaseItem`]s.
///
/// * Binned phase items have a `BinKey` which specifies what bin they're to be
/// placed in. All items in the same bin are eligible to be batched together.
/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase
/// items are good for opaque meshes, in which the order of rendering isn't
/// important. Generally, binned phase items are faster than sorted phase items.
///
/// * Sorted phase items, on the other hand, are placed into one large buffer
/// and then sorted all at once. This is needed for transparent meshes, which
/// have to be sorted back-to-front to render with the painter's algorithm.
/// These types of phase items are generally slower than binned phase items.
pub trait PhaseItem: Sized + Send + Sync + 'static { pub trait PhaseItem: Sized + Send + Sync + 'static {
/// The type used for ordering the items. The smallest values are drawn first.
/// This order can be calculated using the [`ViewRangefinder3d`],
/// based on the view-space `Z` value of the corresponding view matrix.
type SortKey: Ord;
/// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`) /// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)
const AUTOMATIC_BATCHING: bool = true; const AUTOMATIC_BATCHING: bool = true;
@ -148,12 +493,63 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
/// from the render world . /// from the render world .
fn entity(&self) -> Entity; fn entity(&self) -> Entity;
/// Determines the order in which the items are drawn.
fn sort_key(&self) -> Self::SortKey;
/// Specifies the [`Draw`] function used to render the item. /// Specifies the [`Draw`] function used to render the item.
fn draw_function(&self) -> DrawFunctionId; fn draw_function(&self) -> DrawFunctionId;
/// The range of instances that the batch covers. After doing a batched draw, batch range
/// length phase items will be skipped. This design is to avoid having to restructure the
/// render phase unnecessarily.
fn batch_range(&self) -> &Range<u32>;
fn batch_range_mut(&mut self) -> &mut Range<u32>;
fn dynamic_offset(&self) -> Option<NonMaxU32>;
fn dynamic_offset_mut(&mut self) -> &mut Option<NonMaxU32>;
}
/// Represents phase items that are placed into bins. The `BinKey` specifies
/// which bin they're to be placed in. Bin keys are sorted, and items within the
/// same bin are eligible to be batched together. The elements within the bins
/// aren't themselves sorted.
///
/// An example of a binned phase item is `Opaque3d`, for which the rendering
/// order isn't critical.
pub trait BinnedPhaseItem: PhaseItem {
/// The key used for binning [`PhaseItem`]s into bins. Order the members of
/// [`BinnedPhaseItem::BinKey`] by the order of binding for best
/// performance. For example, pipeline id, draw function id, mesh asset id,
/// lowest variable bind group id such as the material bind group id, and
/// its dynamic offsets if any, next bind group and offsets, etc. This
/// reduces the need for rebinding between bins and improves performance.
type BinKey: Clone + Send + Sync + Eq + Ord + Hash;
/// Creates a new binned phase item from the key and per-entity data.
///
/// Unlike [`SortedPhaseItem`]s, this is generally called "just in time"
/// before rendering. The resulting phase item isn't stored in any data
/// structures, resulting in significant memory savings.
fn new(
key: Self::BinKey,
representative_entity: Entity,
batch_range: Range<u32>,
dynamic_offset: Option<NonMaxU32>,
) -> Self;
}
/// Represents phase items that must be sorted. The `SortKey` specifies the
/// order that these items are drawn in. These are placed into a single array,
/// and the array as a whole is then sorted.
///
/// An example of a sorted phase item is `Transparent3d`, which must be sorted
/// back to front in order to correctly render with the painter's algorithm.
pub trait SortedPhaseItem: PhaseItem {
/// The type used for ordering the items. The smallest values are drawn first.
/// This order can be calculated using the [`ViewRangefinder3d`],
/// based on the view-space `Z` value of the corresponding view matrix.
type SortKey: Ord;
/// Determines the order in which the items are drawn.
fn sort_key(&self) -> Self::SortKey;
/// Sorts a slice of phase items into render order. Generally if the same type /// Sorts a slice of phase items into render order. Generally if the same type
/// is batched this should use a stable sort like [`slice::sort_by_key`]. /// is batched this should use a stable sort like [`slice::sort_by_key`].
/// In almost all other cases, this should not be altered from the default, /// In almost all other cases, this should not be altered from the default,
@ -170,15 +566,6 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
fn sort(items: &mut [Self]) { fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(|item| item.sort_key()); items.sort_unstable_by_key(|item| item.sort_key());
} }
/// The range of instances that the batch covers. After doing a batched draw, batch range
/// length phase items will be skipped. This design is to avoid having to restructure the
/// render phase unnecessarily.
fn batch_range(&self) -> &Range<u32>;
fn batch_range_mut(&mut self) -> &mut Range<u32>;
fn dynamic_offset(&self) -> Option<NonMaxU32>;
fn dynamic_offset_mut(&mut self) -> &mut Option<NonMaxU32>;
} }
/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, /// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,
@ -218,8 +605,12 @@ impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {
} }
} }
/// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type. /// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) { /// type.
pub fn sort_phase_system<I>(mut render_phases: Query<&mut SortedRenderPhase<I>>)
where
I: SortedPhaseItem,
{
for mut phase in &mut render_phases { for mut phase in &mut render_phases {
phase.sort(); phase.sort();
} }

View file

@ -50,7 +50,7 @@ pub enum Pipeline {
type CachedPipelineId = usize; type CachedPipelineId = usize;
/// Index of a cached render pipeline in a [`PipelineCache`]. /// Index of a cached render pipeline in a [`PipelineCache`].
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct CachedRenderPipelineId(CachedPipelineId); pub struct CachedRenderPipelineId(CachedPipelineId);
impl CachedRenderPipelineId { impl CachedRenderPipelineId {

View file

@ -148,7 +148,7 @@ macro_rules! render_resource_wrapper {
#[macro_export] #[macro_export]
macro_rules! define_atomic_id { macro_rules! define_atomic_id {
($atomic_id_type:ident) => { ($atomic_id_type:ident) => {
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
pub struct $atomic_id_type(core::num::NonZeroU32); pub struct $atomic_id_type(core::num::NonZeroU32);
// We use new instead of default to indicate that each ID created will be unique. // We use new instead of default to indicate that each ID created will be unique.

View file

@ -17,7 +17,7 @@ use bevy_render::{
render_asset::{prepare_assets, RenderAssets}, render_asset::{prepare_assets, RenderAssets},
render_phase::{ render_phase::{
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
RenderPhase, SetItemPipeline, TrackedRenderPass, SetItemPipeline, SortedRenderPhase, TrackedRenderPass,
}, },
render_resource::{ render_resource::{
AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout,
@ -388,7 +388,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
&VisibleEntities, &VisibleEntities,
Option<&Tonemapping>, Option<&Tonemapping>,
Option<&DebandDither>, Option<&DebandDither>,
&mut RenderPhase<Transparent2d>, &mut SortedRenderPhase<Transparent2d>,
)>, )>,
) where ) where
M::Data: PartialEq + Eq + Hash + Clone, M::Data: PartialEq + Eq + Hash + Clone,

View file

@ -14,7 +14,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::mesh::MeshVertexBufferLayoutRef; use bevy_render::mesh::MeshVertexBufferLayoutRef;
use bevy_render::{ use bevy_render::{
batching::{ batching::{
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, GetBatchData,
NoAutomaticBatching, NoAutomaticBatching,
}, },
globals::{GlobalsBuffer, GlobalsUniform}, globals::{GlobalsBuffer, GlobalsUniform},
@ -101,7 +101,7 @@ impl Plugin for Mesh2dRenderPlugin {
.add_systems( .add_systems(
Render, Render,
( (
batch_and_prepare_render_phase::<Transparent2d, Mesh2dPipeline> batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>
.in_set(RenderSet::PrepareResources), .in_set(RenderSet::PrepareResources),
write_batched_instance_buffer::<Mesh2dPipeline> write_batched_instance_buffer::<Mesh2dPipeline>
.in_set(RenderSet::PrepareResourcesFlush), .in_set(RenderSet::PrepareResourcesFlush),

View file

@ -19,8 +19,8 @@ use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
use bevy_render::{ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{ render_phase::{
DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline,
TrackedRenderPass, SortedRenderPhase, TrackedRenderPass,
}, },
render_resource::{ render_resource::{
binding_types::{sampler, texture_2d, uniform_buffer}, binding_types::{sampler, texture_2d, uniform_buffer},
@ -448,7 +448,7 @@ pub fn queue_sprites(
msaa: Res<Msaa>, msaa: Res<Msaa>,
extracted_sprites: Res<ExtractedSprites>, extracted_sprites: Res<ExtractedSprites>,
mut views: Query<( mut views: Query<(
&mut RenderPhase<Transparent2d>, &mut SortedRenderPhase<Transparent2d>,
&VisibleEntities, &VisibleEntities,
&ExtractedView, &ExtractedView,
Option<&Tonemapping>, Option<&Tonemapping>,
@ -530,7 +530,7 @@ pub fn prepare_sprites(
mut image_bind_groups: ResMut<ImageBindGroups>, mut image_bind_groups: ResMut<ImageBindGroups>,
gpu_images: Res<RenderAssets<Image>>, gpu_images: Res<RenderAssets<Image>>,
extracted_sprites: Res<ExtractedSprites>, extracted_sprites: Res<ExtractedSprites>,
mut phases: Query<&mut RenderPhase<Transparent2d>>, mut phases: Query<&mut SortedRenderPhase<Transparent2d>>,
events: Res<SpriteAssetEvents>, events: Res<SpriteAssetEvents>,
) { ) {
// If an image has changed, the GpuImage has (probably) changed // If an image has changed, the GpuImage has (probably) changed

View file

@ -29,7 +29,7 @@ use bevy_render::{
camera::Camera, camera::Camera,
render_asset::RenderAssets, render_asset::RenderAssets,
render_graph::{RenderGraph, RunGraphOnViewNode}, render_graph::{RenderGraph, RunGraphOnViewNode},
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase}, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, SortedRenderPhase},
render_resource::*, render_resource::*,
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
texture::Image, texture::Image,
@ -704,7 +704,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
.id(); .id();
commands.get_or_spawn(entity).insert(( commands.get_or_spawn(entity).insert((
DefaultCameraView(default_camera_view), DefaultCameraView(default_camera_view),
RenderPhase::<TransparentUi>::default(), SortedRenderPhase::<TransparentUi>::default(),
)); ));
} }
} }
@ -874,7 +874,7 @@ pub fn queue_uinodes(
extracted_uinodes: Res<ExtractedUiNodes>, extracted_uinodes: Res<ExtractedUiNodes>,
ui_pipeline: Res<UiPipeline>, ui_pipeline: Res<UiPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>, mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<TransparentUi>)>, mut views: Query<(&ExtractedView, &mut SortedRenderPhase<TransparentUi>)>,
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
draw_functions: Res<DrawFunctions<TransparentUi>>, draw_functions: Res<DrawFunctions<TransparentUi>>,
) { ) {
@ -921,7 +921,7 @@ pub fn prepare_uinodes(
ui_pipeline: Res<UiPipeline>, ui_pipeline: Res<UiPipeline>,
mut image_bind_groups: ResMut<UiImageBindGroups>, mut image_bind_groups: ResMut<UiImageBindGroups>,
gpu_images: Res<RenderAssets<Image>>, gpu_images: Res<RenderAssets<Image>>,
mut phases: Query<&mut RenderPhase<TransparentUi>>, mut phases: Query<&mut SortedRenderPhase<TransparentUi>>,
events: Res<SpriteAssetEvents>, events: Res<SpriteAssetEvents>,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
) { ) {

View file

@ -20,7 +20,7 @@ use nonmax::NonMaxU32;
pub struct UiPassNode { pub struct UiPassNode {
ui_view_query: QueryState< ui_view_query: QueryState<
( (
&'static RenderPhase<TransparentUi>, &'static SortedRenderPhase<TransparentUi>,
&'static ViewTarget, &'static ViewTarget,
&'static ExtractedCamera, &'static ExtractedCamera,
), ),
@ -96,28 +96,16 @@ pub struct TransparentUi {
} }
impl PhaseItem for TransparentUi { impl PhaseItem for TransparentUi {
type SortKey = (FloatOrd, u32);
#[inline] #[inline]
fn entity(&self) -> Entity { fn entity(&self) -> Entity {
self.entity self.entity
} }
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.sort_key
}
#[inline] #[inline]
fn draw_function(&self) -> DrawFunctionId { fn draw_function(&self) -> DrawFunctionId {
self.draw_function self.draw_function
} }
#[inline]
fn sort(items: &mut [Self]) {
items.sort_by_key(|item| item.sort_key());
}
#[inline] #[inline]
fn batch_range(&self) -> &Range<u32> { fn batch_range(&self) -> &Range<u32> {
&self.batch_range &self.batch_range
@ -139,6 +127,20 @@ impl PhaseItem for TransparentUi {
} }
} }
impl SortedPhaseItem for TransparentUi {
type SortKey = (FloatOrd, u32);
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.sort_key
}
#[inline]
fn sort(items: &mut [Self]) {
items.sort_by_key(|item| item.sort_key());
}
}
impl CachedRenderPipelinePhaseItem for TransparentUi { impl CachedRenderPipelinePhaseItem for TransparentUi {
#[inline] #[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId { fn cached_pipeline(&self) -> CachedRenderPipelineId {

View file

@ -460,7 +460,7 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
view_uniforms: Res<ViewUniforms>, view_uniforms: Res<ViewUniforms>,
globals_buffer: Res<GlobalsBuffer>, globals_buffer: Res<GlobalsBuffer>,
ui_material_pipeline: Res<UiMaterialPipeline<M>>, ui_material_pipeline: Res<UiMaterialPipeline<M>>,
mut phases: Query<&mut RenderPhase<TransparentUi>>, mut phases: Query<&mut SortedRenderPhase<TransparentUi>>,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
) { ) {
if let (Some(view_binding), Some(globals_binding)) = ( if let (Some(view_binding), Some(globals_binding)) = (
@ -757,7 +757,7 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
mut pipelines: ResMut<SpecializedRenderPipelines<UiMaterialPipeline<M>>>, mut pipelines: ResMut<SpecializedRenderPipelines<UiMaterialPipeline<M>>>,
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
render_materials: Res<RenderUiMaterials<M>>, render_materials: Res<RenderUiMaterials<M>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<TransparentUi>)>, mut views: Query<(&ExtractedView, &mut SortedRenderPhase<TransparentUi>)>,
) where ) where
M::Data: PartialEq + Eq + Hash + Clone, M::Data: PartialEq + Eq + Hash + Clone,
{ {

View file

@ -13,7 +13,7 @@ use bevy::{
render::{ render::{
mesh::{Indices, MeshVertexAttribute}, mesh::{Indices, MeshVertexAttribute},
render_asset::{RenderAssetUsages, RenderAssets}, render_asset::{RenderAssetUsages, RenderAssets},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase},
render_resource::{ render_resource::{
BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace,
MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology,
@ -360,7 +360,7 @@ pub fn queue_colored_mesh2d(
render_mesh_instances: Res<RenderMesh2dInstances>, render_mesh_instances: Res<RenderMesh2dInstances>,
mut views: Query<( mut views: Query<(
&VisibleEntities, &VisibleEntities,
&mut RenderPhase<Transparent2d>, &mut SortedRenderPhase<Transparent2d>,
&ExtractedView, &ExtractedView,
)>, )>,
) { ) {

View file

@ -16,7 +16,7 @@ use bevy::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{ render_phase::{
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
RenderPhase, SetItemPipeline, TrackedRenderPass, SetItemPipeline, SortedRenderPhase, TrackedRenderPass,
}, },
render_resource::*, render_resource::*,
renderer::RenderDevice, renderer::RenderDevice,
@ -117,7 +117,7 @@ fn queue_custom(
meshes: Res<RenderAssets<Mesh>>, meshes: Res<RenderAssets<Mesh>>,
render_mesh_instances: Res<RenderMeshInstances>, render_mesh_instances: Res<RenderMeshInstances>,
material_meshes: Query<Entity, With<InstanceMaterialData>>, material_meshes: Query<Entity, With<InstanceMaterialData>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>, mut views: Query<(&ExtractedView, &mut SortedRenderPhase<Transparent3d>)>,
) { ) {
let draw_custom = transparent_3d_draw_functions.read().id::<DrawCustom>(); let draw_custom = transparent_3d_draw_functions.read().id::<DrawCustom>();