Skip to content

Commit 26f7313

Browse files
pcwaltonmockersf
authored andcommitted
Allow phase items not associated with meshes to be binned. (#14029)
As reported in #14004, many third-party plugins, such as Hanabi, enqueue entities that don't have meshes into render phases. However, the introduction of indirect mode added a dependency on mesh-specific data, breaking this workflow. This is because GPU preprocessing requires that the render phases manage indirect draw parameters, which don't apply to objects that aren't meshes. The existing code skips over binned entities that don't have indirect draw parameters, which causes the rendering to be skipped for such objects. To support this workflow, this commit adds a new field, `non_mesh_items`, to `BinnedRenderPhase`. This field contains a simple list of (bin key, entity) pairs. After drawing batchable and unbatchable objects, the non-mesh items are drawn one after another. Bevy itself doesn't enqueue any items into this list; it exists solely for the application and/or plugins to use. Additionally, this commit switches the asset ID in the standard bin keys to be an untyped asset ID rather than that of a mesh. This allows more flexibility, allowing bins to be keyed off any type of asset. This patch adds a new example, `custom_phase_item`, which simultaneously serves to demonstrate how to use this new feature and to act as a regression test so this doesn't break again. Fixes #14004. ## Changelog ### Added * `BinnedRenderPhase` now contains a `non_mesh_items` field for plugins to add custom items to.
1 parent bea8823 commit 26f7313

File tree

17 files changed

+647
-86
lines changed

17 files changed

+647
-86
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,17 @@ description = "Displays an example model with anisotropy"
32193219
category = "3D Rendering"
32203220
wasm = false
32213221

3222+
[[example]]
3223+
name = "custom_phase_item"
3224+
path = "examples/shader/custom_phase_item.rs"
3225+
doc-scrape-examples = true
3226+
3227+
[package.metadata.example.custom_phase_item]
3228+
name = "Custom phase item"
3229+
description = "Demonstrates how to enqueue custom draw commands in a render phase"
3230+
category = "Shaders"
3231+
wasm = true
3232+
32223233
[profile.wasm-release]
32233234
inherits = "release"
32243235
opt-level = "z"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// `custom_phase_item.wgsl`
2+
//
3+
// This shader goes with the `custom_phase_item` example. It demonstrates how to
4+
// enqueue custom rendering logic in a `RenderPhase`.
5+
6+
// The GPU-side vertex structure.
7+
struct Vertex {
8+
// The world-space position of the vertex.
9+
@location(0) position: vec3<f32>,
10+
// The color of the vertex.
11+
@location(1) color: vec3<f32>,
12+
};
13+
14+
// Information passed from the vertex shader to the fragment shader.
15+
struct VertexOutput {
16+
// The clip-space position of the vertex.
17+
@builtin(position) clip_position: vec4<f32>,
18+
// The color of the vertex.
19+
@location(0) color: vec3<f32>,
20+
};
21+
22+
// The vertex shader entry point.
23+
@vertex
24+
fn vertex(vertex: Vertex) -> VertexOutput {
25+
// Use an orthographic projection.
26+
var vertex_output: VertexOutput;
27+
vertex_output.clip_position = vec4(vertex.position.xyz, 1.0);
28+
vertex_output.color = vertex.color;
29+
return vertex_output;
30+
}
31+
32+
// The fragment shader entry point.
33+
@fragment
34+
fn fragment(vertex_output: VertexOutput) -> @location(0) vec4<f32> {
35+
return vec4(vertex_output.color, 1.0);
36+
}

crates/bevy_asset/src/id.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,13 +288,17 @@ impl Hash for UntypedAssetId {
288288
}
289289
}
290290

291+
impl Ord for UntypedAssetId {
292+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
293+
self.type_id()
294+
.cmp(&other.type_id())
295+
.then_with(|| self.internal().cmp(&other.internal()))
296+
}
297+
}
298+
291299
impl PartialOrd for UntypedAssetId {
292300
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
293-
if self.type_id() != other.type_id() {
294-
None
295-
} else {
296-
Some(self.internal().cmp(&other.internal()))
297-
}
301+
Some(self.cmp(other))
298302
}
299303
}
300304

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
6464

6565
use std::ops::Range;
6666

