bevy_render/texture/
fallback_image.rs

1use crate::{
2    render_asset::RenderAssetUsages,
3    render_resource::*,
4    renderer::{RenderDevice, RenderQueue},
5    texture::{DefaultImageSampler, GpuImage},
6};
7use bevy_derive::{Deref, DerefMut};
8use bevy_ecs::{
9    prelude::{FromWorld, Res, ResMut},
10    resource::Resource,
11    system::SystemParam,
12};
13use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
14use bevy_platform::collections::HashMap;
15
16/// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image",
17/// which can be used in situations where an image was not explicitly defined. The most common
18/// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures.
19///
20/// Defaults to a 1x1 fully opaque white texture, (1.0, 1.0, 1.0, 1.0) which makes multiplying
21/// it with other colors a no-op.
22#[derive(Resource)]
23pub struct FallbackImage {
24    /// Fallback image for [`TextureViewDimension::D1`].
25    pub d1: GpuImage,
26    /// Fallback image for [`TextureViewDimension::D2`].
27    pub d2: GpuImage,
28    /// Fallback image for [`TextureViewDimension::D2Array`].
29    pub d2_array: GpuImage,
30    /// Fallback image for [`TextureViewDimension::Cube`].
31    pub cube: GpuImage,
32    /// Fallback image for [`TextureViewDimension::CubeArray`].
33    pub cube_array: GpuImage,
34    /// Fallback image for [`TextureViewDimension::D3`].
35    pub d3: GpuImage,
36}
37
38impl FallbackImage {
39    /// Returns the appropriate fallback image for the given texture dimension.
40    pub fn get(&self, texture_dimension: TextureViewDimension) -> &GpuImage {
41        match texture_dimension {
42            TextureViewDimension::D1 => &self.d1,
43            TextureViewDimension::D2 => &self.d2,
44            TextureViewDimension::D2Array => &self.d2_array,
45            TextureViewDimension::Cube => &self.cube,
46            TextureViewDimension::CubeArray => &self.cube_array,
47            TextureViewDimension::D3 => &self.d3,
48        }
49    }
50}
51
52/// A [`RenderApp`](crate::RenderApp) resource that contains a _zero-filled_ "fallback image",
53/// which can be used in place of [`FallbackImage`], when a fully transparent or black fallback
54/// is required instead of fully opaque white.
55///
56/// Defaults to a 1x1 fully transparent black texture, (0.0, 0.0, 0.0, 0.0) which makes adding
57/// or alpha-blending it to other colors a no-op.
58#[derive(Resource, Deref)]
59pub struct FallbackImageZero(GpuImage);
60
61/// A [`RenderApp`](crate::RenderApp) resource that contains a "cubemap fallback image",
62/// which can be used in situations where an image was not explicitly defined. The most common
63/// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures.
64#[derive(Resource, Deref)]
65pub struct FallbackImageCubemap(GpuImage);
66
67fn fallback_image_new(
68    render_device: &RenderDevice,
69    render_queue: &RenderQueue,
70    default_sampler: &DefaultImageSampler,
71    format: TextureFormat,
72    dimension: TextureViewDimension,
73    samples: u32,
74    value: u8,
75) -> GpuImage {
76    // TODO make this configurable per channel
77
78    let extents = Extent3d {
79        width: 1,
80        height: 1,
81        depth_or_array_layers: match dimension {
82            TextureViewDimension::Cube | TextureViewDimension::CubeArray => 6,
83            _ => 1,
84        },
85    };
86
87    // We can't create textures with data when it's a depth texture or when using multiple samples
88    let create_texture_with_data = !format.is_depth_stencil_format() && samples == 1;
89
90    let image_dimension = dimension.compatible_texture_dimension();
91    let mut image = if create_texture_with_data {
92        let data = vec![value; format.pixel_size()];
93        Image::new_fill(
94            extents,
95            image_dimension,
96            &data,
97            format,
98            RenderAssetUsages::RENDER_WORLD,
99        )
100    } else {
101        let mut image = Image::default_uninit();
102        image.texture_descriptor.dimension = TextureDimension::D2;
103        image.texture_descriptor.size = extents;
104        image.texture_descriptor.format = format;
105        image
106    };
107    image.texture_descriptor.sample_count = samples;
108    if image_dimension == TextureDimension::D2 {
109        image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
110    }
111
112    let texture = if create_texture_with_data {
113        render_device.create_texture_with_data(
114            render_queue,
115            &image.texture_descriptor,
116            TextureDataOrder::default(),
117            &image.data.expect("Image has no data"),
118        )
119    } else {
120        render_device.create_texture(&image.texture_descriptor)
121    };
122
123    let texture_view = texture.create_view(&TextureViewDescriptor {
124        dimension: Some(dimension),
125        array_layer_count: Some(extents.depth_or_array_layers),
126        ..TextureViewDescriptor::default()
127    });
128    let sampler = match image.sampler {
129        ImageSampler::Default => (**default_sampler).clone(),
130        ImageSampler::Descriptor(ref descriptor) => {
131            render_device.create_sampler(&descriptor.as_wgpu())
132        }
133    };
134    GpuImage {
135        texture,
136        texture_view,
137        texture_format: image.texture_descriptor.format,
138        sampler,
139        size: image.texture_descriptor.size,
140        mip_level_count: image.texture_descriptor.mip_level_count,
141    }
142}
143
144impl FromWorld for FallbackImage {
145    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
146        let render_device = world.resource::<RenderDevice>();
147        let render_queue = world.resource::<RenderQueue>();
148        let default_sampler = world.resource::<DefaultImageSampler>();
149        Self {
150            d1: fallback_image_new(
151                render_device,
152                render_queue,
153                default_sampler,
154                TextureFormat::bevy_default(),
155                TextureViewDimension::D1,
156                1,
157                255,
158            ),
159            d2: fallback_image_new(
160                render_device,
161                render_queue,
162                default_sampler,
163                TextureFormat::bevy_default(),
164                TextureViewDimension::D2,
165                1,
166                255,
167            ),
168            d2_array: fallback_image_new(
169                render_device,
170                render_queue,
171                default_sampler,
172                TextureFormat::bevy_default(),
173                TextureViewDimension::D2Array,
174                1,
175                255,
176            ),
177            cube: fallback_image_new(
178                render_device,
179                render_queue,
180                default_sampler,
181                TextureFormat::bevy_default(),
182                TextureViewDimension::Cube,
183                1,
184                255,
185            ),
186            cube_array: fallback_image_new(
187                render_device,
188                render_queue,
189                default_sampler,
190                TextureFormat::bevy_default(),
191                TextureViewDimension::CubeArray,
192                1,
193                255,
194            ),
195            d3: fallback_image_new(
196                render_device,
197                render_queue,
198                default_sampler,
199                TextureFormat::bevy_default(),
200                TextureViewDimension::D3,
201                1,
202                255,
203            ),
204        }
205    }
206}
207
208impl FromWorld for FallbackImageZero {
209    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
210        let render_device = world.resource::<RenderDevice>();
211        let render_queue = world.resource::<RenderQueue>();
212        let default_sampler = world.resource::<DefaultImageSampler>();
213        Self(fallback_image_new(
214            render_device,
215            render_queue,
216            default_sampler,
217            TextureFormat::bevy_default(),
218            TextureViewDimension::D2,
219            1,
220            0,
221        ))
222    }
223}
224
225impl FromWorld for FallbackImageCubemap {
226    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
227        let render_device = world.resource::<RenderDevice>();
228        let render_queue = world.resource::<RenderQueue>();
229        let default_sampler = world.resource::<DefaultImageSampler>();
230        Self(fallback_image_new(
231            render_device,
232            render_queue,
233            default_sampler,
234            TextureFormat::bevy_default(),
235            TextureViewDimension::Cube,
236            1,
237            255,
238        ))
239    }
240}
241
242/// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key
243///
244/// # WARNING
245/// Images using MSAA with sample count > 1 are not initialized with data, therefore,
246/// you shouldn't sample them before writing data to them first.
247#[derive(Resource, Deref, DerefMut, Default)]
248pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>);
249
250#[derive(SystemParam)]
251pub struct FallbackImageMsaa<'w> {
252    cache: ResMut<'w, FallbackImageFormatMsaaCache>,
253    render_device: Res<'w, RenderDevice>,
254    render_queue: Res<'w, RenderQueue>,
255    default_sampler: Res<'w, DefaultImageSampler>,
256}
257
258impl<'w> FallbackImageMsaa<'w> {
259    pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage {
260        self.cache.entry((sample_count, format)).or_insert_with(|| {
261            fallback_image_new(
262                &self.render_device,
263                &self.render_queue,
264                &self.default_sampler,
265                format,
266                TextureViewDimension::D2,
267                sample_count,
268                255,
269            )
270        })
271    }
272}