diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 573bf0d5a0f70..c165a8b5ab5fe 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -28,7 +28,7 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, - DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, + PhaseItem, RenderCommandId, RenderCommands, RenderPhase, }, render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, RenderApp, RenderSet, @@ -51,7 +51,7 @@ impl Plugin for Core2dPlugin { }; render_app - .init_resource::>() + .init_resource::>() .add_system(extract_core_2d_camera_phases.in_schedule(ExtractSchedule)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system( @@ -109,7 +109,7 @@ pub struct Transparent2d { pub sort_key: FloatOrd, pub entity: Entity, pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, /// Range in the vertex buffer of this item pub batch_range: Option>, } @@ -128,8 +128,8 @@ impl PhaseItem for Transparent2d { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 71cfaf6d402f3..d25d5c7d24b4c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -31,8 +31,8 @@ use bevy_render::{ prelude::Msaa, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ - sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, - RenderPhase, + sort_phase_system, CachedRenderPipelinePhaseItem, PhaseItem, RenderCommandId, + RenderCommands, RenderPhase, }, render_resource::{ CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, @@ -65,9 +65,9 @@ impl Plugin for Core3dPlugin { }; render_app - .init_resource::>() - .init_resource::>() - .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() .add_system(extract_core_3d_camera_phases.in_schedule(ExtractSchedule)) .add_system( prepare_core_3d_depth_textures @@ -137,7 +137,7 @@ pub struct Opaque3d { pub distance: f32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for Opaque3d { @@ -155,8 +155,8 @@ impl PhaseItem for Opaque3d { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] @@ -177,7 +177,7 @@ pub struct AlphaMask3d { pub distance: f32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for AlphaMask3d { @@ -195,8 +195,8 @@ impl PhaseItem for AlphaMask3d { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] @@ -217,7 +217,7 @@ pub struct Transparent3d { pub distance: f32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for Transparent3d { @@ -235,8 +235,8 @@ impl PhaseItem for Transparent3d { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 86386a7fc9a06..0f6a5a7c6c036 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -30,8 +30,9 @@ use std::cmp::Reverse; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; +use bevy_render::render_phase::RenderCommandId; use bevy_render::{ - render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, + render_phase::{CachedRenderPipelinePhaseItem, PhaseItem}, render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat}, texture::CachedTexture, }; @@ -73,7 +74,7 @@ pub struct Opaque3dPrepass { pub distance: f32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for Opaque3dPrepass { @@ -91,8 +92,8 @@ impl PhaseItem for Opaque3dPrepass { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] @@ -118,7 +119,7 @@ pub struct AlphaMask3dPrepass { pub distance: f32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for AlphaMask3dPrepass { @@ -136,8 +137,8 @@ impl PhaseItem for AlphaMask3dPrepass { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cb57e68f4d0dd..214e4b5c30fab 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -24,7 +24,7 @@ use bevy_render::{ prelude::Image, render_asset::{PrepareAssetSet, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + AddRenderCommand, PhaseItem, RenderCommand, RenderCommandResult, RenderCommands, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ @@ -190,7 +190,7 @@ where if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app - .init_resource::>() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() @@ -360,9 +360,9 @@ impl RenderCommand

for SetMaterial #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - transparent_draw_functions: Res>, + opaque_render_commands: Res>, + alpha_mask_render_commands: Res>, + transparent_render_commands: Res>, material_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -395,9 +395,9 @@ pub fn queue_material_meshes( mut transparent_phase, ) in &mut views { - let draw_opaque_pbr = opaque_draw_functions.read().id::>(); - let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); - let draw_transparent_pbr = transparent_draw_functions.read().id::>(); + let draw_opaque_pbr = opaque_render_commands.id::>(); + let draw_alpha_mask_pbr = alpha_mask_render_commands.id::>(); + let draw_transparent_pbr = transparent_render_commands.id::>(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -483,7 +483,7 @@ pub fn queue_material_meshes( AlphaMode::Opaque => { opaque_phase.add(Opaque3d { entity: *visible_entity, - draw_function: draw_opaque_pbr, + render_command_id: draw_opaque_pbr, pipeline: pipeline_id, distance, }); @@ -491,7 +491,7 @@ pub fn queue_material_meshes( AlphaMode::Mask(_) => { alpha_mask_phase.add(AlphaMask3d { entity: *visible_entity, - draw_function: draw_alpha_mask_pbr, + render_command_id: draw_alpha_mask_pbr, pipeline: pipeline_id, distance, }); @@ -502,7 +502,7 @@ pub fn queue_material_meshes( | AlphaMode::Multiply => { transparent_phase.add(Transparent3d { entity: *visible_entity, - draw_function: draw_transparent_pbr, + render_command_id: draw_transparent_pbr, pipeline: pipeline_id, distance, }); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 855d1e4ae8e33..c8d80a18a927a 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -21,8 +21,8 @@ use bevy_render::{ prelude::{Camera, Mesh}, render_asset::RenderAssets, render_phase::{ - sort_phase_system, AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, - RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, + sort_phase_system, AddRenderCommand, PhaseItem, RenderCommand, RenderCommandResult, + RenderCommands, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, @@ -138,8 +138,8 @@ where .add_system(queue_prepass_material_meshes::.in_set(RenderSet::Queue)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) - .init_resource::>() - .init_resource::>() + .init_resource::>() + .init_resource::>() .add_render_command::>() .add_render_command::>(); } @@ -590,8 +590,8 @@ pub fn queue_prepass_view_bind_group( #[allow(clippy::too_many_arguments)] pub fn queue_prepass_material_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, + opaque_render_commands: Res>, + alpha_mask_render_commands: Res>, prepass_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -610,14 +610,9 @@ pub fn queue_prepass_material_meshes( ) where M::Data: PartialEq + Eq + Hash + Clone, { - let opaque_draw_prepass = opaque_draw_functions - .read() - .get_id::>() - .unwrap(); - let alpha_mask_draw_prepass = alpha_mask_draw_functions - .read() - .get_id::>() - .unwrap(); + let opaque_draw_prepass = opaque_render_commands.id::>(); + let alpha_mask_draw_prepass = alpha_mask_render_commands.id::>(); + for ( view, visible_entities, @@ -684,7 +679,7 @@ pub fn queue_prepass_material_meshes( AlphaMode::Opaque => { opaque_phase.add(Opaque3dPrepass { entity: *visible_entity, - draw_function: opaque_draw_prepass, + render_command_id: opaque_draw_prepass, pipeline_id, distance, }); @@ -692,7 +687,7 @@ pub fn queue_prepass_material_meshes( AlphaMode::Mask(_) => { alpha_mask_phase.add(AlphaMask3dPrepass { entity: *visible_entity, - draw_function: alpha_mask_draw_prepass, + render_command_id: alpha_mask_draw_prepass, pipeline_id, distance, }); diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index cdf1e7ab1548e..4253c82cb07b4 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -10,15 +10,14 @@ use bevy_asset::Handle; use bevy_core_pipeline::core_3d::Transparent3d; use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; +use bevy_render::render_phase::{RenderCommandId, RenderCommands}; use bevy_render::{ camera::Camera, color::Color, mesh::Mesh, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, - render_phase::{ - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, - }, + render_phase::{CachedRenderPipelinePhaseItem, PhaseItem, RenderPhase}, render_resource::*, renderer::{RenderContext, RenderDevice, RenderQueue}, texture::*, @@ -1545,7 +1544,7 @@ pub fn prepare_clusters( #[allow(clippy::too_many_arguments)] pub fn queue_shadows( - shadow_draw_functions: Res>, + shadow_render_commands: Res>, prepass_pipeline: Res>, casting_meshes: Query<(&Handle, &Handle), Without>, render_meshes: Res>, @@ -1561,7 +1560,7 @@ pub fn queue_shadows( M::Data: PartialEq + Eq + Hash + Clone, { for (entity, view_lights) in &view_lights { - let draw_shadow_mesh = shadow_draw_functions.read().id::>(); + let draw_shadow_mesh = shadow_render_commands.id::>(); for view_light_entity in view_lights.lights.iter().copied() { let (light_entity, mut shadow_phase) = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); @@ -1632,7 +1631,7 @@ pub fn queue_shadows( }; shadow_phase.add(Shadow { - draw_function: draw_shadow_mesh, + render_command_id: draw_shadow_mesh, pipeline: pipeline_id, entity, distance: 0.0, // TODO: sort back-to-front @@ -1648,7 +1647,7 @@ pub struct Shadow { pub distance: f32, pub entity: Entity, pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for Shadow { @@ -1665,8 +1664,8 @@ impl PhaseItem for Shadow { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } #[inline] diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 34d93c91853c8..fe78eeb16a699 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -11,7 +11,7 @@ use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_phase::{AddRenderCommand, RenderCommands, RenderPhase, SetItemPipeline}, render_resource::{ PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, @@ -97,7 +97,7 @@ impl SpecializedMeshPipeline for WireframePipeline { #[allow(clippy::too_many_arguments)] fn queue_wireframes( - opaque_3d_draw_functions: Res>, + opaque_3d_render_commands: Res>, render_meshes: Res>, wireframe_config: Res, wireframe_pipeline: Res, @@ -110,7 +110,7 @@ fn queue_wireframes( )>, mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, ) { - let draw_custom = opaque_3d_draw_functions.read().id::(); + let draw_custom = opaque_3d_render_commands.id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); for (view, visible_entities, mut opaque_phase) in &mut views { let rangefinder = view.rangefinder3d(); @@ -137,7 +137,7 @@ fn queue_wireframes( opaque_phase.add(Opaque3d { entity, pipeline: pipeline_id, - draw_function: draw_custom, + render_command_id: draw_custom, distance: rangefinder.distance(&mesh_uniform.transform), }); } diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 9b370f2e384ed..6daa3ec04148c 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -158,15 +158,7 @@ impl DrawFunctions

{ pub trait RenderCommand { /// Specifies the general ECS data (e.g. resources) required by [`RenderCommand::render`]. /// - /// When fetching resources, note that, due to lifetime limitations of the `Deref` trait, - /// [`SRes::into_inner`] must be called on each [`SRes`] reference in the - /// [`RenderCommand::render`] method, instead of being automatically dereferenced as is the - /// case in normal `systems`. - /// /// All parameters have to be read only. - /// - /// [`SRes`]: bevy_ecs::system::lifetimeless::SRes - /// [`SRes::into_inner`]: bevy_ecs::system::lifetimeless::SRes::into_inner type Param: SystemParam + 'static; /// Specifies the ECS data of the view entity required by [`RenderCommand::render`]. /// diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 47d6a59c1b439..b0396ed45c50d 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -16,22 +16,21 @@ //! Finally the items are rendered using a single [`TrackedRenderPass`], during the //! [`RenderSet::Render`](crate::RenderSet::Render). //! -//! Therefore each phase item is assigned a [`Draw`] function. +//! Each phase item is drawn using a [`RenderCommand`]. //! These set up the state of the [`TrackedRenderPass`] (i.e. select the //! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the //! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call, //! for the corresponding item. -//! -//! The [`Draw`] function trait can either be implemented directly or such a function can be -//! created by composing multiple [`RenderCommand`]s. +//! A [`RenderCommand`] can be implemented directly or composed out of multiple other +//! [`RenderCommand`]s, by wrapping them in a tuple. -mod draw; mod draw_state; mod rangefinder; +mod render_command; -pub use draw::*; pub use draw_state::*; pub use rangefinder::*; +pub use render_command::*; use crate::render_resource::{CachedRenderPipelineId, PipelineCache}; use bevy_ecs::{ @@ -40,7 +39,7 @@ use bevy_ecs::{ }; use std::ops::Range; -/// A collection of all rendering instructions, that will be executed by the GPU, for a +/// A collection of all rendering commands, that will be executed by the GPU, for a /// single render phase for a single view. /// /// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases. @@ -51,47 +50,41 @@ use std::ops::Range; /// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. /// The render pass might be reused for multiple phases to reduce GPU overhead. #[derive(Component)] -pub struct RenderPhase { - pub items: Vec, +pub struct RenderPhase { + pub items: Vec

, } -impl Default for RenderPhase { +impl Default for RenderPhase

{ fn default() -> Self { Self { items: Vec::new() } } } -impl RenderPhase { +impl RenderPhase

{ /// Adds a [`PhaseItem`] to this render phase. #[inline] - pub fn add(&mut self, item: I) { + pub fn add(&mut self, item: P) { self.items.push(item); } /// Sorts all of its [`PhaseItem`]s. pub fn sort(&mut self) { - I::sort(&mut self.items); + P::sort(&mut self.items); } - /// Renders all of its [`PhaseItem`]s using their corresponding draw functions. + /// Renders all of its [`PhaseItem`]s using their corresponding [`RenderCommand`]. pub fn render<'w>( &self, render_pass: &mut TrackedRenderPass<'w>, world: &'w World, view: Entity, ) { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - - for item in &self.items { - let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); - draw_function.draw(world, render_pass, view, item); - } + let render_commands = world.resource::>(); + render_commands.render(world, self, render_pass, view); } } -impl RenderPhase { +impl RenderPhase

{ /// Batches the compatible [`BatchedPhaseItem`]s of this render phase pub fn batch(&mut self) { // TODO: this could be done in-place @@ -144,8 +137,8 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { /// Determines the order in which the items are drawn. fn sort_key(&self) -> Self::SortKey; - /// Specifies the [`Draw`] function used to render the item. - fn draw_function(&self) -> DrawFunctionId; + /// Specifies the [`RenderCommand`] used to render the item. + fn render_command_id(&self) -> RenderCommandId; /// Sorts a slice of phase items into render order. Generally if the same type /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`]. @@ -250,14 +243,14 @@ pub enum BatchResult { } /// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type. -pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { +pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase

>) { for mut phase in &mut render_phases { phase.sort(); } } /// This system batches the [`PhaseItem`]s of all [`RenderPhase`]s of this type. -pub fn batch_phase_system(mut render_phases: Query<&mut RenderPhase>) { +pub fn batch_phase_system(mut render_phases: Query<&mut RenderPhase

>) { for mut phase in &mut render_phases { phase.batch(); } @@ -285,7 +278,7 @@ mod tests { fn sort_key(&self) -> Self::SortKey {} - fn draw_function(&self) -> DrawFunctionId { + fn render_command_id(&self) -> RenderCommandId { unimplemented!(); } } diff --git a/crates/bevy_render/src/render_phase/render_command.rs b/crates/bevy_render/src/render_phase/render_command.rs new file mode 100644 index 0000000000000..10a1d59e105f0 --- /dev/null +++ b/crates/bevy_render/src/render_phase/render_command.rs @@ -0,0 +1,283 @@ +use crate::render_phase::{PhaseItem, RenderPhase, TrackedRenderPass}; +use bevy_app::App; +use bevy_ecs::{ + all_tuples, + entity::Entity, + query::{QueryState, ROQueryItem, ReadOnlyWorldQuery}, + system::{ReadOnlySystemParam, Resource, SystemParamItem, SystemState}, + world::World, +}; +use bevy_utils::HashMap; +use parking_lot::RwLock; +use std::{any::TypeId, fmt::Debug, hash::Hash}; + +/// The result of a [`RenderCommand`]. +pub enum RenderCommandResult { + Success, + Failure, +} + +// TODO: make this generic? +/// An identifier of a [`RenderCommand`] stored in the [`RenderCommands`] collection. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct RenderCommandId(u32); + +/// [`RenderCommand`]s are modular pieces of render logic that are used to render [`PhaseItem`]s. +/// +/// These phase items are rendered during a [`RenderPhase`] for a specific view, +/// by recording commands (e.g. setting pipelines, binding bind groups, +/// setting vertex/index buffers, and issuing draw calls) via the [`TrackedRenderPass`]. +/// +/// The read only ECS data, required by the [`render`](Self::render) method, is fetch automatically, +/// from the render world, using the [`Param`](Self::Param), +/// [`ViewWorldQuery`](Self::ViewWorldQuery), and [`ItemWorldQuery`](Self::ItemWorldQuery). +/// These three parameters are used to access render world resources, +/// components of the view entity, and components of the item entity respectively. +/// +/// Before they can be used, render commands have to be registered on the render app via the +/// [`AddRenderCommand::add_render_command`] method. +/// +/// Multiple render commands can be combined together by wrapping them in a tuple. +/// +/// # Example +/// The `DrawPbr` render command is composed of the following render command tuple. +/// Const generics are used to set specific bind group locations: +/// +/// ```ignore +/// pub type DrawPbr = ( +/// SetItemPipeline, +/// SetMeshViewBindGroup<0>, +/// SetStandardMaterialBindGroup<1>, +/// SetTransformBindGroup<2>, +/// DrawMesh, +/// ); +/// ``` +pub trait RenderCommand: Send + Sync + 'static { + /// Specifies the general ECS data (e.g. resources) required by [`Self::render`]. + /// + /// When fetching resources, note that, due to lifetime limitations of the `Deref` trait, + /// [`SRes::into_inner`] must be called on each [`SRes`] reference in the + /// [`RenderCommand::render`] method, instead of being automatically dereferenced as is the + /// case in normal `systems`. + /// + /// All parameters have to be read only. + /// + /// [`SRes`]: bevy_ecs::system::lifetimeless::SRes + /// [`SRes::into_inner`]: bevy_ecs::system::lifetimeless::SRes::into_inner + type Param: ReadOnlySystemParam; + /// Specifies the ECS data of the view entity required by [`Self::render`]. + /// + /// The view entity refers to the camera, or shadow-casting light, etc. from which the phase + /// item will be rendered from. + /// All components have to be accessed read only. + type ViewWorldQuery: ReadOnlyWorldQuery; + /// Specifies the ECS data of the item entity required by [`RenderCommand::render`]. + /// + /// The item is the entity that will be rendered for the corresponding view. + /// All components have to be accessed read only. + type ItemWorldQuery: ReadOnlyWorldQuery; + + /// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups, + /// setting vertex/index buffers, and issuing draw calls) via the [`TrackedRenderPass`]. + fn render<'w>( + item: &P, + view: ROQueryItem<'w, Self::ViewWorldQuery>, + entity: ROQueryItem<'w, Self::ItemWorldQuery>, + param: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult; +} + +macro_rules! render_command_tuple_impl { + ($(($name: ident, $view: ident, $entity: ident)),*) => { + impl),*> RenderCommand

for ($($name,)*) { + type Param = ($($name::Param,)*); + type ViewWorldQuery = ($($name::ViewWorldQuery,)*); + type ItemWorldQuery = ($($name::ItemWorldQuery,)*); + + #[allow(non_snake_case)] + fn render<'w>( + _item: &P, + ($($view,)*): ROQueryItem<'w, Self::ViewWorldQuery>, + ($($entity,)*): ROQueryItem<'w, Self::ItemWorldQuery>, + ($($name,)*): SystemParamItem<'w, '_, Self::Param>, + _pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + $(if let RenderCommandResult::Failure = $name::render(_item, $view, $entity, $name, _pass) { + return RenderCommandResult::Failure; + })* + RenderCommandResult::Success + } + } + }; +} + +all_tuples!(render_command_tuple_impl, 0, 15, C, V, E); + +struct RenderCommandsInternal { + render_commands: Vec>>, + indices: HashMap, +} + +/// A collection of all [`RenderCommands`] for the [`PhaseItem`] type. +/// +/// To select the render command for each [`PhaseItem`] use the [`id`](Self::id) or +/// [`get_id`](Self::get_id) methods. +#[derive(Resource)] +pub struct RenderCommands { + // TODO: can we avoid this RwLock? + internal: RwLock>, +} + +impl Default for RenderCommands

{ + fn default() -> Self { + Self { + internal: RwLock::new(RenderCommandsInternal { + render_commands: Vec::new(), + indices: HashMap::default(), + }), + } + } +} + +impl RenderCommands

{ + /// Retrieves the id of the corresponding [`RenderCommand`]. + pub fn get_id>(&self) -> Option { + self.internal + .read() + .indices + .get(&TypeId::of::()) + .copied() + } + + /// Retrieves the id of the corresponding [`RenderCommand`]. + /// + /// Fallible wrapper for [`Self::get_id()`] + /// + /// ## Panics + /// If the id doesn't exist, this function will panic. + pub fn id>(&self) -> RenderCommandId { + self.get_id::().unwrap_or_else(|| { + panic!( + "Render command {} not found for {}", + std::any::type_name::(), + std::any::type_name::

() + ) + }) + } + + /// Renders all items of the `render_phase` using their corresponding [`RenderCommand`]. + pub(crate) fn render<'w>( + &self, + world: &'w World, + render_phase: &RenderPhase

, + render_pass: &mut TrackedRenderPass<'w>, + view: Entity, + ) { + let mut internal = self.internal.write(); + for render_command in &mut internal.render_commands { + render_command.prepare(world); + } + + for item in &render_phase.items { + let render_command = &mut internal.render_commands[item.render_command_id().0 as usize]; + render_command.render(world, render_pass, view, item); + } + } + + /// Adds a [`RenderCommand`] (wrapped with a [`RenderCommandState`]) to this collection. + fn add>(&self, render_command: Box>) -> RenderCommandId { + let mut internal = self.internal.write(); + let id = RenderCommandId(internal.render_commands.len() as u32); + internal.render_commands.push(render_command); + internal.indices.insert(TypeId::of::(), id); + id + } +} + +/// Registers a [`RenderCommand`] on the render app. +/// +/// They are stored inside the [`RenderCommands`] resource of the app. +pub trait AddRenderCommand { + /// Adds the [`RenderCommand`] for the specified [`PhaseItem`] type to the app. + fn add_render_command>(&mut self) -> &mut Self; +} + +impl AddRenderCommand for App { + fn add_render_command>(&mut self) -> &mut Self { + let render_command = RenderCommandState::::initialize(&mut self.world); + let render_commands = self + .world + .get_resource::>() + .unwrap_or_else(|| { + panic!( + "RenderCommands<{}> must be added to the world as a resource \ + before adding render commands to it", + std::any::type_name::

(), + ); + }); + render_commands.add::(render_command); + self + } +} + +// TODO: can we get rid of this trait entirely? +trait Command: Send + Sync + 'static { + fn prepare(&mut self, world: &World); + + fn render<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &P, + ); +} + +/// Wraps a [`RenderCommand`] into a state so that it can store system and query states to supply +/// the necessary data in the [`RenderCommand::render`] method. +/// +/// The [`RenderCommand::Param`], [`RenderCommand::ViewWorldQuery`] and +/// [`RenderCommand::ItemWorldQuery`] are fetched from the ECS and passed to the command. +struct RenderCommandState> { + state: SystemState, + view: QueryState, + entity: QueryState, +} + +impl> RenderCommandState { + /// Creates a new [`RenderCommandState`] for the [`RenderCommand`]. + pub fn initialize(world: &mut World) -> Box> { + Box::new(Self { + state: SystemState::new(world), + view: world.query(), + entity: world.query(), + }) + } +} + +impl> Command

for RenderCommandState { + /// Prepares the render command to be used. This is called once and only once before the phase + /// begins. There may be zero or more `draw` calls following a call to this function. + fn prepare(&mut self, world: &'_ World) { + self.state.update_archetypes(world); + self.view.update_archetypes(world); + self.entity.update_archetypes(world); + } + + /// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then, + /// the phase item is rendered using this command. + fn render<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &P, + ) { + let param = self.state.get_manual(world); + let view = self.view.get_manual(world, view).unwrap(); + let entity = self.entity.get_manual(world, item.entity()).unwrap(); + // TODO: handle/log `RenderCommand` failure + C::render(item, view, entity, param, pass); + } +} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4982efe381ac1..2e6be7e4c3466 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -21,7 +21,7 @@ use bevy_render::{ prelude::Image, render_asset::{PrepareAssetSet, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + AddRenderCommand, PhaseItem, RenderCommand, RenderCommandResult, RenderCommands, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ @@ -318,7 +318,7 @@ impl RenderCommand

#[allow(clippy::too_many_arguments)] pub fn queue_material2d_meshes( - transparent_draw_functions: Res>, + transparent_render_commands: Res>, material2d_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -341,7 +341,7 @@ pub fn queue_material2d_meshes( } for (view, visible_entities, tonemapping, dither, mut transparent_phase) in &mut views { - let draw_transparent_pbr = transparent_draw_functions.read().id::>(); + let draw_transparent_pbr = transparent_render_commands.id::>(); let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); @@ -399,7 +399,7 @@ pub fn queue_material2d_meshes( let mesh_z = mesh2d_uniform.transform.w_axis.z; transparent_phase.add(Transparent2d { entity: *visible_entity, - draw_function: draw_transparent_pbr, + render_command_id: draw_transparent_pbr, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the // lowest sort key and getting closer should increase. As we have diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 89d7cbbcce871..efc1dd4230980 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -19,7 +19,7 @@ use bevy_render::{ color::Color, render_asset::RenderAssets, render_phase::{ - BatchedPhaseItem, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + BatchedPhaseItem, PhaseItem, RenderCommand, RenderCommandResult, RenderCommands, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, @@ -477,7 +477,7 @@ pub struct ImageBindGroups { pub fn queue_sprites( mut commands: Commands, mut view_entities: Local, - draw_functions: Res>, + render_commands: Res>, render_device: Res, render_queue: Res, mut sprite_meta: ResMut, @@ -526,7 +526,7 @@ pub fn queue_sprites( layout: &sprite_pipeline.view_layout, })); - let draw_sprite_function = draw_functions.read().id::(); + let draw_sprite_function = render_commands.id::(); // Vertex buffer indices let mut index = 0; @@ -706,7 +706,7 @@ pub fn queue_sprites( let item_end = colored_index; transparent_phase.add(Transparent2d { - draw_function: draw_sprite_function, + render_command_id: draw_sprite_function, pipeline: colored_pipeline, entity: current_batch_entity, sort_key, @@ -724,7 +724,7 @@ pub fn queue_sprites( let item_end = index; transparent_phase.add(Transparent2d { - draw_function: draw_sprite_function, + render_command_id: draw_sprite_function, pipeline, entity: current_batch_entity, sort_key, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 2cfa098d28bb1..4273f81eef397 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -20,7 +20,7 @@ use bevy_render::{ color::Color, render_asset::RenderAssets, render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType}, - render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase}, + render_phase::{sort_phase_system, AddRenderCommand, RenderCommands, RenderPhase}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::Image, @@ -74,7 +74,7 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_render_command::() .add_systems( ( @@ -557,7 +557,7 @@ pub struct UiImageBindGroups { #[allow(clippy::too_many_arguments)] pub fn queue_uinodes( - draw_functions: Res>, + render_commands: Res>, render_device: Res, mut ui_meta: ResMut, view_uniforms: Res, @@ -589,7 +589,7 @@ pub fn queue_uinodes( label: Some("ui_view_bind_group"), layout: &ui_pipeline.view_layout, })); - let draw_ui_function = draw_functions.read().id::(); + let draw_ui_function = render_commands.id::(); for (view, mut transparent_phase) in &mut views { let pipeline = pipelines.specialize( &pipeline_cache, @@ -618,7 +618,7 @@ pub fn queue_uinodes( }) }); transparent_phase.add(TransparentUi { - draw_function: draw_ui_function, + render_command_id: draw_ui_function, pipeline, entity, sort_key: FloatOrd(batch.z), diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 63ff0a47f989c..09ee6b2294206 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -95,7 +95,7 @@ pub struct TransparentUi { pub sort_key: FloatOrd, pub entity: Entity, pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub render_command_id: RenderCommandId, } impl PhaseItem for TransparentUi { @@ -112,8 +112,8 @@ impl PhaseItem for TransparentUi { } #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function + fn render_command_id(&self) -> RenderCommandId { + self.render_command_id } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 635a19c0ceb21..ce5c5447251ac 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -12,7 +12,7 @@ use bevy::{ render::{ mesh::{Indices, MeshVertexAttribute}, render_asset::RenderAssets, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_phase::{AddRenderCommand, RenderCommands, RenderPhase, SetItemPipeline}, render_resource::{ BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, @@ -310,7 +310,7 @@ pub fn extract_colored_mesh2d( /// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function #[allow(clippy::too_many_arguments)] pub fn queue_colored_mesh2d( - transparent_draw_functions: Res>, + transparent_render_commands: Res>, colored_mesh2d_pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, @@ -328,7 +328,7 @@ pub fn queue_colored_mesh2d( } // Iterate each view (a camera is a view) for (visible_entities, mut transparent_phase, view) in &mut views { - let draw_colored_mesh2d = transparent_draw_functions.read().id::(); + let draw_colored_mesh2d = transparent_render_commands.id::(); let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); @@ -349,7 +349,7 @@ pub fn queue_colored_mesh2d( let mesh_z = mesh2d_uniform.transform.w_axis.z; transparent_phase.add(Transparent2d { entity: *visible_entity, - draw_function: draw_colored_mesh2d, + render_command_id: draw_colored_mesh2d, pipeline: pipeline_id, // The 2d render items are sorted according to their z value before rendering, // in order to get correct transparency diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index b6d0f29d1187f..d4fdb0a0c01b3 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -13,7 +13,7 @@ use bevy::{ mesh::{GpuBufferInfo, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + AddRenderCommand, PhaseItem, RenderCommand, RenderCommandResult, RenderCommands, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, @@ -100,7 +100,7 @@ struct InstanceData { #[allow(clippy::too_many_arguments)] fn queue_custom( - transparent_3d_draw_functions: Res>, + transparent_3d_render_commands: Res>, custom_pipeline: Res, msaa: Res, mut pipelines: ResMut>, @@ -109,7 +109,7 @@ fn queue_custom( material_meshes: Query<(Entity, &MeshUniform, &Handle), With>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { - let draw_custom = transparent_3d_draw_functions.read().id::(); + let draw_custom = transparent_3d_render_commands.id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); @@ -126,7 +126,7 @@ fn queue_custom( transparent_phase.add(Transparent3d { entity, pipeline, - draw_function: draw_custom, + render_command_id: draw_custom, distance: rangefinder.distance(&mesh_uniform.transform), }); }