67-
use bevy_asset::AssetId;
67+
use bevy_asset::{AssetId, UntypedAssetId};
6868
use bevy_color::LinearRgba;
6969
pub use camera_3d::*;
7070
pub use main_opaque_pass_3d_node::*;
@@ -76,7 +76,6 @@ use bevy_math::FloatOrd;
7676
use bevy_render::{
7777
camera::{Camera, ExtractedCamera},
7878
extract_component::ExtractComponentPlugin,
79-
mesh::Mesh,
8079
prelude::Msaa,
8180
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
8281
render_phase::{
@@ -221,7 +220,7 @@ pub struct Opaque3d {
221220
pub extra_index: PhaseItemExtraIndex,
222221
}
223222

224-
/// Data that must be identical in order to batch meshes together.
223+
/// Data that must be identical in order to batch phase items together.
225224
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
226225
pub struct Opaque3dBinKey {
227226
/// The identifier of the render pipeline.
@@ -230,8 +229,11 @@ pub struct Opaque3dBinKey {
230229
/// The function used to draw.
231230
pub draw_function: DrawFunctionId,
232231

233-
/// The mesh.
234-
pub asset_id: AssetId<Mesh>,
232+
/// The asset that this phase item is associated with.
233+
///
234+
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
235+
/// the ID of another type of asset.
236+
pub asset_id: UntypedAssetId,
235237

236238
/// The ID of a bind group specific to the material.
237239
///

crates/bevy_core_pipeline/src/deferred/node.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ impl ViewNode for DeferredGBufferPrepassNode {
144144
}
145145

146146
// Opaque draws
147-
if !opaque_deferred_phase.batchable_keys.is_empty()
148-
|| !opaque_deferred_phase.unbatchable_keys.is_empty()
147+
if !opaque_deferred_phase.batchable_mesh_keys.is_empty()
148+
|| !opaque_deferred_phase.unbatchable_mesh_keys.is_empty()
149149
{
150150
#[cfg(feature = "trace")]
151151
let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();

crates/bevy_core_pipeline/src/prepass/mod.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ pub mod node;
2929

3030
use std::ops::Range;
3131

32-
use bevy_asset::AssetId;
32+
use bevy_asset::UntypedAssetId;
3333
use bevy_ecs::prelude::*;
3434
use bevy_math::Mat4;
3535
use bevy_reflect::Reflect;
3636
use bevy_render::{
37-
mesh::Mesh,
3837
render_phase::{
3938
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
4039
PhaseItemExtraIndex,
@@ -147,7 +146,7 @@ pub struct Opaque3dPrepass {
147146
}
148147

149148
// TODO: Try interning these.
150-
/// The data used to bin each opaque 3D mesh in the prepass and deferred pass.
149+
/// The data used to bin each opaque 3D object in the prepass and deferred pass.
151150
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
152151
pub struct OpaqueNoLightmap3dBinKey {
153152
/// The ID of the GPU pipeline.
@@ -156,8 +155,8 @@ pub struct OpaqueNoLightmap3dBinKey {
156155
/// The function used to draw the mesh.
157156
pub draw_function: DrawFunctionId,
158157

159-
/// The ID of the mesh.
160-
pub asset_id: AssetId<Mesh>,
158+
/// The ID of the asset.
159+
pub asset_id: UntypedAssetId,
161160

162161
/// The ID of a bind group specific to the material.
163162
///

crates/bevy_core_pipeline/src/prepass/node.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ impl ViewNode for PrepassNode {
120120
}
121121

122122
// Opaque draws
123-
if !opaque_prepass_phase.batchable_keys.is_empty()
124-
|| !opaque_prepass_phase.unbatchable_keys.is_empty()
123+
if !opaque_prepass_phase.batchable_mesh_keys.is_empty()
124+
|| !opaque_prepass_phase.unbatchable_mesh_keys.is_empty()
125125
{
126126
#[cfg(feature = "trace")]
127127
let _opaque_prepass_span = info_span!("opaque_prepass").entered();

crates/bevy_pbr/src/material.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,11 +763,15 @@ pub fn queue_material_meshes<M: Material>(
763763
let bin_key = Opaque3dBinKey {
764764
draw_function: draw_opaque_pbr,
765765
pipeline: pipeline_id,
766-
asset_id: mesh_instance.mesh_asset_id,
766+
asset_id: mesh_instance.mesh_asset_id.into(),
767767
material_bind_group_id: material.get_bind_group_id().0,
768768
lightmap_image,
769769
};
770-
opaque_phase.add(bin_key, *visible_entity, mesh_instance.should_batch());
770+
opaque_phase.add(
771+
bin_key,
772+
*visible_entity,
773+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
774+
);
771775
}
772776
}
773777
// Alpha mask
@@ -787,13 +791,13 @@ pub fn queue_material_meshes<M: Material>(
787791
let bin_key = OpaqueNoLightmap3dBinKey {
788792
draw_function: draw_alpha_mask_pbr,
789793
pipeline: pipeline_id,
790-
asset_id: mesh_instance.mesh_asset_id,
794+
asset_id: mesh_instance.mesh_asset_id.into(),
791795
material_bind_group_id: material.get_bind_group_id().0,
792796
};
793797
alpha_mask_phase.add(
794798
bin_key,
795799
*visible_entity,
796-
mesh_instance.should_batch(),
800+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
797801
);
798802
}
799803
}

crates/bevy_pbr/src/prepass/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -860,22 +860,22 @@ pub fn queue_prepass_material_meshes<M: Material>(
860860
OpaqueNoLightmap3dBinKey {
861861
draw_function: opaque_draw_deferred,
862862
pipeline: pipeline_id,
863-
asset_id: mesh_instance.mesh_asset_id,
863+
asset_id: mesh_instance.mesh_asset_id.into(),
864864
material_bind_group_id: material.get_bind_group_id().0,
865865
},
866866
*visible_entity,
867-
mesh_instance.should_batch(),
867+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
868868
);
869869
} else if let Some(opaque_phase) = opaque_phase.as_mut() {
870870
opaque_phase.add(
871871
OpaqueNoLightmap3dBinKey {
872872
draw_function: opaque_draw_prepass,
873873
pipeline: pipeline_id,
874-
asset_id: mesh_instance.mesh_asset_id,
874+
asset_id: mesh_instance.mesh_asset_id.into(),
875875
material_bind_group_id: material.get_bind_group_id().0,
876876
},
877877
*visible_entity,
878-
mesh_instance.should_batch(),
878+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
879879
);
880880
}
881881
}
@@ -885,25 +885,25 @@ pub fn queue_prepass_material_meshes<M: Material>(
885885
let bin_key = OpaqueNoLightmap3dBinKey {
886886
pipeline: pipeline_id,
887887
draw_function: alpha_mask_draw_deferred,
888-
asset_id: mesh_instance.mesh_asset_id,
888+
asset_id: mesh_instance.mesh_asset_id.into(),
889889
material_bind_group_id: material.get_bind_group_id().0,
890890
};
891891
alpha_mask_deferred_phase.as_mut().unwrap().add(
892892
bin_key,
893893
*visible_entity,
894-
mesh_instance.should_batch(),
894+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
895895
);
896896
} else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() {
897897
let bin_key = OpaqueNoLightmap3dBinKey {
898898
pipeline: pipeline_id,
899899
draw_function: alpha_mask_draw_prepass,
900-
asset_id: mesh_instance.mesh_asset_id,
900+
asset_id: mesh_instance.mesh_asset_id.into(),
901901
material_bind_group_id: material.get_bind_group_id().0,
902902
};
903903
alpha_mask_phase.add(
904904
bin_key,
905905
*visible_entity,
906-
mesh_instance.should_batch(),
906+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
907907
);
908908
}
909909
}

crates/bevy_pbr/src/render/light.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use bevy_asset::AssetId;
1+
use bevy_asset::UntypedAssetId;
22
use bevy_color::ColorToComponents;
33
use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
44
use bevy_ecs::entity::EntityHashSet;
55
use bevy_ecs::prelude::*;
66
use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read};
77
use bevy_math::{Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
8-
use bevy_render::mesh::Mesh;
98
use bevy_render::{
109
diagnostic::RecordDiagnostics,
1110
mesh::GpuMesh,
@@ -1286,10 +1285,10 @@ pub fn queue_shadows<M: Material>(
12861285
ShadowBinKey {
12871286
draw_function: draw_shadow_mesh,
12881287
pipeline: pipeline_id,
1289-
asset_id: mesh_instance.mesh_asset_id,
1288+
asset_id: mesh_instance.mesh_asset_id.into(),
12901289
},
12911290
entity,
1292-
mesh_instance.should_batch(),
1291+
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
12931292
);
12941293
}
12951294
}
@@ -1303,6 +1302,7 @@ pub struct Shadow {
13031302
pub extra_index: PhaseItemExtraIndex,
13041303
}
13051304

1305+
/// Data used to bin each object in the shadow map phase.
13061306
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13071307
pub struct ShadowBinKey {
13081308
/// The identifier of the render pipeline.
@@ -1311,8 +1311,8 @@ pub struct ShadowBinKey {
13111311
/// The function used to draw.
13121312
pub draw_function: DrawFunctionId,
13131313

1314-
/// The mesh.
1315-
pub asset_id: AssetId<Mesh>,
1314+
/// The object.
1315+
pub asset_id: UntypedAssetId,
13161316
}
13171317

13181318
impl PhaseItem for Shadow {

0 commit comments

Comments
 (0)