From 70b0eacc3b5ccf12028fce925d1efc5ae31b5699 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:37:37 -0800 Subject: [PATCH] Keep track of when a texture is first cleared (#10325) # Objective - Custom render passes, or future passes in the engine (such as https://github.com/bevyengine/bevy/pull/10164) need a better way to know and indicate to the core passes whether the view color/depth/prepass attachments have been cleared or not yet this frame, to know if they should clear it themselves or load it. ## Solution - For all render targets (depth textures, shadow textures, prepass textures, main textures) use an atomic bool to track whether or not each texture has been cleared this frame. Abstracted away in the new ColorAttachment and DepthAttachment wrappers. --- ## Changelog - Changed `ViewTarget::get_color_attachment()`, removed arguments. - Changed `ViewTarget::get_unsampled_color_attachment()`, removed arguments. - Removed `Camera3d::clear_color`. - Removed `Camera2d::clear_color`. - Added `Camera::clear_color`. - Added `ExtractedCamera::clear_color`. - Added `ColorAttachment` and `DepthAttachment` wrappers. - Moved `ClearColor` and `ClearColorConfig` from `bevy::core_pipeline::clear_color` to `bevy::render::camera`. - Core render passes now track when a texture is first bound as an attachment in order to decide whether to clear or load it. ## Migration Guide - Remove arguments to `ViewTarget::get_color_attachment()` and `ViewTarget::get_unsampled_color_attachment()`. - Configure clear color on `Camera` instead of on `Camera3d` and `Camera2d`. - Moved `ClearColor` and `ClearColorConfig` from `bevy::core_pipeline::clear_color` to `bevy::render::camera`. - `ViewDepthTexture` must now be created via the `new()` method --------- Co-authored-by: vero Co-authored-by: Alice Cecile --- crates/bevy_core_pipeline/src/bloom/mod.rs | 7 +- .../src/core_2d/camera_2d.rs | 13 +- .../src/core_2d/main_pass_2d_node.rs | 28 +--- .../src/core_3d/camera_3d.rs | 10 +- .../src/core_3d/main_opaque_pass_3d_node.rs | 61 +-------- .../core_3d/main_transmissive_pass_3d_node.rs | 21 +-- .../core_3d/main_transparent_pass_3d_node.rs | 36 ++--- crates/bevy_core_pipeline/src/core_3d/mod.rs | 28 ++-- .../bevy_core_pipeline/src/deferred/node.rs | 102 ++++---------- crates/bevy_core_pipeline/src/lib.rs | 15 +-- .../bevy_core_pipeline/src/msaa_writeback.rs | 22 ++- crates/bevy_core_pipeline/src/prepass/mod.rs | 32 ++++- crates/bevy_core_pipeline/src/prepass/node.rs | 38 +----- crates/bevy_core_pipeline/src/taa/mod.rs | 4 +- crates/bevy_pbr/src/deferred/mod.rs | 15 +-- .../bevy_pbr/src/prepass/prepass_bindings.rs | 21 +-- crates/bevy_pbr/src/render/light.rs | 19 +-- crates/bevy_pbr/src/ssao/mod.rs | 4 +- crates/bevy_render/src/camera/camera.rs | 7 +- .../src/camera}/clear_color.rs | 2 +- crates/bevy_render/src/camera/mod.rs | 6 + crates/bevy_render/src/lib.rs | 5 +- crates/bevy_render/src/texture/mod.rs | 2 + .../src/texture/texture_attachment.rs | 123 +++++++++++++++++ crates/bevy_render/src/view/mod.rs | 125 ++++++++++-------- crates/bevy_ui/src/render/render_pass.rs | 7 +- examples/3d/render_to_texture.rs | 5 +- examples/3d/split_screen.rs | 8 +- examples/3d/two_passes.rs | 7 +- examples/shader/post_processing.rs | 2 +- tests/window/minimising.rs | 6 +- tests/window/resizing.rs | 6 +- 32 files changed, 374 insertions(+), 413 deletions(-) rename crates/{bevy_core_pipeline/src => bevy_render/src/camera}/clear_color.rs (95%) create mode 100644 crates/bevy_render/src/texture/texture_attachment.rs diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index a0db9509c6..f9adf440f8 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -265,12 +265,7 @@ impl ViewNode for BloomNode { let mut upsampling_final_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_target.get_unsampled_color_attachment( - Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }, - ))], + color_attachments: &[Some(view_target.get_unsampled_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index fcd794029b..f68b302acc 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -1,7 +1,4 @@ -use crate::{ - clear_color::ClearColorConfig, - tonemapping::{DebandDither, Tonemapping}, -}; +use crate::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_render::{ @@ -15,9 +12,7 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; #[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[extract_component_filter(With)] #[reflect(Component)] -pub struct Camera2d { - pub clear_color: ClearColorConfig, -} +pub struct Camera2d; #[derive(Bundle)] pub struct Camera2dBundle { @@ -57,7 +52,7 @@ impl Default for Camera2dBundle { transform, global_transform: Default::default(), camera: Camera::default(), - camera_2d: Camera2d::default(), + camera_2d: Camera2d, tonemapping: Tonemapping::None, deband_dither: DebandDither::Disabled, } @@ -95,7 +90,7 @@ impl Camera2dBundle { transform, global_transform: Default::default(), camera: Camera::default(), - camera_2d: Camera2d::default(), + camera_2d: Camera2d, tonemapping: Tonemapping::None, deband_dither: DebandDither::Disabled, } diff --git a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs index c3a19e93df..656db89100 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs @@ -1,13 +1,10 @@ -use crate::{ - clear_color::{ClearColor, ClearColorConfig}, - core_2d::{camera_2d::Camera2d, Transparent2d}, -}; +use crate::core_2d::Transparent2d; use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::RenderPhase, - render_resource::{LoadOp, Operations, RenderPassDescriptor, StoreOp}, + render_resource::RenderPassDescriptor, renderer::RenderContext, view::{ExtractedView, ViewTarget}, }; @@ -20,7 +17,6 @@ pub struct MainPass2dNode { &'static ExtractedCamera, &'static RenderPhase, &'static ViewTarget, - &'static Camera2d, ), With, >, @@ -46,28 +42,19 @@ impl Node for MainPass2dNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); - let Ok((camera, transparent_phase, target, camera_2d)) = - self.query.get_manual(world, view_entity) + let Ok((camera, transparent_phase, target)) = self.query.get_manual(world, view_entity) else { // no target return Ok(()); }; + { #[cfg(feature = "trace")] let _main_pass_2d = info_span!("main_pass_2d").entered(); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_pass_2d"), - color_attachments: &[Some(target.get_color_attachment(Operations { - 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: StoreOp::Store, - }))], + color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, @@ -88,10 +75,7 @@ impl Node for MainPass2dNode { let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); let pass_descriptor = RenderPassDescriptor { label: Some("reset_viewport_pass_2d"), - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }))], + color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index d866e2ccfb..bdcd81c923 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -1,7 +1,4 @@ -use crate::{ - clear_color::ClearColorConfig, - tonemapping::{DebandDither, Tonemapping}, -}; +use crate::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::prelude::*; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::{ @@ -19,8 +16,6 @@ use serde::{Deserialize, Serialize}; #[extract_component_filter(With)] #[reflect(Component)] pub struct Camera3d { - /// The clear color operation to perform for the main 3d pass. - pub clear_color: ClearColorConfig, /// The depth clear operation to perform for the main 3d pass. pub depth_load_op: Camera3dDepthLoadOp, /// The texture usages for the depth texture created for the main 3d pass. @@ -37,7 +32,7 @@ pub struct Camera3d { /// regardless of this setting. /// - Setting this to `0` disables the screen-space refraction effect entirely, and falls /// back to refracting only the environment map light's texture. - /// - If set to more than `0`, any opaque [`clear_color`](Camera3d::clear_color) will obscure the environment + /// - If set to more than `0`, any opaque [`clear_color`](Camera::clear_color) will obscure the environment /// map light's texture, preventing it from being visible “through” transmissive materials. If you'd like /// to still have the environment map show up in your refractions, you can set the clear color's alpha to `0.0`. /// Keep in mind that depending on the platform and your window settings, this may cause the window to become @@ -55,7 +50,6 @@ pub struct Camera3d { impl Default for Camera3d { fn default() -> Self { Self { - clear_color: ClearColorConfig::Default, depth_load_op: Default::default(), depth_texture_usages: TextureUsages::RENDER_ATTACHMENT.into(), screen_space_specular_transmission_steps: 1, diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 7ed385043c..0b7cf2c3c0 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -1,7 +1,5 @@ use crate::{ - clear_color::{ClearColor, ClearColorConfig}, - core_3d::{Camera3d, Opaque3d}, - prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, + core_3d::Opaque3d, skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; use bevy_ecs::{prelude::World, query::QueryItem}; @@ -9,17 +7,14 @@ use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::RenderPhase, - render_resource::{ - LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor, - StoreOp, - }, + render_resource::{PipelineCache, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use super::{AlphaMask3d, Camera3dDepthLoadOp}; +use super::AlphaMask3d; /// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`]. #[derive(Default)] @@ -29,13 +24,8 @@ impl ViewNode for MainOpaquePass3dNode { &'static ExtractedCamera, &'static RenderPhase, &'static RenderPhase, - &'static Camera3d, &'static ViewTarget, &'static ViewDepthTexture, - Option<&'static DepthPrepass>, - Option<&'static NormalPrepass>, - Option<&'static MotionVectorPrepass>, - Option<&'static DeferredPrepass>, Option<&'static SkyboxPipelineId>, Option<&'static SkyboxBindGroup>, &'static ViewUniformOffset, @@ -49,30 +39,14 @@ impl ViewNode for MainOpaquePass3dNode { camera, opaque_phase, alpha_mask_phase, - camera_3d, target, depth, - depth_prepass, - normal_prepass, - motion_vector_prepass, - deferred_prepass, skybox_pipeline, skybox_bind_group, view_uniform_offset, ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { - let load = if deferred_prepass.is_none() { - match camera_3d.clear_color { - ClearColorConfig::Default => LoadOp::Clear(world.resource::().0.into()), - ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), - ClearColorConfig::None => LoadOp::Load, - } - } else { - // If the deferred lighting pass has run, don't clear again in this pass. - LoadOp::Load - }; - // Run the opaque pass, sorted front-to-back // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -81,33 +55,8 @@ impl ViewNode for MainOpaquePass3dNode { // Setup render pass let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_opaque_pass_3d"), - // NOTE: The opaque pass loads the color - // buffer as well as writing to it. - color_attachments: &[Some(target.get_color_attachment(Operations { - load, - store: StoreOp::Store, - }))], - 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: if depth_prepass.is_some() - || normal_prepass.is_some() - || motion_vector_prepass.is_some() - || deferred_prepass.is_some() - { - // if any prepass runs, it will generate a depth buffer so we should use it, - // even if only the normal_prepass is used. - Camera3dDepthLoadOp::Load - } else { - // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections. - camera_3d.depth_load_op.clone() - } - .into(), - store: StoreOp::Store, - }), - stencil_ops: None, - }), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index f602648e23..99ed34397e 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -5,10 +5,7 @@ use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::RenderPhase, - render_resource::{ - Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor, - StoreOp, - }, + render_resource::{Extent3d, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, }; @@ -45,20 +42,8 @@ impl ViewNode for MainTransmissivePass3dNode { let render_pass_descriptor = RenderPassDescriptor { label: Some("main_transmissive_pass_3d"), - // NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate. - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }))], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &depth.view, - // NOTE: The transmissive main pass loads the depth buffer and possibly overwrites it - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, - }), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }; diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index 21df14577f..ba379edb0d 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -4,9 +4,7 @@ use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::RenderPhase, - render_resource::{ - LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor, StoreOp, - }, + render_resource::{RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, }; @@ -41,25 +39,14 @@ impl ViewNode for MainTransparentPass3dNode { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_transparent_pass_3d"), - // NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate. - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }))], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &depth.view, - // NOTE: For the transparent pass we load the depth buffer. There should be no - // need to write to it, but store is set to `true` as a workaround for issue #3776, - // https://github.com/bevyengine/bevy/issues/3776 - // so that wgpu does not clear the depth buffer. - // As the opaque and alpha mask passes run first, opaque meshes can occlude - // transparent ones. - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, - }), + color_attachments: &[Some(target.get_color_attachment())], + // NOTE: For the transparent pass we load the depth buffer. There should be no + // need to write to it, but store is set to `true` as a workaround for issue #3776, + // https://github.com/bevyengine/bevy/issues/3776 + // so that wgpu does not clear the depth buffer. + // As the opaque and alpha mask passes run first, opaque meshes can occlude + // transparent ones. + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); @@ -79,10 +66,7 @@ impl ViewNode for MainTransparentPass3dNode { let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered(); let pass_descriptor = RenderPassDescriptor { label: Some("reset_viewport_pass_3d"), - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }))], + color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 16d8ebec97..5d6e72e889 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -42,6 +42,7 @@ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::prelude::*; use bevy_render::{ camera::{Camera, ExtractedCamera}, + color::Color, extract_component::ExtractComponentPlugin, prelude::Msaa, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, @@ -54,7 +55,7 @@ use bevy_render::{ TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, - texture::{BevyDefault, TextureCache}, + texture::{BevyDefault, ColorAttachment, TextureCache}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; @@ -526,7 +527,7 @@ pub fn prepare_core_3d_depth_textures( } let mut textures = HashMap::default(); - for (entity, camera, _, _) in &views_3d { + for (entity, camera, _, camera_3d) in &views_3d { let Some(physical_target_size) = camera.physical_target_size else { continue; }; @@ -560,10 +561,13 @@ pub fn prepare_core_3d_depth_textures( }) .clone(); - commands.entity(entity).insert(ViewDepthTexture { - texture: cached_texture.texture, - view: cached_texture.default_view, - }); + commands.entity(entity).insert(ViewDepthTexture::new( + cached_texture, + match camera_3d.depth_load_op { + Camera3dDepthLoadOp::Clear(v) => Some(v), + Camera3dDepthLoadOp::Load => None, + }, + )); } } @@ -824,10 +828,14 @@ pub fn prepare_prepass_textures( }); commands.entity(entity).insert(ViewPrepassTextures { - depth: cached_depth_texture, - normal: cached_normals_texture, - motion_vectors: cached_motion_vectors_texture, - deferred: cached_deferred_texture, + depth: cached_depth_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)), + normal: cached_normals_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)), + // Red and Green channels are X and Y components of the motion vectors + // Blue channel doesn't matter, but set to 0.0 for possible faster clear + // https://gpuopen.com/performance/#clears + motion_vectors: cached_motion_vectors_texture + .map(|t| ColorAttachment::new(t, None, Color::BLACK)), + deferred: cached_deferred_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)), deferred_lighting_pass_id: deferred_lighting_pass_id_texture, size, }); diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 70f765c3ff..8fac4d77fb 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -8,18 +8,14 @@ use bevy_render::{ prelude::Color, render_graph::{NodeRunError, RenderGraphContext}, render_phase::RenderPhase, - render_resource::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, - RenderPassDescriptor, - }, + render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor}, renderer::RenderContext, view::ViewDepthTexture, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use crate::core_3d::{Camera3d, Camera3dDepthLoadOp}; -use crate::prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass, ViewPrepassTextures}; +use crate::prepass::ViewPrepassTextures; use super::{AlphaMask3dDeferred, Opaque3dDeferred}; @@ -36,10 +32,6 @@ impl ViewNode for DeferredGBufferPrepassNode { &'static RenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, - &'static Camera3d, - Option<&'static DepthPrepass>, - Option<&'static NormalPrepass>, - Option<&'static MotionVectorPrepass>, ); fn run( @@ -52,10 +44,6 @@ impl ViewNode for DeferredGBufferPrepassNode { alpha_mask_deferred_phase, view_depth_texture, view_prepass_textures, - camera_3d, - depth_prepass, - normal_prepass, - motion_vector_prepass, ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { @@ -66,37 +54,14 @@ impl ViewNode for DeferredGBufferPrepassNode { view_prepass_textures .normal .as_ref() - .map(|view_normals_texture| RenderPassColorAttachment { - view: &view_normals_texture.default_view, - resolve_target: None, - ops: Operations { - load: if normal_prepass.is_some() { - // Load if the normal_prepass has already run. - // The prepass will have already cleared this for the current frame. - LoadOp::Load - } else { - LoadOp::Clear(Color::BLACK.into()) - }, - store: StoreOp::Store, - }, - }), + .map(|normals_texture| normals_texture.get_attachment()), + ); + color_attachments.push( + view_prepass_textures + .motion_vectors + .as_ref() + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), ); - color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map( - |view_motion_vectors_texture| RenderPassColorAttachment { - view: &view_motion_vectors_texture.default_view, - resolve_target: None, - ops: Operations { - load: if motion_vector_prepass.is_some() { - // Load if the motion_vector_prepass has already run. - // The prepass will have already cleared this for the current frame. - LoadOp::Load - } else { - LoadOp::Clear(Color::BLACK.into()) - }, - store: StoreOp::Store, - }, - }, - )); // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. @@ -106,7 +71,7 @@ impl ViewNode for DeferredGBufferPrepassNode { #[cfg(all(feature = "webgl", target_arch = "wasm32"))] if let Some(deferred_texture) = &view_prepass_textures.deferred { render_context.command_encoder().clear_texture( - &deferred_texture.texture, + &deferred_texture.texture.texture, &bevy_render::render_resource::ImageSubresourceRange::default(), ); } @@ -115,16 +80,20 @@ impl ViewNode for DeferredGBufferPrepassNode { view_prepass_textures .deferred .as_ref() - .map(|deferred_texture| RenderPassColorAttachment { - view: &deferred_texture.default_view, - resolve_target: None, - ops: Operations { - #[cfg(all(feature = "webgl", target_arch = "wasm32"))] - load: LoadOp::Load, - #[cfg(not(all(feature = "webgl", target_arch = "wasm32")))] - load: LoadOp::Clear(Default::default()), - store: StoreOp::Store, - }, + .map(|deferred_texture| { + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + { + RenderPassColorAttachment { + view: &deferred_texture.texture.default_view, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + } + } + #[cfg(not(all(feature = "webgl", target_arch = "wasm32")))] + deferred_texture.get_attachment() }), ); @@ -136,7 +105,7 @@ impl ViewNode for DeferredGBufferPrepassNode { view: &deferred_lighting_pass_id.default_view, resolve_target: None, ops: Operations { - load: LoadOp::Clear(Default::default()), + load: LoadOp::Clear(Color::BLACK.into()), store: StoreOp::Store, }, }), @@ -152,24 +121,7 @@ impl ViewNode for DeferredGBufferPrepassNode { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("deferred"), color_attachments: &color_attachments, - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &view_depth_texture.view, - depth_ops: Some(Operations { - load: if depth_prepass.is_some() - || normal_prepass.is_some() - || motion_vector_prepass.is_some() - { - // If any prepass runs, it will generate a depth buffer so we should use it. - Camera3dDepthLoadOp::Load - } else { - // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections. - camera_3d.depth_load_op.clone() - } - .into(), - store: StoreOp::Store, - }), - stencil_ops: None, - }), + depth_stencil_attachment: Some(view_depth_texture.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); @@ -198,7 +150,7 @@ impl ViewNode for DeferredGBufferPrepassNode { // Copy depth buffer to texture. render_context.command_encoder().copy_texture_to_texture( view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), view_prepass_textures.size, ); } diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index f710b8c704..5aad6703ed 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -1,6 +1,5 @@ pub mod blit; pub mod bloom; -pub mod clear_color; pub mod contrast_adaptive_sharpening; pub mod core_2d; pub mod core_3d; @@ -29,7 +28,6 @@ pub mod experimental { pub mod prelude { #[doc(hidden)] pub use crate::{ - clear_color::ClearColor, core_2d::{Camera2d, Camera2dBundle}, core_3d::{Camera3d, Camera3dBundle}, }; @@ -38,7 +36,6 @@ pub mod prelude { use crate::{ blit::BlitPlugin, bloom::BloomPlugin, - clear_color::{ClearColor, ClearColorConfig}, contrast_adaptive_sharpening::CASPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, @@ -46,13 +43,13 @@ use crate::{ fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, fxaa::FxaaPlugin, msaa_writeback::MsaaWritebackPlugin, - prepass::{DepthPrepass, NormalPrepass}, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::TonemappingPlugin, upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; use bevy_asset::load_internal_asset; -use bevy_render::{extract_resource::ExtractResourcePlugin, prelude::Shader}; +use bevy_render::prelude::Shader; #[derive(Default)] pub struct CorePipelinePlugin; @@ -66,13 +63,11 @@ impl Plugin for CorePipelinePlugin { Shader::from_wgsl ); - app.register_type::() - .register_type::() - .register_type::() + app.register_type::() .register_type::() - .init_resource::() + .register_type::() + .register_type::() .add_plugins(( - ExtractResourcePlugin::::default(), Core2dPlugin, Core3dPlugin, CopyDeferredLightingIdPlugin, diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 096936800c..06f294dae0 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -7,6 +7,7 @@ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, + color::Color, render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::BindGroupEntries, renderer::RenderContext, @@ -60,12 +61,17 @@ impl Node for MsaaWritebackNode { 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> { + if *world.resource::() == Msaa::Off { + return Ok(()); + } + let view_entity = graph.view_entity(); if let Ok((target, blit_pipeline_id)) = self.cameras.get_manual(world, view_entity) { let blit_pipeline = world.resource::(); @@ -81,13 +87,18 @@ impl Node for MsaaWritebackNode { let pass_descriptor = RenderPassDescriptor { label: Some("msaa_writeback"), - // The target's "resolve target" is the "destination" in post_process + // The target's "resolve target" is the "destination" in post_process. // We will indirectly write the results to the "destination" using // the MSAA resolve step. - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Clear(Default::default()), - store: StoreOp::Store, - }))], + color_attachments: &[Some(RenderPassColorAttachment { + // If MSAA is enabled, then the sampled texture will always exist + view: target.sampled_main_texture_view().unwrap(), + resolve_target: Some(post_process.destination), + ops: Operations { + load: LoadOp::Clear(Color::BLACK.into()), + store: StoreOp::Store, + }, + })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, @@ -107,6 +118,7 @@ impl Node for MsaaWritebackNode { render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); } + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 63b8c764af..5006c2a305 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -33,8 +33,8 @@ use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_render::{ render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, - render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat}, - texture::CachedTexture, + render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat, TextureView}, + texture::{CachedTexture, ColorAttachment}, }; use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; @@ -66,16 +66,16 @@ pub struct DeferredPrepass; pub struct ViewPrepassTextures { /// The depth texture generated by the prepass. /// Exists only if [`DepthPrepass`] is added to the [`ViewTarget`](bevy_render::view::ViewTarget) - pub depth: Option, + pub depth: Option, /// The normals texture generated by the prepass. /// Exists only if [`NormalPrepass`] is added to the [`ViewTarget`](bevy_render::view::ViewTarget) - pub normal: Option, + pub normal: Option, /// The motion vectors texture generated by the prepass. /// Exists only if [`MotionVectorPrepass`] is added to the `ViewTarget` - pub motion_vectors: Option, + pub motion_vectors: Option, /// The deferred gbuffer generated by the deferred pass. /// Exists only if [`DeferredPrepass`] is added to the `ViewTarget` - pub deferred: Option, + pub deferred: Option, /// A texture that specifies the deferred lighting pass id for a material. /// Exists only if [`DeferredPrepass`] is added to the `ViewTarget` pub deferred_lighting_pass_id: Option, @@ -83,6 +83,26 @@ pub struct ViewPrepassTextures { pub size: Extent3d, } +impl ViewPrepassTextures { + pub fn depth_view(&self) -> Option<&TextureView> { + self.depth.as_ref().map(|t| &t.texture.default_view) + } + + pub fn normal_view(&self) -> Option<&TextureView> { + self.normal.as_ref().map(|t| &t.texture.default_view) + } + + pub fn motion_vectors_view(&self) -> Option<&TextureView> { + self.motion_vectors + .as_ref() + .map(|t| &t.texture.default_view) + } + + pub fn deferred_view(&self) -> Option<&TextureView> { + self.deferred.as_ref().map(|t| &t.texture.default_view) + } +} + /// Opaque phase of the 3D prepass. /// /// Sorted front-to-back by the z-distance in front of the camera. diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index f9441a1cbd..c37af2110a 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -4,13 +4,9 @@ use bevy_render::render_graph::ViewNode; use bevy_render::render_resource::StoreOp; use bevy_render::{ camera::ExtractedCamera, - prelude::Color, render_graph::{NodeRunError, RenderGraphContext}, render_phase::RenderPhase, - render_resource::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, - RenderPassDescriptor, - }, + render_resource::RenderPassDescriptor, renderer::RenderContext, view::ViewDepthTexture, }; @@ -55,28 +51,11 @@ impl ViewNode for PrepassNode { view_prepass_textures .normal .as_ref() - .map(|view_normals_texture| RenderPassColorAttachment { - view: &view_normals_texture.default_view, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(Color::BLACK.into()), - store: StoreOp::Store, - }, - }), + .map(|normals_texture| normals_texture.get_attachment()), view_prepass_textures .motion_vectors .as_ref() - .map(|view_motion_vectors_texture| RenderPassColorAttachment { - view: &view_motion_vectors_texture.default_view, - resolve_target: None, - ops: Operations { - // Red and Green channels are X and Y components of the motion vectors - // Blue channel doesn't matter, but set to 0.0 for possible faster clear - // https://gpuopen.com/performance/#clears - load: LoadOp::Clear(Color::BLACK.into()), - store: StoreOp::Store, - }, - }), + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), // Use None in place of Deferred attachments None, None, @@ -92,14 +71,7 @@ impl ViewNode for PrepassNode { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("prepass"), color_attachments: &color_attachments, - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &view_depth_texture.view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: StoreOp::Store, - }), - stencil_ops: None, - }), + depth_stencil_attachment: Some(view_depth_texture.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); @@ -128,7 +100,7 @@ impl ViewNode for PrepassNode { // Copy depth buffer to texture render_context.command_encoder().copy_texture_to_texture( view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), view_prepass_textures.size, ); } diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index b4c5b44052..f322571d0c 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -206,8 +206,8 @@ impl ViewNode for TemporalAntiAliasNode { &BindGroupEntries::sequential(( view_target.source, &taa_history_textures.read.default_view, - &prepass_motion_vectors_texture.default_view, - &prepass_depth_texture.default_view, + &prepass_motion_vectors_texture.texture.default_view, + &prepass_depth_texture.texture.default_view, &pipelines.nearest_sampler, &pipelines.linear_sampler, )), diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 857556b350..a47a9da519 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -2,12 +2,10 @@ use crate::{MeshPipeline, MeshViewBindGroup, ScreenSpaceAmbientOcclusionSettings use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, Handle}; use bevy_core_pipeline::{ - clear_color::ClearColorConfig, core_3d, deferred::{ copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, }, - prelude::{Camera3d, ClearColor}, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; @@ -156,7 +154,6 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { &'static MeshViewBindGroup, &'static ViewTarget, &'static DeferredLightingIdDepthTexture, - &'static Camera3d, &'static DeferredLightingPipeline, ); @@ -171,7 +168,6 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { mesh_view_bind_group, target, deferred_lighting_id_depth_texture, - camera_3d, deferred_lighting_pipeline, ): QueryItem, world: &World, @@ -201,16 +197,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("deferred_lighting_pass"), - color_attachments: &[Some(target.get_color_attachment(Operations { - 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: StoreOp::Store, - }))], + color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &deferred_lighting_id_depth_texture.texture.default_view, depth_ops: Some(Operations { diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.rs b/crates/bevy_pbr/src/prepass/prepass_bindings.rs index 77398ce6e5..3c66625ed8 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.rs +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.rs @@ -64,19 +64,12 @@ pub fn get_bindings(prepass_textures: Option<&ViewPrepassTextures>) -> [Option &'static str { #[derive(Component)] pub struct ShadowView { - pub depth_texture_view: TextureView, + pub depth_attachment: DepthAttachment, pub pass_name: String, } @@ -1008,7 +1008,7 @@ pub fn prepare_lights( let view_light_entity = commands .spawn(( ShadowView { - depth_texture_view, + depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)), pass_name: format!( "shadow pass point light {} {}", light_index, @@ -1070,7 +1070,7 @@ pub fn prepare_lights( let view_light_entity = commands .spawn(( ShadowView { - depth_texture_view, + depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)), pass_name: format!("shadow pass spot light {light_index}"), }, ExtractedView { @@ -1135,7 +1135,7 @@ pub fn prepare_lights( let view_light_entity = commands .spawn(( ShadowView { - depth_texture_view, + depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)), pass_name: format!( "shadow pass directional light {light_index} cascade {cascade_index}"), }, @@ -1767,14 +1767,9 @@ impl Node for ShadowPassNode { render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some(&view_light.pass_name), color_attachments: &[], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &view_light.depth_texture_view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: StoreOp::Store, - }), - stencil_ops: None, - }), + depth_stencil_attachment: Some( + view_light.depth_attachment.get_attachment(StoreOp::Store), + ), timestamp_writes: None, occlusion_query_set: None, }); diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 58c975adbe..cbcca32a23 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -681,7 +681,7 @@ fn prepare_ssao_bind_groups( "ssao_preprocess_depth_bind_group", &pipelines.preprocess_depth_bind_group_layout, &BindGroupEntries::sequential(( - &prepass_textures.depth.as_ref().unwrap().default_view, + prepass_textures.depth_view().unwrap(), &create_depth_view(0), &create_depth_view(1), &create_depth_view(2), @@ -695,7 +695,7 @@ fn prepare_ssao_bind_groups( &pipelines.gtao_bind_group_layout, &BindGroupEntries::sequential(( &ssao_textures.preprocessed_depth_texture.default_view, - &prepass_textures.normal.as_ref().unwrap().default_view, + prepass_textures.normal_view().unwrap(), &pipelines.hilbert_index_lut, &ssao_textures.ssao_noisy_texture.default_view, &ssao_textures.depth_differences_texture.default_view, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index ecffba2d82..140cdd9138 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -32,7 +32,7 @@ use bevy_window::{ use std::{borrow::Cow, ops::Range}; use wgpu::{BlendState, LoadOp, TextureFormat}; -use super::Projection; +use super::{ClearColorConfig, Projection}; /// Render viewport configuration for the [`Camera`] component. /// @@ -118,6 +118,8 @@ pub struct Camera { /// "write their results on top" of previous camera results, and include them as a part of their render results. This is enabled by default to ensure /// cameras with MSAA enabled layer their results in the same way as cameras without MSAA enabled by default. pub msaa_writeback: bool, + /// The clear color operation to perform on the render target. + pub clear_color: ClearColorConfig, } impl Default for Camera { @@ -131,6 +133,7 @@ impl Default for Camera { output_mode: Default::default(), hdr: false, msaa_writeback: true, + clear_color: Default::default(), } } } @@ -632,6 +635,7 @@ pub struct ExtractedCamera { pub order: isize, pub output_mode: CameraOutputMode, pub msaa_writeback: bool, + pub clear_color: ClearColorConfig, pub sorted_camera_index_for_target: usize, } @@ -701,6 +705,7 @@ pub fn extract_cameras( order: camera.order, output_mode: camera.output_mode, msaa_writeback: camera.msaa_writeback, + clear_color: camera.clear_color.clone(), // this will be set in sort_cameras sorted_camera_index_for_target: 0, }, diff --git a/crates/bevy_core_pipeline/src/clear_color.rs b/crates/bevy_render/src/camera/clear_color.rs similarity index 95% rename from crates/bevy_core_pipeline/src/clear_color.rs rename to crates/bevy_render/src/camera/clear_color.rs index 94832ce5d1..952e22ad97 100644 --- a/crates/bevy_core_pipeline/src/clear_color.rs +++ b/crates/bevy_render/src/camera/clear_color.rs @@ -1,7 +1,7 @@ +use crate::{color::Color, extract_resource::ExtractResource}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; -use bevy_render::{color::Color, extract_resource::ExtractResource}; use serde::{Deserialize, Serialize}; /// For a camera, specifies the color used to clear the viewport before rendering. diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 2a92ff1596..2796fb36e5 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -1,11 +1,13 @@ #[allow(clippy::module_inception)] mod camera; mod camera_driver_node; +mod clear_color; mod manual_texture_view; mod projection; pub use camera::*; pub use camera_driver_node::*; +pub use clear_color::*; pub use manual_texture_view::*; pub use projection::*; @@ -27,12 +29,16 @@ impl Plugin for CameraPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() .init_resource::() + .init_resource::() .add_plugins(( CameraProjectionPlugin::::default(), CameraProjectionPlugin::::default(), CameraProjectionPlugin::::default(), ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), )); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index a8243422d6..e7ed0545ee 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -27,7 +27,10 @@ pub mod view; pub mod prelude { #[doc(hidden)] pub use crate::{ - camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection}, + camera::{ + Camera, ClearColor, ClearColorConfig, OrthographicProjection, PerspectiveProjection, + Projection, + }, color::Color, mesh::{morph::MorphWeights, shape, Mesh}, render_resource::Shader, diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index d7a31a2beb..866cbc928c 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -14,6 +14,7 @@ mod image; mod image_loader; #[cfg(feature = "ktx2")] mod ktx2; +mod texture_attachment; mod texture_cache; pub(crate) mod image_texture_conversion; @@ -32,6 +33,7 @@ pub use hdr_texture_loader::*; pub use compressed_image_saver::*; pub use fallback_image::*; pub use image_loader::*; +pub use texture_attachment::*; pub use texture_cache::*; use crate::{ diff --git a/crates/bevy_render/src/texture/texture_attachment.rs b/crates/bevy_render/src/texture/texture_attachment.rs new file mode 100644 index 0000000000..908d2e3a28 --- /dev/null +++ b/crates/bevy_render/src/texture/texture_attachment.rs @@ -0,0 +1,123 @@ +use super::CachedTexture; +use crate::{prelude::Color, render_resource::TextureView}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use wgpu::{ + LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, +}; + +/// A wrapper for a [`CachedTexture`] that is used as a [`RenderPassColorAttachment`]. +#[derive(Clone)] +pub struct ColorAttachment { + pub texture: CachedTexture, + pub resolve_target: Option, + clear_color: Color, + is_first_call: Arc, +} + +impl ColorAttachment { + pub fn new( + texture: CachedTexture, + resolve_target: Option, + clear_color: Color, + ) -> Self { + Self { + texture, + resolve_target, + clear_color, + is_first_call: Arc::new(AtomicBool::new(true)), + } + } + + /// Get this texture view as an attachment. The attachment will be cleared with a value of + /// `clear_color` if this is the first time calling this function, otherwise it will be loaded. + /// + /// The returned attachment will always have writing enabled (`store: StoreOp::Load`). + pub fn get_attachment(&self) -> RenderPassColorAttachment { + if let Some(resolve_target) = self.resolve_target.as_ref() { + let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); + + RenderPassColorAttachment { + view: &resolve_target.default_view, + resolve_target: Some(&self.texture.default_view), + ops: Operations { + load: if first_call { + LoadOp::Clear(self.clear_color.into()) + } else { + LoadOp::Load + }, + store: StoreOp::Store, + }, + } + } else { + self.get_unsampled_attachment() + } + } + + /// Get this texture view as an attachment, without the resolve target. The attachment will be cleared with + /// a value of `clear_color` if this is the first time calling this function, otherwise it will be loaded. + /// + /// The returned attachment will always have writing enabled (`store: StoreOp::Load`). + pub fn get_unsampled_attachment(&self) -> RenderPassColorAttachment { + let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); + + RenderPassColorAttachment { + view: &self.texture.default_view, + resolve_target: None, + ops: Operations { + load: if first_call { + LoadOp::Clear(self.clear_color.into()) + } else { + LoadOp::Load + }, + store: StoreOp::Store, + }, + } + } + + pub(crate) fn mark_as_cleared(&self) { + self.is_first_call.store(false, Ordering::SeqCst); + } +} + +/// A wrapper for a [`TextureView`] that is used as a depth-only [`RenderPassDepthStencilAttachment`]. +pub struct DepthAttachment { + pub view: TextureView, + clear_value: Option, + is_first_call: Arc, +} + +impl DepthAttachment { + pub fn new(view: TextureView, clear_value: Option) -> Self { + Self { + view, + clear_value, + is_first_call: Arc::new(AtomicBool::new(clear_value.is_some())), + } + } + + /// Get this texture view as an attachment. The attachment will be cleared with a value of + /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`], + /// and a clear value was provided, otherwise it will be loaded. + pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment { + let first_call = self + .is_first_call + .fetch_and(store != StoreOp::Store, Ordering::SeqCst); + + RenderPassDepthStencilAttachment { + view: &self.view, + depth_ops: Some(Operations { + load: if first_call { + // If first_call is true, then a clear value will always have been provided in the constructor + LoadOp::Clear(self.clear_value.unwrap()) + } else { + LoadOp::Load + }, + store, + }), + stencil_ops: None, + } + } +} diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 7a0b928d7c..af91222152 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -6,7 +6,9 @@ pub use visibility::*; pub use window::*; use crate::{ - camera::{ExtractedCamera, ManualTextureViews, MipBias, TemporalJitter}, + camera::{ + ClearColor, ClearColorConfig, ExtractedCamera, ManualTextureViews, MipBias, TemporalJitter, + }, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::{Image, Shader}, primitives::Frustum, @@ -14,7 +16,7 @@ use crate::{ render_phase::ViewRangefinder3d, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, CachedTexture, TextureCache}, + texture::{BevyDefault, CachedTexture, ColorAttachment, DepthAttachment, TextureCache}, Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; @@ -28,8 +30,8 @@ use std::sync::{ Arc, }; use wgpu::{ - Color, Extent3d, Operations, RenderPassColorAttachment, TextureDescriptor, TextureDimension, - TextureFormat, TextureUsages, + Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, + TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }; pub const VIEW_TYPE_HANDLE: Handle = Handle::weak_from_u128(15421373904451797197); @@ -204,40 +206,30 @@ pub struct PostProcessWrite<'a> { impl ViewTarget { pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; - /// Retrieve this target's color attachment. This will use [`Self::sampled_main_texture_view`] and resolve to [`Self::main_texture`] if - /// the target has sampling enabled. Otherwise it will use [`Self::main_texture`] directly. - pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { - match &self.main_textures.sampled { - Some(CachedTexture { - default_view: sampled_texture_view, - .. - }) => RenderPassColorAttachment { - view: sampled_texture_view, - resolve_target: Some(self.main_texture_view()), - ops, - }, - None => self.get_unsampled_color_attachment(ops), + /// Retrieve this target's main texture's color attachment. + pub fn get_color_attachment(&self) -> RenderPassColorAttachment { + if self.main_texture.load(Ordering::SeqCst) == 0 { + self.main_textures.a.get_attachment() + } else { + self.main_textures.b.get_attachment() } } - /// Retrieve an "unsampled" color attachment using [`Self::main_texture`]. - pub fn get_unsampled_color_attachment( - &self, - ops: Operations, - ) -> RenderPassColorAttachment { - RenderPassColorAttachment { - view: self.main_texture_view(), - resolve_target: None, - ops, + /// Retrieve this target's "unsampled" main texture's color attachment. + pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment { + if self.main_texture.load(Ordering::SeqCst) == 0 { + self.main_textures.a.get_unsampled_attachment() + } else { + self.main_textures.b.get_unsampled_attachment() } } /// The "main" unsampled texture. pub fn main_texture(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.a.texture + &self.main_textures.a.texture.texture } else { - &self.main_textures.b.texture + &self.main_textures.b.texture.texture } } @@ -249,18 +241,18 @@ impl ViewTarget { /// ahead of time. pub fn main_texture_other(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.b.texture + &self.main_textures.b.texture.texture } else { - &self.main_textures.a.texture + &self.main_textures.a.texture.texture } } /// The "main" unsampled texture. pub fn main_texture_view(&self) -> &TextureView { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.a.default_view + &self.main_textures.a.texture.default_view } else { - &self.main_textures.b.default_view + &self.main_textures.b.texture.default_view } } @@ -272,16 +264,17 @@ impl ViewTarget { /// ahead of time. pub fn main_texture_other_view(&self) -> &TextureView { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.b.default_view + &self.main_textures.b.texture.default_view } else { - &self.main_textures.a.default_view + &self.main_textures.a.texture.default_view } } /// The "main" sampled texture. pub fn sampled_main_texture(&self) -> Option<&Texture> { self.main_textures - .sampled + .a + .resolve_target .as_ref() .map(|sampled| &sampled.texture) } @@ -289,7 +282,8 @@ impl ViewTarget { /// The "main" sampled texture view. pub fn sampled_main_texture_view(&self) -> Option<&TextureView> { self.main_textures - .sampled + .a + .resolve_target .as_ref() .map(|sampled| &sampled.default_view) } @@ -328,14 +322,16 @@ impl ViewTarget { let old_is_a_main_texture = self.main_texture.fetch_xor(1, Ordering::SeqCst); // if the old main texture is a, then the post processing must write from a to b if old_is_a_main_texture == 0 { + self.main_textures.b.mark_as_cleared(); PostProcessWrite { - source: &self.main_textures.a.default_view, - destination: &self.main_textures.b.default_view, + source: &self.main_textures.a.texture.default_view, + destination: &self.main_textures.b.texture.default_view, } } else { + self.main_textures.a.mark_as_cleared(); PostProcessWrite { - source: &self.main_textures.b.default_view, - destination: &self.main_textures.a.default_view, + source: &self.main_textures.b.texture.default_view, + destination: &self.main_textures.a.texture.default_view, } } } @@ -344,7 +340,24 @@ impl ViewTarget { #[derive(Component)] pub struct ViewDepthTexture { pub texture: Texture, - pub view: TextureView, + attachment: DepthAttachment, +} + +impl ViewDepthTexture { + pub fn new(texture: CachedTexture, clear_value: Option) -> Self { + Self { + texture: texture.texture, + attachment: DepthAttachment::new(texture.default_view, clear_value), + } + } + + pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment { + self.attachment.get_attachment(store) + } + + pub fn view(&self) -> &TextureView { + &self.attachment.view + } } pub fn prepare_view_uniforms( @@ -420,9 +433,8 @@ pub fn prepare_view_uniforms( #[derive(Clone)] struct MainTargetTextures { - a: CachedTexture, - b: CachedTexture, - sampled: Option, + a: ColorAttachment, + b: ColorAttachment, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, @@ -434,6 +446,7 @@ fn prepare_view_targets( windows: Res, images: Res>, msaa: Res, + clear_color_global: Res, render_device: Res, mut texture_cache: ResMut, cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>, @@ -458,7 +471,12 @@ fn prepare_view_targets( TextureFormat::bevy_default() }; - let main_textures = textures + let clear_color = match camera.clear_color { + ClearColorConfig::Custom(color) => color, + _ => clear_color_global.0, + }; + + let (a, b, sampled) = textures .entry((camera.target.clone(), view.hdr)) .or_insert_with(|| { let descriptor = TextureDescriptor { @@ -509,18 +527,19 @@ fn prepare_view_targets( } else { None }; - MainTargetTextures { - a, - b, - sampled, - main_texture: Arc::new(AtomicUsize::new(0)), - } + (a, b, sampled) }); + let main_textures = MainTargetTextures { + a: ColorAttachment::new(a.clone(), sampled.clone(), clear_color), + b: ColorAttachment::new(b.clone(), sampled.clone(), clear_color), + main_texture: Arc::new(AtomicUsize::new(0)), + }; + commands.entity(entity).insert(ViewTarget { - main_textures: main_textures.clone(), - main_texture_format, main_texture: main_textures.main_texture.clone(), + main_textures, + main_texture_format, out_texture: out_texture_view.clone(), out_texture_format: out_texture_format.add_srgb_suffix(), }); diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 72aa0e67ce..7c58278d00 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ use bevy_render::{ render_graph::*, render_phase::*, - render_resource::{CachedRenderPipelineId, LoadOp, Operations, RenderPassDescriptor, StoreOp}, + render_resource::{CachedRenderPipelineId, RenderPassDescriptor}, renderer::*, view::*, }; @@ -74,10 +74,7 @@ impl Node for UiPassNode { }; let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("ui_pass"), - color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }))], + color_attachments: &[Some(target.get_unsampled_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index a319e7d6e4..a1ab2d7782 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -100,14 +100,11 @@ fn setup( commands.spawn(( Camera3dBundle { - camera_3d: Camera3d { - clear_color: Color::WHITE.into(), - ..default() - }, camera: Camera { // render before the "main pass" camera order: -1, target: RenderTarget::Image(image_handle.clone()), + clear_color: Color::WHITE.into(), ..default() }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 1a0f215d64..23c5e45535 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -3,8 +3,7 @@ use std::f32::consts::PI; use bevy::{ - core_pipeline::clear_color::ClearColorConfig, pbr::CascadeShadowConfigBuilder, prelude::*, - render::camera::Viewport, window::WindowResized, + pbr::CascadeShadowConfigBuilder, prelude::*, render::camera::Viewport, window::WindowResized, }; fn main() { @@ -67,10 +66,7 @@ fn setup( camera: Camera { // Renders the right camera after the left camera, which has a default priority of 0 order: 1, - ..default() - }, - camera_3d: Camera3d { - // don't clear on the second camera because the first camera already cleared the window + // Don't clear on the second camera because the first camera already cleared the window clear_color: ClearColorConfig::None, ..default() }, diff --git a/examples/3d/two_passes.rs b/examples/3d/two_passes.rs index eded967496..8ded87396e 100644 --- a/examples/3d/two_passes.rs +++ b/examples/3d/two_passes.rs @@ -1,6 +1,6 @@ //! Renders two 3d passes to the same window from different perspectives. -use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*}; +use bevy::prelude::*; fn main() { App::new() @@ -47,13 +47,10 @@ fn setup( // camera commands.spawn(Camera3dBundle { transform: Transform::from_xyz(10.0, 10., -5.0).looking_at(Vec3::ZERO, Vec3::Y), - camera_3d: Camera3d { - clear_color: ClearColorConfig::None, - ..default() - }, camera: Camera { // renders after / on top of the main camera order: 1, + clear_color: ClearColorConfig::None, ..default() }, ..default() diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index ad684675d8..cb5168c4f5 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -305,7 +305,7 @@ fn setup( Camera3dBundle { transform: Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)) .looking_at(Vec3::default(), Vec3::Y), - camera_3d: Camera3d { + camera: Camera { clear_color: Color::WHITE.into(), ..default() }, diff --git a/tests/window/minimising.rs b/tests/window/minimising.rs index 40314a2153..b60829cb08 100644 --- a/tests/window/minimising.rs +++ b/tests/window/minimising.rs @@ -1,6 +1,6 @@ //! A test to confirm that `bevy` allows minimising the window //! This is run in CI to ensure that this doesn't regress again. -use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*}; +use bevy::prelude::*; fn main() { // TODO: Combine this with `resizing` once multiple_windows is simpler than @@ -72,11 +72,9 @@ fn setup_2d(mut commands: Commands) { camera: Camera { // render the 2d camera after the 3d camera order: 1, - ..default() - }, - camera_2d: Camera2d { // do not use a clear color clear_color: ClearColorConfig::None, + ..default() }, ..default() }); diff --git a/tests/window/resizing.rs b/tests/window/resizing.rs index 0d9135989c..a6da0450dd 100644 --- a/tests/window/resizing.rs +++ b/tests/window/resizing.rs @@ -1,7 +1,7 @@ //! A test to confirm that `bevy` allows setting the window to arbitrary small sizes //! This is run in CI to ensure that this doesn't regress again. -use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::WindowResolution}; +use bevy::{prelude::*, window::WindowResolution}; // The smallest size reached is 1x1, as X11 doesn't support windows with a 0 dimension // TODO: Add a check for platforms other than X11 for 0xk and kx0, despite those currently unsupported on CI. @@ -155,11 +155,9 @@ fn setup_2d(mut commands: Commands) { camera: Camera { // render the 2d camera after the 3d camera order: 1, - ..default() - }, - camera_2d: Camera2d { // do not use a clear color clear_color: ClearColorConfig::None, + ..default() }, ..default() });