Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bevy_camera/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ pub enum CompositingSpace {
/// Gamma-encoded blending. Matches most image editors. Uses default sRGB target.
#[default]
Srgb,
/// Linear light blending. Physically correct. Requires [`Hdr`].
/// Linear light blending. Physically correct.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this also irked me.

Linear,
/// Perceptually uniform blending. Often smoother gradients. Requires [`Hdr`].
/// Perceptually uniform blending. Often smoother gradients. Requires [`Hdr`] because it can be outside the [0, 1].
Oklab,
}
12 changes: 12 additions & 0 deletions crates/bevy_color/src/srgba.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,18 @@ impl From<Srgba> for Xyza {
}
}

#[cfg(feature = "wgpu-types")]
impl From<Srgba> for wgpu_types::Color {
fn from(color: Srgba) -> Self {
wgpu_types::Color {
r: color.red as f64,
g: color.green as f64,
b: color.blue as f64,
a: color.alpha as f64,
}
}
}

/// Error returned if a hex string could not be parsed as a color.
#[derive(Debug, Error, PartialEq, Eq)]
pub enum HexColorError {
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_image/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ impl BevyDefault for TextureFormat {
}
}

/// Trait used to provide texture srgb view formats with static lifetime for `TextureDescriptor.view_formats`.
/// Trait used to provide texture's srgb formats with static lifetime for `TextureDescriptor.view_formats`.
pub trait TextureSrgbViewFormats {
/// Returns the srgb view formats for a type.
fn srgb_view_formats(&self) -> &'static [TextureFormat];
}

impl TextureSrgbViewFormats for TextureFormat {
/// Returns the srgb view formats if the format has an srgb variant, otherwise returns an empty slice.
/// Returns the srgb formats if the format has an srgb variant, otherwise returns an empty slice.
///
/// The return result covers all the results of [`TextureFormat::add_srgb_suffix`](wgpu_types::TextureFormat::add_srgb_suffix).
/// Covers all the possible results of [`TextureFormat::add_srgb_suffix`](wgpu_types::TextureFormat::add_srgb_suffix).
fn srgb_view_formats(&self) -> &'static [TextureFormat] {
match self {
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
Expand Down
12 changes: 9 additions & 3 deletions crates/bevy_render/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,19 @@ pub fn extract_cameras(
.map(|format| normalize_bgra8(target, format))
})
.unwrap_or(TextureFormat::Rgba8UnormSrgb);

let target_format = if hdr {
TextureFormat::Rgba16Float
} else if compositing_space.is_some_and(|s| *s == CompositingSpace::Srgb) {
TextureFormat::Rgba8Unorm
} else {
output_texture_format
};
}
// Regardless of whether the target format is srgb,
// we use the non-srgb main texture, otherwise it cannot support storage binding.
//
// It doesn't matter what the format we use for the main texture
// as it's intermediate and independent from render target.
.remove_srgb_suffix();

main_pass_formats.insert(render_entity, target_format);

