From 5b62900e8b967ce59ef30f85adc2a2db08d428a8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 29 Apr 2022 15:10:39 -0700 Subject: [PATCH 01/16] Camera Driven Rendering --- Cargo.toml | 4 - crates/bevy_core_pipeline/Cargo.toml | 5 + crates/bevy_core_pipeline/src/clear_color.rs | 40 ++ crates/bevy_core_pipeline/src/clear_pass.rs | 128 ------ .../src/clear_pass_driver.rs | 20 - .../src/core_2d/camera_2d.rs | 82 ++++ .../main_pass_2d_node.rs} | 41 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 130 ++++++ .../src/core_3d/camera_3d.rs | 53 +++ .../main_pass_3d_node.rs} | 27 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 250 +++++++++++ crates/bevy_core_pipeline/src/lib.rs | 415 +----------------- .../src/main_pass_driver.rs | 33 -- crates/bevy_gltf/Cargo.toml | 1 + crates/bevy_gltf/src/loader.rs | 46 +- crates/bevy_pbr/src/lib.rs | 8 +- crates/bevy_pbr/src/light.rs | 18 +- crates/bevy_pbr/src/material.rs | 2 +- crates/bevy_pbr/src/render/light.rs | 2 +- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_render/src/camera/bundle.rs | 164 ------- crates/bevy_render/src/camera/camera.rs | 272 +++++------- .../src/camera/camera_driver_node.rs | 102 +++++ crates/bevy_render/src/camera/mod.rs | 35 +- crates/bevy_render/src/camera/projection.rs | 93 +++- crates/bevy_render/src/lib.rs | 17 +- crates/bevy_render/src/render_graph/node.rs | 36 +- crates/bevy_render/src/view/visibility/mod.rs | 10 +- crates/bevy_sprite/src/lib.rs | 2 +- crates/bevy_sprite/src/mesh2d/material.rs | 2 +- crates/bevy_sprite/src/render/mod.rs | 2 +- crates/bevy_text/src/text2d.rs | 2 +- crates/bevy_ui/src/entity.rs | 60 +-- crates/bevy_ui/src/lib.rs | 4 +- crates/bevy_ui/src/render/camera.rs | 18 - crates/bevy_ui/src/render/mod.rs | 133 +++++- crates/bevy_ui/src/render/render_pass.rs | 71 +-- crates/bevy_window/src/window.rs | 2 +- examples/2d/mesh2d.rs | 2 +- examples/2d/mesh2d_manual.rs | 4 +- examples/2d/move_sprite.rs | 2 +- examples/2d/rotation.rs | 2 +- examples/2d/shapes.rs | 2 +- examples/2d/sprite.rs | 2 +- examples/2d/sprite_flipping.rs | 2 +- examples/2d/sprite_sheet.rs | 2 +- examples/2d/text2d.rs | 2 +- examples/2d/texture_atlas.rs | 2 +- examples/3d/3d_scene.rs | 2 +- examples/3d/lighting.rs | 2 +- examples/3d/load_gltf.rs | 2 +- examples/3d/msaa.rs | 2 +- examples/3d/orthographic.rs | 18 +- examples/3d/parenting.rs | 2 +- examples/3d/pbr.rs | 9 +- examples/3d/render_to_texture.rs | 122 +---- examples/3d/shadow_biases.rs | 2 +- examples/3d/shadow_caster_receiver.rs | 2 +- examples/3d/shapes.rs | 2 +- examples/3d/spherical_area_lights.rs | 2 +- examples/3d/texture.rs | 2 +- examples/3d/two_passes.rs | 220 ---------- examples/3d/update_gltf_scene.rs | 2 +- examples/3d/vertex_colors.rs | 2 +- examples/3d/wireframe.rs | 2 +- examples/README.md | 1 - examples/android/android.rs | 2 +- examples/animation/animated_fox.rs | 2 +- examples/animation/animated_transform.rs | 2 +- examples/animation/custom_skinned_mesh.rs | 2 +- examples/animation/gltf_skinned_mesh.rs | 2 +- examples/app/without_winit.rs | 2 +- examples/asset/asset_loading.rs | 2 +- examples/asset/custom_asset_io.rs | 2 +- examples/asset/hot_asset_reloading.rs | 2 +- examples/async_tasks/async_compute.rs | 2 +- .../external_source_external_thread.rs | 2 +- examples/ecs/hierarchy.rs | 2 +- examples/ecs/iter_combinations.rs | 2 +- examples/ecs/parallel_query.rs | 2 +- examples/ecs/removal_detection.rs | 2 +- examples/ecs/state.rs | 8 +- examples/games/alien_cake_addict.rs | 5 +- examples/games/breakout.rs | 5 +- examples/games/contributors.rs | 3 +- examples/games/game_menu.rs | 3 +- examples/ios/src/lib.rs | 2 +- examples/scene/scene.rs | 2 +- examples/shader/animate_shader.rs | 4 +- .../shader/compute_shader_game_of_life.rs | 8 +- examples/shader/custom_vertex_attribute.rs | 2 +- examples/shader/shader_defs.rs | 4 +- examples/shader/shader_instancing.rs | 4 +- examples/shader/shader_material.rs | 2 +- examples/shader/shader_material_glsl.rs | 2 +- .../shader_material_screenspace_texture.rs | 6 +- examples/stress_tests/bevymark.rs | 3 +- examples/stress_tests/many_cubes.rs | 4 +- examples/stress_tests/many_foxes.rs | 2 +- examples/stress_tests/many_lights.rs | 27 +- examples/stress_tests/many_sprites.rs | 2 +- examples/stress_tests/transform_hierarchy.rs | 2 +- examples/tools/scene_viewer.rs | 161 +++---- examples/transforms/3d_rotation.rs | 2 +- .../transforms/global_vs_local_translation.rs | 5 +- examples/transforms/scale.rs | 2 +- examples/transforms/transform.rs | 2 +- examples/transforms/translation.rs | 2 +- examples/ui/button.rs | 2 +- examples/ui/font_atlas_debug.rs | 2 +- examples/ui/text.rs | 2 +- examples/ui/text_debug.rs | 2 +- examples/ui/ui.rs | 4 +- examples/window/clear_color.rs | 2 +- examples/window/low_power.rs | 3 +- examples/window/multiple_windows.rs | 105 +---- examples/window/scale_factor_override.rs | 4 +- examples/window/transparent_window.rs | 2 +- notes.md | 208 +++++++++ tests/window/minimising.rs | 18 +- tests/window/resizing.rs | 18 +- 121 files changed, 1664 insertions(+), 1762 deletions(-) create mode 100644 crates/bevy_core_pipeline/src/clear_color.rs delete mode 100644 crates/bevy_core_pipeline/src/clear_pass.rs delete mode 100644 crates/bevy_core_pipeline/src/clear_pass_driver.rs create mode 100644 crates/bevy_core_pipeline/src/core_2d/camera_2d.rs rename crates/bevy_core_pipeline/src/{main_pass_2d.rs => core_2d/main_pass_2d_node.rs} (65%) create mode 100644 crates/bevy_core_pipeline/src/core_2d/mod.rs create mode 100644 crates/bevy_core_pipeline/src/core_3d/camera_3d.rs rename crates/bevy_core_pipeline/src/{main_pass_3d.rs => core_3d/main_pass_3d_node.rs} (89%) create mode 100644 crates/bevy_core_pipeline/src/core_3d/mod.rs delete mode 100644 crates/bevy_core_pipeline/src/main_pass_driver.rs delete mode 100644 crates/bevy_render/src/camera/bundle.rs create mode 100644 crates/bevy_render/src/camera/camera_driver_node.rs delete mode 100644 crates/bevy_ui/src/render/camera.rs delete mode 100644 examples/3d/two_passes.rs create mode 100644 notes.md diff --git a/Cargo.toml b/Cargo.toml index 97e1e90b41e51..6518fb54c427b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,10 +224,6 @@ path = "examples/3d/spherical_area_lights.rs" name = "texture" path = "examples/3d/texture.rs" -[[example]] -name = "two_passes" -path = "examples/3d/two_passes.rs" - [[example]] name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 55ec3c771351a..50abf04a60988 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -19,7 +19,12 @@ trace = [] # bevy bevy_app = { path = "../bevy_app", version = "0.8.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.8.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev" } bevy_render = { path = "../bevy_render", version = "0.8.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } +serde = { version = "1", features = ["derive"] } + diff --git a/crates/bevy_core_pipeline/src/clear_color.rs b/crates/bevy_core_pipeline/src/clear_color.rs new file mode 100644 index 0000000000000..b8ac5d6438cf9 --- /dev/null +++ b/crates/bevy_core_pipeline/src/clear_color.rs @@ -0,0 +1,40 @@ +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::prelude::*; +use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_render::{color::Color, extract_resource::ExtractResource, RenderWorld}; +use serde::{Deserialize, Serialize}; + +#[derive(Reflect, Serialize, Deserialize, Clone, Debug)] +#[reflect_value(Serialize, Deserialize)] +pub enum ClearColorConfig { + Default, + Custom(Color), + None, +} + +impl Default for ClearColorConfig { + fn default() -> Self { + ClearColorConfig::Default + } +} + +/// When used as a resource, sets the color that is used to clear the screen between frames. +/// +/// This color appears as the "background" color for simple apps, when +/// there are portions of the screen with nothing rendered. +#[derive(Component, Clone, Debug, Deref, DerefMut, ExtractResource)] +pub struct ClearColor(pub Color); + +impl Default for ClearColor { + fn default() -> Self { + Self(Color::rgb(0.4, 0.4, 0.4)) + } +} + +pub fn extract_clear_color(clear_color: Res, mut render_world: ResMut) { + // If the clear color has changed + if clear_color.is_changed() { + // Update the clear color resource in the render world + render_world.insert_resource(clear_color.clone()); + } +} diff --git a/crates/bevy_core_pipeline/src/clear_pass.rs b/crates/bevy_core_pipeline/src/clear_pass.rs deleted file mode 100644 index ae379e62f89e5..0000000000000 --- a/crates/bevy_core_pipeline/src/clear_pass.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::collections::HashSet; - -use crate::{ClearColor, RenderTargetClearColors}; -use bevy_ecs::prelude::*; -use bevy_render::{ - camera::{ExtractedCamera, RenderTarget}, - prelude::Image, - render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo}, - render_resource::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, - RenderPassDescriptor, - }, - renderer::RenderContext, - view::{ExtractedView, ExtractedWindows, ViewDepthTexture, ViewTarget}, -}; - -pub struct ClearPassNode { - query: QueryState< - ( - &'static ViewTarget, - Option<&'static ViewDepthTexture>, - Option<&'static ExtractedCamera>, - ), - With, - >, -} - -impl ClearPassNode { - pub fn new(world: &mut World) -> Self { - Self { - query: QueryState::new(world), - } - } -} - -impl Node for ClearPassNode { - fn input(&self) -> Vec { - vec![] - } - - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let mut cleared_targets = HashSet::new(); - let clear_color = world.resource::(); - let render_target_clear_colors = world.resource::(); - - // This gets all ViewTargets and ViewDepthTextures and clears its attachments - // TODO: This has the potential to clear the same target multiple times, if there - // are multiple views drawing to the same target. This should be fixed when we make - // clearing happen on "render targets" instead of "views" (see the TODO below for more context). - for (target, depth, camera) in self.query.iter_manual(world) { - let mut color = &clear_color.0; - if let Some(camera) = camera { - cleared_targets.insert(&camera.target); - if let Some(target_color) = render_target_clear_colors.get(&camera.target) { - color = target_color; - } - } - let pass_descriptor = RenderPassDescriptor { - label: Some("clear_pass"), - color_attachments: &[target.get_color_attachment(Operations { - load: LoadOp::Clear((*color).into()), - store: true, - })], - depth_stencil_attachment: depth.map(|depth| RenderPassDepthStencilAttachment { - view: &depth.view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: true, - }), - stencil_ops: None, - }), - }; - - render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - } - - // TODO: This is a hack to ensure we don't call present() on frames without any work, - // which will cause panics. The real fix here is to clear "render targets" directly - // instead of "views". This should be removed once full RenderTargets are implemented. - let windows = world.resource::(); - let images = world.resource::>(); - for target in render_target_clear_colors.colors.keys().cloned().chain( - windows - .values() - .map(|window| RenderTarget::Window(window.id)), - ) { - // skip windows that have already been cleared - if cleared_targets.contains(&target) { - continue; - } - let pass_descriptor = RenderPassDescriptor { - label: Some("clear_pass"), - color_attachments: &[RenderPassColorAttachment { - view: target.get_texture_view(windows, images).unwrap(), - resolve_target: None, - ops: Operations { - load: LoadOp::Clear( - (*render_target_clear_colors - .get(&target) - .unwrap_or(&clear_color.0)) - .into(), - ), - store: true, - }, - }], - depth_stencil_attachment: None, - }; - - render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - } - - Ok(()) - } -} diff --git a/crates/bevy_core_pipeline/src/clear_pass_driver.rs b/crates/bevy_core_pipeline/src/clear_pass_driver.rs deleted file mode 100644 index 405c544f11077..0000000000000 --- a/crates/bevy_core_pipeline/src/clear_pass_driver.rs +++ /dev/null @@ -1,20 +0,0 @@ -use bevy_ecs::world::World; -use bevy_render::{ - render_graph::{Node, NodeRunError, RenderGraphContext}, - renderer::RenderContext, -}; - -pub struct ClearPassDriverNode; - -impl Node for ClearPassDriverNode { - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - graph.run_sub_graph(crate::clear_graph::NAME, vec![])?; - - Ok(()) - } -} diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs new file mode 100644 index 0000000000000..9b9bcaab21d4e --- /dev/null +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -0,0 +1,82 @@ +use crate::clear_color::ClearColorConfig; +use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_reflect::Reflect; +use bevy_render::{ + camera::{ + Camera, CameraProjection, CameraRenderGraph, DepthCalculation, OrthographicProjection, + }, + extract_component::ExtractComponent, + primitives::Frustum, + view::VisibleEntities, +}; +use bevy_transform::prelude::{GlobalTransform, Transform}; + +#[derive(Component, Default, Reflect, Clone)] +#[reflect(Component)] +pub struct Camera2d { + pub clear_color: ClearColorConfig, +} + +impl ExtractComponent for Camera2d { + type Query = &'static Self; + type Filter = With; + + fn extract_component(item: QueryItem) -> Self { + item.clone() + } +} + +#[derive(Bundle)] +pub struct Camera2dBundle { + pub camera: Camera, + pub camera_render_graph: CameraRenderGraph, + pub projection: OrthographicProjection, + pub visible_entities: VisibleEntities, + pub frustum: Frustum, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub camera_2d: Camera2d, +} + +impl Default for Camera2dBundle { + fn default() -> Self { + Self::new_with_far(1000.0) + } +} + +impl Camera2dBundle { + /// Create an orthographic projection camera with a custom Z position. + /// + /// The camera is placed at `Z=far-0.1`, looking toward the world origin `(0,0,0)`. + /// Its orthographic projection extends from `0.0` to `-far` in camera view space, + /// corresponding to `Z=far-0.1` (closest to camera) to `Z=-0.1` (furthest away from + /// camera) in world space. + pub fn new_with_far(far: f32) -> Self { + // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset + // the camera's translation by far and use a right handed coordinate system + let projection = OrthographicProjection { + far, + depth_calculation: DepthCalculation::ZDifference, + ..Default::default() + }; + let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); + let view_projection = + projection.get_projection_matrix() * transform.compute_matrix().inverse(); + let frustum = Frustum::from_view_projection( + &view_projection, + &transform.translation, + &transform.back(), + projection.far(), + ); + Self { + camera_render_graph: CameraRenderGraph::new(crate::core_2d::graph::NAME), + projection, + visible_entities: VisibleEntities::default(), + frustum, + transform, + global_transform: Default::default(), + camera: Camera::default(), + camera_2d: Camera2d::default(), + } + } +} diff --git a/crates/bevy_core_pipeline/src/main_pass_2d.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs similarity index 65% rename from crates/bevy_core_pipeline/src/main_pass_2d.rs rename to crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs index 6fc8537a158bf..4dd912c3f142a 100644 --- a/crates/bevy_core_pipeline/src/main_pass_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs @@ -1,4 +1,7 @@ -use crate::Transparent2d; +use crate::{ + clear_color::{ClearColor, ClearColorConfig}, + core_2d::{camera_2d::Camera2d, Transparent2d}, +}; use bevy_ecs::prelude::*; use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -9,8 +12,14 @@ use bevy_render::{ }; pub struct MainPass2dNode { - query: - QueryState<(&'static RenderPhase, &'static ViewTarget), With>, + query: QueryState< + ( + &'static RenderPhase, + &'static ViewTarget, + &'static Camera2d, + ), + With, + >, } impl MainPass2dNode { @@ -18,7 +27,7 @@ impl MainPass2dNode { pub fn new(world: &mut World) -> Self { Self { - query: QueryState::new(world), + query: world.query_filtered(), } } } @@ -39,20 +48,24 @@ impl Node for MainPass2dNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - // If there is no view entity, do not try to process the render phase for the view - let (transparent_phase, target) = match self.query.get_manual(world, view_entity) { - Ok(it) => it, - _ => return Ok(()), - }; - - if transparent_phase.items.is_empty() { - return Ok(()); - } + let (transparent_phase, target, camera_2d) = + if let Ok(result) = self.query.get_manual(world, view_entity) { + result + } else { + // no target + return Ok(()); + }; let pass_descriptor = RenderPassDescriptor { label: Some("main_pass_2d"), color_attachments: &[target.get_color_attachment(Operations { - load: LoadOp::Load, + load: match camera_2d.clear_color { + ClearColorConfig::Default => { + LoadOp::Clear(world.resource::().0.into()) + } + ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + ClearColorConfig::None => LoadOp::Load, + }, store: true, })], depth_stencil_attachment: None, diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs new file mode 100644 index 0000000000000..8acdb42da54ef --- /dev/null +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -0,0 +1,130 @@ +mod camera_2d; +mod main_pass_2d_node; + +pub mod graph { + pub const NAME: &str = "core_2d"; + pub mod input { + pub const VIEW_ENTITY: &str = "view_entity"; + } + pub mod node { + pub const MAIN_PASS: &str = "main_pass"; + } +} + +pub use camera_2d::*; +pub use main_pass_2d_node::*; + +use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; +use bevy_render::{ + camera::Camera, + extract_component::ExtractComponentPlugin, + render_graph::{RenderGraph, SlotInfo, SlotType}, + render_phase::{ + batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, + DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase, + }, + render_resource::CachedRenderPipelineId, + RenderApp, RenderStage, +}; +use bevy_utils::FloatOrd; +use std::ops::Range; + +pub struct Core2dPlugin; + +impl Plugin for Core2dPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .add_plugin(ExtractComponentPlugin::::default()); + + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, batch_phase_system::); + + let pass_node_2d = MainPass2dNode::new(&mut render_app.world); + let mut graph = render_app.world.resource_mut::(); + + let mut draw_2d_graph = RenderGraph::default(); + draw_2d_graph.add_node(graph::node::MAIN_PASS, pass_node_2d); + let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( + graph::input::VIEW_ENTITY, + SlotType::Entity, + )]); + draw_2d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass2dNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(graph::NAME, draw_2d_graph); + } +} + +pub struct Transparent2d { + pub sort_key: FloatOrd, + pub entity: Entity, + pub pipeline: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, + /// Range in the vertex buffer of this item + pub batch_range: Option>, +} + +impl PhaseItem for Transparent2d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +impl EntityPhaseItem for Transparent2d { + #[inline] + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for Transparent2d { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline + } +} + +impl BatchedPhaseItem for Transparent2d { + fn batch_range(&self) -> &Option> { + &self.batch_range + } + + fn batch_range_mut(&mut self) -> &mut Option> { + &mut self.batch_range + } +} + +pub fn extract_core_2d_camera_phases( + mut commands: Commands, + cameras_2d: Query<(Entity, &Camera), With>, +) { + for (entity, camera) in cameras_2d.iter() { + if camera.is_active { + commands + .get_or_spawn(entity) + .insert(RenderPhase::::default()); + } + } +} diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs new file mode 100644 index 0000000000000..a04a3df38d5fa --- /dev/null +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -0,0 +1,53 @@ +use crate::clear_color::ClearColorConfig; +use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_reflect::Reflect; +use bevy_render::{ + camera::{Camera, CameraRenderGraph, Projection}, + extract_component::ExtractComponent, + primitives::Frustum, + view::VisibleEntities, +}; +use bevy_transform::prelude::{GlobalTransform, Transform}; + +#[derive(Component, Default, Reflect, Clone)] +#[reflect(Component)] +pub struct Camera3d { + pub clear_color: ClearColorConfig, +} + +impl ExtractComponent for Camera3d { + type Query = &'static Self; + type Filter = With; + + fn extract_component(item: QueryItem) -> Self { + item.clone() + } +} + +#[derive(Bundle)] +pub struct Camera3dBundle { + pub camera: Camera, + pub camera_render_graph: CameraRenderGraph, + pub projection: Projection, + pub visible_entities: VisibleEntities, + pub frustum: Frustum, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub camera_3d: Camera3d, +} + +// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference +impl Default for Camera3dBundle { + fn default() -> Self { + Self { + camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME), + camera: Default::default(), + projection: Default::default(), + visible_entities: Default::default(), + frustum: Default::default(), + transform: Default::default(), + global_transform: Default::default(), + camera_3d: Default::default(), + } + } +} diff --git a/crates/bevy_core_pipeline/src/main_pass_3d.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs similarity index 89% rename from crates/bevy_core_pipeline/src/main_pass_3d.rs rename to crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index bd2ab599c4998..3289c915b53ac 100644 --- a/crates/bevy_core_pipeline/src/main_pass_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -1,4 +1,7 @@ -use crate::{AlphaMask3d, Opaque3d, Transparent3d}; +use crate::{ + clear_color::{ClearColor, ClearColorConfig}, + core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d}, +}; use bevy_ecs::prelude::*; use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -16,6 +19,7 @@ pub struct MainPass3dNode { &'static RenderPhase, &'static RenderPhase, &'static RenderPhase, + &'static Camera3d, &'static ViewTarget, &'static ViewDepthTexture, ), @@ -28,7 +32,7 @@ impl MainPass3dNode { pub fn new(world: &mut World) -> Self { Self { - query: QueryState::new(world), + query: world.query_filtered(), } } } @@ -49,13 +53,16 @@ impl Node for MainPass3dNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth) = + let (opaque_phase, alpha_mask_phase, transparent_phase, camera_3d, target, depth) = match self.query.get_manual(world, view_entity) { Ok(query) => query, - Err(_) => return Ok(()), // No window + Err(_) => { + return Ok(()); + } // No window }; - if !opaque_phase.items.is_empty() { + // Always run opaque pass to ensure screen is cleared + { // Run the opaque pass, sorted front-to-back // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -65,14 +72,20 @@ impl Node for MainPass3dNode { // NOTE: The opaque pass loads the color // buffer as well as writing to it. color_attachments: &[target.get_color_attachment(Operations { - load: LoadOp::Load, + load: match camera_3d.clear_color { + ClearColorConfig::Default => { + LoadOp::Clear(world.resource::().0.into()) + } + ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + ClearColorConfig::None => LoadOp::Load, + }, store: true, })], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &depth.view, // NOTE: The opaque main pass loads the depth buffer and possibly overwrites it depth_ops: Some(Operations { - load: LoadOp::Load, + load: LoadOp::Clear(0.0), store: true, }), stencil_ops: None, diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs new file mode 100644 index 0000000000000..0dcdd5d10ace5 --- /dev/null +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -0,0 +1,250 @@ +mod camera_3d; +mod main_pass_3d_node; + +pub mod graph { + pub const NAME: &str = "core_3d"; + pub mod input { + pub const VIEW_ENTITY: &str = "view_entity"; + } + pub mod node { + pub const MAIN_PASS: &str = "main_pass"; + } +} + +pub use camera_3d::*; +pub use main_pass_3d_node::*; + +use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; +use bevy_render::{ + camera::{Camera, ExtractedCamera}, + extract_component::ExtractComponentPlugin, + prelude::Msaa, + render_graph::{RenderGraph, SlotInfo, SlotType}, + render_phase::{ + sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, + EntityPhaseItem, PhaseItem, RenderPhase, + }, + render_resource::{ + CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, + }, + renderer::RenderDevice, + texture::TextureCache, + view::{ExtractedView, ViewDepthTexture}, + RenderApp, RenderStage, +}; +use bevy_utils::{FloatOrd, HashMap}; + +pub struct Core3dPlugin; + +impl Plugin for Core3dPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .add_plugin(ExtractComponentPlugin::::default()); + + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::>() + .init_resource::>() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases) + .add_system_to_stage(RenderStage::Prepare, prepare_core_3d_views_system) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); + + let pass_node_3d = MainPass3dNode::new(&mut render_app.world); + let mut graph = render_app.world.resource_mut::(); + + let mut draw_3d_graph = RenderGraph::default(); + draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d); + let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( + graph::input::VIEW_ENTITY, + SlotType::Entity, + )]); + draw_3d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass3dNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(graph::NAME, draw_3d_graph); + } +} + +pub struct Opaque3d { + pub distance: f32, + pub pipeline: CachedRenderPipelineId, + pub entity: Entity, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for Opaque3d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +impl EntityPhaseItem for Opaque3d { + #[inline] + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for Opaque3d { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline + } +} + +pub struct AlphaMask3d { + pub distance: f32, + pub pipeline: CachedRenderPipelineId, + pub entity: Entity, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for AlphaMask3d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +impl EntityPhaseItem for AlphaMask3d { + #[inline] + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for AlphaMask3d { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline + } +} + +pub struct Transparent3d { + pub distance: f32, + pub pipeline: CachedRenderPipelineId, + pub entity: Entity, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for Transparent3d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +impl EntityPhaseItem for Transparent3d { + #[inline] + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for Transparent3d { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline + } +} + +pub fn extract_core_3d_camera_phases( + mut commands: Commands, + cameras_3d: Query<(Entity, &Camera), With>, +) { + for (entity, camera) in cameras_3d.iter() { + if camera.is_active { + commands.get_or_spawn(entity).insert_bundle(( + RenderPhase::::default(), + RenderPhase::::default(), + RenderPhase::::default(), + )); + } + } +} + +pub fn prepare_core_3d_views_system( + mut commands: Commands, + mut texture_cache: ResMut, + msaa: Res, + render_device: Res, + views_3d: Query< + (Entity, &ExtractedView, Option<&ExtractedCamera>), + ( + With>, + With>, + With>, + ), + >, +) { + let mut textures = HashMap::default(); + for (entity, view, camera) in views_3d.iter() { + let mut get_cached_texture = || { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("view_depth_texture"), + size: Extent3d { + depth_or_array_layers: 1, + width: view.width as u32, + height: view.height as u32, + }, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 + * bit depth for better performance */ + usage: TextureUsages::RENDER_ATTACHMENT, + }, + ) + }; + let cached_texture = if let Some(camera) = camera { + textures + .entry(camera.target.clone()) + .or_insert_with(get_cached_texture) + .clone() + } else { + get_cached_texture() + }; + commands.entity(entity).insert(ViewDepthTexture { + texture: cached_texture.texture, + view: cached_texture.default_view, + }); + } +} diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 31bb9077c1ef2..f8f5db70a3d8c 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -1,419 +1,28 @@ -mod clear_pass; -mod clear_pass_driver; -mod main_pass_2d; -mod main_pass_3d; -mod main_pass_driver; +pub mod clear_color; +pub mod core_2d; +pub mod core_3d; pub mod prelude { #[doc(hidden)] - pub use crate::ClearColor; + pub use crate::{ + clear_color::ClearColor, + core_2d::{Camera2d, Camera2dBundle}, + core_3d::{Camera3d, Camera3dBundle}, + }; } -use bevy_utils::HashMap; - -pub use clear_pass::*; -pub use clear_pass_driver::*; -pub use main_pass_2d::*; -pub use main_pass_3d::*; -pub use main_pass_driver::*; - -use std::ops::Range; - +use crate::{clear_color::ClearColor, core_2d::Core2dPlugin, core_3d::Core3dPlugin}; use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; -use bevy_render::{ - camera::{ActiveCamera, Camera2d, Camera3d, ExtractedCamera, RenderTarget}, - color::Color, - extract_resource::{ExtractResource, ExtractResourcePlugin}, - render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, - render_phase::{ - batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, - DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase, - }, - render_resource::*, - renderer::RenderDevice, - texture::TextureCache, - view::{ExtractedView, Msaa, ViewDepthTexture}, - RenderApp, RenderStage, -}; -use bevy_utils::FloatOrd; - -/// When used as a resource, sets the color that is used to clear the screen between frames. -/// -/// This color appears as the "background" color for simple apps, when -/// there are portions of the screen with nothing rendered. -#[derive(Clone, Debug, ExtractResource)] -pub struct ClearColor(pub Color); - -impl Default for ClearColor { - fn default() -> Self { - Self(Color::rgb(0.4, 0.4, 0.4)) - } -} - -#[derive(Clone, Debug, Default, ExtractResource)] -pub struct RenderTargetClearColors { - colors: HashMap, -} - -impl RenderTargetClearColors { - pub fn get(&self, target: &RenderTarget) -> Option<&Color> { - self.colors.get(target) - } - pub fn insert(&mut self, target: RenderTarget, color: Color) { - self.colors.insert(target, color); - } -} - -// Plugins that contribute to the RenderGraph should use the following label conventions: -// 1. Graph modules should have a NAME, input module, and node module (where relevant) -// 2. The "top level" graph is the plugin module root. Just add things like `pub mod node` directly under the plugin module -// 3. "sub graph" modules should be nested beneath their parent graph module - -pub mod node { - pub const MAIN_PASS_DEPENDENCIES: &str = "main_pass_dependencies"; - pub const MAIN_PASS_DRIVER: &str = "main_pass_driver"; - pub const CLEAR_PASS_DRIVER: &str = "clear_pass_driver"; -} - -pub mod draw_2d_graph { - pub const NAME: &str = "draw_2d"; - pub mod input { - pub const VIEW_ENTITY: &str = "view_entity"; - } - pub mod node { - pub const MAIN_PASS: &str = "main_pass"; - } -} - -pub mod draw_3d_graph { - pub const NAME: &str = "draw_3d"; - pub mod input { - pub const VIEW_ENTITY: &str = "view_entity"; - } - pub mod node { - pub const MAIN_PASS: &str = "main_pass"; - } -} - -pub mod clear_graph { - pub const NAME: &str = "clear"; - pub mod node { - pub const CLEAR_PASS: &str = "clear_pass"; - } -} +use bevy_render::extract_resource::ExtractResourcePlugin; #[derive(Default)] pub struct CorePipelinePlugin; -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] -pub enum CorePipelineRenderSystems { - SortTransparent2d, -} - impl Plugin for CorePipelinePlugin { fn build(&self, app: &mut App) { app.init_resource::() - .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) - .add_plugin(ExtractResourcePlugin::::default()); - - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; - - render_app - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases) - .add_system_to_stage(RenderStage::Prepare, prepare_core_views_system) - .add_system_to_stage( - RenderStage::PhaseSort, - sort_phase_system:: - .label(CorePipelineRenderSystems::SortTransparent2d), - ) - .add_system_to_stage( - RenderStage::PhaseSort, - batch_phase_system:: - .after(CorePipelineRenderSystems::SortTransparent2d), - ) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); - - let clear_pass_node = ClearPassNode::new(&mut render_app.world); - let pass_node_2d = MainPass2dNode::new(&mut render_app.world); - let pass_node_3d = MainPass3dNode::new(&mut render_app.world); - let mut graph = render_app.world.resource_mut::(); - - let mut draw_2d_graph = RenderGraph::default(); - draw_2d_graph.add_node(draw_2d_graph::node::MAIN_PASS, pass_node_2d); - let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( - draw_2d_graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_2d_graph - .add_slot_edge( - input_node_id, - draw_2d_graph::input::VIEW_ENTITY, - draw_2d_graph::node::MAIN_PASS, - MainPass2dNode::IN_VIEW, - ) - .unwrap(); - graph.add_sub_graph(draw_2d_graph::NAME, draw_2d_graph); - - let mut draw_3d_graph = RenderGraph::default(); - draw_3d_graph.add_node(draw_3d_graph::node::MAIN_PASS, pass_node_3d); - let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( - draw_3d_graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_3d_graph - .add_slot_edge( - input_node_id, - draw_3d_graph::input::VIEW_ENTITY, - draw_3d_graph::node::MAIN_PASS, - MainPass3dNode::IN_VIEW, - ) - .unwrap(); - graph.add_sub_graph(draw_3d_graph::NAME, draw_3d_graph); - - let mut clear_graph = RenderGraph::default(); - clear_graph.add_node(clear_graph::node::CLEAR_PASS, clear_pass_node); - graph.add_sub_graph(clear_graph::NAME, clear_graph); - - graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode); - graph.add_node(node::MAIN_PASS_DRIVER, MainPassDriverNode); - graph - .add_node_edge(node::MAIN_PASS_DEPENDENCIES, node::MAIN_PASS_DRIVER) - .unwrap(); - graph.add_node(node::CLEAR_PASS_DRIVER, ClearPassDriverNode); - graph - .add_node_edge(node::CLEAR_PASS_DRIVER, node::MAIN_PASS_DRIVER) - .unwrap(); - } -} - -pub struct Transparent2d { - pub sort_key: FloatOrd, - pub entity: Entity, - pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, - /// Range in the vertex buffer of this item - pub batch_range: Option>, -} - -impl PhaseItem for Transparent2d { - type SortKey = FloatOrd; - - #[inline] - fn sort_key(&self) -> Self::SortKey { - self.sort_key - } - - #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } -} - -impl EntityPhaseItem for Transparent2d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - -impl CachedRenderPipelinePhaseItem for Transparent2d { - #[inline] - fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline - } -} - -impl BatchedPhaseItem for Transparent2d { - fn batch_range(&self) -> &Option> { - &self.batch_range - } - - fn batch_range_mut(&mut self) -> &mut Option> { - &mut self.batch_range - } -} - -pub struct Opaque3d { - pub distance: f32, - pub pipeline: CachedRenderPipelineId, - pub entity: Entity, - pub draw_function: DrawFunctionId, -} - -impl PhaseItem for Opaque3d { - type SortKey = FloatOrd; - - #[inline] - fn sort_key(&self) -> Self::SortKey { - FloatOrd(self.distance) - } - - #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } -} - -impl EntityPhaseItem for Opaque3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - -impl CachedRenderPipelinePhaseItem for Opaque3d { - #[inline] - fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline - } -} - -pub struct AlphaMask3d { - pub distance: f32, - pub pipeline: CachedRenderPipelineId, - pub entity: Entity, - pub draw_function: DrawFunctionId, -} - -impl PhaseItem for AlphaMask3d { - type SortKey = FloatOrd; - - #[inline] - fn sort_key(&self) -> Self::SortKey { - FloatOrd(self.distance) - } - - #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } -} - -impl EntityPhaseItem for AlphaMask3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - -impl CachedRenderPipelinePhaseItem for AlphaMask3d { - #[inline] - fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline - } -} - -pub struct Transparent3d { - pub distance: f32, - pub pipeline: CachedRenderPipelineId, - pub entity: Entity, - pub draw_function: DrawFunctionId, -} - -impl PhaseItem for Transparent3d { - type SortKey = FloatOrd; - - #[inline] - fn sort_key(&self) -> Self::SortKey { - FloatOrd(self.distance) - } - - #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } -} - -impl EntityPhaseItem for Transparent3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - -impl CachedRenderPipelinePhaseItem for Transparent3d { - #[inline] - fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline - } -} - -pub fn extract_core_pipeline_camera_phases( - mut commands: Commands, - active_2d: Res>, - active_3d: Res>, -) { - if let Some(entity) = active_2d.get() { - commands - .get_or_spawn(entity) - .insert(RenderPhase::::default()); - } - if let Some(entity) = active_3d.get() { - commands.get_or_spawn(entity).insert_bundle(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - )); - } -} - -pub fn prepare_core_views_system( - mut commands: Commands, - mut texture_cache: ResMut, - msaa: Res, - render_device: Res, - views_3d: Query< - (Entity, &ExtractedView, Option<&ExtractedCamera>), - ( - With>, - With>, - With>, - ), - >, -) { - let mut textures = HashMap::default(); - for (entity, view, camera) in views_3d.iter() { - let mut get_cached_texture = || { - texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("view_depth_texture"), - size: Extent3d { - depth_or_array_layers: 1, - width: view.width as u32, - height: view.height as u32, - }, - mip_level_count: 1, - sample_count: msaa.samples, - dimension: TextureDimension::D2, - format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 - * bit depth for better performance */ - usage: TextureUsages::RENDER_ATTACHMENT, - }, - ) - }; - let cached_texture = if let Some(camera) = camera { - textures - .entry(camera.target.clone()) - .or_insert_with(get_cached_texture) - .clone() - } else { - get_cached_texture() - }; - commands.entity(entity).insert(ViewDepthTexture { - texture: cached_texture.texture, - view: cached_texture.default_view, - }); + .add_plugin(Core2dPlugin) + .add_plugin(Core3dPlugin); } } diff --git a/crates/bevy_core_pipeline/src/main_pass_driver.rs b/crates/bevy_core_pipeline/src/main_pass_driver.rs deleted file mode 100644 index c4347ef14eab1..0000000000000 --- a/crates/bevy_core_pipeline/src/main_pass_driver.rs +++ /dev/null @@ -1,33 +0,0 @@ -use bevy_ecs::world::World; -use bevy_render::{ - camera::{ActiveCamera, Camera2d, Camera3d}, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, - renderer::RenderContext, -}; - -pub struct MainPassDriverNode; - -impl Node for MainPassDriverNode { - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - if let Some(camera_3d) = world.resource::>().get() { - graph.run_sub_graph( - crate::draw_3d_graph::NAME, - vec![SlotValue::Entity(camera_3d)], - )?; - } - - if let Some(camera_2d) = world.resource::>().get() { - graph.run_sub_graph( - crate::draw_2d_graph::NAME, - vec![SlotValue::Entity(camera_2d)], - )?; - } - - Ok(()) - } -} diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index c2cb4bcf6dfd0..c35ac483c044a 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -14,6 +14,7 @@ bevy_animation = { path = "../bevy_animation", version = "0.8.0-dev", optional = bevy_app = { path = "../bevy_app", version = "0.8.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" } bevy_core = { path = "../bevy_core", version = "0.8.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.8.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.8.0-dev" } bevy_log = { path = "../bevy_log", version = "0.8.0-dev" } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 35025aded6246..28e0366a14ae5 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -3,6 +3,7 @@ use bevy_asset::{ AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset, }; use bevy_core::Name; +use bevy_core_pipeline::prelude::Camera3d; use bevy_ecs::{entity::Entity, prelude::FromWorld, world::World}; use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder}; use bevy_log::warn; @@ -13,7 +14,7 @@ use bevy_pbr::{ }; use bevy_render::{ camera::{ - Camera, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection, + Camera, CameraRenderGraph, OrthographicProjection, PerspectiveProjection, Projection, ScalingMode, }, color::Color, @@ -444,6 +445,7 @@ async fn load_gltf<'a, 'b>( let mut scenes = vec![]; let mut named_scenes = HashMap::default(); + let mut active_camera_found = false; for scene in gltf.scenes() { let mut err = None; let mut world = World::default(); @@ -462,6 +464,7 @@ async fn load_gltf<'a, 'b>( &buffer_data, &mut node_index_to_entity_map, &mut entity_to_skin_index_map, + &mut active_camera_found, ); if result.is_err() { err = Some(result); @@ -686,6 +689,7 @@ fn load_node( buffer_data: &[Vec], node_index_to_entity_map: &mut HashMap, entity_to_skin_index_map: &mut HashMap, + active_camera_found: &mut bool, ) -> Result<(), GltfError> { let transform = gltf_node.transform(); let mut gltf_error = None; @@ -703,14 +707,7 @@ fn load_node( // create camera node if let Some(camera) = gltf_node.camera() { - node.insert_bundle(( - VisibleEntities { - ..Default::default() - }, - Frustum::default(), - )); - - match camera.projection() { + let projection = match camera.projection() { gltf::camera::Projection::Orthographic(orthographic) => { let xmag = orthographic.xmag(); let orthographic_projection: OrthographicProjection = OrthographicProjection { @@ -721,12 +718,7 @@ fn load_node( ..Default::default() }; - node.insert(Camera { - projection_matrix: orthographic_projection.get_projection_matrix(), - ..Default::default() - }); - node.insert(orthographic_projection); - node.insert(Camera3d); + Projection::Orthographic(orthographic_projection) } gltf::camera::Projection::Perspective(perspective) => { let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { @@ -740,14 +732,23 @@ fn load_node( if let Some(aspect_ratio) = perspective.aspect_ratio() { perspective_projection.aspect_ratio = aspect_ratio; } - node.insert(Camera { - projection_matrix: perspective_projection.get_projection_matrix(), - ..Default::default() - }); - node.insert(perspective_projection); - node.insert(Camera3d); + Projection::Perspective(perspective_projection) } - } + }; + + node.insert_bundle(( + projection, + Camera { + is_active: !*active_camera_found, + ..Default::default() + }, + VisibleEntities::default(), + Frustum::default(), + Camera3d::default(), + CameraRenderGraph::new(bevy_core_pipeline::core_3d::graph::NAME), + )); + + *active_camera_found = true; } // Map node index to entity @@ -860,6 +861,7 @@ fn load_node( buffer_data, node_index_to_entity_map, entity_to_skin_index_map, + active_camera_found, ) { gltf_error = Some(err); return; diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 83585694ba659..c7a1b04d21e9f 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -39,6 +39,7 @@ use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::{ + camera::CameraUpdateSystem, extract_resource::ExtractResourcePlugin, prelude::Color, render_graph::RenderGraph, @@ -91,6 +92,7 @@ impl Plugin for PbrPlugin { assign_lights_to_clusters .label(SimulationLightSystems::AssignLightsToClusters) .after(TransformSystem::TransformPropagate) + .after(CameraUpdateSystem) .after(ModifiesWindows), ) .add_system_to_stage( @@ -176,19 +178,19 @@ impl Plugin for PbrPlugin { render_app.add_render_command::(); let mut graph = render_app.world.resource_mut::(); let draw_3d_graph = graph - .get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME) + .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) .unwrap(); draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node); draw_3d_graph .add_node_edge( draw_3d_graph::node::SHADOW_PASS, - bevy_core_pipeline::draw_3d_graph::node::MAIN_PASS, + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, ) .unwrap(); draw_3d_graph .add_slot_edge( draw_3d_graph.input_node().unwrap().id, - bevy_core_pipeline::draw_3d_graph::input::VIEW_ENTITY, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, draw_3d_graph::node::SHADOW_PASS, ShadowPassNode::IN_VIEW, ) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index d72546efac2ea..9963f75224ff4 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,6 +1,5 @@ use std::collections::HashSet; -use bevy_asset::Assets; use bevy_ecs::prelude::*; use bevy_math::{ const_vec2, Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles, @@ -10,7 +9,6 @@ use bevy_render::{ camera::{Camera, CameraProjection, OrthographicProjection}, color::Color, extract_resource::ExtractResource, - prelude::Image, primitives::{Aabb, CubemapFrusta, Frustum, Plane, Sphere}, render_resource::BufferBindingType, renderer::RenderDevice, @@ -18,7 +16,6 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::tracing::warn; -use bevy_window::Windows; use crate::{ calculate_cluster_factors, CubeMapFace, CubemapVisibleEntities, ViewClusterBindings, @@ -637,8 +634,6 @@ impl GlobalVisiblePointLights { pub(crate) fn assign_lights_to_clusters( mut commands: Commands, mut global_lights: ResMut, - windows: Res, - images: Res>, mut views: Query<( Entity, &GlobalTransform, @@ -741,13 +736,12 @@ pub(crate) fn assign_lights_to_clusters( continue; } - let screen_size = - if let Some(screen_size) = camera.target.get_physical_size(&windows, &images) { - screen_size - } else { - clusters.clear(); - continue; - }; + let screen_size = if let Some(screen_size) = camera.physical_target_size { + screen_size + } else { + clusters.clear(); + continue; + }; let mut requested_cluster_dimensions = config.dimensions_for_screen_size(screen_size); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index b0df8d0116613..10ca167e403aa 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Asset, AssetServer, Handle}; -use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; +use bevy_core_pipeline::core_3d::{AlphaMask3d, Opaque3d, Transparent3d}; use bevy_ecs::{ entity::Entity, prelude::World, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 57a1b825b56d1..63cf180e0e810 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -4,7 +4,7 @@ use crate::{ PointLight, PointLightShadowMap, SetMeshBindGroup, VisiblePointLights, SHADOW_SHADER_HANDLE, }; use bevy_asset::Handle; -use bevy_core_pipeline::Transparent3d; +use bevy_core_pipeline::core_3d::Transparent3d; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index b6bf1646a2cae..6a7a4c3c297c2 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -2,7 +2,7 @@ use crate::MeshPipeline; use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; -use bevy_core_pipeline::Opaque3d; +use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; diff --git a/crates/bevy_render/src/camera/bundle.rs b/crates/bevy_render/src/camera/bundle.rs deleted file mode 100644 index 39bda7802bbd8..0000000000000 --- a/crates/bevy_render/src/camera/bundle.rs +++ /dev/null @@ -1,164 +0,0 @@ -use super::{CameraProjection, ScalingMode}; -use crate::{ - camera::{Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection}, - primitives::Frustum, - view::VisibleEntities, -}; -use bevy_ecs::reflect::ReflectComponent; -use bevy_ecs::{bundle::Bundle, prelude::Component}; -use bevy_math::Vec3; -use bevy_reflect::Reflect; -use bevy_transform::components::{GlobalTransform, Transform}; - -#[derive(Component, Default, Reflect)] -#[reflect(Component)] -pub struct Camera3d; - -#[derive(Component, Default, Reflect)] -#[reflect(Component)] -pub struct Camera2d; - -/// Component bundle for camera entities with perspective projection -/// -/// Use this for 3D rendering. -#[derive(Bundle)] -pub struct PerspectiveCameraBundle { - pub camera: Camera, - pub perspective_projection: PerspectiveProjection, - pub visible_entities: VisibleEntities, - pub frustum: Frustum, - pub transform: Transform, - pub global_transform: GlobalTransform, - pub marker: M, -} - -impl Default for PerspectiveCameraBundle { - fn default() -> Self { - PerspectiveCameraBundle::new_3d() - } -} - -impl PerspectiveCameraBundle { - pub fn new_3d() -> Self { - PerspectiveCameraBundle::new() - } -} - -impl PerspectiveCameraBundle { - pub fn new() -> Self { - let perspective_projection = PerspectiveProjection::default(); - let view_projection = perspective_projection.get_projection_matrix(); - let frustum = Frustum::from_view_projection( - &view_projection, - &Vec3::ZERO, - &Vec3::Z, - perspective_projection.far(), - ); - PerspectiveCameraBundle { - camera: Camera::default(), - perspective_projection, - visible_entities: VisibleEntities::default(), - frustum, - transform: Default::default(), - global_transform: Default::default(), - marker: M::default(), - } - } -} - -/// Component bundle for camera entities with orthographic projection -/// -/// Use this for 2D games, isometric games, CAD-like 3D views. -#[derive(Bundle)] -pub struct OrthographicCameraBundle { - pub camera: Camera, - pub orthographic_projection: OrthographicProjection, - pub visible_entities: VisibleEntities, - pub frustum: Frustum, - pub transform: Transform, - pub global_transform: GlobalTransform, - pub marker: M, -} - -impl OrthographicCameraBundle { - pub fn new_3d() -> Self { - let orthographic_projection = OrthographicProjection { - scaling_mode: ScalingMode::FixedVertical, - depth_calculation: DepthCalculation::Distance, - ..Default::default() - }; - let view_projection = orthographic_projection.get_projection_matrix(); - let frustum = Frustum::from_view_projection( - &view_projection, - &Vec3::ZERO, - &Vec3::Z, - orthographic_projection.far(), - ); - OrthographicCameraBundle { - camera: Camera::default(), - orthographic_projection, - visible_entities: VisibleEntities::default(), - frustum, - transform: Default::default(), - global_transform: Default::default(), - marker: Camera3d, - } - } -} - -impl OrthographicCameraBundle { - /// Create an orthographic projection camera to render 2D content. - /// - /// The projection creates a camera space where X points to the right of the screen, - /// Y points to the top of the screen, and Z points out of the screen (backward), - /// forming a right-handed coordinate system. The center of the screen is at `X=0` and - /// `Y=0`. - /// - /// The default scaling mode is [`ScalingMode::WindowSize`], resulting in a resolution - /// where 1 unit in X and Y in camera space corresponds to 1 logical pixel on the screen. - /// That is, for a screen of 1920 pixels in width, the X coordinates visible on screen go - /// from `X=-960` to `X=+960` in world space, left to right. This can be changed by changing - /// the [`OrthographicProjection::scaling_mode`] field. - /// - /// The camera is placed at `Z=+1000-0.1`, looking toward the world origin `(0,0,0)`. - /// Its orthographic projection extends from `0.0` to `-1000.0` in camera view space, - /// corresponding to `Z=+999.9` (closest to camera) to `Z=-0.1` (furthest away from - /// camera) in world space. - pub fn new_2d() -> Self { - Self::new_2d_with_far(1000.0) - } - - /// Create an orthographic projection camera with a custom Z position. - /// - /// The camera is placed at `Z=far-0.1`, looking toward the world origin `(0,0,0)`. - /// Its orthographic projection extends from `0.0` to `-far` in camera view space, - /// corresponding to `Z=far-0.1` (closest to camera) to `Z=-0.1` (furthest away from - /// camera) in world space. - pub fn new_2d_with_far(far: f32) -> Self { - // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset - // the camera's translation by far and use a right handed coordinate system - let orthographic_projection = OrthographicProjection { - far, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }; - let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); - let view_projection = - orthographic_projection.get_projection_matrix() * transform.compute_matrix().inverse(); - let frustum = Frustum::from_view_projection( - &view_projection, - &transform.translation, - &transform.back(), - orthographic_projection.far(), - ); - OrthographicCameraBundle { - camera: Camera::default(), - orthographic_projection, - visible_entities: VisibleEntities::default(), - frustum, - transform, - global_transform: Default::default(), - marker: Camera2d, - } - } -} diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index be5ce81e46868..771a32bed9b39 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,24 +1,20 @@ -use std::marker::PhantomData; - use crate::{ camera::CameraProjection, prelude::Image, render_asset::RenderAssets, render_resource::TextureView, view::{ExtractedView, ExtractedWindows, VisibleEntities}, - RenderApp, RenderStage, }; -use bevy_app::{App, CoreStage, Plugin, StartupStage}; use bevy_asset::{AssetEvent, Assets, Handle}; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, component::Component, entity::Entity, event::EventReader, - prelude::With, query::Added, reflect::ReflectComponent, - system::{Commands, ParamSet, Query, Res, ResMut}, + system::{Commands, ParamSet, Query, Res}, }; use bevy_math::{Mat4, UVec2, Vec2, Vec3}; use bevy_reflect::prelude::*; @@ -26,19 +22,94 @@ use bevy_transform::components::GlobalTransform; use bevy_utils::HashSet; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; use serde::{Deserialize, Serialize}; +use std::borrow::Cow; use wgpu::Extent3d; -#[derive(Component, Default, Debug, Reflect, Clone)] -#[reflect(Component, Default)] +#[derive(Component, Debug, Reflect, Clone)] +#[reflect(Component)] pub struct Camera { pub projection_matrix: Mat4, + pub logical_target_size: Option, + pub physical_target_size: Option, + pub priority: isize, + pub is_active: bool, #[reflect(ignore)] pub target: RenderTarget, #[reflect(ignore)] pub depth_calculation: DepthCalculation, } -#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)] +impl Default for Camera { + fn default() -> Self { + Self { + is_active: true, + priority: 0, + projection_matrix: Default::default(), + logical_target_size: Default::default(), + physical_target_size: Default::default(), + target: Default::default(), + depth_calculation: Default::default(), + } + } +} + +impl Camera { + /// Given a position in world space, use the camera to compute the screen space coordinates. + /// + /// To get the coordinates in Normalized Device Coordinates, you should use + /// [`world_to_ndc`](Self::world_to_ndc). + pub fn world_to_screen( + &self, + camera_transform: &GlobalTransform, + world_position: Vec3, + ) -> Option { + let window_size = self.logical_target_size?; + let ndc_space_coords = self.world_to_ndc(camera_transform, world_position)?; + // NDC z-values outside of 0 < z < 1 are outside the camera frustum and are thus not in screen space + if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 { + return None; + } + + // Once in NDC space, we can discard the z element and rescale x/y to fit the screen + Some((ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size) + } + + /// Given a position in world space, use the camera to compute the Normalized Device Coordinates. + /// + /// Values returned will be between -1.0 and 1.0 when the position is in screen space. + /// To get the coordinates in the render target dimensions, you should use + /// [`world_to_screen`](Self::world_to_screen). + pub fn world_to_ndc( + &self, + camera_transform: &GlobalTransform, + world_position: Vec3, + ) -> Option { + // Build a transform to convert from world to NDC using camera data + let world_to_ndc: Mat4 = + self.projection_matrix * camera_transform.compute_matrix().inverse(); + let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position); + + if !ndc_space_coords.is_nan() { + Some(ndc_space_coords) + } else { + None + } + } +} + +/// Configures the [`RenderGraph`](crate::render_graph::RenderGraph) name assigned to be run for a given [`Camera`] entity. +#[derive(Component, Deref, DerefMut, Reflect, Default)] +#[reflect(Component)] +pub struct CameraRenderGraph(Cow<'static, str>); + +impl CameraRenderGraph { + #[inline] + pub fn new>>(name: T) -> Self { + Self(name.into()) + } +} + +#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum RenderTarget { /// Window to which the camera's view is rendered. Window(WindowId), @@ -118,52 +189,6 @@ impl Default for DepthCalculation { } } -impl Camera { - /// Given a position in world space, use the camera to compute the screen space coordinates. - /// - /// To get the coordinates in Normalized Device Coordinates, you should use - /// [`world_to_ndc`](Self::world_to_ndc). - pub fn world_to_screen( - &self, - windows: &Windows, - images: &Assets, - camera_transform: &GlobalTransform, - world_position: Vec3, - ) -> Option { - let window_size = self.target.get_logical_size(windows, images)?; - let ndc_space_coords = self.world_to_ndc(camera_transform, world_position)?; - // NDC z-values outside of 0 < z < 1 are outside the camera frustum and are thus not in screen space - if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 { - return None; - } - - // Once in NDC space, we can discard the z element and rescale x/y to fit the screen - Some((ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size) - } - - /// Given a position in world space, use the camera to compute the Normalized Device Coordinates. - /// - /// Values returned will be between -1.0 and 1.0 when the position is in screen space. - /// To get the coordinates in the render target dimensions, you should use - /// [`world_to_screen`](Self::world_to_screen). - pub fn world_to_ndc( - &self, - camera_transform: &GlobalTransform, - world_position: Vec3, - ) -> Option { - // Build a transform to convert from world to NDC using camera data - let world_to_ndc: Mat4 = - self.projection_matrix * camera_transform.compute_matrix().inverse(); - let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position); - - if !ndc_space_coords.is_nan() { - Some(ndc_space_coords) - } else { - None - } - } -} - pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, @@ -218,8 +243,11 @@ pub fn camera_system( || added_cameras.contains(&entity) || camera_projection.is_changed() { - if let Some(size) = camera.target.get_logical_size(&windows, &images) { + camera.logical_target_size = camera.target.get_logical_size(&windows, &images); + camera.physical_target_size = camera.target.get_physical_size(&windows, &images); + if let Some(size) = camera.logical_target_size { camera_projection.update(size.x, size.y); + camera.logical_target_size = Some(size); camera.projection_matrix = camera_projection.get_projection_matrix(); camera.depth_calculation = camera_projection.depth_calculation(); } @@ -227,116 +255,44 @@ pub fn camera_system( } } -pub struct CameraTypePlugin(PhantomData); - -impl Default for CameraTypePlugin { - fn default() -> Self { - Self(Default::default()) - } -} - -impl Plugin for CameraTypePlugin { - fn build(&self, app: &mut App) { - app.init_resource::>() - .add_startup_system_to_stage(StartupStage::PostStartup, set_active_camera::) - .add_system_to_stage(CoreStage::PostUpdate, set_active_camera::); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage(RenderStage::Extract, extract_cameras::); - } - } -} - -/// The canonical source of the "active camera" of the given camera type `T`. -#[derive(Debug)] -pub struct ActiveCamera { - camera: Option, - marker: PhantomData, -} - -impl Default for ActiveCamera { - fn default() -> Self { - Self { - camera: Default::default(), - marker: Default::default(), - } - } -} - -impl Clone for ActiveCamera { - fn clone(&self) -> Self { - Self { - camera: self.camera, - marker: self.marker, - } - } -} - -impl ActiveCamera { - /// Sets the active camera to the given `camera` entity. - pub fn set(&mut self, camera: Entity) { - self.camera = Some(camera); - } - - /// Returns the active camera, if it exists. - pub fn get(&self) -> Option { - self.camera - } -} - -pub fn set_active_camera( - mut active_camera: ResMut>, - cameras: Query, With)>, -) { - // Check if there is already an active camera set and - // that it has not been deleted on the previous frame - if let Some(camera) = active_camera.get() { - if cameras.contains(camera) { - return; - } - } - - // If the previous active camera ceased to exist - // fallback to another camera of the same type T - if let Some(camera) = cameras.iter().next() { - active_camera.camera = Some(camera); - } else { - active_camera.camera = None; - } -} - #[derive(Component, Debug)] pub struct ExtractedCamera { pub target: RenderTarget, pub physical_size: Option, + pub render_graph: Cow<'static, str>, + pub priority: isize, } -pub fn extract_cameras( +pub fn extract_cameras( mut commands: Commands, - windows: Res, - images: Res>, - active_camera: Res>, - query: Query<(&Camera, &GlobalTransform, &VisibleEntities), With>, + query: Query<( + Entity, + &Camera, + &CameraRenderGraph, + &GlobalTransform, + &VisibleEntities, + )>, ) { - if let Some(entity) = active_camera.get() { - if let Ok((camera, transform, visible_entities)) = query.get(entity) { - if let Some(size) = camera.target.get_physical_size(&windows, &images) { - commands.get_or_spawn(entity).insert_bundle(( - ExtractedCamera { - target: camera.target.clone(), - physical_size: camera.target.get_physical_size(&windows, &images), - }, - ExtractedView { - projection: camera.projection_matrix, - transform: *transform, - width: size.x, - height: size.y, - }, - visible_entities.clone(), - M::default(), - )); - } + for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() { + if !camera.is_active { + continue; + } + if let Some(size) = camera.physical_target_size { + commands.get_or_spawn(entity).insert_bundle(( + ExtractedCamera { + target: camera.target.clone(), + physical_size: Some(size), + render_graph: camera_render_graph.0.clone(), + priority: camera.priority, + }, + ExtractedView { + projection: camera.projection_matrix, + transform: *transform, + width: size.x, + height: size.y, + }, + visible_entities.clone(), + )); } } - - commands.insert_resource(active_camera.clone()); } diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs new file mode 100644 index 0000000000000..5f453b561ae59 --- /dev/null +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -0,0 +1,102 @@ +use crate::{ + camera::{ExtractedCamera, RenderTarget}, + render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, + renderer::RenderContext, + view::ExtractedWindows, +}; +use bevy_ecs::{entity::Entity, prelude::QueryState, world::World}; +use bevy_utils::{tracing::warn, HashSet}; +use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor}; + +pub struct CameraDriverNode { + cameras: QueryState<(Entity, &'static ExtractedCamera)>, +} + +impl CameraDriverNode { + pub fn new(world: &mut World) -> Self { + Self { + cameras: world.query(), + } + } +} + +impl Node for CameraDriverNode { + fn update(&mut self, world: &mut World) { + self.cameras.update_archetypes(world); + } + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let mut sorted_cameras = self + .cameras + .iter_manual(world) + .map(|(e, c)| (e, c.priority, c.target.clone())) + .collect::>(); + // sort by priority and ensure within a priority, RenderTargets of the same type are packed together + sorted_cameras.sort_by(|(_, p1, t1), (_, p2, t2)| match p1.cmp(p2) { + std::cmp::Ordering::Equal => t1.cmp(t2), + ord => ord, + }); + let mut camera_windows = HashSet::new(); + let mut previous_priority_target = None; + let mut ambiguities = HashSet::new(); + for (entity, priority, target) in sorted_cameras { + let new_priority_target = (priority, target); + if let Some(previous_priority_target) = previous_priority_target { + if previous_priority_target == new_priority_target { + ambiguities.insert(new_priority_target.clone()); + } + } + previous_priority_target = Some(new_priority_target); + if let Ok((_, camera)) = self.cameras.get_manual(world, entity) { + if let RenderTarget::Window(id) = camera.target { + camera_windows.insert(id); + } + graph + .run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?; + } + } + + if !ambiguities.is_empty() { + warn!("Camera priority ambiguities detected for active cameras with the following priorites: {:?}. To fix this, ensure there is exactly one Camera entity spawned with a given priority for a given RenderTarget. Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, ambiguities could result in unpredictable render results.", ambiguities); + } + + // Wgpu (and some backends) require doing work for swap chains if you call `get_curent_texture()` and `present()` + // This ensures that Bevy doesn't crash, even there are no cameras (and therefore no work submitted). + for (id, window) in world.resource::().iter() { + if camera_windows.contains(id) { + continue; + } + + let swap_chain_texture = if let Some(swap_chain_texture) = &window.swap_chain_texture { + swap_chain_texture + } else { + continue; + }; + + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("no_camera_clear_pass").entered(); + let pass_descriptor = RenderPassDescriptor { + label: Some("no_camera_clear_pass"), + color_attachments: &[RenderPassColorAttachment { + view: swap_chain_texture, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(wgpu::Color::BLACK), + store: true, + }, + }], + depth_stencil_attachment: None, + }; + + render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + } + + Ok(()) + } +} diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index bf1d1d5d8c101..b092cc15b405a 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -1,19 +1,19 @@ -mod bundle; #[allow(clippy::module_inception)] mod camera; +mod camera_driver_node; mod projection; -pub use bundle::*; pub use camera::*; +pub use camera_driver_node::*; pub use projection::*; use crate::{ primitives::Aabb, + render_graph::RenderGraph, view::{ComputedVisibility, Visibility, VisibleEntities}, + RenderApp, RenderStage, }; -use bevy_app::{App, CoreStage, Plugin}; -use bevy_ecs::schedule::ParallelSystemDescriptorCoercion; -use bevy_window::ModifiesWindows; +use bevy_app::{App, Plugin}; #[derive(Default)] pub struct CameraPlugin; @@ -23,24 +23,21 @@ impl Plugin for CameraPlugin { app.register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() - .add_system_to_stage( - CoreStage::PostUpdate, - crate::camera::camera_system::.after(ModifiesWindows), - ) - .add_system_to_stage( - CoreStage::PostUpdate, - crate::camera::camera_system::.after(ModifiesWindows), - ) - .add_plugin(CameraTypePlugin::::default()) - .add_plugin(CameraTypePlugin::::default()); + .register_type::() + .add_plugin(CameraProjectionPlugin::::default()) + .add_plugin(CameraProjectionPlugin::::default()) + .add_plugin(CameraProjectionPlugin::::default()); + + let render_app = app.sub_app_mut(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract_cameras); + + let camera_driver_node = CameraDriverNode::new(&mut render_app.world); + let mut render_graph = render_app.world.resource_mut::(); + render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); } } diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 3c2a7d745da21..ded5c7173ff7a 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,10 +1,41 @@ +use std::marker::PhantomData; + use super::DepthCalculation; -use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_app::{App, CoreStage, Plugin, StartupStage}; +use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_math::Mat4; -use bevy_reflect::std_traits::ReflectDefault; -use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_reflect::{std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize}; +use bevy_window::ModifiesWindows; use serde::{Deserialize, Serialize}; +/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type. +pub struct CameraProjectionPlugin(PhantomData); + +impl Default for CameraProjectionPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +#[derive(SystemLabel, Clone, Eq, PartialEq, Hash, Debug)] +pub struct CameraUpdateSystem; + +impl Plugin for CameraProjectionPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .add_startup_system_to_stage( + StartupStage::PostStartup, + crate::camera::camera_system::, + ) + .add_system_to_stage( + CoreStage::PostUpdate, + crate::camera::camera_system:: + .label(CameraUpdateSystem) + .after(ModifiesWindows), + ); + } +} + pub trait CameraProjection { fn get_projection_matrix(&self) -> Mat4; fn update(&mut self, width: f32, height: f32); @@ -12,6 +43,62 @@ pub trait CameraProjection { fn far(&self) -> f32; } +/// A configurable [`CameraProjection`] that can select its projection type at runtime. +#[derive(Component, Debug, Clone, Reflect)] +#[reflect(Component, Default)] +pub enum Projection { + Perspective(PerspectiveProjection), + Orthographic(OrthographicProjection), +} + +impl From for Projection { + fn from(p: PerspectiveProjection) -> Self { + Self::Perspective(p) + } +} + +impl From for Projection { + fn from(p: OrthographicProjection) -> Self { + Self::Orthographic(p) + } +} + +impl CameraProjection for Projection { + fn get_projection_matrix(&self) -> Mat4 { + match self { + Projection::Perspective(projection) => projection.get_projection_matrix(), + Projection::Orthographic(projection) => projection.get_projection_matrix(), + } + } + + fn update(&mut self, width: f32, height: f32) { + match self { + Projection::Perspective(projection) => projection.update(width, height), + Projection::Orthographic(projection) => projection.update(width, height), + } + } + + fn depth_calculation(&self) -> DepthCalculation { + match self { + Projection::Perspective(projection) => projection.depth_calculation(), + Projection::Orthographic(projection) => projection.depth_calculation(), + } + } + + fn far(&self) -> f32 { + match self { + Projection::Perspective(projection) => projection.far(), + Projection::Orthographic(projection) => projection.far(), + } + } +} + +impl Default for Projection { + fn default() -> Self { + Projection::Perspective(Default::default()) + } +} + #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component, Default)] pub struct PerspectiveProjection { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 1dadbf149a6a9..888d0d6865b96 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -18,10 +18,7 @@ pub mod view; pub mod prelude { #[doc(hidden)] pub use crate::{ - camera::{ - Camera, OrthographicCameraBundle, OrthographicProjection, PerspectiveCameraBundle, - PerspectiveProjection, - }, + camera::{Camera, OrthographicProjection, PerspectiveProjection}, color::Color, mesh::{shape, Mesh}, render_resource::Shader, @@ -30,7 +27,6 @@ pub mod prelude { }; } -use bevy_utils::tracing::debug; pub use once_cell; use crate::{ @@ -47,6 +43,7 @@ use crate::{ use bevy_app::{App, AppLabel, Plugin}; use bevy_asset::{AddAsset, AssetServer}; use bevy_ecs::prelude::*; +use bevy_utils::tracing::debug; use std::ops::{Deref, DerefMut}; /// Contains the default Bevy rendering backend based on wgpu. @@ -99,6 +96,12 @@ impl DerefMut for RenderWorld { } } +pub mod main_graph { + pub mod node { + pub const CAMERA_DRIVER: &str = "camera_driver"; + } +} + /// A Label for the rendering sub-app. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderApp; @@ -171,13 +174,13 @@ impl Plugin for RenderPlugin { .with_system(render_system.exclusive_system().at_end()), ) .add_stage(RenderStage::Cleanup, SystemStage::parallel()) + .init_resource::() .insert_resource(instance) .insert_resource(device) .insert_resource(queue) .insert_resource(adapter_info) .insert_resource(pipeline_cache) - .insert_resource(asset_server) - .init_resource::(); + .insert_resource(asset_server); app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { #[cfg(feature = "trace")] diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index 5b806ad31b1e5..ab60925e6fbe1 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -1,7 +1,7 @@ use crate::{ render_graph::{ Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, - RunSubGraphError, SlotInfo, SlotInfos, + RunSubGraphError, SlotInfo, SlotInfos, SlotType, SlotValue, }, renderer::RenderContext, }; @@ -331,3 +331,37 @@ impl Node for EmptyNode { Ok(()) } } + +/// A [`RenderGraph`](super::RenderGraph) [`Node`] that takes a view entity as input and runs the configured graph name once. +/// This makes it easier to insert sub-graph runs into a graph. +pub struct RunGraphOnViewNode { + graph_name: Cow<'static, str>, +} + +impl RunGraphOnViewNode { + pub const IN_VIEW: &'static str = "view"; + pub fn new>>(graph_name: T) -> Self { + Self { + graph_name: graph_name.into(), + } + } +} + +impl Node for RunGraphOnViewNode { + fn input(&self) -> Vec { + vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)] + } + fn run( + &self, + graph: &mut RenderGraphContext, + _render_context: &mut RenderContext, + _world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + graph.run_sub_graph( + self.graph_name.clone(), + vec![SlotValue::Entity(view_entity)], + )?; + Ok(()) + } +} diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 478c635572dc9..104c72757ddc0 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -12,7 +12,7 @@ use bevy_transform::components::GlobalTransform; use bevy_transform::TransformSystem; use crate::{ - camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection}, + camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, Projection}, mesh::Mesh, primitives::{Aabb, Frustum, Sphere}, }; @@ -73,6 +73,7 @@ pub enum VisibilitySystems { CalculateBounds, UpdateOrthographicFrusta, UpdatePerspectiveFrusta, + UpdateProjectionFrusta, CheckVisibility, } @@ -98,6 +99,12 @@ impl Plugin for VisibilityPlugin { .label(UpdatePerspectiveFrusta) .after(TransformSystem::TransformPropagate), ) + .add_system_to_stage( + CoreStage::PostUpdate, + update_frusta:: + .label(UpdateProjectionFrusta) + .after(TransformSystem::TransformPropagate), + ) .add_system_to_stage( CoreStage::PostUpdate, check_visibility @@ -105,6 +112,7 @@ impl Plugin for VisibilityPlugin { .after(CalculateBounds) .after(UpdateOrthographicFrusta) .after(UpdatePerspectiveFrusta) + .after(UpdateProjectionFrusta) .after(TransformSystem::TransformPropagate), ); } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index b24833d52c0ef..a7be1a4ab983b 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -30,7 +30,7 @@ pub use texture_atlas_builder::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Assets, HandleUntyped}; -use bevy_core_pipeline::Transparent2d; +use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_reflect::TypeUuid; use bevy_render::{ diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index bd938c91dbd96..9ed1ce540757b 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,6 +1,6 @@ use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Asset, AssetServer, Handle}; -use bevy_core_pipeline::Transparent2d; +use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::{ entity::Entity, prelude::{Bundle, World}, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index cf07b16e65aad..c0066482014f7 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -5,7 +5,7 @@ use crate::{ Rect, Sprite, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, Assets, Handle, HandleId}; -use bevy_core_pipeline::Transparent2d; +use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 8685c2523020b..d0bebbad3609a 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -48,7 +48,7 @@ impl Default for Text2dBounds { } } -/// The bundle of components needed to draw text in a 2D scene via a 2D `OrthographicCameraBundle`. +/// The bundle of components needed to draw text in a 2D scene via a 2D `Camera2dBundle`. /// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs) #[derive(Bundle, Clone, Debug, Default)] pub struct Text2dBundle { diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index c2a6b218bbc8c..d62575c260845 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -4,11 +4,12 @@ use crate::{ widget::{Button, ImageMode}, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, }; -use bevy_ecs::{bundle::Bundle, prelude::Component}; -use bevy_render::{ - camera::{Camera, DepthCalculation, OrthographicProjection, WindowOrigin}, - view::{Visibility, VisibleEntities}, +use bevy_ecs::{ + bundle::Bundle, + prelude::{Component, With}, + query::QueryItem, }; +use bevy_render::{camera::Camera, extract_component::ExtractComponent, view::Visibility}; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -135,45 +136,22 @@ impl Default for ButtonBundle { } } } -#[derive(Component, Default)] -pub struct CameraUi; - -/// The camera that is needed to see UI elements -#[derive(Bundle, Debug)] -pub struct UiCameraBundle { - /// The camera component - pub camera: Camera, - /// The orthographic projection settings - pub orthographic_projection: OrthographicProjection, - /// The transform of the camera - pub transform: Transform, - /// The global transform of the camera - pub global_transform: GlobalTransform, - /// Contains visible entities - // FIXME there is no frustrum culling for UI - pub visible_entities: VisibleEntities, - pub marker: M, +#[derive(Component, Clone)] +pub struct CameraUi { + pub is_enabled: bool, } -impl Default for UiCameraBundle { +impl Default for CameraUi { fn default() -> Self { - // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset - // the camera's translation by far and use a right handed coordinate system - let far = 1000.0; - UiCameraBundle { - camera: Camera { - ..Default::default() - }, - orthographic_projection: OrthographicProjection { - far, - window_origin: WindowOrigin::BottomLeft, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }, - transform: Transform::from_xyz(0.0, 0.0, far - 0.1), - global_transform: Default::default(), - visible_entities: Default::default(), - marker: CameraUi, - } + Self { is_enabled: true } + } +} + +impl ExtractComponent for CameraUi { + type Query = &'static Self; + type Filter = With; + + fn extract_component(item: QueryItem) -> Self { + item.clone() } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 4155bf11729cc..629ad851ec510 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -12,7 +12,7 @@ pub mod entity; pub mod update; pub mod widget; -use bevy_render::camera::CameraTypePlugin; +use bevy_render::extract_component::ExtractComponentPlugin; pub use flex::*; pub use focus::*; pub use geometry::*; @@ -50,7 +50,7 @@ pub enum UiSystem { impl Plugin for UiPlugin { fn build(&self, app: &mut App) { - app.add_plugin(CameraTypePlugin::::default()) + app.add_plugin(ExtractComponentPlugin::::default()) .init_resource::() .register_type::() .register_type::() diff --git a/crates/bevy_ui/src/render/camera.rs b/crates/bevy_ui/src/render/camera.rs deleted file mode 100644 index d03c972951714..0000000000000 --- a/crates/bevy_ui/src/render/camera.rs +++ /dev/null @@ -1,18 +0,0 @@ -use bevy_ecs::prelude::*; -use bevy_render::{camera::ActiveCamera, render_phase::RenderPhase}; - -use crate::prelude::CameraUi; - -use super::TransparentUi; - -/// Inserts the [`RenderPhase`] into the UI camera -pub fn extract_ui_camera_phases( - mut commands: Commands, - active_camera: Res>, -) { - if let Some(entity) = active_camera.get() { - commands - .get_or_spawn(entity) - .insert(RenderPhase::::default()); - } -} diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index e42d0a83347be..4cc4b03c78f25 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -1,26 +1,26 @@ -mod camera; mod pipeline; mod render_pass; -pub use camera::*; +use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::CameraUi, CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_reflect::TypeUuid; use bevy_render::{ + camera::{Camera, CameraProjection, DepthCalculation, OrthographicProjection, WindowOrigin}, color::Color, render_asset::RenderAssets, - render_graph::{RenderGraph, SlotInfo, SlotType}, + render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType}, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::Image, - view::{ViewUniforms, Visibility}, + view::{ExtractedView, ViewUniforms, Visibility}, RenderApp, RenderStage, RenderWorld, }; use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas}; @@ -70,7 +70,14 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::>() .add_render_command::() - .add_system_to_stage(RenderStage::Extract, extract_ui_camera_phases) + .add_system_to_stage( + RenderStage::Extract, + extract_default_ui_camera_view::, + ) + .add_system_to_stage( + RenderStage::Extract, + extract_default_ui_camera_view::, + ) .add_system_to_stage( RenderStage::Extract, extract_uinodes.label(RenderUiSystem::ExtractNode), @@ -84,16 +91,64 @@ pub fn build_ui_render(app: &mut App) { .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); // Render graph - let ui_pass_node = UiPassNode::new(&mut render_app.world); + let ui_graph_2d = get_ui_graph(render_app); + let ui_graph_3d = get_ui_graph(render_app); let mut graph = render_app.world.resource_mut::(); - let mut draw_ui_graph = RenderGraph::default(); - draw_ui_graph.add_node(draw_ui_graph::node::UI_PASS, ui_pass_node); - let input_node_id = draw_ui_graph.set_input(vec![SlotInfo::new( + if let Some(graph_2d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_2d::graph::NAME) { + graph_2d.add_sub_graph(draw_ui_graph::NAME, ui_graph_2d); + graph_2d.add_node( + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::new(draw_ui_graph::NAME), + ); + graph_2d + .add_node_edge( + bevy_core_pipeline::core_2d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_2d + .add_slot_edge( + graph_2d.input_node().unwrap().id, + bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ) + .unwrap(); + } + + if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) { + graph_3d.add_sub_graph(draw_ui_graph::NAME, ui_graph_3d); + graph_3d.add_node( + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::new(draw_ui_graph::NAME), + ); + graph_3d + .add_node_edge( + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_3d + .add_slot_edge( + graph_3d.input_node().unwrap().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ) + .unwrap(); + } +} + +fn get_ui_graph(render_app: &mut App) -> RenderGraph { + let ui_pass_node = UiPassNode::new(&mut render_app.world); + let mut ui_graph = RenderGraph::default(); + ui_graph.add_node(draw_ui_graph::node::UI_PASS, ui_pass_node); + let input_node_id = ui_graph.set_input(vec![SlotInfo::new( draw_ui_graph::input::VIEW_ENTITY, SlotType::Entity, )]); - draw_ui_graph + ui_graph .add_slot_edge( input_node_id, draw_ui_graph::input::VIEW_ENTITY, @@ -101,15 +156,7 @@ pub fn build_ui_render(app: &mut App) { UiPassNode::IN_VIEW, ) .unwrap(); - graph.add_sub_graph(draw_ui_graph::NAME, draw_ui_graph); - - graph.add_node(node::UI_PASS_DRIVER, UiPassDriverNode); - graph - .add_node_edge( - bevy_core_pipeline::node::MAIN_PASS_DRIVER, - node::UI_PASS_DRIVER, - ) - .unwrap(); + ui_graph } pub struct ExtractedUiNode { @@ -163,6 +210,51 @@ pub fn extract_uinodes( } } +#[derive(Component)] +pub struct DefaultCameraView(pub Entity); + +pub fn extract_default_ui_camera_view( + mut commands: Commands, + render_world: Res, + query: Query<(Entity, &Camera, Option<&CameraUi>), With>, +) { + for (entity, camera, camera_ui) in query.iter() { + // ignore cameras with disabled ui + if let Some(&CameraUi { + is_enabled: false, .. + }) = camera_ui + { + continue; + } + if let (Some(logical_size), Some(physical_size)) = + (camera.logical_target_size, camera.physical_target_size) + { + let far = 1000.0; + let mut projection = OrthographicProjection { + far, + window_origin: WindowOrigin::BottomLeft, + depth_calculation: DepthCalculation::ZDifference, + ..Default::default() + }; + projection.update(logical_size.x, logical_size.y); + // This roundabout approach is required because spawn().id() won't work in this context + let default_camera_view = render_world.entities().reserve_entity(); + commands + .get_or_spawn(default_camera_view) + .insert(ExtractedView { + projection: projection.get_projection_matrix(), + transform: GlobalTransform::from_xyz(0.0, 0.0, far - 0.1), + width: physical_size.x, + height: physical_size.y, + }); + commands.get_or_spawn(entity).insert_bundle(( + DefaultCameraView(default_camera_view), + RenderPhase::::default(), + )); + } + } +} + pub fn extract_text_uinodes( mut render_world: ResMut, texture_atlases: Res>, @@ -447,7 +539,6 @@ pub fn queue_uinodes( layout: &ui_pipeline.image_layout, }) }); - transparent_phase.add(TransparentUi { draw_function: draw_ui_function, pipeline, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 90d80296027e8..36a51b61a7c34 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -1,9 +1,10 @@ +use super::{UiBatch, UiImageBindGroups, UiMeta}; +use crate::{prelude::CameraUi, DefaultCameraView}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, }; use bevy_render::{ - camera::ActiveCamera, render_graph::*, render_phase::*, render_resource::{ @@ -14,30 +15,16 @@ use bevy_render::{ }; use bevy_utils::FloatOrd; -use crate::prelude::CameraUi; - -use super::{draw_ui_graph, UiBatch, UiImageBindGroups, UiMeta}; - -pub struct UiPassDriverNode; - -impl Node for UiPassDriverNode { - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - if let Some(camera_ui) = world.resource::>().get() { - graph.run_sub_graph(draw_ui_graph::NAME, vec![SlotValue::Entity(camera_ui)])?; - } - - Ok(()) - } -} - pub struct UiPassNode { - query: - QueryState<(&'static RenderPhase, &'static ViewTarget), With>, + ui_view_query: QueryState< + ( + &'static RenderPhase, + &'static ViewTarget, + Option<&'static CameraUi>, + ), + With, + >, + default_camera_view_query: QueryState<&'static DefaultCameraView>, } impl UiPassNode { @@ -45,7 +32,8 @@ impl UiPassNode { pub fn new(world: &mut World) -> Self { Self { - query: QueryState::new(world), + ui_view_query: world.query_filtered(), + default_camera_view_query: world.query(), } } } @@ -56,7 +44,8 @@ impl Node for UiPassNode { } fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); + self.ui_view_query.update_archetypes(world); + self.default_camera_view_query.update_archetypes(world); } fn run( @@ -65,17 +54,31 @@ impl Node for UiPassNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - - // If there is no view entity, do not try to process the render phase for the view - let (transparent_phase, target) = match self.query.get_manual(world, view_entity) { - Ok(it) => it, - _ => return Ok(()), - }; - + let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?; + + let (transparent_phase, target, camera_ui) = + if let Ok(result) = self.ui_view_query.get_manual(world, input_view_entity) { + result + } else { + return Ok(()); + }; if transparent_phase.items.is_empty() { return Ok(()); } + // Don't render UI for cameras where it is explicitly disabled + if let Some(&CameraUi { is_enabled: false }) = camera_ui { + return Ok(()); + } + + // use the "default" view entity if it is defined + let view_entity = if let Ok(default_view) = self + .default_camera_view_query + .get_manual(world, input_view_entity) + { + default_view.0 + } else { + input_view_entity + }; let pass_descriptor = RenderPassDescriptor { label: Some("ui_pass"), color_attachments: &[RenderPassColorAttachment { diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index dd414022c9590..19aa6cbbb8267 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -2,7 +2,7 @@ use bevy_math::{DVec2, IVec2, Vec2}; use bevy_utils::{tracing::warn, Uuid}; use raw_window_handle::RawWindowHandle; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct WindowId(Uuid); /// Presentation mode for a window. diff --git a/examples/2d/mesh2d.rs b/examples/2d/mesh2d.rs index 6412c97f88b68..c45b2aec2e079 100644 --- a/examples/2d/mesh2d.rs +++ b/examples/2d/mesh2d.rs @@ -14,7 +14,7 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); commands.spawn_bundle(MaterialMesh2dBundle { mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), transform: Transform::default().with_scale(Vec3::splat(128.)), diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 0fea936b4f44f..31172f0b33034 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -4,7 +4,7 @@ //! Check out the "mesh2d" example for simpler / higher level 2d meshes. use bevy::{ - core_pipeline::Transparent2d, + core_pipeline::core_2d::Transparent2d, prelude::*, reflect::TypeUuid, render::{ @@ -108,7 +108,7 @@ fn star( )); commands // And use an orthographic projection - .spawn_bundle(OrthographicCameraBundle::new_2d()); + .spawn_bundle(Camera2dBundle::default()); } /// A marker component for colored 2d meshes diff --git a/examples/2d/move_sprite.rs b/examples/2d/move_sprite.rs index 856adcd31bea7..295d340f853e1 100644 --- a/examples/2d/move_sprite.rs +++ b/examples/2d/move_sprite.rs @@ -17,7 +17,7 @@ enum Direction { } fn setup(mut commands: Commands, asset_server: Res) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); commands .spawn_bundle(SpriteBundle { texture: asset_server.load("branding/icon.png"), diff --git a/examples/2d/rotation.rs b/examples/2d/rotation.rs index 8b0e4bd0ea034..d38b911d23f4d 100644 --- a/examples/2d/rotation.rs +++ b/examples/2d/rotation.rs @@ -59,7 +59,7 @@ fn setup(mut commands: Commands, asset_server: Res) { let enemy_b_handle = asset_server.load("textures/simplespace/enemy_B.png"); // 2D orthographic camera - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); let horizontal_margin = BOUNDS.x / 4.0; let vertical_margin = BOUNDS.y / 4.0; diff --git a/examples/2d/shapes.rs b/examples/2d/shapes.rs index 8585c42121e34..b3f7c504b3c72 100644 --- a/examples/2d/shapes.rs +++ b/examples/2d/shapes.rs @@ -14,7 +14,7 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); // Rectangle commands.spawn_bundle(SpriteBundle { diff --git a/examples/2d/sprite.rs b/examples/2d/sprite.rs index 6ebe25dc19c95..eac15a94c35a9 100644 --- a/examples/2d/sprite.rs +++ b/examples/2d/sprite.rs @@ -10,7 +10,7 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); commands.spawn_bundle(SpriteBundle { texture: asset_server.load("branding/icon.png"), ..default() diff --git a/examples/2d/sprite_flipping.rs b/examples/2d/sprite_flipping.rs index 761741572086e..b77e18c2b5da1 100644 --- a/examples/2d/sprite_flipping.rs +++ b/examples/2d/sprite_flipping.rs @@ -10,7 +10,7 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); commands.spawn_bundle(SpriteBundle { texture: asset_server.load("branding/icon.png"), sprite: Sprite { diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 59970231f3821..43ad1deb7dd0d 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -40,7 +40,7 @@ fn setup( let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1); let texture_atlas_handle = texture_atlases.add(texture_atlas); - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); commands .spawn_bundle(SpriteSheetBundle { texture_atlas: texture_atlas_handle, diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index bb7c9087696a0..f967501f71f13 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -36,7 +36,7 @@ fn setup(mut commands: Commands, asset_server: Res) { horizontal: HorizontalAlign::Center, }; // 2d camera - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); // Demonstrate changing translation commands .spawn_bundle(Text2dBundle { diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index dbc90e8aa48a9..5daabe22e116c 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -62,7 +62,7 @@ fn setup( let atlas_handle = texture_atlases.add(texture_atlas); // set up a scene to display our texture atlas - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(Camera2dBundle::default()); // draw a sprite from the atlas commands.spawn_bundle(SpriteSheetBundle { transform: Transform { diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index c71e5f8ed29ed..e084acbf9febc 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -39,7 +39,7 @@ fn setup( ..default() }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index 592807c140f02..10fff72614aa5 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -203,7 +203,7 @@ fn setup( }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index c8a6047d997af..ef4eed5739ae4 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -16,7 +16,7 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), ..default() }); diff --git a/examples/3d/msaa.rs b/examples/3d/msaa.rs index d7f8534da3edb..6d8a95170cfe8 100644 --- a/examples/3d/msaa.rs +++ b/examples/3d/msaa.rs @@ -38,7 +38,7 @@ fn setup( ..default() }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-3.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/orthographic.rs b/examples/3d/orthographic.rs index 922a3f0dfbc6d..730328f2cab11 100644 --- a/examples/3d/orthographic.rs +++ b/examples/3d/orthographic.rs @@ -1,6 +1,6 @@ //! Shows how to create a 3D orthographic view (for isometric-look games or CAD applications). -use bevy::prelude::*; +use bevy::{prelude::*, render::camera::ScalingMode}; fn main() { App::new() @@ -15,13 +15,17 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - // set up the camera - let mut camera = OrthographicCameraBundle::new_3d(); - camera.orthographic_projection.scale = 3.0; - camera.transform = Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y); - // camera - commands.spawn_bundle(camera); + commands.spawn_bundle(Camera3dBundle { + projection: OrthographicProjection { + scale: 3.0, + scaling_mode: ScalingMode::FixedVertical, + ..default() + } + .into(), + transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); // plane commands.spawn_bundle(PbrBundle { diff --git a/examples/3d/parenting.rs b/examples/3d/parenting.rs index 1ed76fa394bfb..b8a4c99237d0a 100644 --- a/examples/3d/parenting.rs +++ b/examples/3d/parenting.rs @@ -58,7 +58,7 @@ fn setup( ..default() }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(5.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index 7a5a6bd7b490c..49913252a95a3 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -64,12 +64,13 @@ fn setup( ..default() }); // camera - commands.spawn_bundle(OrthographicCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y), - orthographic_projection: OrthographicProjection { + projection: OrthographicProjection { scale: 0.01, ..default() - }, - ..OrthographicCameraBundle::new_3d() + } + .into(), + ..default() }); } diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index 262bf9191f55b..76a88a293515b 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -1,104 +1,24 @@ //! Shows how to render to a texture. Useful for mirrors, UI, or exporting images. use bevy::{ - core_pipeline::{ - draw_3d_graph, node, AlphaMask3d, Opaque3d, RenderTargetClearColors, Transparent3d, - }, + core_pipeline::clear_color::ClearColorConfig, prelude::*, render::{ - camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget}, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, - render_phase::RenderPhase, + camera::RenderTarget, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, - renderer::RenderContext, view::RenderLayers, - RenderApp, RenderStage, }, }; -#[derive(Component, Default)] -pub struct FirstPassCamera; - -// The name of the final node of the first pass. -pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; - fn main() { - let mut app = App::new(); - app.add_plugins(DefaultPlugins) - .add_plugin(CameraTypePlugin::::default()) + App::new() + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(cube_rotator_system) - .add_system(rotator_system); - - let render_app = app.sub_app_mut(RenderApp); - let driver = FirstPassCameraDriver::new(&mut render_app.world); - // This will add 3D render phases for the new camera. - render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases); - - let mut graph = render_app.world.resource_mut::(); - - // Add a node for the first pass. - graph.add_node(FIRST_PASS_DRIVER, driver); - - // The first pass's dependencies include those of the main pass. - graph - .add_node_edge(node::MAIN_PASS_DEPENDENCIES, FIRST_PASS_DRIVER) - .unwrap(); - - // Insert the first pass node: CLEAR_PASS_DRIVER -> FIRST_PASS_DRIVER -> MAIN_PASS_DRIVER - graph - .add_node_edge(node::CLEAR_PASS_DRIVER, FIRST_PASS_DRIVER) - .unwrap(); - graph - .add_node_edge(FIRST_PASS_DRIVER, node::MAIN_PASS_DRIVER) - .unwrap(); - app.run(); -} - -// Add 3D render phases for FIRST_PASS_CAMERA. -fn extract_first_pass_camera_phases( - mut commands: Commands, - active: Res>, -) { - if let Some(entity) = active.get() { - commands.get_or_spawn(entity).insert_bundle(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - )); - } -} - -// A node for the first pass camera that runs draw_3d_graph with this camera. -struct FirstPassCameraDriver { - query: QueryState>, -} - -impl FirstPassCameraDriver { - pub fn new(render_world: &mut World) -> Self { - Self { - query: QueryState::new(render_world), - } - } -} -impl Node for FirstPassCameraDriver { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - for camera in self.query.iter_manual(world) { - graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?; - } - Ok(()) - } + .add_system(rotator_system) + .run(); } // Marks the first pass cube (rendered to a texture.) @@ -114,7 +34,6 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, mut images: ResMut>, - mut clear_colors: ResMut, ) { let size = Extent3d { width: 512, @@ -172,33 +91,22 @@ fn setup( ..default() }); - // First pass camera - let render_target = RenderTarget::Image(image_handle.clone()); - clear_colors.insert(render_target.clone(), Color::WHITE); commands - .spawn_bundle(PerspectiveCameraBundle:: { + .spawn_bundle(Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + }, camera: Camera { - target: render_target, + // render before the "main pass" camera + priority: -1, + target: RenderTarget::Image(image_handle.clone()), ..default() }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), - ..PerspectiveCameraBundle::new() + ..default() }) .insert(first_pass_layer); - // NOTE: omitting the RenderLayers component for this camera may cause a validation error: - // - // thread 'main' panicked at 'wgpu error: Validation Error - // - // Caused by: - // In a RenderPass - // note: encoder = `` - // In a pass parameter - // note: command buffer = `` - // Attempted to use texture (5, 1, Metal) mips 0..1 layers 0..1 as a combination of COLOR_TARGET within a usage scope. - // - // This happens because the texture would be written and read in the same frame, which is not allowed. - // So either render layers must be used to avoid this, or the texture must be double buffered. let cube_size = 4.0; let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); @@ -226,7 +134,7 @@ fn setup( .insert(MainPassCube); // The main pass camera. - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), ..default() diff --git a/examples/3d/shadow_biases.rs b/examples/3d/shadow_biases.rs index 2f5e7adb306f8..8273201643ebc 100644 --- a/examples/3d/shadow_biases.rs +++ b/examples/3d/shadow_biases.rs @@ -86,7 +86,7 @@ fn setup( // camera commands - .spawn_bundle(PerspectiveCameraBundle { + .spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-1.0, 1.0, 1.0) .looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y), ..default() diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index c58994ce5b479..9ba7052dbb3ed 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -111,7 +111,7 @@ fn setup( }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-5.0, 5.0, 5.0) .looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y), ..default() diff --git a/examples/3d/shapes.rs b/examples/3d/shapes.rs index b099508a12bcf..23b7fefc71d61 100644 --- a/examples/3d/shapes.rs +++ b/examples/3d/shapes.rs @@ -78,7 +78,7 @@ fn setup( ..Default::default() }); - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y), ..Default::default() }); diff --git a/examples/3d/spherical_area_lights.rs b/examples/3d/spherical_area_lights.rs index 094b66809fcfd..ba6b94bacdd11 100644 --- a/examples/3d/spherical_area_lights.rs +++ b/examples/3d/spherical_area_lights.rs @@ -16,7 +16,7 @@ fn setup( mut materials: ResMut>, ) { // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(1.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/texture.rs b/examples/3d/texture.rs index 9a0e44d93fcd0..c61ac2a316133 100644 --- a/examples/3d/texture.rs +++ b/examples/3d/texture.rs @@ -87,7 +87,7 @@ fn setup( ..default() }); // camera - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(3.0, 5.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/examples/3d/two_passes.rs b/examples/3d/two_passes.rs deleted file mode 100644 index b161380ad112c..0000000000000 --- a/examples/3d/two_passes.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! Shows how to render multiple passes to the same window, useful for rendering different views -//! or drawing an object on top regardless of depth. - -use bevy::{ - core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d}, - prelude::*, - render::{ - camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget}, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, - render_phase::RenderPhase, - renderer::RenderContext, - view::RenderLayers, - RenderApp, RenderStage, - }, - window::WindowId, -}; - -// The name of the final node of the first pass. -pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; - -// Marks the camera that determines the view rendered in the first pass. -#[derive(Component, Default)] -struct FirstPassCamera; - -fn main() { - let mut app = App::new(); - app.add_plugins(DefaultPlugins) - .add_plugin(CameraTypePlugin::::default()) - .add_startup_system(setup) - .add_system(cube_rotator_system) - .add_system(rotator_system) - .add_system(toggle_msaa); - - let render_app = app.sub_app_mut(RenderApp); - let driver = FirstPassCameraDriver::new(&mut render_app.world); - - // This will add 3D render phases for the new camera. - render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases); - - let mut graph = render_app.world.resource_mut::(); - - // Add a node for the first pass. - graph.add_node(FIRST_PASS_DRIVER, driver); - - // The first pass's dependencies include those of the main pass. - graph - .add_node_edge(node::MAIN_PASS_DEPENDENCIES, FIRST_PASS_DRIVER) - .unwrap(); - - // Insert the first pass node: CLEAR_PASS_DRIVER -> FIRST_PASS_DRIVER -> MAIN_PASS_DRIVER - graph - .add_node_edge(node::CLEAR_PASS_DRIVER, FIRST_PASS_DRIVER) - .unwrap(); - graph - .add_node_edge(FIRST_PASS_DRIVER, node::MAIN_PASS_DRIVER) - .unwrap(); - app.run(); -} - -// Add 3D render phases for FirstPassCamera. -fn extract_first_pass_camera_phases( - mut commands: Commands, - active: Res>, -) { - if let Some(entity) = active.get() { - commands.get_or_spawn(entity).insert_bundle(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - )); - } -} - -// A node for the first pass camera that runs draw_3d_graph with this camera. -struct FirstPassCameraDriver { - query: QueryState>, -} - -impl FirstPassCameraDriver { - pub fn new(render_world: &mut World) -> Self { - Self { - query: QueryState::new(render_world), - } - } -} - -impl Node for FirstPassCameraDriver { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - for camera in self.query.iter_manual(world) { - graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?; - } - Ok(()) - } -} - -// Marks the first pass cube. -#[derive(Component)] -struct FirstPassCube; - -// Marks the main pass cube. -#[derive(Component)] -struct MainPassCube; - -fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 })); - let cube_material_handle = materials.add(StandardMaterial { - base_color: Color::GREEN, - reflectance: 0.02, - unlit: false, - ..Default::default() - }); - - let split = 2.0; - - // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube. - let first_pass_layer = RenderLayers::layer(1); - - // The first pass cube. - commands - .spawn_bundle(PbrBundle { - mesh: cube_handle, - material: cube_material_handle, - transform: Transform::from_translation(Vec3::new(-split, 0.0, 1.0)), - ..Default::default() - }) - .insert(FirstPassCube) - .insert(first_pass_layer); - - // Light - // NOTE: Currently lights are shared between passes - see https://github.com/bevyengine/bevy/issues/3462 - commands.spawn_bundle(PointLightBundle { - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), - ..Default::default() - }); - - // First pass camera - commands - .spawn_bundle(PerspectiveCameraBundle:: { - camera: Camera { - target: RenderTarget::Window(WindowId::primary()), - ..Default::default() - }, - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) - .looking_at(Vec3::default(), Vec3::Y), - ..PerspectiveCameraBundle::new() - }) - .insert(first_pass_layer); - - let cube_size = 4.0; - let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); - - let material_handle = materials.add(StandardMaterial { - base_color: Color::RED, - reflectance: 0.02, - unlit: false, - ..Default::default() - }); - - // Main pass cube. - commands - .spawn_bundle(PbrBundle { - mesh: cube_handle, - material: material_handle, - transform: Transform { - translation: Vec3::new(split, 0.0, -4.5), - rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), - ..Default::default() - }, - ..Default::default() - }) - .insert(MainPassCube); - - // The main pass camera. - commands.spawn_bundle(PerspectiveCameraBundle { - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) - .looking_at(Vec3::default(), Vec3::Y), - ..Default::default() - }); -} - -/// Rotates the inner cube (first pass) -fn rotator_system(time: Res