diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 6220843883ac80..64e5ebe5eb2e2c 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -1,13 +1,14 @@ -use crate::blit::{BlitPipeline, BlitPipelineKey}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_render::camera::{CameraOutputMode, ExtractedCamera}; use bevy_render::view::ViewTarget; use bevy_render::{render_resource::*, Render, RenderApp, RenderSet}; +use bevy_utils::HashSet; +pub use node::UpscalingNode; -mod node; +use crate::blit::{BlitPipeline, BlitPipelineKey}; -pub use node::UpscalingNode; +mod node; pub struct UpscalingPlugin; @@ -32,16 +33,32 @@ fn prepare_view_upscaling_pipelines( blit_pipeline: Res, view_targets: Query<(Entity, &ViewTarget, Option<&ExtractedCamera>)>, ) { + let mut output_textures = HashSet::new(); for (entity, view_target, camera) in view_targets.iter() { + let out_texture_id = view_target.out_texture().id(); let blend_state = if let Some(ExtractedCamera { output_mode: CameraOutputMode::Write { blend_state, .. }, .. }) = camera { - *blend_state + match *blend_state { + None => { + // If we've already seen this output for a camera and it doesn't have a output blend + // mode configured, default to alpha blend so that we don't accidentally overwrite + // the output texture + if output_textures.contains(&out_texture_id) { + Some(BlendState::ALPHA_BLENDING) + } else { + None + } + } + _ => *blend_state, + } } else { None }; + output_textures.insert(out_texture_id); + let key = BlitPipelineKey { texture_format: view_target.out_texture_format(), blend_state, diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 59232f51c05f4c..c0a703b208a8e3 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -1,11 +1,11 @@ use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline}; use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_render::camera::{ClearColor, ClearColorConfig}; use bevy_render::{ camera::{CameraOutputMode, ExtractedCamera}, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ - BindGroup, BindGroupEntries, LoadOp, Operations, PipelineCache, RenderPassColorAttachment, - RenderPassDescriptor, StoreOp, TextureViewId, + BindGroup, BindGroupEntries, PipelineCache, RenderPassDescriptor, TextureViewId, }, renderer::RenderContext, view::ViewTarget, @@ -33,19 +33,22 @@ impl ViewNode for UpscalingNode { ) -> Result<(), NodeRunError> { let pipeline_cache = world.get_resource::().unwrap(); let blit_pipeline = world.get_resource::().unwrap(); + let clear_color_global = world.get_resource::().unwrap(); - let color_attachment_load_op = if let Some(camera) = camera { + let clear_color = if let Some(camera) = camera { match camera.output_mode { - CameraOutputMode::Write { - color_attachment_load_op, - .. - } => color_attachment_load_op, + CameraOutputMode::Write { clear_color, .. } => clear_color, CameraOutputMode::Skip => return Ok(()), } } else { - LoadOp::Clear(Default::default()) + ClearColorConfig::Default }; - + let clear_color = match clear_color { + ClearColorConfig::Default => Some(clear_color_global.0), + ClearColorConfig::Custom(color) => Some(color), + ClearColorConfig::None => None, + }; + let converted_clear_color = clear_color.map(|color| color.into()); let upscaled_texture = target.main_texture_view(); let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); @@ -69,14 +72,9 @@ impl ViewNode for UpscalingNode { let pass_descriptor = RenderPassDescriptor { label: Some("upscaling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: target.out_texture(), - resolve_target: None, - ops: Operations { - load: color_attachment_load_op, - store: StoreOp::Store, - }, - })], + color_attachments: &[Some( + target.out_texture_color_attachment(converted_clear_color), + )], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d0d9a4a6e1cd13..f155c5ef5d4c8c 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -35,7 +35,7 @@ use bevy_window::{ WindowScaleFactorChanged, }; use std::ops::Range; -use wgpu::{BlendState, LoadOp, TextureFormat, TextureUsages}; +use wgpu::{BlendState, TextureFormat, TextureUsages}; use super::{ClearColorConfig, Projection}; @@ -488,9 +488,8 @@ pub enum CameraOutputMode { Write { /// The blend state that will be used by the pipeline that writes the intermediate render textures to the final render target texture. blend_state: Option, - /// The color attachment load operation that will be used by the pipeline that writes the intermediate render textures to the final render - /// target texture. - color_attachment_load_op: LoadOp, + /// The clear color operation to perform on the final render target texture. + clear_color: ClearColorConfig, }, /// Skips writing the camera output to the configured render target. The output will remain in the /// Render Target's "intermediate" textures, which a camera with a higher order should write to the render target @@ -505,7 +504,7 @@ impl Default for CameraOutputMode { fn default() -> Self { CameraOutputMode::Write { blend_state: None, - color_attachment_load_op: LoadOp::Clear(Default::default()), + clear_color: ClearColorConfig::Default, } } } diff --git a/crates/bevy_render/src/camera/clear_color.rs b/crates/bevy_render/src/camera/clear_color.rs index e986b3d30b29ae..63f291aff41171 100644 --- a/crates/bevy_render/src/camera/clear_color.rs +++ b/crates/bevy_render/src/camera/clear_color.rs @@ -6,7 +6,7 @@ use bevy_reflect::prelude::*; use serde::{Deserialize, Serialize}; /// For a camera, specifies the color used to clear the viewport before rendering. -#[derive(Reflect, Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Debug, Default)] #[reflect(Serialize, Deserialize, Default)] pub enum ClearColorConfig { /// The clear color is taken from the world's [`ClearColor`] resource. diff --git a/crates/bevy_render/src/texture/texture_attachment.rs b/crates/bevy_render/src/texture/texture_attachment.rs index 220b9c5e0316ca..a9b56a43f8fb9f 100644 --- a/crates/bevy_render/src/texture/texture_attachment.rs +++ b/crates/bevy_render/src/texture/texture_attachment.rs @@ -1,14 +1,18 @@ -use super::CachedTexture; -use crate::render_resource::TextureView; -use bevy_color::LinearRgba; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; + use wgpu::{ LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, }; +use bevy_color::LinearRgba; + +use crate::render_resource::{TextureFormat, TextureView}; + +use super::CachedTexture; + /// A wrapper for a [`CachedTexture`] that is used as a [`RenderPassColorAttachment`]. #[derive(Clone)] pub struct ColorAttachment { @@ -120,3 +124,41 @@ impl DepthAttachment { } } } + +#[derive(Clone)] +pub struct OutputColorAttachment { + pub view: TextureView, + pub format: TextureFormat, + is_first_call: Arc, +} + +/// A wrapper for a [`TextureView`] that is used as a [`RenderPassColorAttachment`] for a view +/// targets final output texture. +impl OutputColorAttachment { + pub fn new(view: TextureView, format: TextureFormat) -> Self { + Self { + view, + format, + is_first_call: Arc::new(AtomicBool::new(true)), + } + } + + /// Get this texture view as an attachment. The attachment will be cleared with a value of + /// the provided `clear_color` if this is the first time calling this function, otherwise it + /// will be loaded. + pub fn get_attachment(&self, clear_color: Option) -> RenderPassColorAttachment { + let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); + + RenderPassColorAttachment { + view: &self.view, + resolve_target: None, + ops: Operations { + load: match (clear_color, first_call) { + (Some(clear_color), true) => LoadOp::Clear(clear_color.into()), + (None, _) | (Some(_), false) => LoadOp::Load, + }, + store: StoreOp::Store, + }, + } + } +} diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 0a6dcc5e0020ce..62c168ca863707 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -1,10 +1,28 @@ -pub mod visibility; -pub mod window; +use std::{ + ops::Range, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; + +use wgpu::{ + BufferUsages, Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, + TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, +}; +use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; +use bevy_color::LinearRgba; +use bevy_ecs::prelude::*; +use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_transform::components::GlobalTransform; +use bevy_utils::HashMap; pub use visibility::*; pub use window::*; +use crate::texture::OutputColorAttachment; use crate::{ camera::{ CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, ExtractedCamera, @@ -22,23 +40,9 @@ use crate::{ }, Render, RenderApp, RenderSet, }; -use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; -use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_transform::components::GlobalTransform; -use bevy_utils::HashMap; -use std::{ - ops::Range, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; -use wgpu::{ - BufferUsages, Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, - TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, -}; + +pub mod visibility; +pub mod window; pub const VIEW_TYPE_HANDLE: Handle = Handle::weak_from_u128(15421373904451797197); @@ -451,8 +455,7 @@ pub struct ViewTarget { /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, - out_texture: TextureView, - out_texture_format: TextureFormat, + out_texture: OutputColorAttachment, } pub struct PostProcessWrite<'a> { @@ -644,13 +647,20 @@ impl ViewTarget { /// The final texture this view will render to. #[inline] pub fn out_texture(&self) -> &TextureView { - &self.out_texture + &self.out_texture.view + } + + pub fn out_texture_color_attachment( + &self, + clear_color: Option, + ) -> RenderPassColorAttachment { + self.out_texture.get_attachment(clear_color) } /// The format of the final texture this view will render to #[inline] pub fn out_texture_format(&self) -> TextureFormat { - self.out_texture_format + self.out_texture.format } /// This will start a new "post process write", which assumes that the caller @@ -802,12 +812,22 @@ pub fn prepare_view_targets( manual_texture_views: Res, ) { let mut textures = HashMap::default(); + let mut output_textures = HashMap::default(); for (entity, camera, view, texture_usage) in cameras.iter() { if let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) { - if let (Some(out_texture_view), Some(out_texture_format)) = ( - target.get_texture_view(&windows, &images, &manual_texture_views), - target.get_texture_format(&windows, &images, &manual_texture_views), - ) { + if let Some(out_texture) = output_textures.entry(target.clone()).or_insert_with(|| { + if let (Some(out_texture_view), Some(out_texture_format)) = ( + target.get_texture_view(&windows, &images, &manual_texture_views), + target.get_texture_format(&windows, &images, &manual_texture_views), + ) { + Some(OutputColorAttachment::new( + out_texture_view.clone(), + out_texture_format.add_srgb_suffix().clone(), + )) + } else { + None + } + }) { let size = Extent3d { width: target_size.x, height: target_size.y, @@ -891,8 +911,7 @@ pub fn prepare_view_targets( 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(), + out_texture: out_texture.clone(), }); } }