mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
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.
This commit is contained in:
parent
1baa1a11b7
commit
44db8b7fac
17 changed files with 647 additions and 86 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -3234,6 +3234,17 @@ description = "Displays an example model with anisotropy"
|
||||||
category = "3D Rendering"
|
category = "3D Rendering"
|
||||||
wasm = false
|
wasm = false
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "custom_phase_item"
|
||||||
|
path = "examples/shader/custom_phase_item.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.custom_phase_item]
|
||||||
|
name = "Custom phase item"
|
||||||
|
description = "Demonstrates how to enqueue custom draw commands in a render phase"
|
||||||
|
category = "Shaders"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|
36
assets/shaders/custom_phase_item.wgsl
Normal file
36
assets/shaders/custom_phase_item.wgsl
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// `custom_phase_item.wgsl`
|
||||||
|
//
|
||||||
|
// This shader goes with the `custom_phase_item` example. It demonstrates how to
|
||||||
|
// enqueue custom rendering logic in a `RenderPhase`.
|
||||||
|
|
||||||
|
// The GPU-side vertex structure.
|
||||||
|
struct Vertex {
|
||||||
|
// The world-space position of the vertex.
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
// The color of the vertex.
|
||||||
|
@location(1) color: vec3<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Information passed from the vertex shader to the fragment shader.
|
||||||
|
struct VertexOutput {
|
||||||
|
// The clip-space position of the vertex.
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
// The color of the vertex.
|
||||||
|
@location(0) color: vec3<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The vertex shader entry point.
|
||||||
|
@vertex
|
||||||
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
|
// Use an orthographic projection.
|
||||||
|
var vertex_output: VertexOutput;
|
||||||
|
vertex_output.clip_position = vec4(vertex.position.xyz, 1.0);
|
||||||
|
vertex_output.color = vertex.color;
|
||||||
|
return vertex_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fragment shader entry point.
|
||||||
|
@fragment
|
||||||
|
fn fragment(vertex_output: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return vec4(vertex_output.color, 1.0);
|
||||||
|
}
|
|
@ -288,13 +288,17 @@ impl Hash for UntypedAssetId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ord for UntypedAssetId {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.type_id()
|
||||||
|
.cmp(&other.type_id())
|
||||||
|
.then_with(|| self.internal().cmp(&other.internal()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialOrd for UntypedAssetId {
|
impl PartialOrd for UntypedAssetId {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
if self.type_id() != other.type_id() {
|
Some(self.cmp(other))
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.internal().cmp(&other.internal()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use bevy_asset::AssetId;
|
use bevy_asset::{AssetId, UntypedAssetId};
|
||||||
use bevy_color::LinearRgba;
|
use bevy_color::LinearRgba;
|
||||||
pub use camera_3d::*;
|
pub use camera_3d::*;
|
||||||
pub use main_opaque_pass_3d_node::*;
|
pub use main_opaque_pass_3d_node::*;
|
||||||
|
@ -76,7 +76,6 @@ use bevy_math::FloatOrd;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{Camera, ExtractedCamera},
|
camera::{Camera, ExtractedCamera},
|
||||||
extract_component::ExtractComponentPlugin,
|
extract_component::ExtractComponentPlugin,
|
||||||
mesh::Mesh,
|
|
||||||
prelude::Msaa,
|
prelude::Msaa,
|
||||||
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
|
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
|
||||||
render_phase::{
|
render_phase::{
|
||||||
|
@ -221,7 +220,7 @@ pub struct Opaque3d {
|
||||||
pub extra_index: PhaseItemExtraIndex,
|
pub extra_index: PhaseItemExtraIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data that must be identical in order to batch meshes together.
|
/// Data that must be identical in order to batch phase items together.
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Opaque3dBinKey {
|
pub struct Opaque3dBinKey {
|
||||||
/// The identifier of the render pipeline.
|
/// The identifier of the render pipeline.
|
||||||
|
@ -230,8 +229,11 @@ pub struct Opaque3dBinKey {
|
||||||
/// The function used to draw.
|
/// The function used to draw.
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
|
|
||||||
/// The mesh.
|
/// The asset that this phase item is associated with.
|
||||||
pub asset_id: AssetId<Mesh>,
|
///
|
||||||
|
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
|
||||||
|
/// the ID of another type of asset.
|
||||||
|
pub asset_id: UntypedAssetId,
|
||||||
|
|
||||||
/// The ID of a bind group specific to the material.
|
/// The ID of a bind group specific to the material.
|
||||||
///
|
///
|
||||||
|
|
|
@ -144,8 +144,8 @@ impl ViewNode for DeferredGBufferPrepassNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque draws
|
// Opaque draws
|
||||||
if !opaque_deferred_phase.batchable_keys.is_empty()
|
if !opaque_deferred_phase.batchable_mesh_keys.is_empty()
|
||||||
|| !opaque_deferred_phase.unbatchable_keys.is_empty()
|
|| !opaque_deferred_phase.unbatchable_mesh_keys.is_empty()
|
||||||
{
|
{
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
|
let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
|
||||||
|
|
|
@ -29,12 +29,11 @@ pub mod node;
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use bevy_asset::AssetId;
|
use bevy_asset::UntypedAssetId;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::Mat4;
|
use bevy_math::Mat4;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::Mesh,
|
|
||||||
render_phase::{
|
render_phase::{
|
||||||
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
|
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
|
||||||
PhaseItemExtraIndex,
|
PhaseItemExtraIndex,
|
||||||
|
@ -147,7 +146,7 @@ pub struct Opaque3dPrepass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Try interning these.
|
// TODO: Try interning these.
|
||||||
/// The data used to bin each opaque 3D mesh in the prepass and deferred pass.
|
/// The data used to bin each opaque 3D object in the prepass and deferred pass.
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct OpaqueNoLightmap3dBinKey {
|
pub struct OpaqueNoLightmap3dBinKey {
|
||||||
/// The ID of the GPU pipeline.
|
/// The ID of the GPU pipeline.
|
||||||
|
@ -156,8 +155,8 @@ pub struct OpaqueNoLightmap3dBinKey {
|
||||||
/// The function used to draw the mesh.
|
/// The function used to draw the mesh.
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
|
|
||||||
/// The ID of the mesh.
|
/// The ID of the asset.
|
||||||
pub asset_id: AssetId<Mesh>,
|
pub asset_id: UntypedAssetId,
|
||||||
|
|
||||||
/// The ID of a bind group specific to the material.
|
/// The ID of a bind group specific to the material.
|
||||||
///
|
///
|
||||||
|
|
|
@ -120,8 +120,8 @@ impl ViewNode for PrepassNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque draws
|
// Opaque draws
|
||||||
if !opaque_prepass_phase.batchable_keys.is_empty()
|
if !opaque_prepass_phase.batchable_mesh_keys.is_empty()
|
||||||
|| !opaque_prepass_phase.unbatchable_keys.is_empty()
|
|| !opaque_prepass_phase.unbatchable_mesh_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();
|
||||||
|
|
|
@ -763,11 +763,15 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
let bin_key = Opaque3dBinKey {
|
let bin_key = Opaque3dBinKey {
|
||||||
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.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
lightmap_image,
|
lightmap_image,
|
||||||
};
|
};
|
||||||
opaque_phase.add(bin_key, *visible_entity, mesh_instance.should_batch());
|
opaque_phase.add(
|
||||||
|
bin_key,
|
||||||
|
*visible_entity,
|
||||||
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Alpha mask
|
// Alpha mask
|
||||||
|
@ -787,13 +791,13 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
let bin_key = OpaqueNoLightmap3dBinKey {
|
let bin_key = OpaqueNoLightmap3dBinKey {
|
||||||
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.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
};
|
};
|
||||||
alpha_mask_phase.add(
|
alpha_mask_phase.add(
|
||||||
bin_key,
|
bin_key,
|
||||||
*visible_entity,
|
*visible_entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -860,22 +860,22 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
OpaqueNoLightmap3dBinKey {
|
OpaqueNoLightmap3dBinKey {
|
||||||
draw_function: opaque_draw_deferred,
|
draw_function: opaque_draw_deferred,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
asset_id: mesh_instance.mesh_asset_id,
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
},
|
},
|
||||||
*visible_entity,
|
*visible_entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(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(
|
opaque_phase.add(
|
||||||
OpaqueNoLightmap3dBinKey {
|
OpaqueNoLightmap3dBinKey {
|
||||||
draw_function: opaque_draw_prepass,
|
draw_function: opaque_draw_prepass,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
asset_id: mesh_instance.mesh_asset_id,
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
},
|
},
|
||||||
*visible_entity,
|
*visible_entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,25 +885,25 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
let bin_key = OpaqueNoLightmap3dBinKey {
|
let bin_key = OpaqueNoLightmap3dBinKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: alpha_mask_draw_deferred,
|
draw_function: alpha_mask_draw_deferred,
|
||||||
asset_id: mesh_instance.mesh_asset_id,
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
};
|
};
|
||||||
alpha_mask_deferred_phase.as_mut().unwrap().add(
|
alpha_mask_deferred_phase.as_mut().unwrap().add(
|
||||||
bin_key,
|
bin_key,
|
||||||
*visible_entity,
|
*visible_entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(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() {
|
||||||
let bin_key = OpaqueNoLightmap3dBinKey {
|
let bin_key = OpaqueNoLightmap3dBinKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: alpha_mask_draw_prepass,
|
draw_function: alpha_mask_draw_prepass,
|
||||||
asset_id: mesh_instance.mesh_asset_id,
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material.get_bind_group_id().0,
|
material_bind_group_id: material.get_bind_group_id().0,
|
||||||
};
|
};
|
||||||
alpha_mask_phase.add(
|
alpha_mask_phase.add(
|
||||||
bin_key,
|
bin_key,
|
||||||
*visible_entity,
|
*visible_entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use bevy_asset::AssetId;
|
use bevy_asset::UntypedAssetId;
|
||||||
use bevy_color::ColorToComponents;
|
use bevy_color::ColorToComponents;
|
||||||
use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
|
use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
|
||||||
use bevy_ecs::entity::EntityHashSet;
|
use bevy_ecs::entity::EntityHashSet;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read};
|
use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read};
|
||||||
use bevy_math::{Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
use bevy_math::{Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||||
use bevy_render::mesh::Mesh;
|
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
diagnostic::RecordDiagnostics,
|
diagnostic::RecordDiagnostics,
|
||||||
mesh::GpuMesh,
|
mesh::GpuMesh,
|
||||||
|
@ -1286,10 +1285,10 @@ pub fn queue_shadows<M: Material>(
|
||||||
ShadowBinKey {
|
ShadowBinKey {
|
||||||
draw_function: draw_shadow_mesh,
|
draw_function: draw_shadow_mesh,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
asset_id: mesh_instance.mesh_asset_id,
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
},
|
},
|
||||||
entity,
|
entity,
|
||||||
mesh_instance.should_batch(),
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,6 +1302,7 @@ pub struct Shadow {
|
||||||
pub extra_index: PhaseItemExtraIndex,
|
pub extra_index: PhaseItemExtraIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data used to bin each object in the shadow map phase.
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ShadowBinKey {
|
pub struct ShadowBinKey {
|
||||||
/// The identifier of the render pipeline.
|
/// The identifier of the render pipeline.
|
||||||
|
@ -1311,8 +1311,8 @@ pub struct ShadowBinKey {
|
||||||
/// The function used to draw.
|
/// The function used to draw.
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
|
|
||||||
/// The mesh.
|
/// The object.
|
||||||
pub asset_id: AssetId<Mesh>,
|
pub asset_id: UntypedAssetId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for Shadow {
|
impl PhaseItem for Shadow {
|
||||||
|
|
|
@ -523,9 +523,9 @@ pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
|
||||||
|
|
||||||
// Prepare batchables.
|
// Prepare batchables.
|
||||||
|
|
||||||
for key in &phase.batchable_keys {
|
for key in &phase.batchable_mesh_keys {
|
||||||
let mut batch: Option<BinnedRenderPhaseBatch> = None;
|
let mut batch: Option<BinnedRenderPhaseBatch> = None;
|
||||||
for &entity in &phase.batchable_values[key] {
|
for &entity in &phase.batchable_mesh_values[key] {
|
||||||
let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else {
|
let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -583,8 +583,8 @@ pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare unbatchables.
|
// Prepare unbatchables.
|
||||||
for key in &phase.unbatchable_keys {
|
for key in &phase.unbatchable_mesh_keys {
|
||||||
let unbatchables = phase.unbatchable_values.get_mut(key).unwrap();
|
let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap();
|
||||||
for &entity in &unbatchables.entities {
|
for &entity in &unbatchables.entities {
|
||||||
let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else {
|
let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -156,8 +156,8 @@ where
|
||||||
BPI: BinnedPhaseItem,
|
BPI: BinnedPhaseItem,
|
||||||
{
|
{
|
||||||
for phase in phases.values_mut() {
|
for phase in phases.values_mut() {
|
||||||
phase.batchable_keys.sort_unstable();
|
phase.batchable_mesh_keys.sort_unstable();
|
||||||
phase.unbatchable_keys.sort_unstable();
|
phase.unbatchable_mesh_keys.sort_unstable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,9 +104,9 @@ pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
|
||||||
for phase in phases.values_mut() {
|
for phase in phases.values_mut() {
|
||||||
// Prepare batchables.
|
// Prepare batchables.
|
||||||
|
|
||||||
for key in &phase.batchable_keys {
|
for key in &phase.batchable_mesh_keys {
|
||||||
let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![];
|
let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![];
|
||||||
for &entity in &phase.batchable_values[key] {
|
for &entity in &phase.batchable_mesh_values[key] {
|
||||||
let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
|
let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -141,8 +141,8 @@ pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare unbatchables.
|
// Prepare unbatchables.
|
||||||
for key in &phase.unbatchable_keys {
|
for key in &phase.unbatchable_mesh_keys {
|
||||||
let unbatchables = phase.unbatchable_values.get_mut(key).unwrap();
|
let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap();
|
||||||
for &entity in &unbatchables.entities {
|
for &entity in &unbatchables.entities {
|
||||||
let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
|
let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -94,24 +94,33 @@ where
|
||||||
///
|
///
|
||||||
/// These are accumulated in `queue_material_meshes` and then sorted in
|
/// These are accumulated in `queue_material_meshes` and then sorted in
|
||||||
/// `batch_and_prepare_binned_render_phase`.
|
/// `batch_and_prepare_binned_render_phase`.
|
||||||
pub batchable_keys: Vec<BPI::BinKey>,
|
pub batchable_mesh_keys: Vec<BPI::BinKey>,
|
||||||
|
|
||||||
/// The batchable bins themselves.
|
/// The batchable bins themselves.
|
||||||
///
|
///
|
||||||
/// Each bin corresponds to a single batch set. For unbatchable entities,
|
/// Each bin corresponds to a single batch set. For unbatchable entities,
|
||||||
/// prefer `unbatchable_values` instead.
|
/// prefer `unbatchable_values` instead.
|
||||||
pub(crate) batchable_values: HashMap<BPI::BinKey, Vec<Entity>>,
|
pub(crate) batchable_mesh_values: HashMap<BPI::BinKey, Vec<Entity>>,
|
||||||
|
|
||||||
/// A list of `BinKey`s for unbatchable items.
|
/// A list of `BinKey`s for unbatchable items.
|
||||||
///
|
///
|
||||||
/// These are accumulated in `queue_material_meshes` and then sorted in
|
/// These are accumulated in `queue_material_meshes` and then sorted in
|
||||||
/// `batch_and_prepare_binned_render_phase`.
|
/// `batch_and_prepare_binned_render_phase`.
|
||||||
pub unbatchable_keys: Vec<BPI::BinKey>,
|
pub unbatchable_mesh_keys: Vec<BPI::BinKey>,
|
||||||
|
|
||||||
/// The unbatchable bins.
|
/// The unbatchable bins.
|
||||||
///
|
///
|
||||||
/// Each entity here is rendered in a separate drawcall.
|
/// Each entity here is rendered in a separate drawcall.
|
||||||
pub(crate) unbatchable_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
|
pub(crate) unbatchable_mesh_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
|
||||||
|
|
||||||
|
/// Items in the bin that aren't meshes at all.
|
||||||
|
///
|
||||||
|
/// Bevy itself doesn't place anything in this list, but plugins or your app
|
||||||
|
/// can in order to execute custom drawing commands. Draw functions for each
|
||||||
|
/// entity are simply called in order at rendering time.
|
||||||
|
///
|
||||||
|
/// See the `custom_phase_item` example for an example of how to use this.
|
||||||
|
pub non_mesh_items: Vec<(BPI::BinKey, Entity)>,
|
||||||
|
|
||||||
/// Information on each batch set.
|
/// Information on each batch set.
|
||||||
///
|
///
|
||||||
|
@ -199,6 +208,30 @@ pub(crate) struct UnbatchableBinnedEntityIndices {
|
||||||
pub(crate) extra_index: PhaseItemExtraIndex,
|
pub(crate) extra_index: PhaseItemExtraIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be
|
||||||
|
/// placed in.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum BinnedRenderPhaseType {
|
||||||
|
/// The item is a mesh that's eligible for indirect rendering and can be
|
||||||
|
/// batched with other meshes of the same type.
|
||||||
|
BatchableMesh,
|
||||||
|
|
||||||
|
/// The item is a mesh that's eligible for indirect rendering, but can't be
|
||||||
|
/// batched with other meshes of the same type.
|
||||||
|
///
|
||||||
|
/// At the moment, this is used for skinned meshes.
|
||||||
|
UnbatchableMesh,
|
||||||
|
|
||||||
|
/// The item isn't a mesh at all.
|
||||||
|
///
|
||||||
|
/// Bevy will simply invoke the drawing commands for such items one after
|
||||||
|
/// another, with no further processing.
|
||||||
|
///
|
||||||
|
/// The engine itself doesn't enqueue any items of this type, but it's
|
||||||
|
/// available for use in your application and/or plugins.
|
||||||
|
NonMesh,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices
|
impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices
|
||||||
where
|
where
|
||||||
T: Clone + ShaderSize + WriteInto,
|
T: Clone + ShaderSize + WriteInto,
|
||||||
|
@ -240,28 +273,38 @@ where
|
||||||
{
|
{
|
||||||
/// Bins a new entity.
|
/// Bins a new entity.
|
||||||
///
|
///
|
||||||
/// `batchable` specifies whether the entity can be batched with other
|
/// The `phase_type` parameter specifies whether the entity is a
|
||||||
/// entities of the same type.
|
/// preprocessable mesh and whether it can be binned with meshes of the same
|
||||||
pub fn add(&mut self, key: BPI::BinKey, entity: Entity, batchable: bool) {
|
/// type.
|
||||||
if batchable {
|
pub fn add(&mut self, key: BPI::BinKey, entity: Entity, phase_type: BinnedRenderPhaseType) {
|
||||||
match self.batchable_values.entry(key.clone()) {
|
match phase_type {
|
||||||
Entry::Occupied(mut entry) => entry.get_mut().push(entity),
|
BinnedRenderPhaseType::BatchableMesh => {
|
||||||
Entry::Vacant(entry) => {
|
match self.batchable_mesh_values.entry(key.clone()) {
|
||||||
self.batchable_keys.push(key);
|
Entry::Occupied(mut entry) => entry.get_mut().push(entity),
|
||||||
entry.insert(vec![entity]);
|
Entry::Vacant(entry) => {
|
||||||
|
self.batchable_mesh_keys.push(key);
|
||||||
|
entry.insert(vec![entity]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
match self.unbatchable_values.entry(key.clone()) {
|
BinnedRenderPhaseType::UnbatchableMesh => {
|
||||||
Entry::Occupied(mut entry) => entry.get_mut().entities.push(entity),
|
match self.unbatchable_mesh_values.entry(key.clone()) {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Occupied(mut entry) => entry.get_mut().entities.push(entity),
|
||||||
self.unbatchable_keys.push(key);
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(UnbatchableBinnedEntities {
|
self.unbatchable_mesh_keys.push(key);
|
||||||
entities: vec![entity],
|
entry.insert(UnbatchableBinnedEntities {
|
||||||
buffer_indices: default(),
|
entities: vec![entity],
|
||||||
});
|
buffer_indices: default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinnedRenderPhaseType::NonMesh => {
|
||||||
|
// We don't process these items further.
|
||||||
|
self.non_mesh_items.push((key, entity));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,14 +314,33 @@ where
|
||||||
render_pass: &mut TrackedRenderPass<'w>,
|
render_pass: &mut TrackedRenderPass<'w>,
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
view: Entity,
|
view: Entity,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let draw_functions = world.resource::<DrawFunctions<BPI>>();
|
||||||
|
let mut draw_functions = draw_functions.write();
|
||||||
|
draw_functions.prepare(world);
|
||||||
|
// Make sure to drop the reader-writer lock here to avoid recursive
|
||||||
|
// locks.
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_batchable_meshes(render_pass, world, view);
|
||||||
|
self.render_unbatchable_meshes(render_pass, world, view);
|
||||||
|
self.render_non_meshes(render_pass, world, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders all batchable meshes queued in this phase.
|
||||||
|
fn render_batchable_meshes<'w>(
|
||||||
|
&self,
|
||||||
|
render_pass: &mut TrackedRenderPass<'w>,
|
||||||
|
world: &'w World,
|
||||||
|
view: Entity,
|
||||||
) {
|
) {
|
||||||
let draw_functions = world.resource::<DrawFunctions<BPI>>();
|
let draw_functions = world.resource::<DrawFunctions<BPI>>();
|
||||||
let mut draw_functions = draw_functions.write();
|
let mut draw_functions = draw_functions.write();
|
||||||
draw_functions.prepare(world);
|
|
||||||
|
|
||||||
// Encode draws for batchables.
|
debug_assert_eq!(self.batchable_mesh_keys.len(), self.batch_sets.len());
|
||||||
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 (key, batch_set) in self.batchable_mesh_keys.iter().zip(self.batch_sets.iter()) {
|
||||||
for batch in batch_set {
|
for batch in batch_set {
|
||||||
let binned_phase_item = BPI::new(
|
let binned_phase_item = BPI::new(
|
||||||
key.clone(),
|
key.clone(),
|
||||||
|
@ -296,11 +358,20 @@ where
|
||||||
draw_function.draw(world, render_pass, view, &binned_phase_item);
|
draw_function.draw(world, render_pass, view, &binned_phase_item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encode draws for unbatchables.
|
/// Renders all unbatchable meshes queued in this phase.
|
||||||
|
fn render_unbatchable_meshes<'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();
|
||||||
|
|
||||||
for key in &self.unbatchable_keys {
|
for key in &self.unbatchable_mesh_keys {
|
||||||
let unbatchable_entities = &self.unbatchable_values[key];
|
let unbatchable_entities = &self.unbatchable_mesh_values[key];
|
||||||
for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() {
|
for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() {
|
||||||
let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
|
let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
|
||||||
UnbatchableBinnedEntityIndexSet::NoEntities => {
|
UnbatchableBinnedEntityIndexSet::NoEntities => {
|
||||||
|
@ -346,15 +417,44 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`].
|
||||||
|
///
|
||||||
|
/// These will have been added by plugins or the application.
|
||||||
|
fn render_non_meshes<'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();
|
||||||
|
|
||||||
|
for &(ref key, entity) in &self.non_mesh_items {
|
||||||
|
// Come up with a fake batch range and extra index. The draw
|
||||||
|
// function is expected to manage any sort of batching logic itself.
|
||||||
|
let binned_phase_item = BPI::new(key.clone(), entity, 0..1, PhaseItemExtraIndex(0));
|
||||||
|
|
||||||
|
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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.batchable_keys.is_empty() && self.unbatchable_keys.is_empty()
|
self.batchable_mesh_keys.is_empty()
|
||||||
|
&& self.unbatchable_mesh_keys.is_empty()
|
||||||
|
&& self.non_mesh_items.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.batchable_keys.clear();
|
self.batchable_mesh_keys.clear();
|
||||||
self.batchable_values.clear();
|
self.batchable_mesh_values.clear();
|
||||||
self.unbatchable_keys.clear();
|
self.unbatchable_mesh_keys.clear();
|
||||||
self.unbatchable_values.clear();
|
self.unbatchable_mesh_values.clear();
|
||||||
|
self.non_mesh_items.clear();
|
||||||
self.batch_sets.clear();
|
self.batch_sets.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,10 +465,11 @@ where
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
batchable_keys: vec![],
|
batchable_mesh_keys: vec![],
|
||||||
batchable_values: HashMap::default(),
|
batchable_mesh_values: HashMap::default(),
|
||||||
unbatchable_keys: vec![],
|
unbatchable_mesh_keys: vec![],
|
||||||
unbatchable_values: HashMap::default(),
|
unbatchable_mesh_values: HashMap::default(),
|
||||||
|
non_mesh_items: vec![],
|
||||||
batch_sets: vec![],
|
batch_sets: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -995,3 +1096,15 @@ where
|
||||||
phase.sort();
|
phase.sort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BinnedRenderPhaseType {
|
||||||
|
/// Creates the appropriate [`BinnedRenderPhaseType`] for a mesh, given its
|
||||||
|
/// batchability.
|
||||||
|
pub fn mesh(batchable: bool) -> BinnedRenderPhaseType {
|
||||||
|
if batchable {
|
||||||
|
BinnedRenderPhaseType::BatchableMesh
|
||||||
|
} else {
|
||||||
|
BinnedRenderPhaseType::UnbatchableMesh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ impl Plugin for ViewPlugin {
|
||||||
/// .run();
|
/// .run();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(
|
#[derive(
|
||||||
Resource, Default, Clone, Copy, ExtractResource, Reflect, PartialEq, PartialOrd, Debug,
|
Resource, Default, Clone, Copy, ExtractResource, Reflect, PartialEq, PartialOrd, Eq, Hash, Debug,
|
||||||
)]
|
)]
|
||||||
#[reflect(Resource, Default)]
|
#[reflect(Resource, Default)]
|
||||||
pub enum Msaa {
|
pub enum Msaa {
|
||||||
|
|
|
@ -368,6 +368,7 @@ Example | Description
|
||||||
[Array Texture](../examples/shader/array_texture.rs) | A shader that shows how to reuse the core bevy PBR shading functionality in a custom material that obtains the base color from an array texture.
|
[Array Texture](../examples/shader/array_texture.rs) | A shader that shows how to reuse the core bevy PBR shading functionality in a custom material that obtains the base color from an array texture.
|
||||||
[Compute - Game of Life](../examples/shader/compute_shader_game_of_life.rs) | A compute shader that simulates Conway's Game of Life
|
[Compute - Game of Life](../examples/shader/compute_shader_game_of_life.rs) | A compute shader that simulates Conway's Game of Life
|
||||||
[Custom Vertex Attribute](../examples/shader/custom_vertex_attribute.rs) | A shader that reads a mesh's custom vertex attribute
|
[Custom Vertex Attribute](../examples/shader/custom_vertex_attribute.rs) | A shader that reads a mesh's custom vertex attribute
|
||||||
|
[Custom phase item](../examples/shader/custom_phase_item.rs) | Demonstrates how to enqueue custom draw commands in a render phase
|
||||||
[Extended Material](../examples/shader/extended_material.rs) | A custom shader that builds on the standard material
|
[Extended Material](../examples/shader/extended_material.rs) | A custom shader that builds on the standard material
|
||||||
[GPU readback](../examples/shader/gpu_readback.rs) | A very simple compute shader that writes to a buffer that is read by the cpu
|
[GPU readback](../examples/shader/gpu_readback.rs) | A very simple compute shader that writes to a buffer that is read by the cpu
|
||||||
[Instancing](../examples/shader/shader_instancing.rs) | A shader that renders a mesh multiple times in one draw call
|
[Instancing](../examples/shader/shader_instancing.rs) | A shader that renders a mesh multiple times in one draw call
|
||||||
|
|
391
examples/shader/custom_phase_item.rs
Normal file
391
examples/shader/custom_phase_item.rs
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
//! Demonstrates how to enqueue custom draw commands in a render phase.
|
||||||
|
//!
|
||||||
|
//! This example shows how to use the built-in
|
||||||
|
//! [`bevy_render::render_phase::BinnedRenderPhase`] functionality with a
|
||||||
|
//! custom [`RenderCommand`] to allow inserting arbitrary GPU drawing logic
|
||||||
|
//! into Bevy's pipeline. This is not the only way to add custom rendering code
|
||||||
|
//! into Bevy—render nodes are another, lower-level method—but it does allow
|
||||||
|
//! for better reuse of parts of Bevy's built-in mesh rendering logic.
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
|
||||||
|
ecs::{
|
||||||
|
query::ROQueryItem,
|
||||||
|
system::{lifetimeless::SRes, SystemParamItem},
|
||||||
|
},
|
||||||
|
math::{vec3, Vec3A},
|
||||||
|
prelude::*,
|
||||||
|
render::{
|
||||||
|
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||||
|
primitives::Aabb,
|
||||||
|
render_phase::{
|
||||||
|
AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, PhaseItem, RenderCommand,
|
||||||
|
RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
|
||||||
|
},
|
||||||
|
render_resource::{
|
||||||
|
BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState,
|
||||||
|
FragmentState, IndexFormat, MultisampleState, PipelineCache, PrimitiveState,
|
||||||
|
RawBufferVec, RenderPipelineDescriptor, SpecializedRenderPipeline,
|
||||||
|
SpecializedRenderPipelines, TextureFormat, VertexAttribute, VertexBufferLayout,
|
||||||
|
VertexFormat, VertexState, VertexStepMode,
|
||||||
|
},
|
||||||
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
texture::BevyDefault as _,
|
||||||
|
view::{self, ExtractedView, VisibilitySystems, VisibleEntities},
|
||||||
|
Render, RenderApp, RenderSet,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
/// A marker component that represents an entity that is to be rendered using
|
||||||
|
/// our custom phase item.
|
||||||
|
///
|
||||||
|
/// Note the [`ExtractComponent`] trait implementation. This is necessary to
|
||||||
|
/// tell Bevy that this object should be pulled into the render world.
|
||||||
|
#[derive(Clone, Component, ExtractComponent)]
|
||||||
|
struct CustomRenderedEntity;
|
||||||
|
|
||||||
|
/// Holds a reference to our shader.
|
||||||
|
///
|
||||||
|
/// This is loaded at app creation time.
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct CustomPhasePipeline {
|
||||||
|
shader: Handle<Shader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`RenderCommand`] that binds the vertex and index buffers and issues the
|
||||||
|
/// draw command for our custom phase item.
|
||||||
|
struct DrawCustomPhaseItem;
|
||||||
|
|
||||||
|
impl<P> RenderCommand<P> for DrawCustomPhaseItem
|
||||||
|
where
|
||||||
|
P: PhaseItem,
|
||||||
|
{
|
||||||
|
type Param = SRes<CustomPhaseItemBuffers>;
|
||||||
|
|
||||||
|
type ViewQuery = ();
|
||||||
|
|
||||||
|
type ItemQuery = ();
|
||||||
|
|
||||||
|
fn render<'w>(
|
||||||
|
_: &P,
|
||||||
|
_: ROQueryItem<'w, Self::ViewQuery>,
|
||||||
|
_: Option<ROQueryItem<'w, Self::ItemQuery>>,
|
||||||
|
custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,
|
||||||
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
|
) -> RenderCommandResult {
|
||||||
|
// Borrow check workaround.
|
||||||
|
let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
|
||||||
|
|
||||||
|
// Tell the GPU where the vertices are.
|
||||||
|
pass.set_vertex_buffer(
|
||||||
|
0,
|
||||||
|
custom_phase_item_buffers
|
||||||
|
.vertices
|
||||||
|
.buffer()
|
||||||
|
.unwrap()
|
||||||
|
.slice(..),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tell the GPU where the indices are.
|
||||||
|
pass.set_index_buffer(
|
||||||
|
custom_phase_item_buffers
|
||||||
|
.indices
|
||||||
|
.buffer()
|
||||||
|
.unwrap()
|
||||||
|
.slice(..),
|
||||||
|
0,
|
||||||
|
IndexFormat::Uint32,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw one triangle (3 vertices).
|
||||||
|
pass.draw_indexed(0..3, 0, 0..1);
|
||||||
|
|
||||||
|
RenderCommandResult::Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The GPU vertex and index buffers for our custom phase item.
|
||||||
|
///
|
||||||
|
/// As the custom phase item is a single triangle, these are uploaded once and
|
||||||
|
/// then left alone.
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct CustomPhaseItemBuffers {
|
||||||
|
/// The vertices for the single triangle.
|
||||||
|
///
|
||||||
|
/// This is a [`RawBufferVec`] because that's the simplest and fastest type
|
||||||
|
/// of GPU buffer, and [`Vertex`] objects are simple.
|
||||||
|
vertices: RawBufferVec<Vertex>,
|
||||||
|
|
||||||
|
/// The indices of the single triangle.
|
||||||
|
///
|
||||||
|
/// As above, this is a [`RawBufferVec`] because `u32` values have trivial
|
||||||
|
/// size and alignment.
|
||||||
|
indices: RawBufferVec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The CPU-side structure that describes a single vertex of the triangle.
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex {
|
||||||
|
/// The 3D position of the triangle vertex.
|
||||||
|
position: Vec3,
|
||||||
|
/// Padding.
|
||||||
|
pad0: u32,
|
||||||
|
/// The color of the triangle vertex.
|
||||||
|
color: Vec3,
|
||||||
|
/// Padding.
|
||||||
|
pad1: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
/// Creates a new vertex structure.
|
||||||
|
const fn new(position: Vec3, color: Vec3) -> Vertex {
|
||||||
|
Vertex {
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
pad0: 0,
|
||||||
|
pad1: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The custom draw commands that Bevy executes for each entity we enqueue into
|
||||||
|
/// the render phase.
|
||||||
|
type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
|
||||||
|
|
||||||
|
/// A query filter that tells [`view::check_visibility`] about our custom
|
||||||
|
/// rendered entity.
|
||||||
|
type WithCustomRenderedEntity = With<CustomRenderedEntity>;
|
||||||
|
|
||||||
|
/// A single triangle's worth of vertices, for demonstration purposes.
|
||||||
|
static VERTICES: [Vertex; 3] = [
|
||||||
|
Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
|
||||||
|
Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
|
||||||
|
Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The entry point.
|
||||||
|
fn main() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
// Make sure to tell Bevy to check our entity for visibility. Bevy won't
|
||||||
|
// do this by default, for efficiency reasons.
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
view::check_visibility::<WithCustomRenderedEntity>
|
||||||
|
.in_set(VisibilitySystems::CheckVisibility),
|
||||||
|
);
|
||||||
|
|
||||||
|
// We make sure to add these to the render app, not the main app.
|
||||||
|
app.get_sub_app_mut(RenderApp)
|
||||||
|
.unwrap()
|
||||||
|
.init_resource::<CustomPhasePipeline>()
|
||||||
|
.init_resource::<SpecializedRenderPipelines<CustomPhasePipeline>>()
|
||||||
|
.add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
prepare_custom_phase_item_buffers.in_set(RenderSet::Prepare),
|
||||||
|
)
|
||||||
|
.add_systems(Render, queue_custom_phase_item.in_set(RenderSet::Queue));
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns the objects in the scene.
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
// Spawn a single entity that has custom rendering. It'll be extracted into
|
||||||
|
// the render world via [`ExtractComponent`].
|
||||||
|
commands
|
||||||
|
.spawn(SpatialBundle {
|
||||||
|
visibility: Visibility::Visible,
|
||||||
|
transform: Transform::IDENTITY,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
// This `Aabb` is necessary for the visibility checks to work.
|
||||||
|
.insert(Aabb {
|
||||||
|
center: Vec3A::ZERO,
|
||||||
|
half_extents: Vec3A::splat(0.5),
|
||||||
|
})
|
||||||
|
.insert(CustomRenderedEntity);
|
||||||
|
|
||||||
|
// Spawn the camera.
|
||||||
|
commands.spawn(Camera3dBundle {
|
||||||
|
transform: Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the [`CustomPhaseItemBuffers`] resource.
|
||||||
|
///
|
||||||
|
/// This must be done in a startup system because it needs the [`RenderDevice`]
|
||||||
|
/// and [`RenderQueue`] to exist, and they don't until [`App::run`] is called.
|
||||||
|
fn prepare_custom_phase_item_buffers(mut commands: Commands) {
|
||||||
|
commands.init_resource::<CustomPhaseItemBuffers>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A render-world system that enqueues the entity with custom rendering into
|
||||||
|
/// the opaque render phases of each view.
|
||||||
|
fn queue_custom_phase_item(
|
||||||
|
pipeline_cache: Res<PipelineCache>,
|
||||||
|
custom_phase_pipeline: Res<CustomPhasePipeline>,
|
||||||
|
msaa: Res<Msaa>,
|
||||||
|
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
|
||||||
|
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||||
|
mut specialized_render_pipelines: ResMut<SpecializedRenderPipelines<CustomPhasePipeline>>,
|
||||||
|
views: Query<(Entity, &VisibleEntities), With<ExtractedView>>,
|
||||||
|
) {
|
||||||
|
let draw_custom_phase_item = opaque_draw_functions
|
||||||
|
.read()
|
||||||
|
.id::<DrawCustomPhaseItemCommands>();
|
||||||
|
|
||||||
|
// Render phases are per-view, so we need to iterate over all views so that
|
||||||
|
// the entity appears in them. (In this example, we have only one view, but
|
||||||
|
// it's good practice to loop over all views anyway.)
|
||||||
|
for (view_entity, view_visible_entities) in views.iter() {
|
||||||
|
let Some(opaque_phase) = opaque_render_phases.get_mut(&view_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all the custom rendered entities that are visible from this
|
||||||
|
// view.
|
||||||
|
for &entity in view_visible_entities
|
||||||
|
.get::<WithCustomRenderedEntity>()
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
// Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain
|
||||||
|
// some per-view settings, such as whether the view is HDR, but for
|
||||||
|
// simplicity's sake we simply hard-code the view's characteristics,
|
||||||
|
// with the exception of number of MSAA samples.
|
||||||
|
let pipeline_id = specialized_render_pipelines.specialize(
|
||||||
|
&pipeline_cache,
|
||||||
|
&custom_phase_pipeline,
|
||||||
|
*msaa,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the custom render item. We use the
|
||||||
|
// [`BinnedRenderPhaseType::NonMesh`] type to skip the special
|
||||||
|
// handling that Bevy has for meshes (preprocessing, indirect
|
||||||
|
// draws, etc.)
|
||||||
|
//
|
||||||
|
// The asset ID is arbitrary; we simply use [`AssetId::invalid`],
|
||||||
|
// but you can use anything you like. Note that the asset ID need
|
||||||
|
// not be the ID of a [`Mesh`].
|
||||||
|
opaque_phase.add(
|
||||||
|
Opaque3dBinKey {
|
||||||
|
draw_function: draw_custom_phase_item,
|
||||||
|
pipeline: pipeline_id,
|
||||||
|
asset_id: AssetId::<Mesh>::invalid().untyped(),
|
||||||
|
material_bind_group_id: None,
|
||||||
|
lightmap_image: None,
|
||||||
|
},
|
||||||
|
entity,
|
||||||
|
BinnedRenderPhaseType::NonMesh,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecializedRenderPipeline for CustomPhasePipeline {
|
||||||
|
type Key = Msaa;
|
||||||
|
|
||||||
|
fn specialize(&self, msaa: Self::Key) -> RenderPipelineDescriptor {
|
||||||
|
RenderPipelineDescriptor {
|
||||||
|
label: Some("custom render pipeline".into()),
|
||||||
|
layout: vec![],
|
||||||
|
push_constant_ranges: vec![],
|
||||||
|
vertex: VertexState {
|
||||||
|
shader: self.shader.clone(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "vertex".into(),
|
||||||
|
buffers: vec![VertexBufferLayout {
|
||||||
|
array_stride: mem::size_of::<Vertex>() as u64,
|
||||||
|
step_mode: VertexStepMode::Vertex,
|
||||||
|
// This needs to match the layout of [`Vertex`].
|
||||||
|
attributes: vec![
|
||||||
|
VertexAttribute {
|
||||||
|
format: VertexFormat::Float32x3,
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 0,
|
||||||
|
},
|
||||||
|
VertexAttribute {
|
||||||
|
format: VertexFormat::Float32x3,
|
||||||
|
offset: 16,
|
||||||
|
shader_location: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(FragmentState {
|
||||||
|
shader: self.shader.clone(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "fragment".into(),
|
||||||
|
targets: vec![Some(ColorTargetState {
|
||||||
|
// Ordinarily, you'd want to check whether the view has the
|
||||||
|
// HDR format and substitute the appropriate texture format
|
||||||
|
// here, but we omit that for simplicity.
|
||||||
|
format: TextureFormat::bevy_default(),
|
||||||
|
blend: None,
|
||||||
|
write_mask: ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: PrimitiveState::default(),
|
||||||
|
// Note that if your view has no depth buffer this will need to be
|
||||||
|
// changed.
|
||||||
|
depth_stencil: Some(DepthStencilState {
|
||||||
|
format: CORE_3D_DEPTH_FORMAT,
|
||||||
|
depth_write_enabled: false,
|
||||||
|
depth_compare: CompareFunction::Always,
|
||||||
|
stencil: default(),
|
||||||
|
bias: default(),
|
||||||
|
}),
|
||||||
|
multisample: MultisampleState {
|
||||||
|
count: msaa.samples(),
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for CustomPhaseItemBuffers {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
let render_queue = world.resource::<RenderQueue>();
|
||||||
|
|
||||||
|
// Create the vertex and index buffers.
|
||||||
|
let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
|
||||||
|
let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
|
||||||
|
|
||||||
|
for vertex in &VERTICES {
|
||||||
|
vbo.push(*vertex);
|
||||||
|
}
|
||||||
|
for index in 0..3 {
|
||||||
|
ibo.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These two lines are required in order to trigger the upload to GPU.
|
||||||
|
vbo.write_buffer(render_device, render_queue);
|
||||||
|
ibo.write_buffer(render_device, render_queue);
|
||||||
|
|
||||||
|
CustomPhaseItemBuffers {
|
||||||
|
vertices: vbo,
|
||||||
|
indices: ibo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for CustomPhasePipeline {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
// Load and compile the shader in the background.
|
||||||
|
let asset_server = world.resource::<AssetServer>();
|
||||||
|
|
||||||
|
CustomPhasePipeline {
|
||||||
|
shader: asset_server.load("shaders/custom_phase_item.wgsl"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue