bevy_render/texture/
image.rs

1#[cfg(feature = "basis-universal")]
2use super::basis::*;
3#[cfg(feature = "dds")]
4use super::dds::*;
5#[cfg(feature = "ktx2")]
6use super::ktx2::*;
7
8use crate::{
9    render_asset::{PrepareAssetError, RenderAsset, RenderAssetUsages},
10    render_resource::{Sampler, Texture, TextureView},
11    renderer::{RenderDevice, RenderQueue},
12    texture::BevyDefault,
13};
14use bevy_asset::Asset;
15use bevy_derive::{Deref, DerefMut};
16use bevy_ecs::system::{lifetimeless::SRes, Resource, SystemParamItem};
17use bevy_math::{AspectRatio, UVec2, Vec2};
18use bevy_reflect::prelude::*;
19use serde::{Deserialize, Serialize};
20use std::hash::Hash;
21use thiserror::Error;
22use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor};
23
24pub const TEXTURE_ASSET_INDEX: u64 = 0;
25pub const SAMPLER_ASSET_INDEX: u64 = 1;
26
27#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
28pub enum ImageFormat {
29    Avif,
30    Basis,
31    Bmp,
32    Dds,
33    Farbfeld,
34    Gif,
35    OpenExr,
36    Hdr,
37    Ico,
38    Jpeg,
39    Ktx2,
40    Png,
41    Pnm,
42    Tga,
43    Tiff,
44    WebP,
45}
46
47impl ImageFormat {
48    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
49        Some(match mime_type.to_ascii_lowercase().as_str() {
50            "image/avif" => ImageFormat::Avif,
51            "image/bmp" | "image/x-bmp" => ImageFormat::Bmp,
52            "image/vnd-ms.dds" => ImageFormat::Dds,
53            "image/vnd.radiance" => ImageFormat::Hdr,
54            "image/gif" => ImageFormat::Gif,
55            "image/x-icon" => ImageFormat::Ico,
56            "image/jpeg" => ImageFormat::Jpeg,
57            "image/ktx2" => ImageFormat::Ktx2,
58            "image/png" => ImageFormat::Png,
59            "image/x-exr" => ImageFormat::OpenExr,
60            "image/x-portable-bitmap"
61            | "image/x-portable-graymap"
62            | "image/x-portable-pixmap"
63            | "image/x-portable-anymap" => ImageFormat::Pnm,
64            "image/x-targa" | "image/x-tga" => ImageFormat::Tga,
65            "image/tiff" => ImageFormat::Tiff,
66            "image/webp" => ImageFormat::WebP,
67            _ => return None,
68        })
69    }
70
71    pub fn from_extension(extension: &str) -> Option<Self> {
72        Some(match extension.to_ascii_lowercase().as_str() {
73            "avif" => ImageFormat::Avif,
74            "basis" => ImageFormat::Basis,
75            "bmp" => ImageFormat::Bmp,
76            "dds" => ImageFormat::Dds,
77            "ff" | "farbfeld" => ImageFormat::Farbfeld,
78            "gif" => ImageFormat::Gif,
79            "exr" => ImageFormat::OpenExr,
80            "hdr" => ImageFormat::Hdr,
81            "ico" => ImageFormat::Ico,
82            "jpg" | "jpeg" => ImageFormat::Jpeg,
83            "ktx2" => ImageFormat::Ktx2,
84            "pbm" | "pam" | "ppm" | "pgm" => ImageFormat::Pnm,
85            "png" => ImageFormat::Png,
86            "tga" => ImageFormat::Tga,
87            "tif" | "tiff" => ImageFormat::Tiff,
88            "webp" => ImageFormat::WebP,
89            _ => return None,
90        })
91    }
92
93    pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
94        Some(match self {
95            ImageFormat::Avif => image::ImageFormat::Avif,
96            ImageFormat::Bmp => image::ImageFormat::Bmp,
97            ImageFormat::Dds => image::ImageFormat::Dds,
98            ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
99            ImageFormat::Gif => image::ImageFormat::Gif,
100            ImageFormat::OpenExr => image::ImageFormat::OpenExr,
101            ImageFormat::Hdr => image::ImageFormat::Hdr,
102            ImageFormat::Ico => image::ImageFormat::Ico,
103            ImageFormat::Jpeg => image::ImageFormat::Jpeg,
104            ImageFormat::Png => image::ImageFormat::Png,
105            ImageFormat::Pnm => image::ImageFormat::Pnm,
106            ImageFormat::Tga => image::ImageFormat::Tga,
107            ImageFormat::Tiff => image::ImageFormat::Tiff,
108            ImageFormat::WebP => image::ImageFormat::WebP,
109            ImageFormat::Basis | ImageFormat::Ktx2 => return None,
110        })
111    }
112
113    pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
114        Some(match format {
115            image::ImageFormat::Avif => ImageFormat::Avif,
116            image::ImageFormat::Bmp => ImageFormat::Bmp,
117            image::ImageFormat::Dds => ImageFormat::Dds,
118            image::ImageFormat::Farbfeld => ImageFormat::Farbfeld,
119            image::ImageFormat::Gif => ImageFormat::Gif,
120            image::ImageFormat::OpenExr => ImageFormat::OpenExr,
121            image::ImageFormat::Hdr => ImageFormat::Hdr,
122            image::ImageFormat::Ico => ImageFormat::Ico,
123            image::ImageFormat::Jpeg => ImageFormat::Jpeg,
124            image::ImageFormat::Png => ImageFormat::Png,
125            image::ImageFormat::Pnm => ImageFormat::Pnm,
126            image::ImageFormat::Tga => ImageFormat::Tga,
127            image::ImageFormat::Tiff => ImageFormat::Tiff,
128            image::ImageFormat::WebP => ImageFormat::WebP,
129            _ => return None,
130        })
131    }
132}
133
134#[derive(Asset, Reflect, Debug, Clone)]
135#[reflect_value(Default)]
136pub struct Image {
137    pub data: Vec<u8>,
138    // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors
139    pub texture_descriptor: wgpu::TextureDescriptor<'static>,
140    /// The [`ImageSampler`] to use during rendering.
141    pub sampler: ImageSampler,
142    pub texture_view_descriptor: Option<TextureViewDescriptor<'static>>,
143    pub asset_usage: RenderAssetUsages,
144}
145
146/// Used in [`Image`], this determines what image sampler to use when rendering. The default setting,
147/// [`ImageSampler::Default`], will read the sampler from the [`ImagePlugin`](super::ImagePlugin) at setup.
148/// Setting this to [`ImageSampler::Descriptor`] will override the global default descriptor for this [`Image`].
149#[derive(Debug, Default, Clone, Serialize, Deserialize)]
150pub enum ImageSampler {
151    /// Default image sampler, derived from the [`ImagePlugin`](super::ImagePlugin) setup.
152    #[default]
153    Default,
154    /// Custom sampler for this image which will override global default.
155    Descriptor(ImageSamplerDescriptor),
156}
157
158impl ImageSampler {
159    /// Returns an image sampler with [`ImageFilterMode::Linear`] min and mag filters
160    #[inline]
161    pub fn linear() -> ImageSampler {
162        ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
163    }
164
165    /// Returns an image sampler with [`ImageFilterMode::Nearest`] min and mag filters
166    #[inline]
167    pub fn nearest() -> ImageSampler {
168        ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
169    }
170}
171
172/// A rendering resource for the default image sampler which is set during renderer
173/// initialization.
174///
175/// The [`ImagePlugin`](super::ImagePlugin) can be set during app initialization to change the default
176/// image sampler.
177#[derive(Resource, Debug, Clone, Deref, DerefMut)]
178pub struct DefaultImageSampler(pub(crate) Sampler);
179
180/// How edges should be handled in texture addressing.
181///
182/// See [`ImageSamplerDescriptor`] for information how to configure this.
183///
184/// This type mirrors [`wgpu::AddressMode`].
185#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
186pub enum ImageAddressMode {
187    /// Clamp the value to the edge of the texture.
188    ///
189    /// -0.25 -> 0.0
190    /// 1.25  -> 1.0
191    #[default]
192    ClampToEdge,
193    /// Repeat the texture in a tiling fashion.
194    ///
195    /// -0.25 -> 0.75
196    /// 1.25 -> 0.25
197    Repeat,
198    /// Repeat the texture, mirroring it every repeat.
199    ///
200    /// -0.25 -> 0.25
201    /// 1.25 -> 0.75
202    MirrorRepeat,
203    /// Clamp the value to the border of the texture
204    /// Requires the wgpu feature [`wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER`].
205    ///
206    /// -0.25 -> border
207    /// 1.25 -> border
208    ClampToBorder,
209}
210
211/// Texel mixing mode when sampling between texels.
212///
213/// This type mirrors [`wgpu::FilterMode`].
214#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
215pub enum ImageFilterMode {
216    /// Nearest neighbor sampling.
217    ///
218    /// This creates a pixelated effect when used as a mag filter.
219    #[default]
220    Nearest,
221    /// Linear Interpolation.
222    ///
223    /// This makes textures smooth but blurry when used as a mag filter.
224    Linear,
225}
226
227/// Comparison function used for depth and stencil operations.
228///
229/// This type mirrors [`wgpu::CompareFunction`].
230#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
231pub enum ImageCompareFunction {
232    /// Function never passes
233    Never,
234    /// Function passes if new value less than existing value
235    Less,
236    /// Function passes if new value is equal to existing value. When using
237    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
238    /// output as `@invariant` to prevent artifacting.
239    Equal,
240    /// Function passes if new value is less than or equal to existing value
241    LessEqual,
242    /// Function passes if new value is greater than existing value
243    Greater,
244    /// Function passes if new value is not equal to existing value. When using
245    /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)`
246    /// output as `@invariant` to prevent artifacting.
247    NotEqual,
248    /// Function passes if new value is greater than or equal to existing value
249    GreaterEqual,
250    /// Function always passes
251    Always,
252}
253
254/// Color variation to use when the sampler addressing mode is [`ImageAddressMode::ClampToBorder`].
255///
256/// This type mirrors [`wgpu::SamplerBorderColor`].
257#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
258pub enum ImageSamplerBorderColor {
259    /// RGBA color `[0, 0, 0, 0]`.
260    TransparentBlack,
261    /// RGBA color `[0, 0, 0, 1]`.
262    OpaqueBlack,
263    /// RGBA color `[1, 1, 1, 1]`.
264    OpaqueWhite,
265    /// On the Metal wgpu backend, this is equivalent to [`Self::TransparentBlack`] for
266    /// textures that have an alpha component, and equivalent to [`Self::OpaqueBlack`]
267    /// for textures that do not have an alpha component. On other backends,
268    /// this is equivalent to [`Self::TransparentBlack`]. Requires
269    /// [`wgpu::Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web.
270    Zero,
271}
272
273/// Indicates to an [`ImageLoader`](super::ImageLoader) how an [`Image`] should be sampled.
274/// As this type is part of the [`ImageLoaderSettings`](super::ImageLoaderSettings),
275/// it will be serialized to an image asset `.meta` file which might require a migration in case of
276/// a breaking change.
277///
278/// This types mirrors [`wgpu::SamplerDescriptor`], but that might change in future versions.
279#[derive(Clone, Debug, Serialize, Deserialize)]
280pub struct ImageSamplerDescriptor {
281    pub label: Option<String>,
282    /// How to deal with out of bounds accesses in the u (i.e. x) direction.
283    pub address_mode_u: ImageAddressMode,
284    /// How to deal with out of bounds accesses in the v (i.e. y) direction.
285    pub address_mode_v: ImageAddressMode,
286    /// How to deal with out of bounds accesses in the w (i.e. z) direction.
287    pub address_mode_w: ImageAddressMode,
288    /// How to filter the texture when it needs to be magnified (made larger).
289    pub mag_filter: ImageFilterMode,
290    /// How to filter the texture when it needs to be minified (made smaller).
291    pub min_filter: ImageFilterMode,
292    /// How to filter between mip map levels
293    pub mipmap_filter: ImageFilterMode,
294    /// Minimum level of detail (i.e. mip level) to use.
295    pub lod_min_clamp: f32,
296    /// Maximum level of detail (i.e. mip level) to use.
297    pub lod_max_clamp: f32,
298    /// If this is enabled, this is a comparison sampler using the given comparison function.
299    pub compare: Option<ImageCompareFunction>,
300    /// Must be at least 1. If this is not 1, all filter modes must be linear.
301    pub anisotropy_clamp: u16,
302    /// Border color to use when `address_mode` is [`ImageAddressMode::ClampToBorder`].
303    pub border_color: Option<ImageSamplerBorderColor>,
304}
305
306impl Default for ImageSamplerDescriptor {
307    fn default() -> Self {
308        Self {
309            address_mode_u: Default::default(),
310            address_mode_v: Default::default(),
311            address_mode_w: Default::default(),
312            mag_filter: Default::default(),
313            min_filter: Default::default(),
314            mipmap_filter: Default::default(),
315            lod_min_clamp: 0.0,
316            lod_max_clamp: 32.0,
317            compare: None,
318            anisotropy_clamp: 1,
319            border_color: None,
320            label: None,
321        }
322    }
323}
324
325impl ImageSamplerDescriptor {
326    /// Returns a sampler descriptor with [`Linear`](crate::render_resource::FilterMode::Linear) min and mag filters
327    #[inline]
328    pub fn linear() -> ImageSamplerDescriptor {
329        ImageSamplerDescriptor {
330            mag_filter: ImageFilterMode::Linear,
331            min_filter: ImageFilterMode::Linear,
332            mipmap_filter: ImageFilterMode::Linear,
333            ..Default::default()
334        }
335    }
336
337    /// Returns a sampler descriptor with [`Nearest`](crate::render_resource::FilterMode::Nearest) min and mag filters
338    #[inline]
339    pub fn nearest() -> ImageSamplerDescriptor {
340        ImageSamplerDescriptor {
341            mag_filter: ImageFilterMode::Nearest,
342            min_filter: ImageFilterMode::Nearest,
343            mipmap_filter: ImageFilterMode::Nearest,
344            ..Default::default()
345        }
346    }
347
348    pub fn as_wgpu(&self) -> wgpu::SamplerDescriptor {
349        wgpu::SamplerDescriptor {
350            label: self.label.as_deref(),
351            address_mode_u: self.address_mode_u.into(),
352            address_mode_v: self.address_mode_v.into(),
353            address_mode_w: self.address_mode_w.into(),
354            mag_filter: self.mag_filter.into(),
355            min_filter: self.min_filter.into(),
356            mipmap_filter: self.mipmap_filter.into(),
357            lod_min_clamp: self.lod_min_clamp,
358            lod_max_clamp: self.lod_max_clamp,
359            compare: self.compare.map(Into::into),
360            anisotropy_clamp: self.anisotropy_clamp,
361            border_color: self.border_color.map(Into::into),
362        }
363    }
364}
365
366impl From<ImageAddressMode> for wgpu::AddressMode {
367    fn from(value: ImageAddressMode) -> Self {
368        match value {
369            ImageAddressMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
370            ImageAddressMode::Repeat => wgpu::AddressMode::Repeat,
371            ImageAddressMode::MirrorRepeat => wgpu::AddressMode::MirrorRepeat,
372            ImageAddressMode::ClampToBorder => wgpu::AddressMode::ClampToBorder,
373        }
374    }
375}
376
377impl From<ImageFilterMode> for wgpu::FilterMode {
378    fn from(value: ImageFilterMode) -> Self {
379        match value {
380            ImageFilterMode::Nearest => wgpu::FilterMode::Nearest,
381            ImageFilterMode::Linear => wgpu::FilterMode::Linear,
382        }
383    }
384}
385
386impl From<ImageCompareFunction> for wgpu::CompareFunction {
387    fn from(value: ImageCompareFunction) -> Self {
388        match value {
389            ImageCompareFunction::Never => wgpu::CompareFunction::Never,
390            ImageCompareFunction::Less => wgpu::CompareFunction::Less,
391            ImageCompareFunction::Equal => wgpu::CompareFunction::Equal,
392            ImageCompareFunction::LessEqual => wgpu::CompareFunction::LessEqual,
393            ImageCompareFunction::Greater => wgpu::CompareFunction::Greater,
394            ImageCompareFunction::NotEqual => wgpu::CompareFunction::NotEqual,
395            ImageCompareFunction::GreaterEqual => wgpu::CompareFunction::GreaterEqual,
396            ImageCompareFunction::Always => wgpu::CompareFunction::Always,
397        }
398    }
399}
400
401impl From<ImageSamplerBorderColor> for wgpu::SamplerBorderColor {
402    fn from(value: ImageSamplerBorderColor) -> Self {
403        match value {
404            ImageSamplerBorderColor::TransparentBlack => wgpu::SamplerBorderColor::TransparentBlack,
405            ImageSamplerBorderColor::OpaqueBlack => wgpu::SamplerBorderColor::OpaqueBlack,
406            ImageSamplerBorderColor::OpaqueWhite => wgpu::SamplerBorderColor::OpaqueWhite,
407            ImageSamplerBorderColor::Zero => wgpu::SamplerBorderColor::Zero,
408        }
409    }
410}
411
412impl From<wgpu::AddressMode> for ImageAddressMode {
413    fn from(value: wgpu::AddressMode) -> Self {
414        match value {
415            wgpu::AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
416            wgpu::AddressMode::Repeat => ImageAddressMode::Repeat,
417            wgpu::AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
418            wgpu::AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
419        }
420    }
421}
422
423impl From<wgpu::FilterMode> for ImageFilterMode {
424    fn from(value: wgpu::FilterMode) -> Self {
425        match value {
426            wgpu::FilterMode::Nearest => ImageFilterMode::Nearest,
427            wgpu::FilterMode::Linear => ImageFilterMode::Linear,
428        }
429    }
430}
431
432impl From<wgpu::CompareFunction> for ImageCompareFunction {
433    fn from(value: wgpu::CompareFunction) -> Self {
434        match value {
435            wgpu::CompareFunction::Never => ImageCompareFunction::Never,
436            wgpu::CompareFunction::Less => ImageCompareFunction::Less,
437            wgpu::CompareFunction::Equal => ImageCompareFunction::Equal,
438            wgpu::CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
439            wgpu::CompareFunction::Greater => ImageCompareFunction::Greater,
440            wgpu::CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
441            wgpu::CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
442            wgpu::CompareFunction::Always => ImageCompareFunction::Always,
443        }
444    }
445}
446
447impl From<wgpu::SamplerBorderColor> for ImageSamplerBorderColor {
448    fn from(value: wgpu::SamplerBorderColor) -> Self {
449        match value {
450            wgpu::SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
451            wgpu::SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
452            wgpu::SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
453            wgpu::SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
454        }
455    }
456}
457
458impl<'a> From<wgpu::SamplerDescriptor<'a>> for ImageSamplerDescriptor {
459    fn from(value: wgpu::SamplerDescriptor) -> Self {
460        ImageSamplerDescriptor {
461            label: value.label.map(|l| l.to_string()),
462            address_mode_u: value.address_mode_u.into(),
463            address_mode_v: value.address_mode_v.into(),
464            address_mode_w: value.address_mode_w.into(),
465            mag_filter: value.mag_filter.into(),
466            min_filter: value.min_filter.into(),
467            mipmap_filter: value.mipmap_filter.into(),
468            lod_min_clamp: value.lod_min_clamp,
469            lod_max_clamp: value.lod_max_clamp,
470            compare: value.compare.map(Into::into),
471            anisotropy_clamp: value.anisotropy_clamp,
472            border_color: value.border_color.map(Into::into),
473        }
474    }
475}
476
477impl Default for Image {
478    /// default is a 1x1x1 all '1.0' texture
479    fn default() -> Self {
480        let format = TextureFormat::bevy_default();
481        let data = vec![255; format.pixel_size()];
482        Image {
483            data,
484            texture_descriptor: wgpu::TextureDescriptor {
485                size: Extent3d {
486                    width: 1,
487                    height: 1,
488                    depth_or_array_layers: 1,
489                },
490                format,
491                dimension: TextureDimension::D2,
492                label: None,
493                mip_level_count: 1,
494                sample_count: 1,
495                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
496                view_formats: &[],
497            },
498            sampler: ImageSampler::Default,
499            texture_view_descriptor: None,
500            asset_usage: RenderAssetUsages::default(),
501        }
502    }
503}
504
505impl Image {
506    /// Creates a new image from raw binary data and the corresponding metadata.
507    ///
508    /// # Panics
509    /// Panics if the length of the `data`, volume of the `size` and the size of the `format`
510    /// do not match.
511    pub fn new(
512        size: Extent3d,
513        dimension: TextureDimension,
514        data: Vec<u8>,
515        format: TextureFormat,
516        asset_usage: RenderAssetUsages,
517    ) -> Self {
518        debug_assert_eq!(
519            size.volume() * format.pixel_size(),
520            data.len(),
521            "Pixel data, size and format have to match",
522        );
523        let mut image = Self {
524            data,
525            ..Default::default()
526        };
527        image.texture_descriptor.dimension = dimension;
528        image.texture_descriptor.size = size;
529        image.texture_descriptor.format = format;
530        image.asset_usage = asset_usage;
531        image
532    }
533
534    /// A transparent white 1x1x1 image.
535    ///
536    /// Contrast to [`Image::default`], which is opaque.
537    pub fn transparent() -> Image {
538        // We rely on the default texture format being RGBA8UnormSrgb
539        // when constructing a transparent color from bytes.
540        // If this changes, this function will need to be updated.
541        let format = TextureFormat::bevy_default();
542        debug_assert!(format.pixel_size() == 4);
543        let data = vec![255, 255, 255, 0];
544        Image {
545            data,
546            texture_descriptor: wgpu::TextureDescriptor {
547                size: Extent3d {
548                    width: 1,
549                    height: 1,
550                    depth_or_array_layers: 1,
551                },
552                format,
553                dimension: TextureDimension::D2,
554                label: None,
555                mip_level_count: 1,
556                sample_count: 1,
557                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
558                view_formats: &[],
559            },
560            sampler: ImageSampler::Default,
561            texture_view_descriptor: None,
562            asset_usage: RenderAssetUsages::default(),
563        }
564    }
565
566    /// Creates a new image from raw binary data and the corresponding metadata, by filling
567    /// the image data with the `pixel` data repeated multiple times.
568    ///
569    /// # Panics
570    /// Panics if the size of the `format` is not a multiple of the length of the `pixel` data.
571    pub fn new_fill(
572        size: Extent3d,
573        dimension: TextureDimension,
574        pixel: &[u8],
575        format: TextureFormat,
576        asset_usage: RenderAssetUsages,
577    ) -> Self {
578        let mut value = Image::default();
579        value.texture_descriptor.format = format;
580        value.texture_descriptor.dimension = dimension;
581        value.asset_usage = asset_usage;
582        value.resize(size);
583
584        debug_assert_eq!(
585            pixel.len() % format.pixel_size(),
586            0,
587            "Must not have incomplete pixel data (pixel size is {}B).",
588            format.pixel_size(),
589        );
590        debug_assert!(
591            pixel.len() <= value.data.len(),
592            "Fill data must fit within pixel buffer (expected {}B).",
593            value.data.len(),
594        );
595
596        for current_pixel in value.data.chunks_exact_mut(pixel.len()) {
597            current_pixel.copy_from_slice(pixel);
598        }
599        value
600    }
601
602    /// Returns the width of a 2D image.
603    #[inline]
604    pub fn width(&self) -> u32 {
605        self.texture_descriptor.size.width
606    }
607
608    /// Returns the height of a 2D image.
609    #[inline]
610    pub fn height(&self) -> u32 {
611        self.texture_descriptor.size.height
612    }
613
614    /// Returns the aspect ratio (width / height) of a 2D image.
615    #[inline]
616    pub fn aspect_ratio(&self) -> AspectRatio {
617        AspectRatio::from_pixels(self.width(), self.height())
618    }
619
620    /// Returns the size of a 2D image as f32.
621    #[inline]
622    pub fn size_f32(&self) -> Vec2 {
623        Vec2::new(self.width() as f32, self.height() as f32)
624    }
625
626    /// Returns the size of a 2D image.
627    #[inline]
628    pub fn size(&self) -> UVec2 {
629        UVec2::new(self.width(), self.height())
630    }
631
632    /// Resizes the image to the new size, by removing information or appending 0 to the `data`.
633    /// Does not properly resize the contents of the image, but only its internal `data` buffer.
634    pub fn resize(&mut self, size: Extent3d) {
635        self.texture_descriptor.size = size;
636        self.data.resize(
637            size.volume() * self.texture_descriptor.format.pixel_size(),
638            0,
639        );
640    }
641
642    /// Changes the `size`, asserting that the total number of data elements (pixels) remains the
643    /// same.
644    ///
645    /// # Panics
646    /// Panics if the `new_size` does not have the same volume as to old one.
647    pub fn reinterpret_size(&mut self, new_size: Extent3d) {
648        assert_eq!(
649            new_size.volume(),
650            self.texture_descriptor.size.volume(),
651            "Incompatible sizes: old = {:?} new = {:?}",
652            self.texture_descriptor.size,
653            new_size
654        );
655
656        self.texture_descriptor.size = new_size;
657    }
658
659    /// Takes a 2D image containing vertically stacked images of the same size, and reinterprets
660    /// it as a 2D array texture, where each of the stacked images becomes one layer of the
661    /// array. This is primarily for use with the `texture2DArray` shader uniform type.
662    ///
663    /// # Panics
664    /// Panics if the texture is not 2D, has more than one layers or is not evenly dividable into
665    /// the `layers`.
666    pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
667        // Must be a stacked image, and the height must be divisible by layers.
668        assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2);
669        assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1);
670        assert_eq!(self.height() % layers, 0);
671
672        self.reinterpret_size(Extent3d {
673            width: self.width(),
674            height: self.height() / layers,
675            depth_or_array_layers: layers,
676        });
677    }
678
679    /// Convert a texture from a format to another. Only a few formats are
680    /// supported as input and output:
681    /// - `TextureFormat::R8Unorm`
682    /// - `TextureFormat::Rg8Unorm`
683    /// - `TextureFormat::Rgba8UnormSrgb`
684    ///
685    /// To get [`Image`] as a [`image::DynamicImage`] see:
686    /// [`Image::try_into_dynamic`].
687    pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
688        self.clone()
689            .try_into_dynamic()
690            .ok()
691            .and_then(|img| match new_format {
692                TextureFormat::R8Unorm => {
693                    Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
694                }
695                TextureFormat::Rg8Unorm => Some((
696                    image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
697                    false,
698                )),
699                TextureFormat::Rgba8UnormSrgb => {
700                    Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
701                }
702                _ => None,
703            })
704            .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
705    }
706
707    /// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image`
708    /// crate
709    pub fn from_buffer(
710        #[cfg(all(debug_assertions, feature = "dds"))] name: String,
711        buffer: &[u8],
712        image_type: ImageType,
713        #[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats,
714        is_srgb: bool,
715        image_sampler: ImageSampler,
716        asset_usage: RenderAssetUsages,
717    ) -> Result<Image, TextureError> {
718        let format = image_type.to_image_format()?;
719
720        // Load the image in the expected format.
721        // Some formats like PNG allow for R or RG textures too, so the texture
722        // format needs to be determined. For RGB textures an alpha channel
723        // needs to be added, so the image data needs to be converted in those
724        // cases.
725
726        let mut image = match format {
727            #[cfg(feature = "basis-universal")]
728            ImageFormat::Basis => {
729                basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
730            }
731            #[cfg(feature = "dds")]
732            ImageFormat::Dds => dds_buffer_to_image(
733                #[cfg(debug_assertions)]
734                name,
735                buffer,
736                supported_compressed_formats,
737                is_srgb,
738            )?,
739            #[cfg(feature = "ktx2")]
740            ImageFormat::Ktx2 => {
741                ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
742            }
743            _ => {
744                let image_crate_format = format
745                    .as_image_crate_format()
746                    .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
747                let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
748                reader.set_format(image_crate_format);
749                reader.no_limits();
750                let dyn_img = reader.decode()?;
751                Self::from_dynamic(dyn_img, is_srgb, asset_usage)
752            }
753        };
754        image.sampler = image_sampler;
755        Ok(image)
756    }
757
758    /// Whether the texture format is compressed or uncompressed
759    pub fn is_compressed(&self) -> bool {
760        let format_description = self.texture_descriptor.format;
761        format_description
762            .required_features()
763            .contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC)
764            || format_description
765                .required_features()
766                .contains(wgpu::Features::TEXTURE_COMPRESSION_BC)
767            || format_description
768                .required_features()
769                .contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2)
770    }
771}
772
773#[derive(Clone, Copy, Debug)]
774pub enum DataFormat {
775    Rgb,
776    Rgba,
777    Rrr,
778    Rrrg,
779    Rg,
780}
781
782#[derive(Clone, Copy, Debug)]
783pub enum TranscodeFormat {
784    Etc1s,
785    Uastc(DataFormat),
786    // Has to be transcoded to R8Unorm for use with `wgpu`
787    R8UnormSrgb,
788    // Has to be transcoded to R8G8Unorm for use with `wgpu`
789    Rg8UnormSrgb,
790    // Has to be transcoded to Rgba8 for use with `wgpu`
791    Rgb8,
792}
793
794/// An error that occurs when loading a texture
795#[derive(Error, Debug)]
796pub enum TextureError {
797    #[error("invalid image mime type: {0}")]
798    InvalidImageMimeType(String),
799    #[error("invalid image extension: {0}")]
800    InvalidImageExtension(String),
801    #[error("failed to load an image: {0}")]
802    ImageError(#[from] image::ImageError),
803    #[error("unsupported texture format: {0}")]
804    UnsupportedTextureFormat(String),
805    #[error("supercompression not supported: {0}")]
806    SuperCompressionNotSupported(String),
807    #[error("failed to load an image: {0}")]
808    SuperDecompressionError(String),
809    #[error("invalid data: {0}")]
810    InvalidData(String),
811    #[error("transcode error: {0}")]
812    TranscodeError(String),
813    #[error("format requires transcoding: {0:?}")]
814    FormatRequiresTranscodingError(TranscodeFormat),
815    /// Only cubemaps with six faces are supported.
816    #[error("only cubemaps with six faces are supported")]
817    IncompleteCubemap,
818}
819
820/// The type of a raw image buffer.
821#[derive(Debug)]
822pub enum ImageType<'a> {
823    /// The mime type of an image, for example `"image/png"`.
824    MimeType(&'a str),
825    /// The extension of an image file, for example `"png"`.
826    Extension(&'a str),
827    /// The direct format of the image
828    Format(ImageFormat),
829}
830
831impl<'a> ImageType<'a> {
832    pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
833        match self {
834            ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
835                .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
836            ImageType::Extension(extension) => ImageFormat::from_extension(extension)
837                .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
838            ImageType::Format(format) => Ok(*format),
839        }
840    }
841}
842
843/// Used to calculate the volume of an item.
844pub trait Volume {
845    fn volume(&self) -> usize;
846}
847
848impl Volume for Extent3d {
849    /// Calculates the volume of the [`Extent3d`].
850    fn volume(&self) -> usize {
851        (self.width * self.height * self.depth_or_array_layers) as usize
852    }
853}
854
855/// Extends the wgpu [`TextureFormat`] with information about the pixel.
856pub trait TextureFormatPixelInfo {
857    /// Returns the size of a pixel in bytes of the format.
858    fn pixel_size(&self) -> usize;
859}
860
861impl TextureFormatPixelInfo for TextureFormat {
862    fn pixel_size(&self) -> usize {
863        let info = self;
864        match info.block_dimensions() {
865            (1, 1) => info.block_copy_size(None).unwrap() as usize,
866            _ => panic!("Using pixel_size for compressed textures is invalid"),
867        }
868    }
869}
870
871/// The GPU-representation of an [`Image`].
872/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's size.
873#[derive(Debug, Clone)]
874pub struct GpuImage {
875    pub texture: Texture,
876    pub texture_view: TextureView,
877    pub texture_format: TextureFormat,
878    pub sampler: Sampler,
879    pub size: UVec2,
880    pub mip_level_count: u32,
881}
882
883impl RenderAsset for GpuImage {
884    type SourceAsset = Image;
885    type Param = (
886        SRes<RenderDevice>,
887        SRes<RenderQueue>,
888        SRes<DefaultImageSampler>,
889    );
890
891    #[inline]
892    fn asset_usage(image: &Self::SourceAsset) -> RenderAssetUsages {
893        image.asset_usage
894    }
895
896    #[inline]
897    fn byte_len(image: &Self::SourceAsset) -> Option<usize> {
898        Some(image.data.len())
899    }
900
901    /// Converts the extracted image into a [`GpuImage`].
902    fn prepare_asset(
903        image: Self::SourceAsset,
904        (render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
905    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
906        let texture = render_device.create_texture_with_data(
907            render_queue,
908            &image.texture_descriptor,
909            // TODO: Is this correct? Do we need to use `MipMajor` if it's a ktx2 file?
910            wgpu::util::TextureDataOrder::default(),
911            &image.data,
912        );
913
914        let size = image.size();
915        let texture_view = texture.create_view(
916            image
917                .texture_view_descriptor
918                .or_else(|| Some(TextureViewDescriptor::default()))
919                .as_ref()
920                .unwrap(),
921        );
922        let sampler = match image.sampler {
923            ImageSampler::Default => (***default_sampler).clone(),
924            ImageSampler::Descriptor(descriptor) => {
925                render_device.create_sampler(&descriptor.as_wgpu())
926            }
927        };
928
929        Ok(GpuImage {
930            texture,
931            texture_view,
932            texture_format: image.texture_descriptor.format,
933            sampler,
934            size,
935            mip_level_count: image.texture_descriptor.mip_level_count,
936        })
937    }
938}
939
940bitflags::bitflags! {
941    #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
942    #[repr(transparent)]
943    pub struct CompressedImageFormats: u32 {
944        const NONE     = 0;
945        const ASTC_LDR = 1 << 0;
946        const BC       = 1 << 1;
947        const ETC2     = 1 << 2;
948    }
949}
950
951impl CompressedImageFormats {
952    pub fn from_features(features: wgpu::Features) -> Self {
953        let mut supported_compressed_formats = Self::default();
954        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) {
955            supported_compressed_formats |= Self::ASTC_LDR;
956        }
957        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
958            supported_compressed_formats |= Self::BC;
959        }
960        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) {
961            supported_compressed_formats |= Self::ETC2;
962        }
963        supported_compressed_formats
964    }
965
966    pub fn supports(&self, format: TextureFormat) -> bool {
967        match format {
968            TextureFormat::Bc1RgbaUnorm
969            | TextureFormat::Bc1RgbaUnormSrgb
970            | TextureFormat::Bc2RgbaUnorm
971            | TextureFormat::Bc2RgbaUnormSrgb
972            | TextureFormat::Bc3RgbaUnorm
973            | TextureFormat::Bc3RgbaUnormSrgb
974            | TextureFormat::Bc4RUnorm
975            | TextureFormat::Bc4RSnorm
976            | TextureFormat::Bc5RgUnorm
977            | TextureFormat::Bc5RgSnorm
978            | TextureFormat::Bc6hRgbUfloat
979            | TextureFormat::Bc6hRgbFloat
980            | TextureFormat::Bc7RgbaUnorm
981            | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
982            TextureFormat::Etc2Rgb8Unorm
983            | TextureFormat::Etc2Rgb8UnormSrgb
984            | TextureFormat::Etc2Rgb8A1Unorm
985            | TextureFormat::Etc2Rgb8A1UnormSrgb
986            | TextureFormat::Etc2Rgba8Unorm
987            | TextureFormat::Etc2Rgba8UnormSrgb
988            | TextureFormat::EacR11Unorm
989            | TextureFormat::EacR11Snorm
990            | TextureFormat::EacRg11Unorm
991            | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
992            TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
993            _ => true,
994        }
995    }
996}
997
998#[cfg(test)]
999mod test {
1000    use super::*;
1001
1002    #[test]
1003    fn image_size() {
1004        let size = Extent3d {
1005            width: 200,
1006            height: 100,
1007            depth_or_array_layers: 1,
1008        };
1009        let image = Image::new_fill(
1010            size,
1011            TextureDimension::D2,
1012            &[0, 0, 0, 255],
1013            TextureFormat::Rgba8Unorm,
1014            RenderAssetUsages::MAIN_WORLD,
1015        );
1016        assert_eq!(
1017            Vec2::new(size.width as f32, size.height as f32),
1018            image.size_f32()
1019        );
1020    }
1021
1022    #[test]
1023    fn image_default_size() {
1024        let image = Image::default();
1025        assert_eq!(UVec2::ONE, image.size());
1026        assert_eq!(Vec2::ONE, image.size_f32());
1027    }
1028}