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() });