bevy_render/texture/
fallback_image.rs

1use crate::{
2    render_resource::*,
3    renderer::{RenderDevice, RenderQueue},
4    texture::{DefaultImageSampler, GpuImage},
5};
6use bevy_asset::RenderAssetUsages;
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().unwrap_or(0)];
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        texture_view_format: image.texture_view_descriptor.and_then(|v| v.format),
139        sampler,
140        size: image.texture_descriptor.size,
141        mip_level_count: image.texture_descriptor.mip_level_count,
142        had_data: true,
143    }
144}
145
146impl FromWorld for FallbackImage {
147    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
148        let render_device = world.resource::<RenderDevice>();
149        let render_queue = world.resource::<RenderQueue>();
150        let default_sampler = world.resource::<DefaultImageSampler>();
151        Self {
152            d1: fallback_image_new(
153                render_device,
154                render_queue,
155                default_sampler,
156                TextureFormat::bevy_default(),
157                TextureViewDimension::D1,
158                1,
159                255,
160            ),
161            d2: fallback_image_new(
162                render_device,
163                render_queue,
164                default_sampler,
165                TextureFormat::bevy_default(),
166                TextureViewDimension::D2,
167                1,
168                255,
169            ),
170            d2_array: fallback_image_new(
171                render_device,
172                render_queue,
173                default_sampler,
174                TextureFormat::bevy_default(),
175                TextureViewDimension::D2Array,
176                1,
177                255,
178            ),
179            cube: fallback_image_new(
180                render_device,
181                render_queue,
182                default_sampler,
183                TextureFormat::bevy_default(),
184                TextureViewDimension::Cube,
185                1,
186                255,
187            ),
188            cube_array: fallback_image_new(
189                render_device,
190                render_queue,
191                default_sampler,
192                TextureFormat::bevy_default(),
193                TextureViewDimension::CubeArray,
194                1,
195                255,
196            ),
197            d3: fallback_image_new(
198                render_device,
199                render_queue,
200                default_sampler,
201                TextureFormat::bevy_default(),
202                TextureViewDimension::D3,
203                1,
204                255,
205            ),
206        }
207    }
208}
209
210impl FromWorld for FallbackImageZero {
211    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
212        let render_device = world.resource::<RenderDevice>();
213        let render_queue = world.resource::<RenderQueue>();
214        let default_sampler = world.resource::<DefaultImageSampler>();
215        Self(fallback_image_new(
216            render_device,
217            render_queue,
218            default_sampler,
219            TextureFormat::bevy_default(),
220            TextureViewDimension::D2,
221            1,
222            0,
223        ))
224    }
225}
226
227impl FromWorld for FallbackImageCubemap {
228    fn from_world(world: &mut bevy_ecs::prelude::World) -> Self {
229        let render_device = world.resource::<RenderDevice>();
230        let render_queue = world.resource::<RenderQueue>();
231        let default_sampler = world.resource::<DefaultImageSampler>();
232        Self(fallback_image_new(
233            render_device,
234            render_queue,
235            default_sampler,
236            TextureFormat::bevy_default(),
237            TextureViewDimension::Cube,
238            1,
239            255,
240        ))
241    }
242}
243
244/// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key
245///
246/// # WARNING
247/// Images using MSAA with sample count > 1 are not initialized with data, therefore,
248/// you shouldn't sample them before writing data to them first.
249#[derive(Resource, Deref, DerefMut, Default)]
250pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>);
251
252#[derive(SystemParam)]
253pub struct FallbackImageMsaa<'w> {
254    cache: ResMut<'w, FallbackImageFormatMsaaCache>,
255    render_device: Res<'w, RenderDevice>,
256    render_queue: Res<'w, RenderQueue>,
257    default_sampler: Res<'w, DefaultImageSampler>,
258}
259
260impl<'w> FallbackImageMsaa<'w> {
261    pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage {
262        self.cache.entry((sample_count, format)).or_insert_with(|| {
263            fallback_image_new(
264                &self.render_device,
265                &self.render_queue,
266                &self.default_sampler,
267                format,
268                TextureViewDimension::D2,
269                sample_count,
270                255,
271            )
272        })
273    }
274}