let mut commands = commands.entity(render_entity);
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_render/src/texture/texture_attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use wgpu::{
#[derive(Clone)]
pub struct ColorAttachment {
pub texture: CachedTexture,
pub resolve_target: Option<CachedTexture>,
pub multisampled: Option<CachedTexture>,
pub previous_frame_texture: Option<CachedTexture>,
clear_color: Option<WgpuColor>,
is_first_call: Arc<AtomicBool>,
Expand All @@ -21,13 +21,13 @@ pub struct ColorAttachment {
impl ColorAttachment {
pub fn new(
texture: CachedTexture,
resolve_target: Option<CachedTexture>,
multisampled: Option<CachedTexture>,
previous_frame_texture: Option<CachedTexture>,
clear_color: Option<WgpuColor>,
) -> Self {
Self {
texture,
resolve_target,
multisampled,
previous_frame_texture,
clear_color,
is_first_call: Arc::new(AtomicBool::new(true)),
Expand All @@ -39,11 +39,11 @@ impl ColorAttachment {
///
/// 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() {
if let Some(multisampled) = self.multisampled.as_ref() {
let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);

RenderPassColorAttachment {
view: &resolve_target.default_view,
view: &multisampled.default_view,
depth_slice: None,
resolve_target: Some(&self.texture.default_view),
ops: Operations {
Expand Down
51 changes: 24 additions & 27 deletions crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ use crate::{
};
use alloc::sync::Arc;
use bevy_app::{App, Plugin};
use bevy_color::{LinearRgba, Oklaba};
use bevy_color::{LinearRgba, Oklaba, Srgba};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_image::ToExtents;
use bevy_image::{TextureSrgbViewFormats, ToExtents};
use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
use bevy_platform::collections::{hash_map::Entry, HashMap};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
Expand Down Expand Up @@ -826,6 +826,8 @@ impl ViewTarget {
}

/// The "main" unsampled texture.
///
/// This is always non-srgb format in order to support storage binding.
pub fn main_texture(&self) -> &Texture {
if self.main_texture.load(Ordering::SeqCst) == 0 {
&self.main_textures.a.texture.texture
Expand All @@ -840,6 +842,8 @@ impl ViewTarget {
///
/// A use case for this is to be able to prepare a bind group for all main textures
/// ahead of time.
///
/// This is always non-srgb format in order to support storage binding.
pub fn main_texture_other(&self) -> &Texture {
if self.main_texture.load(Ordering::SeqCst) == 0 {
&self.main_textures.b.texture.texture
Expand All @@ -848,7 +852,7 @@ impl ViewTarget {
}
}

/// The "main" unsampled texture.
/// The "main" unsampled texture view.
pub fn main_texture_view(&self) -> &TextureView {
if self.main_texture.load(Ordering::SeqCst) == 0 {
&self.main_textures.a.texture.default_view
Expand All @@ -875,7 +879,7 @@ impl ViewTarget {
pub fn sampled_main_texture(&self) -> Option<&Texture> {
self.main_textures
.a
.resolve_target
.multisampled
.as_ref()
.map(|sampled| &sampled.texture)
}
Expand All @@ -884,16 +888,18 @@ impl ViewTarget {
pub fn sampled_main_texture_view(&self) -> Option<&TextureView> {
self.main_textures
.a
.resolve_target
.multisampled
.as_ref()
.map(|sampled| &sampled.default_view)
}

/// Currently bevy's main texture format can be:
/// Returns the format of [`Self::main_texture_view`].
///
/// Currently bevy's main texture view's format can be:
/// - If rendering to screen:
/// For HDR, it's `Rgba16Float`.
/// For LDR, it's `Rgba8Unorm` when [`CompositingSpace::Srgb`], otherwise `Rgba8UnormSrgb`.
/// - If rendering to texture: the format is the same as texture view's format.
/// For LDR, it's `Rgba8Unorm`.
/// - If rendering to texture: the format is the texture view's format but with srgb suffix removed.
#[inline]
pub fn main_texture_format(&self) -> TextureFormat {
self.main_texture_format
Expand Down Expand Up @@ -1167,25 +1173,19 @@ pub fn prepare_view_targets(
};

// Convert clear color to the format expected by the main texture
let converted_clear_color: Option<WgpuColor> = clear_color.map(|color| {
let linear: LinearRgba = color.into();
if camera
.compositing_space
.is_some_and(|s| s == CompositingSpace::Oklab)
{
// Main texture stores Oklab; convert linear RGB to Oklab for correct clear
let oklab: Oklaba = linear.into();
oklab.into()
} else {
linear.into()
}
});
let converted_clear_color: Option<WgpuColor> =
clear_color.map(|color| match camera.compositing_space {
// If main texture stores Oklab or Srgb, convert Color to it for correct clear.
Some(CompositingSpace::Oklab) => Oklaba::from(color).into(),
Some(CompositingSpace::Srgb) => Srgba::from(color).into(),
_ => LinearRgba::from(color).into(),
});

let (a, b, sampled, main_texture) = textures
.entry((
camera.target.clone(),
texture_usage.0,
main_texture_format,
view.target_format,
msaa,
))
.or_insert_with(|| {
Expand All @@ -1197,11 +1197,8 @@ pub fn prepare_view_targets(
dimension: TextureDimension::D2,
format: main_texture_format,
usage: texture_usage.0,
view_formats: match main_texture_format {
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
_ => &[],
},
// `main_texture_format` is always non-srgb. We don't use srgb view but we allow it for users.
view_formats: main_texture_format.srgb_view_formats(),
};
let a = texture_cache.get(
&render_device,
Expand Down
7 changes: 4 additions & 3 deletions crates/bevy_render/src/view/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,10 @@ pub fn create_surfaces(
// Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available.
let mut format = *formats.first().expect("No supported formats for surface");
for available_format in formats {
// Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces.
if available_format == TextureFormat::Rgba8UnormSrgb
|| available_format == TextureFormat::Bgra8UnormSrgb
// Rgba8Unorm and Bgra8Unorm and the only formats that we can use for surfaces on WebGPU.
// Our renderer is in linear space and store the result to the srgb texture view.
if available_format == TextureFormat::Rgba8Unorm
|| available_format == TextureFormat::Bgra8Unorm
{
format = available_format;
break;
Expand Down