diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 3c46f22a56a08..053ce5007a8a7 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -72,7 +72,7 @@ fn prepare_view_upscaling_pipelines( }; let key = BlitPipelineKey { - texture_format: view_target.out_texture_format(), + texture_format: view_target.out_texture_view_format(), blend_state, samples: 1, }; diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index aeeb58b1a7d98..ad2db13e2b182 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -38,6 +38,130 @@ impl BevyDefault for TextureFormat { } } +/// Trait used to provide texture srgb view 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. + /// + /// The return result covers all the 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], + TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb], + TextureFormat::Bc1RgbaUnorm => &[TextureFormat::Bc1RgbaUnormSrgb], + TextureFormat::Bc2RgbaUnorm => &[TextureFormat::Bc2RgbaUnormSrgb], + TextureFormat::Bc3RgbaUnorm => &[TextureFormat::Bc3RgbaUnormSrgb], + TextureFormat::Bc7RgbaUnorm => &[TextureFormat::Bc7RgbaUnormSrgb], + TextureFormat::Etc2Rgb8Unorm => &[TextureFormat::Etc2Rgb8UnormSrgb], + TextureFormat::Etc2Rgb8A1Unorm => &[TextureFormat::Etc2Rgb8A1UnormSrgb], + TextureFormat::Etc2Rgba8Unorm => &[TextureFormat::Etc2Rgba8UnormSrgb], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B4x4, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B4x4, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B5x4, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B5x4, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B5x5, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B5x5, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B6x5, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B6x5, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B6x6, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B6x6, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x5, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x5, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x6, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x6, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x8, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B8x8, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x5, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x5, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x6, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x6, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x8, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x8, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x10, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B10x10, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B12x10, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B12x10, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + TextureFormat::Astc { + block: wgpu_types::AstcBlock::B12x12, + channel: wgpu_types::AstcChannel::Unorm, + } => &[TextureFormat::Astc { + block: wgpu_types::AstcBlock::B12x12, + channel: wgpu_types::AstcChannel::UnormSrgb, + }], + _ => &[], + } + } +} + /// A handle to a 1 x 1 transparent white image. /// /// Like [`Handle::default`], this is a handle to a fallback image asset. @@ -1010,7 +1134,12 @@ impl Image { /// /// [`Camera`]: https://docs.rs/bevy/latest/bevy/render/camera/struct.Camera.html /// [`RenderTarget::Image`]: https://docs.rs/bevy/latest/bevy/render/camera/enum.RenderTarget.html#variant.Image - pub fn new_target_texture(width: u32, height: u32, format: TextureFormat) -> Self { + pub fn new_target_texture( + width: u32, + height: u32, + format: TextureFormat, + view_format: Option, + ) -> Self { let size = Extent3d { width, height, @@ -1039,10 +1168,16 @@ impl Image { mip_level_count: 1, sample_count: 1, usage, - view_formats: &[], + view_formats: match view_format { + Some(_) => format.srgb_view_formats(), + None => &[], + }, }, sampler: ImageSampler::Default, - texture_view_descriptor: None, + texture_view_descriptor: view_format.map(|f| TextureViewDescriptor { + format: Some(f), + ..Default::default() + }), asset_usage: RenderAssetUsages::default(), copy_on_resize: true, } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 013849e8ee8d2..bd2b853fd3b48 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1878,6 +1878,7 @@ pub fn build_dummy_white_gpu_image( texture, texture_view, texture_format: image.texture_descriptor.format, + texture_view_format: image.texture_view_descriptor.and_then(|v| v.format), sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 84988a2fc6534..886897d700753 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -156,7 +156,7 @@ pub trait NormalizedRenderTargetExt { ) -> Option<&'a TextureView>; /// Retrieves the [`TextureFormat`] of this render target, if it exists. - fn get_texture_format<'a>( + fn get_texture_view_format<'a>( &self, windows: &'a ExtractedWindows, images: &'a RenderAssets, @@ -199,8 +199,8 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { } } - /// Retrieves the [`TextureFormat`] of this render target, if it exists. - fn get_texture_format<'a>( + /// Retrieves the texture view's [`TextureFormat`] of this render target, if it exists. + fn get_texture_view_format<'a>( &self, windows: &'a ExtractedWindows, images: &'a RenderAssets, @@ -209,12 +209,12 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { match self { NormalizedRenderTarget::Window(window_ref) => windows .get(&window_ref.entity()) - .and_then(|window| window.swap_chain_texture_format), + .and_then(|window| window.swap_chain_texture_view_format), NormalizedRenderTarget::Image(image_target) => images .get(&image_target.handle) - .map(|image| image.texture_format), + .map(|image| image.texture_view_format.unwrap_or(image.texture_format)), NormalizedRenderTarget::TextureView(id) => { - manual_texture_views.get(id).map(|tex| tex.format) + manual_texture_views.get(id).map(|tex| tex.view_format) } NormalizedRenderTarget::None { .. } => None, } diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 095f27eecec69..c6d4c271f958c 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -135,6 +135,7 @@ fn fallback_image_new( texture, texture_view, texture_format: image.texture_descriptor.format, + texture_view_format: image.texture_view_descriptor.and_then(|v| v.format), sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index 622ffd16fb156..92e9d5b2be9b3 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -17,6 +17,7 @@ pub struct GpuImage { pub texture: Texture, pub texture_view: TextureView, pub texture_format: TextureFormat, + pub texture_view_format: Option, pub sampler: Sampler, pub size: Extent3d, pub mip_level_count: u32, @@ -92,9 +93,8 @@ impl RenderAsset for GpuImage { let texture_view = texture.create_view( image .texture_view_descriptor - .or_else(|| Some(TextureViewDescriptor::default())) .as_ref() - .unwrap(), + .unwrap_or(&TextureViewDescriptor::default()), ); let sampler = match image.sampler { ImageSampler::Default => (***default_sampler).clone(), @@ -107,6 +107,7 @@ impl RenderAsset for GpuImage { texture, texture_view, texture_format: image.texture_descriptor.format, + texture_view_format: image.texture_view_descriptor.and_then(|v| v.format), sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, diff --git a/crates/bevy_render/src/texture/manual_texture_view.rs b/crates/bevy_render/src/texture/manual_texture_view.rs index b9faa84ab78f6..a9f3a3ab9add0 100644 --- a/crates/bevy_render/src/texture/manual_texture_view.rs +++ b/crates/bevy_render/src/texture/manual_texture_view.rs @@ -13,7 +13,7 @@ use crate::render_resource::TextureView; pub struct ManualTextureView { pub texture_view: TextureView, pub size: UVec2, - pub format: TextureFormat, + pub view_format: TextureFormat, } impl ManualTextureView { @@ -21,7 +21,7 @@ impl ManualTextureView { Self { texture_view, size, - format: TextureFormat::bevy_default(), + view_format: TextureFormat::bevy_default(), } } } diff --git a/crates/bevy_render/src/texture/texture_attachment.rs b/crates/bevy_render/src/texture/texture_attachment.rs index 269c2f1422820..f815092ea515f 100644 --- a/crates/bevy_render/src/texture/texture_attachment.rs +++ b/crates/bevy_render/src/texture/texture_attachment.rs @@ -127,15 +127,15 @@ impl DepthAttachment { #[derive(Clone)] pub struct OutputColorAttachment { pub view: TextureView, - pub format: TextureFormat, + pub view_format: TextureFormat, is_first_call: Arc, } impl OutputColorAttachment { - pub fn new(view: TextureView, format: TextureFormat) -> Self { + pub fn new(view: TextureView, view_format: TextureFormat) -> Self { Self { view, - format, + view_format, is_first_call: Arc::new(AtomicBool::new(true)), } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index e9898c9deaddf..67c35a7be14bf 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -847,8 +847,8 @@ impl ViewTarget { /// The format of the final texture this view will render to #[inline] - pub fn out_texture_format(&self) -> TextureFormat { - self.out_texture.format + pub fn out_texture_view_format(&self) -> TextureFormat { + self.out_texture.view_format } /// This will start a new "post process write", which assumes that the caller @@ -1024,10 +1024,8 @@ pub fn prepare_view_attachments( let Some(attachment) = target .get_texture_view(&windows, &images, &manual_texture_views) .cloned() - .zip(target.get_texture_format(&windows, &images, &manual_texture_views)) - .map(|(view, format)| { - OutputColorAttachment::new(view.clone(), format.add_srgb_suffix()) - }) + .zip(target.get_texture_view_format(&windows, &images, &manual_texture_views)) + .map(|(view, format)| OutputColorAttachment::new(view.clone(), format)) else { continue; }; diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index ae5389e906820..d297e2d240cac 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -60,6 +60,7 @@ pub struct ExtractedWindow { pub swap_chain_texture_view: Option, pub swap_chain_texture: Option, pub swap_chain_texture_format: Option, + pub swap_chain_texture_view_format: Option, pub size_changed: bool, pub present_mode_changed: bool, pub alpha_mode: CompositeAlphaMode, @@ -67,8 +68,9 @@ pub struct ExtractedWindow { impl ExtractedWindow { fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) { + self.swap_chain_texture_view_format = Some(frame.texture.format().add_srgb_suffix()); let texture_view_descriptor = TextureViewDescriptor { - format: Some(frame.texture.format().add_srgb_suffix()), + format: self.swap_chain_texture_view_format, ..default() }; self.swap_chain_texture_view = Some(TextureView::from( @@ -140,6 +142,7 @@ fn extract_windows( swap_chain_texture_view: None, size_changed: false, swap_chain_texture_format: None, + swap_chain_texture_view_format: None, present_mode_changed: false, alpha_mode: window.composite_alpha_mode, }); @@ -191,6 +194,7 @@ struct SurfaceData { // TODO: what lifetime should this be? surface: WgpuWrapper>, configuration: SurfaceConfiguration, + texture_view_format: Option, } #[derive(Resource, Default)] @@ -363,6 +367,11 @@ pub fn create_surfaces( } } + let texture_view_format = if !format.is_srgb() { + Some(format.add_srgb_suffix()) + } else { + None + }; let configuration = SurfaceConfiguration { format, width: window.physical_width, @@ -391,10 +400,9 @@ pub fn create_surfaces( } CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, }, - view_formats: if !format.is_srgb() { - vec![format.add_srgb_suffix()] - } else { - vec![] + view_formats: match texture_view_format { + Some(format) => vec![format], + None => vec![], }, }; @@ -403,6 +411,7 @@ pub fn create_surfaces( SurfaceData { surface: WgpuWrapper::new(surface), configuration, + texture_view_format, } }); diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 4aba6c6cee791..655b9b30bbe4e 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -278,7 +278,9 @@ fn prepare_screenshots( warn!("Unknown window for screenshot, skipping: {}", window); continue; }; - let format = surface_data.configuration.format.add_srgb_suffix(); + let view_format = surface_data + .texture_view_format + .unwrap_or(surface_data.configuration.format); let size = Extent3d { width: surface_data.configuration.width, height: surface_data.configuration.height, @@ -286,7 +288,7 @@ fn prepare_screenshots( }; let (texture_view, state) = prepare_screenshot_state( size, - format, + view_format, &render_device, &screenshot_pipeline, &pipeline_cache, @@ -295,7 +297,7 @@ fn prepare_screenshots( prepared.insert(*entity, state); view_target_attachments.insert( target.clone(), - OutputColorAttachment::new(texture_view.clone(), format.add_srgb_suffix()), + OutputColorAttachment::new(texture_view.clone(), view_format), ); } NormalizedRenderTarget::Image(image) => { @@ -303,10 +305,12 @@ fn prepare_screenshots( warn!("Unknown image for screenshot, skipping: {:?}", image); continue; }; - let format = gpu_image.texture_format; + let view_format = gpu_image + .texture_view_format + .unwrap_or(gpu_image.texture_format); let (texture_view, state) = prepare_screenshot_state( gpu_image.size, - format, + view_format, &render_device, &screenshot_pipeline, &pipeline_cache, @@ -315,7 +319,7 @@ fn prepare_screenshots( prepared.insert(*entity, state); view_target_attachments.insert( target.clone(), - OutputColorAttachment::new(texture_view.clone(), format.add_srgb_suffix()), + OutputColorAttachment::new(texture_view.clone(), view_format), ); } NormalizedRenderTarget::TextureView(texture_view) => { @@ -326,11 +330,11 @@ fn prepare_screenshots( ); continue; }; - let format = manual_texture_view.format; + let view_format = manual_texture_view.view_format; let size = manual_texture_view.size.to_extents(); let (texture_view, state) = prepare_screenshot_state( size, - format, + view_format, &render_device, &screenshot_pipeline, &pipeline_cache, @@ -339,7 +343,7 @@ fn prepare_screenshots( prepared.insert(*entity, state); view_target_attachments.insert( target.clone(), - OutputColorAttachment::new(texture_view.clone(), format.add_srgb_suffix()), + OutputColorAttachment::new(texture_view.clone(), view_format), ); } NormalizedRenderTarget::None { .. } => { @@ -502,13 +506,12 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc }; let width = window.physical_width; let height = window.physical_height; - let Some(texture_format) = window.swap_chain_texture_format else { + let Some(texture_format) = window.swap_chain_texture_view_format else { continue; }; - let Some(swap_chain_texture) = window.swap_chain_texture.as_ref() else { + let Some(swap_chain_texture_view) = window.swap_chain_texture_view.as_ref() else { continue; }; - let texture_view = swap_chain_texture.texture.create_view(&Default::default()); render_screenshot( encoder, prepared, @@ -517,7 +520,7 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc width, height, texture_format, - &texture_view, + swap_chain_texture_view, ); } NormalizedRenderTarget::Image(image) => { @@ -550,7 +553,7 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc }; let width = texture_view.size.x; let height = texture_view.size.y; - let texture_format = texture_view.format; + let texture_format = texture_view.view_format; let texture_view = texture_view.texture_view.deref(); render_screenshot( encoder, diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index f6b02ff98300d..16be4689b995d 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -27,7 +27,12 @@ fn setup( mut images: ResMut>, ) { // This is the texture that will be rendered to. - let image = Image::new_target_texture(512, 512, TextureFormat::bevy_default()); + let image = Image::new_target_texture( + 512, + 512, + TextureFormat::Rgba8Unorm, + Some(TextureFormat::Rgba8UnormSrgb), + ); let image_handle = images.add(image); diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index b6aa5bf674636..26e7df7f2222a 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -248,13 +248,13 @@ fn setup_render_target( // This is the texture that will be rendered to. let mut render_target_image = - Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default()); + Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default(), None); render_target_image.texture_descriptor.usage |= TextureUsages::COPY_SRC; let render_target_image_handle = images.add(render_target_image); // This is the texture that will be copied to. let cpu_image = - Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default()); + Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default(), None); let cpu_image_handle = images.add(cpu_image); commands.spawn(ImageCopier::new( diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index b79d1f7a201d7..e9c3af5481eab 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -52,7 +52,7 @@ fn main() { } fn setup(mut commands: Commands, mut images: ResMut>) { - let mut image = Image::new_target_texture(SIZE.x, SIZE.y, TextureFormat::Rgba32Float); + let mut image = Image::new_target_texture(SIZE.x, SIZE.y, TextureFormat::Rgba32Float, None); image.asset_usage = RenderAssetUsages::RENDER_WORLD; image.texture_descriptor.usage = TextureUsages::COPY_DST | TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING;