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