Skip to content

Commit

Permalink
Allow mix of hdr and non-hdr cameras to same render target
Browse files Browse the repository at this point in the history
Changes:
- Track whether an output texture has been written to yet and only
clear it on the first write.
- Use `ClearColorConfig` on `CameraOutputMode` instead of a raw
`LoadOp`.
- Track whether a output texture has been seen when specializing
the upscaling pipeline and use alpha blending for extra cameras
rendering to that texture that do not specify an explicit blend
mode.

Fixes bevyengine#6754
  • Loading branch information
tychedelia committed May 18, 2024
1 parent ee6dfd3 commit 55012f1
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 61 deletions.
27 changes: 22 additions & 5 deletions crates/bevy_core_pipeline/src/upscaling/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::blit::{BlitPipeline, BlitPipelineKey};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_render::{Render, render_resource::*, RenderApp, RenderSet};
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;

Expand All @@ -32,16 +33,32 @@ fn prepare_view_upscaling_pipelines(
blit_pipeline: Res<BlitPipeline>,
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,
Expand Down
30 changes: 15 additions & 15 deletions crates/bevy_core_pipeline/src/upscaling/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ 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,
};
use std::sync::Mutex;
use bevy_render::camera::{ClearColor, ClearColorConfig};

#[derive(Default)]
pub struct UpscalingNode {
Expand All @@ -33,19 +34,25 @@ impl ViewNode for UpscalingNode {
) -> Result<(), NodeRunError> {
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();
let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap();
let clear_color_global = world.get_resource::<ClearColor>().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,
clear_color,
..
} => color_attachment_load_op,
} => 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();
Expand All @@ -69,14 +76,7 @@ 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,
Expand Down
9 changes: 4 additions & 5 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<BlendState>,
/// 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<wgpu::Color>,
/// 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
Expand All @@ -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,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/camera/clear_color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
51 changes: 47 additions & 4 deletions crates/bevy_render/src/texture/texture_attachment.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use super::CachedTexture;
use crate::render_resource::TextureView;
use bevy_color::LinearRgba;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
atomic::{AtomicBool, Ordering},
};

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 {
Expand Down Expand Up @@ -120,3 +124,42 @@ impl DepthAttachment {
}
}
}

#[derive(Clone)]
pub struct OutputColorAttachment {
pub view: TextureView,
pub format: TextureFormat,
is_first_call: Arc<AtomicBool>,
}


/// 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<LinearRgba>) -> 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,
},
}
}
}
79 changes: 48 additions & 31 deletions crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
pub mod visibility;
pub mod window;
use std::{
ops::Range,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};

use bevy_asset::{load_internal_asset, Handle};
use wgpu::{
BufferUsages, Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};

use bevy_app::{App, Plugin};
use bevy_asset::{Handle, load_internal_asset};
use bevy_color::LinearRgba;
use bevy_ecs::prelude::*;
use bevy_math::{mat3, Mat3, Mat4, UVec4, vec2, Vec2, vec3, Vec3, Vec4, Vec4Swizzles};
use bevy_reflect::{Reflect, std_traits::ReflectDefault};
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap;
pub use visibility::*;
pub use window::*;

Expand All @@ -13,32 +30,19 @@ use crate::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
prelude::Shader,
primitives::Frustum,
Render,
render_asset::RenderAssets,
render_phase::ViewRangefinder3d,
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::{
RenderApp,
renderer::{RenderDevice, RenderQueue}, RenderSet, texture::{
BevyDefault, CachedTexture, ColorAttachment, DepthAttachment, GpuImage, TextureCache,
},
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,
};
use crate::texture::OutputColorAttachment;

pub mod visibility;
pub mod window;

pub const VIEW_TYPE_HANDLE: Handle<Shader> = Handle::weak_from_u128(15421373904451797197);

Expand Down Expand Up @@ -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<AtomicUsize>,
out_texture: TextureView,
out_texture_format: TextureFormat,
out_texture: OutputColorAttachment,
}

pub struct PostProcessWrite<'a> {
Expand Down Expand Up @@ -645,12 +648,18 @@ impl ViewTarget {
#[inline]
pub fn out_texture(&self) -> &TextureView {
&self.out_texture
.view
}

pub fn out_texture_color_attachment(&self, clear_color: Option<LinearRgba>) -> 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
Expand Down Expand Up @@ -802,11 +811,20 @@ pub fn prepare_view_targets(
manual_texture_views: Res<ManualTextureViews>,
) {
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,
Expand Down Expand Up @@ -891,8 +909,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(),
});
}
}
Expand Down

0 comments on commit 55012f1

Please sign in to comment.