use crate::{ render_asset::RenderAssetUsages, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{DefaultImageSampler, GpuImage}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{FromWorld, Res, ResMut}, resource::Resource, system::SystemParam, }; use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_platform_support::collections::HashMap; /// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image", /// which can be used in situations where an image was not explicitly defined. The most common /// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. /// /// Defaults to a 1x1 fully opaque white texture, (1.0, 1.0, 1.0, 1.0) which makes multiplying /// it with other colors a no-op. #[derive(Resource)] pub struct FallbackImage { /// Fallback image for [`TextureViewDimension::D1`]. pub d1: GpuImage, /// Fallback image for [`TextureViewDimension::D2`]. pub d2: GpuImage, /// Fallback image for [`TextureViewDimension::D2Array`]. pub d2_array: GpuImage, /// Fallback image for [`TextureViewDimension::Cube`]. pub cube: GpuImage, /// Fallback image for [`TextureViewDimension::CubeArray`]. pub cube_array: GpuImage, /// Fallback image for [`TextureViewDimension::D3`]. pub d3: GpuImage, } impl FallbackImage { /// Returns the appropriate fallback image for the given texture dimension. pub fn get(&self, texture_dimension: TextureViewDimension) -> &GpuImage { match texture_dimension { TextureViewDimension::D1 => &self.d1, TextureViewDimension::D2 => &self.d2, TextureViewDimension::D2Array => &self.d2_array, TextureViewDimension::Cube => &self.cube, TextureViewDimension::CubeArray => &self.cube_array, TextureViewDimension::D3 => &self.d3, } } } /// A [`RenderApp`](crate::RenderApp) resource that contains a _zero-filled_ "fallback image", /// which can be used in place of [`FallbackImage`], when a fully transparent or black fallback /// is required instead of fully opaque white. /// /// Defaults to a 1x1 fully transparent black texture, (0.0, 0.0, 0.0, 0.0) which makes adding /// or alpha-blending it to other colors a no-op. #[derive(Resource, Deref)] pub struct FallbackImageZero(GpuImage); /// A [`RenderApp`](crate::RenderApp) resource that contains a "cubemap fallback image", /// which can be used in situations where an image was not explicitly defined. The most common /// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. #[derive(Resource, Deref)] pub struct FallbackImageCubemap(GpuImage); fn fallback_image_new( render_device: &RenderDevice, render_queue: &RenderQueue, default_sampler: &DefaultImageSampler, format: TextureFormat, dimension: TextureViewDimension, samples: u32, value: u8, ) -> GpuImage { // TODO make this configurable per channel let extents = Extent3d { width: 1, height: 1, depth_or_array_layers: match dimension { TextureViewDimension::Cube | TextureViewDimension::CubeArray => 6, _ => 1, }, }; // We can't create textures with data when it's a depth texture or when using multiple samples let create_texture_with_data = !format.is_depth_stencil_format() && samples == 1; let image_dimension = dimension.compatible_texture_dimension(); let mut image = if create_texture_with_data { let data = vec![value; format.pixel_size()]; Image::new_fill( extents, image_dimension, &data, format, RenderAssetUsages::RENDER_WORLD, ) } else { let mut image = Image::default_uninit(); image.texture_descriptor.dimension = TextureDimension::D2; image.texture_descriptor.size = extents; image.texture_descriptor.format = format; image }; image.texture_descriptor.sample_count = samples; if image_dimension == TextureDimension::D2 { image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; } let texture = if create_texture_with_data { render_device.create_texture_with_data( render_queue, &image.texture_descriptor, TextureDataOrder::default(), &image.data.expect("Image has no data"), ) } else { render_device.create_texture(&image.texture_descriptor) }; let texture_view = texture.create_view(&TextureViewDescriptor { dimension: Some(dimension), array_layer_count: Some(extents.depth_or_array_layers), ..TextureViewDescriptor::default() }); let sampler = match image.sampler { ImageSampler::Default => (**default_sampler).clone(), ImageSampler::Descriptor(ref descriptor) => { render_device.create_sampler(&descriptor.as_wgpu()) } }; GpuImage { texture, texture_view, texture_format: image.texture_descriptor.format, sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, } } impl FromWorld for FallbackImage { fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { let render_device = world.resource::(); let render_queue = world.resource::(); let default_sampler = world.resource::(); Self { d1: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::D1, 1, 255, ), d2: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::D2, 1, 255, ), d2_array: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::D2Array, 1, 255, ), cube: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::Cube, 1, 255, ), cube_array: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::CubeArray, 1, 255, ), d3: fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::D3, 1, 255, ), } } } impl FromWorld for FallbackImageZero { fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { let render_device = world.resource::(); let render_queue = world.resource::(); let default_sampler = world.resource::(); Self(fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::D2, 1, 0, )) } } impl FromWorld for FallbackImageCubemap { fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { let render_device = world.resource::(); let render_queue = world.resource::(); let default_sampler = world.resource::(); Self(fallback_image_new( render_device, render_queue, default_sampler, TextureFormat::bevy_default(), TextureViewDimension::Cube, 1, 255, )) } } /// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key /// /// # WARNING /// Images using MSAA with sample count > 1 are not initialized with data, therefore, /// you shouldn't sample them before writing data to them first. #[derive(Resource, Deref, DerefMut, Default)] pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>); #[derive(SystemParam)] pub struct FallbackImageMsaa<'w> { cache: ResMut<'w, FallbackImageFormatMsaaCache>, render_device: Res<'w, RenderDevice>, render_queue: Res<'w, RenderQueue>, default_sampler: Res<'w, DefaultImageSampler>, } impl<'w> FallbackImageMsaa<'w> { pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage { self.cache.entry((sample_count, format)).or_insert_with(|| { fallback_image_new( &self.render_device, &self.render_queue, &self.default_sampler, format, TextureViewDimension::D2, sample_count, 255, ) }) } }