From d38c048c00913423308f5f2d5dc4bc1298f0b868 Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Tue, 21 Dec 2021 07:43:10 -0800 Subject: [PATCH 01/16] Add CameraTarget --- crates/bevy_core_pipeline/src/clear_pass.rs | 8 +- crates/bevy_pbr/src/light.rs | 90 +++++++++++---------- crates/bevy_render/src/camera/camera.rs | 71 +++++++++++++--- crates/bevy_render/src/camera/mod.rs | 15 ++-- crates/bevy_render/src/view/mod.rs | 90 ++++++++++++--------- examples/window/multiple_windows.rs | 4 +- 6 files changed, 175 insertions(+), 103 deletions(-) diff --git a/crates/bevy_core_pipeline/src/clear_pass.rs b/crates/bevy_core_pipeline/src/clear_pass.rs index 89c283aef8b10..e6434373c29da 100644 --- a/crates/bevy_core_pipeline/src/clear_pass.rs +++ b/crates/bevy_core_pipeline/src/clear_pass.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use crate::ClearColor; use bevy_ecs::prelude::*; use bevy_render::{ - camera::ExtractedCamera, + camera::{ExtractedCamera, RenderTarget}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo}, render_resource::{ LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, @@ -47,7 +47,7 @@ impl Node for ClearPassNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let mut cleared_windows = HashSet::new(); + let mut cleared_targets = HashSet::new(); let clear_color = world.get_resource::().unwrap(); // This gets all ViewTargets and ViewDepthTextures and clears its attachments @@ -56,7 +56,7 @@ impl Node for ClearPassNode { // 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) { if let Some(camera) = camera { - cleared_windows.insert(camera.window_id); + cleared_targets.insert(&camera.target); } let pass_descriptor = RenderPassDescriptor { label: Some("clear_pass"), @@ -85,7 +85,7 @@ impl Node for ClearPassNode { let windows = world.get_resource::().unwrap(); for window in windows.values() { // skip windows that have already been cleared - if cleared_windows.contains(&window.id) { + if cleared_targets.contains(&RenderTarget::Window(window.id)) { continue; } let pass_descriptor = RenderPassDescriptor { diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 61f416913e370..4abea29e40959 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,11 +1,13 @@ use std::collections::HashSet; +use bevy_asset::Assets; use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_reflect::Reflect; use bevy_render::{ camera::{Camera, CameraProjection, OrthographicProjection}, color::Color, + prelude::Image, primitives::{Aabb, CubemapFrusta, Frustum, Sphere}, view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities}, }; @@ -354,62 +356,62 @@ const Z_SLICES: u32 = 24; pub fn add_clusters( mut commands: Commands, windows: Res, + images: Res>, cameras: Query<(Entity, &Camera), Without>, ) { for (entity, camera) in cameras.iter() { - let window = match windows.get(camera.window) { - Some(window) => window, - None => continue, - }; - let clusters = Clusters::from_screen_size_and_z_slices( - UVec2::new(window.physical_width(), window.physical_height()), - Z_SLICES, - ); - commands.entity(entity).insert(clusters); + if let Some(size) = camera.get_physical_size(&windows, &images) { + let clusters = Clusters::from_screen_size_and_z_slices(size, Z_SLICES); + commands.entity(entity).insert(clusters); + } } } -pub fn update_clusters(windows: Res, mut views: Query<(&Camera, &mut Clusters)>) { +pub fn update_clusters( + windows: Res, + images: Res>, + mut views: Query<(&Camera, &mut Clusters)>, +) { for (camera, mut clusters) in views.iter_mut() { let is_orthographic = camera.projection_matrix.w_axis.w == 1.0; let inverse_projection = camera.projection_matrix.inverse(); - let window = windows.get(camera.window).unwrap(); - let screen_size_u32 = UVec2::new(window.physical_width(), window.physical_height()); - // Don't update clusters if screen size is 0. - if screen_size_u32.x == 0 || screen_size_u32.y == 0 { - continue; - } - *clusters = - Clusters::from_screen_size_and_z_slices(screen_size_u32, clusters.axis_slices.z); - let screen_size = screen_size_u32.as_vec2(); - let tile_size_u32 = clusters.tile_size; - let tile_size = tile_size_u32.as_vec2(); - - // Calculate view space AABBs - // NOTE: It is important that these are iterated in a specific order - // so that we can calculate the cluster index in the fragment shader! - // I (Rob Swain) choose to scan along rows of tiles in x,y, and for each tile then scan - // along z - let mut aabbs = Vec::with_capacity( - (clusters.axis_slices.y * clusters.axis_slices.x * clusters.axis_slices.z) as usize, - ); - for y in 0..clusters.axis_slices.y { - for x in 0..clusters.axis_slices.x { - for z in 0..clusters.axis_slices.z { - aabbs.push(compute_aabb_for_cluster( - clusters.near, - camera.far, - tile_size, - screen_size, - inverse_projection, - is_orthographic, - clusters.axis_slices, - UVec3::new(x, y, z), - )); + if let Some(screen_size_u32) = camera.get_physical_size(&windows, &images) { + // Don't update clusters if screen size is 0. + if screen_size_u32.x == 0 || screen_size_u32.y == 0 { + continue; + } + *clusters = + Clusters::from_screen_size_and_z_slices(screen_size_u32, clusters.axis_slices.z); + let screen_size = screen_size_u32.as_vec2(); + let tile_size_u32 = clusters.tile_size; + let tile_size = tile_size_u32.as_vec2(); + + // Calculate view space AABBs + // NOTE: It is important that these are iterated in a specific order + // so that we can calculate the cluster index in the fragment shader! + // I (Rob Swain) choose to scan along rows of tiles in x,y, and for each tile then scan + // along z + let mut aabbs = Vec::with_capacity( + (clusters.axis_slices.y * clusters.axis_slices.x * clusters.axis_slices.z) as usize, + ); + for y in 0..clusters.axis_slices.y { + for x in 0..clusters.axis_slices.x { + for z in 0..clusters.axis_slices.z { + aabbs.push(compute_aabb_for_cluster( + clusters.near, + camera.far, + tile_size, + screen_size, + inverse_projection, + is_orthographic, + clusters.axis_slices, + UVec3::new(x, y, z), + )); + } } } + clusters.aabbs = aabbs; } - clusters.aabbs = aabbs; } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 4f73ebaf90230..21d80b65bf8a3 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,4 +1,5 @@ -use crate::camera::CameraProjection; +use crate::{camera::CameraProjection, prelude::Image}; +use bevy_asset::{Assets, Handle}; use bevy_ecs::{ component::Component, entity::Entity, @@ -8,11 +9,12 @@ use bevy_ecs::{ reflect::ReflectComponent, system::{QuerySet, Res}, }; -use bevy_math::{Mat4, Vec2, Vec3}; +use bevy_math::{Mat4, UVec2, Vec2, Vec3}; use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_transform::components::GlobalTransform; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; use serde::{Deserialize, Serialize}; +use wgpu::Extent3d; #[derive(Component, Default, Debug, Reflect)] #[reflect(Component)] @@ -20,13 +22,27 @@ pub struct Camera { pub projection_matrix: Mat4, pub name: Option, #[reflect(ignore)] - pub window: WindowId, + pub target: RenderTarget, #[reflect(ignore)] pub depth_calculation: DepthCalculation, pub near: f32, pub far: f32, } +#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)] +pub enum RenderTarget { + /// Window to which the camera's view is rendered. + Window(WindowId), + /// Image to which the camera's view is rendered. + Image(Handle), +} + +impl Default for RenderTarget { + fn default() -> Self { + Self::Window(Default::default()) + } +} + #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] #[reflect_value(Serialize, Deserialize)] pub enum DepthCalculation { @@ -43,15 +59,44 @@ impl Default for DepthCalculation { } impl Camera { + pub fn get_window(&self) -> Option { + if let RenderTarget::Window(window) = self.target { + Some(window) + } else { + None + } + } + pub fn get_physical_size(&self, windows: &Windows, images: &Assets) -> Option { + match &self.target { + RenderTarget::Window(window_id) => windows + .get(*window_id) + .map(|window| UVec2::new(window.physical_width(), window.physical_height())), + RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { + let Extent3d { width, height, .. } = image.texture_descriptor.size; + UVec2::new(width, height) + }), + } + } + pub fn get_logical_size(&self, windows: &Windows, images: &Assets) -> Option { + match &self.target { + RenderTarget::Window(window_id) => windows + .get(*window_id) + .map(|window| Vec2::new(window.width(), window.height())), + RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { + let Extent3d { width, height, .. } = image.texture_descriptor.size; + Vec2::new(width as f32, height as f32) + }), + } + } /// Given a position in world space, use the camera to compute the screen space coordinates. pub fn world_to_screen( &self, windows: &Windows, + images: &Assets, camera_transform: &GlobalTransform, world_position: Vec3, ) -> Option { - let window = windows.get(self.window)?; - let window_size = Vec2::new(window.width(), window.height()); + let window_size = self.get_logical_size(windows, images)?; // 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(); @@ -75,6 +120,7 @@ pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, windows: Res, + images: Res>, mut queries: QuerySet<( QueryState<(Entity, &mut Camera, &mut T)>, QueryState>, @@ -106,12 +152,15 @@ pub fn camera_system( added_cameras.push(entity); } for (entity, mut camera, mut camera_projection) in queries.q0().iter_mut() { - if let Some(window) = windows.get(camera.window) { - if changed_window_ids.contains(&window.id()) - || added_cameras.contains(&entity) - || camera_projection.is_changed() - { - camera_projection.update(window.width(), window.height()); + if camera + .get_window() + .map(|window_id| changed_window_ids.contains(&window_id)) + .unwrap_or(false) + || added_cameras.contains(&entity) + || camera_projection.is_changed() + { + if let Some(size) = camera.get_logical_size(&windows, &images) { + camera_projection.update(size.x, size.y); camera.projection_matrix = camera_projection.get_projection_matrix(); camera.depth_calculation = camera_projection.depth_calculation(); } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 5be7298689707..b5221494df500 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -5,14 +5,16 @@ mod camera; mod projection; pub use active_cameras::*; +use bevy_asset::Assets; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; -use bevy_window::{WindowId, Windows}; +use bevy_window::Windows; pub use bundle::*; pub use camera::*; pub use projection::*; use crate::{ + prelude::Image, primitives::Aabb, view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities}, RenderApp, RenderStage, @@ -68,7 +70,7 @@ pub struct ExtractedCameraNames { #[derive(Component, Debug)] pub struct ExtractedCamera { - pub window_id: WindowId, + pub target: RenderTarget, pub name: Option, } @@ -76,6 +78,7 @@ fn extract_cameras( mut commands: Commands, active_cameras: Res, windows: Res, + images: Res>, query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>, ) { let mut entities = HashMap::default(); @@ -84,18 +87,18 @@ fn extract_cameras( if let Some((entity, camera, transform, visible_entities)) = camera.entity.and_then(|e| query.get(e).ok()) { - if let Some(window) = windows.get(camera.window) { + if let Some(size) = camera.get_physical_size(&windows, &images) { entities.insert(name.clone(), entity); commands.get_or_spawn(entity).insert_bundle(( ExtractedCamera { - window_id: camera.window, + target: camera.target.clone(), name: camera.name.clone(), }, ExtractedView { projection: camera.projection_matrix, transform: *transform, - width: window.physical_width().max(1), - height: window.physical_height().max(1), + width: size.x.max(1), + height: size.y.max(1), near: camera.near, far: camera.far, }, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index abee8bd1aeee0..0a5a07c0dbcce 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -9,7 +9,9 @@ use wgpu::{ pub use window::*; use crate::{ - camera::{ExtractedCamera, ExtractedCameraNames}, + camera::{ExtractedCamera, ExtractedCameraNames, RenderTarget}, + prelude::Image, + render_asset::RenderAssets, render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, TextureCache}, @@ -174,6 +176,7 @@ fn prepare_view_targets( mut commands: Commands, camera_names: Res, windows: Res, + gpu_images: Res>, msaa: Res, render_device: Res, mut texture_cache: ResMut, @@ -185,41 +188,56 @@ fn prepare_view_targets( } else { continue; }; - let window = if let Some(window) = windows.get(&camera.window_id) { - window - } else { - continue; - }; - let swap_chain_texture = if let Some(texture) = &window.swap_chain_texture { - texture - } else { - continue; - }; - let sampled_target = if msaa.samples > 1 { - let sampled_texture = texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("sampled_color_attachment_texture"), - size: Extent3d { - width: window.physical_width, - height: window.physical_height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: msaa.samples, - dimension: TextureDimension::D2, - format: TextureFormat::bevy_default(), - usage: TextureUsages::RENDER_ATTACHMENT, - }, - ); - Some(sampled_texture.default_view.clone()) - } else { - None + let view_target = match &camera.target { + RenderTarget::Window(window_id) => { + let window = if let Some(window) = windows.get(window_id) { + window + } else { + continue; + }; + let swap_chain_texture = if let Some(texture) = &window.swap_chain_texture { + texture + } else { + continue; + }; + let sampled_target = if msaa.samples > 1 { + let sampled_texture = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("sampled_color_attachment_texture"), + size: Extent3d { + width: window.physical_width, + height: window.physical_height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + usage: TextureUsages::RENDER_ATTACHMENT, + }, + ); + Some(sampled_texture.default_view.clone()) + } else { + None + }; + ViewTarget { + view: swap_chain_texture.clone(), + sampled_target, + } + } + RenderTarget::Image(image_handle) => { + if let Some(gpu_image) = gpu_images.get(image_handle) { + // TODO: MSAA support + ViewTarget { + view: gpu_image.texture_view.clone(), + sampled_target: None, + } + } else { + continue; + } + } }; - - commands.entity(entity).insert(ViewTarget { - view: swap_chain_texture.clone(), - sampled_target, - }); + commands.entity(entity).insert(view_target); } } diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index b84d3eaec7f37..9fe14aa00c0b5 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -2,7 +2,7 @@ use bevy::{ core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d}, prelude::*, render::{ - camera::{ActiveCameras, ExtractedCameraNames}, + camera::{ActiveCameras, ExtractedCameraNames, RenderTarget}, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, render_phase::RenderPhase, renderer::RenderContext, @@ -65,7 +65,7 @@ fn create_new_window( // second window camera commands.spawn_bundle(PerspectiveCameraBundle { camera: Camera { - window: window_id, + target: RenderTarget::Window(window_id), name: Some(SECONDARY_CAMERA_NAME.into()), ..Default::default() }, From 6c099ea219f14bf9988f3698408f38815f8ec068 Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Wed, 22 Dec 2021 12:39:16 -0800 Subject: [PATCH 02/16] Work on ClearColor --- crates/bevy_core_pipeline/src/clear_pass.rs | 19 +++++++++++---- crates/bevy_core_pipeline/src/lib.rs | 26 ++++++++++++++++++--- crates/bevy_render/src/camera/camera.rs | 22 ++++++++++++++++- examples/app/return_after_run.rs | 4 ++-- examples/game/breakout.rs | 2 +- examples/ui/font_atlas_debug.rs | 2 +- examples/window/clear_color.rs | 4 ++-- examples/window/transparent_window.rs | 2 +- 8 files changed, 65 insertions(+), 16 deletions(-) diff --git a/crates/bevy_core_pipeline/src/clear_pass.rs b/crates/bevy_core_pipeline/src/clear_pass.rs index e6434373c29da..55af844a52ecd 100644 --- a/crates/bevy_core_pipeline/src/clear_pass.rs +++ b/crates/bevy_core_pipeline/src/clear_pass.rs @@ -4,6 +4,8 @@ use crate::ClearColor; 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, @@ -55,13 +57,15 @@ impl Node for ClearPassNode { // 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.default_color; if let Some(camera) = camera { cleared_targets.insert(&camera.target); + color = clear_color.get(&camera.target); } let pass_descriptor = RenderPassDescriptor { label: Some("clear_pass"), color_attachments: &[target.get_color_attachment(Operations { - load: LoadOp::Clear(clear_color.0.into()), + load: LoadOp::Clear((*color).into()), store: true, })], depth_stencil_attachment: depth.map(|depth| RenderPassDepthStencilAttachment { @@ -83,18 +87,23 @@ impl Node for ClearPassNode { // 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.get_resource::().unwrap(); - for window in windows.values() { + let images = world.get_resource::>().unwrap(); + for target in clear_color.per_target.keys().cloned().chain( + windows + .values() + .map(|window| RenderTarget::Window(window.id)), + ) { // skip windows that have already been cleared - if cleared_targets.contains(&RenderTarget::Window(window.id)) { + if cleared_targets.contains(&target) { continue; } let pass_descriptor = RenderPassDescriptor { label: Some("clear_pass"), color_attachments: &[RenderPassColorAttachment { - view: window.swap_chain_texture.as_ref().unwrap(), + view: target.get_texture_view(windows, images).unwrap(), resolve_target: None, ops: Operations { - load: LoadOp::Clear(clear_color.0.into()), + load: LoadOp::Clear((*clear_color.get(&target)).into()), store: true, }, }], diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index a00a4b734cb24..879a098acc406 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -9,6 +9,8 @@ pub mod prelude { pub use crate::ClearColor; } +use std::collections::HashMap; + pub use clear_pass::*; pub use clear_pass_driver::*; pub use main_pass_2d::*; @@ -21,7 +23,7 @@ use bevy_app::{App, Plugin}; use bevy_core::FloatOrd; use bevy_ecs::prelude::*; use bevy_render::{ - camera::{ActiveCameras, CameraPlugin}, + camera::{ActiveCameras, CameraPlugin, RenderTarget}, color::Color, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ @@ -40,11 +42,29 @@ use bevy_render::{ /// This color appears as the "background" color for simple apps, when /// there are portions of the screen with nothing rendered. #[derive(Clone, Debug)] -pub struct ClearColor(pub Color); +pub struct ClearColor { + pub default_color: Color, + pub per_target: HashMap, +} + +impl ClearColor { + pub fn from_default_color(default_color: Color) -> Self { + Self { + default_color, + per_target: HashMap::new(), + } + } + pub fn get(&self, target: &RenderTarget) -> &Color { + self.per_target.get(target).unwrap_or(&self.default_color) + } + pub fn insert(&mut self, target: RenderTarget, color: Color) { + self.per_target.insert(target, color); + } +} impl Default for ClearColor { fn default() -> Self { - Self(Color::rgb(0.4, 0.4, 0.4)) + Self::from_default_color(Color::rgb(0.4, 0.4, 0.4)) } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 21d80b65bf8a3..5c43dc18b5bfb 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,4 +1,7 @@ -use crate::{camera::CameraProjection, prelude::Image}; +use crate::{ + camera::CameraProjection, prelude::Image, render_asset::RenderAssets, + render_resource::TextureView, view::ExtractedWindows, +}; use bevy_asset::{Assets, Handle}; use bevy_ecs::{ component::Component, @@ -43,6 +46,23 @@ impl Default for RenderTarget { } } +impl RenderTarget { + pub fn get_texture_view<'a>( + &self, + windows: &'a ExtractedWindows, + images: &'a RenderAssets, + ) -> Option<&'a TextureView> { + match self { + RenderTarget::Window(window_id) => windows + .get(&window_id) + .and_then(|window| window.swap_chain_texture.as_ref()), + RenderTarget::Image(image_handle) => { + images.get(image_handle).map(|image| &image.texture_view) + } + } + } +} + #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] #[reflect_value(Serialize, Deserialize)] pub enum DepthCalculation { diff --git a/examples/app/return_after_run.rs b/examples/app/return_after_run.rs index ff73928a11b4a..ab41127180934 100644 --- a/examples/app/return_after_run.rs +++ b/examples/app/return_after_run.rs @@ -6,7 +6,7 @@ fn main() { .insert_resource(WinitConfig { return_from_run: true, }) - .insert_resource(ClearColor(Color::rgb(0.2, 0.2, 0.8))) + .insert_resource(ClearColor::from_default_color(Color::rgb(0.2, 0.2, 0.8))) .add_plugins(DefaultPlugins) .add_system(system1) .run(); @@ -15,7 +15,7 @@ fn main() { .insert_resource(WinitConfig { return_from_run: true, }) - .insert_resource(ClearColor(Color::rgb(0.2, 0.8, 0.2))) + .insert_resource(ClearColor::from_default_color(Color::rgb(0.2, 0.8, 0.2))) .add_plugins_with(DefaultPlugins, |group| { group.disable::() }) diff --git a/examples/game/breakout.rs b/examples/game/breakout.rs index 66246feecc620..bf91f004ef1bc 100644 --- a/examples/game/breakout.rs +++ b/examples/game/breakout.rs @@ -10,7 +10,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(Scoreboard { score: 0 }) - .insert_resource(ClearColor(Color::rgb(0.9, 0.9, 0.9))) + .insert_resource(ClearColor::from_default_color(Color::rgb(0.9, 0.9, 0.9))) .add_startup_system(setup) .add_system_set( SystemSet::new() diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index aef0075bca909..3d4283c386f07 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -6,7 +6,7 @@ use bevy::{prelude::*, text::FontAtlasSet}; fn main() { App::new() .init_resource::() - .insert_resource(ClearColor(Color::BLACK)) + .insert_resource(ClearColor::from_default_color(Color::BLACK)) .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(text_update_system) diff --git a/examples/window/clear_color.rs b/examples/window/clear_color.rs index b7813ee2c34d4..361d593be17a3 100644 --- a/examples/window/clear_color.rs +++ b/examples/window/clear_color.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; fn main() { App::new() - .insert_resource(ClearColor(Color::rgb(0.5, 0.5, 0.9))) + .insert_resource(ClearColor::from_default_color(Color::rgb(0.5, 0.5, 0.9))) .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(change_clear_color) @@ -15,6 +15,6 @@ fn setup(mut commands: Commands) { fn change_clear_color(input: Res>, mut clear_color: ResMut) { if input.just_pressed(KeyCode::Space) { - clear_color.0 = Color::PURPLE; + clear_color.default_color = Color::PURPLE; } } diff --git a/examples/window/transparent_window.rs b/examples/window/transparent_window.rs index 422de305c22b2..e0be4d7680515 100644 --- a/examples/window/transparent_window.rs +++ b/examples/window/transparent_window.rs @@ -5,7 +5,7 @@ use bevy::{prelude::*, window::WindowDescriptor}; fn main() { App::new() // ClearColor must have 0 alpha, otherwise some color will bleed through - .insert_resource(ClearColor(Color::NONE)) + .insert_resource(ClearColor::from_default_color(Color::NONE)) .insert_resource(WindowDescriptor { // Setting `transparent` allows the `ClearColor`'s alpha value to take effect transparent: true, From 29b041d5dce443f63d7e037f8b320b2f84adb6e2 Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Thu, 23 Dec 2021 13:30:38 -0800 Subject: [PATCH 03/16] Add const to RenderLayers constructors --- .../src/view/visibility/render_layers.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index 3315beed3308f..2d4f5312eba92 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -49,17 +49,17 @@ impl RenderLayers { pub const TOTAL_LAYERS: usize = std::mem::size_of::() * 8; /// Create a new `RenderLayers` belonging to the given layer. - pub fn layer(n: Layer) -> Self { + pub const fn layer(n: Layer) -> Self { RenderLayers(0).with(n) } /// Create a new `RenderLayers` that belongs to all layers. - pub fn all() -> Self { + pub const fn all() -> Self { RenderLayers(u32::MAX) } /// Create a new `RenderLayers` that belongs to no layers. - pub fn none() -> Self { + pub const fn none() -> Self { RenderLayers(0) } @@ -75,9 +75,8 @@ impl RenderLayers { /// /// # Panics /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - #[must_use] - pub fn with(mut self, layer: Layer) -> Self { - assert!(usize::from(layer) < Self::TOTAL_LAYERS); + pub const fn with(mut self, layer: Layer) -> Self { + assert!((layer as usize) < Self::TOTAL_LAYERS); self.0 |= 1 << layer; self } @@ -86,9 +85,8 @@ impl RenderLayers { /// /// # Panics /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - #[must_use] - pub fn without(mut self, layer: Layer) -> Self { - assert!(usize::from(layer) < Self::TOTAL_LAYERS); + pub const fn without(mut self, layer: Layer) -> Self { + assert!((layer as usize) < Self::TOTAL_LAYERS); self.0 &= !(1 << layer); self } From a955d0ff913a00bf6416f3dad1027f6e69a9d64e Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Thu, 23 Dec 2021 14:36:58 -0800 Subject: [PATCH 04/16] Fixed MSAA for textures --- crates/bevy_pbr/src/light.rs | 4 +- crates/bevy_render/src/camera/camera.rs | 50 ++++++++++++------------- crates/bevy_render/src/camera/mod.rs | 5 ++- crates/bevy_render/src/view/mod.rs | 42 +++++---------------- 4 files changed, 41 insertions(+), 60 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 4abea29e40959..d4bd8943a1889 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -360,7 +360,7 @@ pub fn add_clusters( cameras: Query<(Entity, &Camera), Without>, ) { for (entity, camera) in cameras.iter() { - if let Some(size) = camera.get_physical_size(&windows, &images) { + if let Some(size) = camera.target.get_physical_size(&windows, &images) { let clusters = Clusters::from_screen_size_and_z_slices(size, Z_SLICES); commands.entity(entity).insert(clusters); } @@ -375,7 +375,7 @@ pub fn update_clusters( for (camera, mut clusters) in views.iter_mut() { let is_orthographic = camera.projection_matrix.w_axis.w == 1.0; let inverse_projection = camera.projection_matrix.inverse(); - if let Some(screen_size_u32) = camera.get_physical_size(&windows, &images) { + if let Some(screen_size_u32) = camera.target.get_physical_size(&windows, &images) { // Don't update clusters if screen size is 0. if screen_size_u32.x == 0 || screen_size_u32.y == 0 { continue; diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 5c43dc18b5bfb..e531c9afbfb4b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -54,13 +54,35 @@ impl RenderTarget { ) -> Option<&'a TextureView> { match self { RenderTarget::Window(window_id) => windows - .get(&window_id) + .get(window_id) .and_then(|window| window.swap_chain_texture.as_ref()), RenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| &image.texture_view) } } } + pub fn get_physical_size(&self, windows: &Windows, images: &Assets) -> Option { + match self { + RenderTarget::Window(window_id) => windows + .get(*window_id) + .map(|window| UVec2::new(window.physical_width(), window.physical_height())), + RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { + let Extent3d { width, height, .. } = image.texture_descriptor.size; + UVec2::new(width, height) + }), + } + } + pub fn get_logical_size(&self, windows: &Windows, images: &Assets) -> Option { + match self { + RenderTarget::Window(window_id) => windows + .get(*window_id) + .map(|window| Vec2::new(window.width(), window.height())), + RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { + let Extent3d { width, height, .. } = image.texture_descriptor.size; + Vec2::new(width as f32, height as f32) + }), + } + } } #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] @@ -86,28 +108,6 @@ impl Camera { None } } - pub fn get_physical_size(&self, windows: &Windows, images: &Assets) -> Option { - match &self.target { - RenderTarget::Window(window_id) => windows - .get(*window_id) - .map(|window| UVec2::new(window.physical_width(), window.physical_height())), - RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { - let Extent3d { width, height, .. } = image.texture_descriptor.size; - UVec2::new(width, height) - }), - } - } - pub fn get_logical_size(&self, windows: &Windows, images: &Assets) -> Option { - match &self.target { - RenderTarget::Window(window_id) => windows - .get(*window_id) - .map(|window| Vec2::new(window.width(), window.height())), - RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| { - let Extent3d { width, height, .. } = image.texture_descriptor.size; - Vec2::new(width as f32, height as f32) - }), - } - } /// Given a position in world space, use the camera to compute the screen space coordinates. pub fn world_to_screen( &self, @@ -116,7 +116,7 @@ impl Camera { camera_transform: &GlobalTransform, world_position: Vec3, ) -> Option { - let window_size = self.get_logical_size(windows, images)?; + let window_size = self.target.get_logical_size(windows, images)?; // 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(); @@ -179,7 +179,7 @@ pub fn camera_system( || added_cameras.contains(&entity) || camera_projection.is_changed() { - if let Some(size) = camera.get_logical_size(&windows, &images) { + if let Some(size) = camera.target.get_logical_size(&windows, &images) { camera_projection.update(size.x, size.y); camera.projection_matrix = camera_projection.get_projection_matrix(); camera.depth_calculation = camera_projection.depth_calculation(); diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index b5221494df500..35e3451a4320e 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -6,6 +6,7 @@ mod projection; pub use active_cameras::*; use bevy_asset::Assets; +use bevy_math::UVec2; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; use bevy_window::Windows; @@ -72,6 +73,7 @@ pub struct ExtractedCameraNames { pub struct ExtractedCamera { pub target: RenderTarget, pub name: Option, + pub physical_size: Option, } fn extract_cameras( @@ -87,12 +89,13 @@ fn extract_cameras( if let Some((entity, camera, transform, visible_entities)) = camera.entity.and_then(|e| query.get(e).ok()) { - if let Some(size) = camera.get_physical_size(&windows, &images) { + if let Some(size) = camera.target.get_physical_size(&windows, &images) { entities.insert(name.clone(), entity); commands.get_or_spawn(entity).insert_bundle(( ExtractedCamera { target: camera.target.clone(), name: camera.name.clone(), + physical_size: camera.target.get_physical_size(&windows, &images), }, ExtractedView { projection: camera.projection_matrix, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 0a5a07c0dbcce..ce4b0c6755b5a 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -9,7 +9,7 @@ use wgpu::{ pub use window::*; use crate::{ - camera::{ExtractedCamera, ExtractedCameraNames, RenderTarget}, + camera::{ExtractedCamera, ExtractedCameraNames}, prelude::Image, render_asset::RenderAssets, render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView}, @@ -176,7 +176,7 @@ fn prepare_view_targets( mut commands: Commands, camera_names: Res, windows: Res, - gpu_images: Res>, + images: Res>, msaa: Res, render_device: Res, mut texture_cache: ResMut, @@ -188,26 +188,16 @@ fn prepare_view_targets( } else { continue; }; - let view_target = match &camera.target { - RenderTarget::Window(window_id) => { - let window = if let Some(window) = windows.get(window_id) { - window - } else { - continue; - }; - let swap_chain_texture = if let Some(texture) = &window.swap_chain_texture { - texture - } else { - continue; - }; + if let Some(size) = camera.physical_size { + if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { let sampled_target = if msaa.samples > 1 { let sampled_texture = texture_cache.get( &render_device, TextureDescriptor { label: Some("sampled_color_attachment_texture"), size: Extent3d { - width: window.physical_width, - height: window.physical_height, + width: size.x, + height: size.y, depth_or_array_layers: 1, }, mip_level_count: 1, @@ -221,23 +211,11 @@ fn prepare_view_targets( } else { None }; - ViewTarget { - view: swap_chain_texture.clone(), + commands.entity(entity).insert(ViewTarget { + view: texture_view.clone(), sampled_target, - } + }); } - RenderTarget::Image(image_handle) => { - if let Some(gpu_image) = gpu_images.get(image_handle) { - // TODO: MSAA support - ViewTarget { - view: gpu_image.texture_view.clone(), - sampled_target: None, - } - } else { - continue; - } - } - }; - commands.entity(entity).insert(view_target); + } } } From 8dd7697e066c8aef45f16dfdcf228b8f4e41000e Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Fri, 24 Dec 2021 14:45:25 -0800 Subject: [PATCH 05/16] Add an example --- Cargo.toml | 4 + examples/3d/render_to_texture.rs | 216 +++++++++++++++++++++++++++++++ examples/README.md | 1 + 3 files changed, 221 insertions(+) create mode 100644 examples/3d/render_to_texture.rs diff --git a/Cargo.toml b/Cargo.toml index eb7cab0be8825..8bcd8d217c0aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -212,6 +212,10 @@ path = "examples/3d/spherical_area_lights.rs" name = "texture" path = "examples/3d/texture.rs" +[[example]] +name = "render_to_texture" +path = "examples/3d/render_to_texture.rs" + [[example]] name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs new file mode 100644 index 0000000000000..12435fae23e5a --- /dev/null +++ b/examples/3d/render_to_texture.rs @@ -0,0 +1,216 @@ +use bevy::{ + core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d}, + prelude::*, + reflect::TypeUuid, + render::{ + camera::{ActiveCameras, Camera, CameraProjection, ExtractedCameraNames, RenderTarget}, + render_graph::{NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, + render_phase::RenderPhase, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + renderer::RenderContext, + view::RenderLayers, + RenderApp, RenderStage, + }, +}; + +pub const RENDER_IMAGE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13378939762009864029); + +pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; +pub const FIRST_PASS_CAMERA: &str = "first_pass_camera"; + +#[derive(Component)] +struct FirstPassCube; +#[derive(Component)] +struct MainPassCube; + +/// rotates the inner cube (first pass) +fn rotator_system(time: Res