1#[cfg(feature = "basis-universal")]
2use super::basis::*;
3#[cfg(feature = "dds")]
4use super::dds::*;
5#[cfg(feature = "ktx2")]
6use super::ktx2::*;
7
8use bevy_asset::{Asset, RenderAssetUsages};
9use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
10use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
11use bevy_reflect::std_traits::ReflectDefault;
12use bevy_reflect::Reflect;
13use core::hash::Hash;
14use derive_more::derive::{Display, Error, From};
15use serde::{Deserialize, Serialize};
16use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor};
17
18pub trait BevyDefault {
19 fn bevy_default() -> Self;
20}
21
22impl BevyDefault for TextureFormat {
23 fn bevy_default() -> Self {
24 TextureFormat::Rgba8UnormSrgb
25 }
26}
27
28pub const TEXTURE_ASSET_INDEX: u64 = 0;
29pub const SAMPLER_ASSET_INDEX: u64 = 1;
30
31#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
32pub enum ImageFormat {
33 #[cfg(feature = "basis-universal")]
34 Basis,
35 #[cfg(feature = "bmp")]
36 Bmp,
37 #[cfg(feature = "dds")]
38 Dds,
39 #[cfg(feature = "ff")]
40 Farbfeld,
41 #[cfg(feature = "gif")]
42 Gif,
43 #[cfg(feature = "exr")]
44 OpenExr,
45 #[cfg(feature = "hdr")]
46 Hdr,
47 #[cfg(feature = "ico")]
48 Ico,
49 #[cfg(feature = "jpeg")]
50 Jpeg,
51 #[cfg(feature = "ktx2")]
52 Ktx2,
53 #[cfg(feature = "png")]
54 Png,
55 #[cfg(feature = "pnm")]
56 Pnm,
57 #[cfg(feature = "qoi")]
58 Qoi,
59 #[cfg(feature = "tga")]
60 Tga,
61 #[cfg(feature = "tiff")]
62 Tiff,
63 #[cfg(feature = "webp")]
64 WebP,
65}
66
67macro_rules! feature_gate {
68 ($feature: tt, $value: ident) => {{
69 #[cfg(not(feature = $feature))]
70 {
71 bevy_utils::tracing::warn!("feature \"{}\" is not enabled", $feature);
72 return None;
73 }
74 #[cfg(feature = $feature)]
75 ImageFormat::$value
76 }};
77}
78
79impl ImageFormat {
80 pub const fn to_file_extensions(&self) -> &'static [&'static str] {
82 match self {
83 #[cfg(feature = "basis-universal")]
84 ImageFormat::Basis => &["basis"],
85 #[cfg(feature = "bmp")]
86 ImageFormat::Bmp => &["bmp"],
87 #[cfg(feature = "dds")]
88 ImageFormat::Dds => &["dds"],
89 #[cfg(feature = "ff")]
90 ImageFormat::Farbfeld => &["ff", "farbfeld"],
91 #[cfg(feature = "gif")]
92 ImageFormat::Gif => &["gif"],
93 #[cfg(feature = "exr")]
94 ImageFormat::OpenExr => &["exr"],
95 #[cfg(feature = "hdr")]
96 ImageFormat::Hdr => &["hdr"],
97 #[cfg(feature = "ico")]
98 ImageFormat::Ico => &["ico"],
99 #[cfg(feature = "jpeg")]
100 ImageFormat::Jpeg => &["jpg", "jpeg"],
101 #[cfg(feature = "ktx2")]
102 ImageFormat::Ktx2 => &["ktx2"],
103 #[cfg(feature = "pnm")]
104 ImageFormat::Pnm => &["pam", "pbm", "pgm", "ppm"],
105 #[cfg(feature = "png")]
106 ImageFormat::Png => &["png"],
107 #[cfg(feature = "qoi")]
108 ImageFormat::Qoi => &["qoi"],
109 #[cfg(feature = "tga")]
110 ImageFormat::Tga => &["tga"],
111 #[cfg(feature = "tiff")]
112 ImageFormat::Tiff => &["tif", "tiff"],
113 #[cfg(feature = "webp")]
114 ImageFormat::WebP => &["webp"],
115 #[allow(unreachable_patterns)]
117 _ => &[],
118 }
119 }
120
121 pub const fn to_mime_types(&self) -> &'static [&'static str] {
125 match self {
126 #[cfg(feature = "basis-universal")]
127 ImageFormat::Basis => &["image/basis", "image/x-basis"],
128 #[cfg(feature = "bmp")]
129 ImageFormat::Bmp => &["image/bmp", "image/x-bmp"],
130 #[cfg(feature = "dds")]
131 ImageFormat::Dds => &["image/vnd-ms.dds"],
132 #[cfg(feature = "hdr")]
133 ImageFormat::Hdr => &["image/vnd.radiance"],
134 #[cfg(feature = "gif")]
135 ImageFormat::Gif => &["image/gif"],
136 #[cfg(feature = "ff")]
137 ImageFormat::Farbfeld => &[],
138 #[cfg(feature = "ico")]
139 ImageFormat::Ico => &["image/x-icon"],
140 #[cfg(feature = "jpeg")]
141 ImageFormat::Jpeg => &["image/jpeg"],
142 #[cfg(feature = "ktx2")]
143 ImageFormat::Ktx2 => &["image/ktx2"],
144 #[cfg(feature = "png")]
145 ImageFormat::Png => &["image/png"],
146 #[cfg(feature = "qoi")]
147 ImageFormat::Qoi => &["image/qoi", "image/x-qoi"],
148 #[cfg(feature = "exr")]
149 ImageFormat::OpenExr => &["image/x-exr"],
150 #[cfg(feature = "pnm")]
151 ImageFormat::Pnm => &[
152 "image/x-portable-bitmap",
153 "image/x-portable-graymap",
154 "image/x-portable-pixmap",
155 "image/x-portable-anymap",
156 ],
157 #[cfg(feature = "tga")]
158 ImageFormat::Tga => &["image/x-targa", "image/x-tga"],
159 #[cfg(feature = "tiff")]
160 ImageFormat::Tiff => &["image/tiff"],
161 #[cfg(feature = "webp")]
162 ImageFormat::WebP => &["image/webp"],
163 #[allow(unreachable_patterns)]
165 _ => &[],
166 }
167 }
168
169 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
170 #[allow(unreachable_code)]
171 Some(match mime_type.to_ascii_lowercase().as_str() {
172 "image/basis" | "image/x-basis" => feature_gate!("basis-universal", Basis),
174 "image/bmp" | "image/x-bmp" => feature_gate!("bmp", Bmp),
175 "image/vnd-ms.dds" => feature_gate!("dds", Dds),
176 "image/vnd.radiance" => feature_gate!("hdr", Hdr),
177 "image/gif" => feature_gate!("gif", Gif),
178 "image/x-icon" => feature_gate!("ico", Ico),
179 "image/jpeg" => feature_gate!("jpeg", Jpeg),
180 "image/ktx2" => feature_gate!("ktx2", Ktx2),
181 "image/png" => feature_gate!("png", Png),
182 "image/qoi" | "image/x-qoi" => feature_gate!("qoi", Qoi),
183 "image/x-exr" => feature_gate!("exr", OpenExr),
184 "image/x-portable-bitmap"
185 | "image/x-portable-graymap"
186 | "image/x-portable-pixmap"
187 | "image/x-portable-anymap" => feature_gate!("pnm", Pnm),
188 "image/x-targa" | "image/x-tga" => feature_gate!("tga", Tga),
189 "image/tiff" => feature_gate!("tiff", Tiff),
190 "image/webp" => feature_gate!("webp", WebP),
191 _ => return None,
192 })
193 }
194
195 pub fn from_extension(extension: &str) -> Option<Self> {
196 #[allow(unreachable_code)]
197 Some(match extension.to_ascii_lowercase().as_str() {
198 "basis" => feature_gate!("basis-universal", Basis),
199 "bmp" => feature_gate!("bmp", Bmp),
200 "dds" => feature_gate!("dds", Dds),
201 "ff" | "farbfeld" => feature_gate!("ff", Farbfeld),
202 "gif" => feature_gate!("gif", Gif),
203 "exr" => feature_gate!("exr", OpenExr),
204 "hdr" => feature_gate!("hdr", Hdr),
205 "ico" => feature_gate!("ico", Ico),
206 "jpg" | "jpeg" => feature_gate!("jpeg", Jpeg),
207 "ktx2" => feature_gate!("ktx2", Ktx2),
208 "pam" | "pbm" | "pgm" | "ppm" => feature_gate!("pnm", Pnm),
209 "png" => feature_gate!("png", Png),
210 "qoi" => feature_gate!("qoi", Qoi),
211 "tga" => feature_gate!("tga", Tga),
212 "tif" | "tiff" => feature_gate!("tiff", Tiff),
213 "webp" => feature_gate!("webp", WebP),
214 _ => return None,
215 })
216 }
217
218 pub fn as_image_crate_format(&self) -> Option<image::ImageFormat> {
219 #[allow(unreachable_code)]
220 Some(match self {
221 #[cfg(feature = "bmp")]
222 ImageFormat::Bmp => image::ImageFormat::Bmp,
223 #[cfg(feature = "dds")]
224 ImageFormat::Dds => image::ImageFormat::Dds,
225 #[cfg(feature = "ff")]
226 ImageFormat::Farbfeld => image::ImageFormat::Farbfeld,
227 #[cfg(feature = "gif")]
228 ImageFormat::Gif => image::ImageFormat::Gif,
229 #[cfg(feature = "exr")]
230 ImageFormat::OpenExr => image::ImageFormat::OpenExr,
231 #[cfg(feature = "hdr")]
232 ImageFormat::Hdr => image::ImageFormat::Hdr,
233 #[cfg(feature = "ico")]
234 ImageFormat::Ico => image::ImageFormat::Ico,
235 #[cfg(feature = "jpeg")]
236 ImageFormat::Jpeg => image::ImageFormat::Jpeg,
237 #[cfg(feature = "png")]
238 ImageFormat::Png => image::ImageFormat::Png,
239 #[cfg(feature = "pnm")]
240 ImageFormat::Pnm => image::ImageFormat::Pnm,
241 #[cfg(feature = "qoi")]
242 ImageFormat::Qoi => image::ImageFormat::Qoi,
243 #[cfg(feature = "tga")]
244 ImageFormat::Tga => image::ImageFormat::Tga,
245 #[cfg(feature = "tiff")]
246 ImageFormat::Tiff => image::ImageFormat::Tiff,
247 #[cfg(feature = "webp")]
248 ImageFormat::WebP => image::ImageFormat::WebP,
249 #[cfg(feature = "basis-universal")]
250 ImageFormat::Basis => return None,
251 #[cfg(feature = "ktx2")]
252 ImageFormat::Ktx2 => return None,
253 #[allow(unreachable_patterns)]
255 _ => return None,
256 })
257 }
258
259 pub fn from_image_crate_format(format: image::ImageFormat) -> Option<ImageFormat> {
260 #[allow(unreachable_code)]
261 Some(match format {
262 image::ImageFormat::Bmp => feature_gate!("bmp", Bmp),
263 image::ImageFormat::Dds => feature_gate!("dds", Dds),
264 image::ImageFormat::Farbfeld => feature_gate!("ff", Farbfeld),
265 image::ImageFormat::Gif => feature_gate!("gif", Gif),
266 image::ImageFormat::OpenExr => feature_gate!("exr", OpenExr),
267 image::ImageFormat::Hdr => feature_gate!("hdr", Hdr),
268 image::ImageFormat::Ico => feature_gate!("ico", Ico),
269 image::ImageFormat::Jpeg => feature_gate!("jpeg", Jpeg),
270 image::ImageFormat::Png => feature_gate!("png", Png),
271 image::ImageFormat::Pnm => feature_gate!("pnm", Pnm),
272 image::ImageFormat::Qoi => feature_gate!("qoi", Qoi),
273 image::ImageFormat::Tga => feature_gate!("tga", Tga),
274 image::ImageFormat::Tiff => feature_gate!("tiff", Tiff),
275 image::ImageFormat::WebP => feature_gate!("webp", WebP),
276 _ => return None,
277 })
278 }
279}
280
281#[derive(Asset, Reflect, Debug, Clone)]
282#[reflect(opaque)]
283#[reflect(Default, Debug)]
284pub struct Image {
285 pub data: Vec<u8>,
286 pub texture_descriptor: wgpu::TextureDescriptor<'static>,
288 pub sampler: ImageSampler,
290 pub texture_view_descriptor: Option<TextureViewDescriptor<'static>>,
291 pub asset_usage: RenderAssetUsages,
292}
293
294#[derive(Debug, Default, Clone, Serialize, Deserialize)]
298pub enum ImageSampler {
299 #[default]
301 Default,
302 Descriptor(ImageSamplerDescriptor),
304}
305
306impl ImageSampler {
307 #[inline]
309 pub fn linear() -> ImageSampler {
310 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
311 }
312
313 #[inline]
315 pub fn nearest() -> ImageSampler {
316 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
317 }
318
319 pub fn get_or_init_descriptor(&mut self) -> &mut ImageSamplerDescriptor {
324 match self {
325 ImageSampler::Default => {
326 *self = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
327 match self {
328 ImageSampler::Descriptor(descriptor) => descriptor,
329 _ => unreachable!(),
330 }
331 }
332 ImageSampler::Descriptor(descriptor) => descriptor,
333 }
334 }
335}
336
337#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
343pub enum ImageAddressMode {
344 #[default]
349 ClampToEdge,
350 Repeat,
355 MirrorRepeat,
360 ClampToBorder,
366}
367
368#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
372pub enum ImageFilterMode {
373 #[default]
377 Nearest,
378 Linear,
382}
383
384#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
388pub enum ImageCompareFunction {
389 Never,
391 Less,
393 Equal,
397 LessEqual,
399 Greater,
401 NotEqual,
405 GreaterEqual,
407 Always,
409}
410
411#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
415pub enum ImageSamplerBorderColor {
416 TransparentBlack,
418 OpaqueBlack,
420 OpaqueWhite,
422 Zero,
428}
429
430#[derive(Clone, Debug, Serialize, Deserialize)]
438pub struct ImageSamplerDescriptor {
439 pub label: Option<String>,
440 pub address_mode_u: ImageAddressMode,
442 pub address_mode_v: ImageAddressMode,
444 pub address_mode_w: ImageAddressMode,
446 pub mag_filter: ImageFilterMode,
448 pub min_filter: ImageFilterMode,
450 pub mipmap_filter: ImageFilterMode,
452 pub lod_min_clamp: f32,
454 pub lod_max_clamp: f32,
456 pub compare: Option<ImageCompareFunction>,
458 pub anisotropy_clamp: u16,
460 pub border_color: Option<ImageSamplerBorderColor>,
462}
463
464impl Default for ImageSamplerDescriptor {
465 fn default() -> Self {
466 Self {
467 address_mode_u: Default::default(),
468 address_mode_v: Default::default(),
469 address_mode_w: Default::default(),
470 mag_filter: Default::default(),
471 min_filter: Default::default(),
472 mipmap_filter: Default::default(),
473 lod_min_clamp: 0.0,
474 lod_max_clamp: 32.0,
475 compare: None,
476 anisotropy_clamp: 1,
477 border_color: None,
478 label: None,
479 }
480 }
481}
482
483impl ImageSamplerDescriptor {
484 #[inline]
486 pub fn linear() -> ImageSamplerDescriptor {
487 ImageSamplerDescriptor {
488 mag_filter: ImageFilterMode::Linear,
489 min_filter: ImageFilterMode::Linear,
490 mipmap_filter: ImageFilterMode::Linear,
491 ..Default::default()
492 }
493 }
494
495 #[inline]
497 pub fn nearest() -> ImageSamplerDescriptor {
498 ImageSamplerDescriptor {
499 mag_filter: ImageFilterMode::Nearest,
500 min_filter: ImageFilterMode::Nearest,
501 mipmap_filter: ImageFilterMode::Nearest,
502 ..Default::default()
503 }
504 }
505
506 pub fn as_wgpu(&self) -> wgpu::SamplerDescriptor {
507 wgpu::SamplerDescriptor {
508 label: self.label.as_deref(),
509 address_mode_u: self.address_mode_u.into(),
510 address_mode_v: self.address_mode_v.into(),
511 address_mode_w: self.address_mode_w.into(),
512 mag_filter: self.mag_filter.into(),
513 min_filter: self.min_filter.into(),
514 mipmap_filter: self.mipmap_filter.into(),
515 lod_min_clamp: self.lod_min_clamp,
516 lod_max_clamp: self.lod_max_clamp,
517 compare: self.compare.map(Into::into),
518 anisotropy_clamp: self.anisotropy_clamp,
519 border_color: self.border_color.map(Into::into),
520 }
521 }
522}
523
524impl From<ImageAddressMode> for wgpu::AddressMode {
525 fn from(value: ImageAddressMode) -> Self {
526 match value {
527 ImageAddressMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
528 ImageAddressMode::Repeat => wgpu::AddressMode::Repeat,
529 ImageAddressMode::MirrorRepeat => wgpu::AddressMode::MirrorRepeat,
530 ImageAddressMode::ClampToBorder => wgpu::AddressMode::ClampToBorder,
531 }
532 }
533}
534
535impl From<ImageFilterMode> for wgpu::FilterMode {
536 fn from(value: ImageFilterMode) -> Self {
537 match value {
538 ImageFilterMode::Nearest => wgpu::FilterMode::Nearest,
539 ImageFilterMode::Linear => wgpu::FilterMode::Linear,
540 }
541 }
542}
543
544impl From<ImageCompareFunction> for wgpu::CompareFunction {
545 fn from(value: ImageCompareFunction) -> Self {
546 match value {
547 ImageCompareFunction::Never => wgpu::CompareFunction::Never,
548 ImageCompareFunction::Less => wgpu::CompareFunction::Less,
549 ImageCompareFunction::Equal => wgpu::CompareFunction::Equal,
550 ImageCompareFunction::LessEqual => wgpu::CompareFunction::LessEqual,
551 ImageCompareFunction::Greater => wgpu::CompareFunction::Greater,
552 ImageCompareFunction::NotEqual => wgpu::CompareFunction::NotEqual,
553 ImageCompareFunction::GreaterEqual => wgpu::CompareFunction::GreaterEqual,
554 ImageCompareFunction::Always => wgpu::CompareFunction::Always,
555 }
556 }
557}
558
559impl From<ImageSamplerBorderColor> for wgpu::SamplerBorderColor {
560 fn from(value: ImageSamplerBorderColor) -> Self {
561 match value {
562 ImageSamplerBorderColor::TransparentBlack => wgpu::SamplerBorderColor::TransparentBlack,
563 ImageSamplerBorderColor::OpaqueBlack => wgpu::SamplerBorderColor::OpaqueBlack,
564 ImageSamplerBorderColor::OpaqueWhite => wgpu::SamplerBorderColor::OpaqueWhite,
565 ImageSamplerBorderColor::Zero => wgpu::SamplerBorderColor::Zero,
566 }
567 }
568}
569
570impl From<wgpu::AddressMode> for ImageAddressMode {
571 fn from(value: wgpu::AddressMode) -> Self {
572 match value {
573 wgpu::AddressMode::ClampToEdge => ImageAddressMode::ClampToEdge,
574 wgpu::AddressMode::Repeat => ImageAddressMode::Repeat,
575 wgpu::AddressMode::MirrorRepeat => ImageAddressMode::MirrorRepeat,
576 wgpu::AddressMode::ClampToBorder => ImageAddressMode::ClampToBorder,
577 }
578 }
579}
580
581impl From<wgpu::FilterMode> for ImageFilterMode {
582 fn from(value: wgpu::FilterMode) -> Self {
583 match value {
584 wgpu::FilterMode::Nearest => ImageFilterMode::Nearest,
585 wgpu::FilterMode::Linear => ImageFilterMode::Linear,
586 }
587 }
588}
589
590impl From<wgpu::CompareFunction> for ImageCompareFunction {
591 fn from(value: wgpu::CompareFunction) -> Self {
592 match value {
593 wgpu::CompareFunction::Never => ImageCompareFunction::Never,
594 wgpu::CompareFunction::Less => ImageCompareFunction::Less,
595 wgpu::CompareFunction::Equal => ImageCompareFunction::Equal,
596 wgpu::CompareFunction::LessEqual => ImageCompareFunction::LessEqual,
597 wgpu::CompareFunction::Greater => ImageCompareFunction::Greater,
598 wgpu::CompareFunction::NotEqual => ImageCompareFunction::NotEqual,
599 wgpu::CompareFunction::GreaterEqual => ImageCompareFunction::GreaterEqual,
600 wgpu::CompareFunction::Always => ImageCompareFunction::Always,
601 }
602 }
603}
604
605impl From<wgpu::SamplerBorderColor> for ImageSamplerBorderColor {
606 fn from(value: wgpu::SamplerBorderColor) -> Self {
607 match value {
608 wgpu::SamplerBorderColor::TransparentBlack => ImageSamplerBorderColor::TransparentBlack,
609 wgpu::SamplerBorderColor::OpaqueBlack => ImageSamplerBorderColor::OpaqueBlack,
610 wgpu::SamplerBorderColor::OpaqueWhite => ImageSamplerBorderColor::OpaqueWhite,
611 wgpu::SamplerBorderColor::Zero => ImageSamplerBorderColor::Zero,
612 }
613 }
614}
615
616impl<'a> From<wgpu::SamplerDescriptor<'a>> for ImageSamplerDescriptor {
617 fn from(value: wgpu::SamplerDescriptor) -> Self {
618 ImageSamplerDescriptor {
619 label: value.label.map(ToString::to_string),
620 address_mode_u: value.address_mode_u.into(),
621 address_mode_v: value.address_mode_v.into(),
622 address_mode_w: value.address_mode_w.into(),
623 mag_filter: value.mag_filter.into(),
624 min_filter: value.min_filter.into(),
625 mipmap_filter: value.mipmap_filter.into(),
626 lod_min_clamp: value.lod_min_clamp,
627 lod_max_clamp: value.lod_max_clamp,
628 compare: value.compare.map(Into::into),
629 anisotropy_clamp: value.anisotropy_clamp,
630 border_color: value.border_color.map(Into::into),
631 }
632 }
633}
634
635impl Default for Image {
636 fn default() -> Self {
638 let format = TextureFormat::bevy_default();
639 let data = vec![255; format.pixel_size()];
640 Image {
641 data,
642 texture_descriptor: wgpu::TextureDescriptor {
643 size: Extent3d {
644 width: 1,
645 height: 1,
646 depth_or_array_layers: 1,
647 },
648 format,
649 dimension: TextureDimension::D2,
650 label: None,
651 mip_level_count: 1,
652 sample_count: 1,
653 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
654 view_formats: &[],
655 },
656 sampler: ImageSampler::Default,
657 texture_view_descriptor: None,
658 asset_usage: RenderAssetUsages::default(),
659 }
660 }
661}
662
663impl Image {
664 pub fn new(
670 size: Extent3d,
671 dimension: TextureDimension,
672 data: Vec<u8>,
673 format: TextureFormat,
674 asset_usage: RenderAssetUsages,
675 ) -> Self {
676 debug_assert_eq!(
677 size.volume() * format.pixel_size(),
678 data.len(),
679 "Pixel data, size and format have to match",
680 );
681 let mut image = Self {
682 data,
683 ..Default::default()
684 };
685 image.texture_descriptor.dimension = dimension;
686 image.texture_descriptor.size = size;
687 image.texture_descriptor.format = format;
688 image.asset_usage = asset_usage;
689 image
690 }
691
692 pub fn transparent() -> Image {
696 let format = TextureFormat::bevy_default();
700 debug_assert!(format.pixel_size() == 4);
701 let data = vec![255, 255, 255, 0];
702 Image {
703 data,
704 texture_descriptor: wgpu::TextureDescriptor {
705 size: Extent3d {
706 width: 1,
707 height: 1,
708 depth_or_array_layers: 1,
709 },
710 format,
711 dimension: TextureDimension::D2,
712 label: None,
713 mip_level_count: 1,
714 sample_count: 1,
715 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
716 view_formats: &[],
717 },
718 sampler: ImageSampler::Default,
719 texture_view_descriptor: None,
720 asset_usage: RenderAssetUsages::default(),
721 }
722 }
723
724 pub fn new_fill(
730 size: Extent3d,
731 dimension: TextureDimension,
732 pixel: &[u8],
733 format: TextureFormat,
734 asset_usage: RenderAssetUsages,
735 ) -> Self {
736 let mut value = Image::default();
737 value.texture_descriptor.format = format;
738 value.texture_descriptor.dimension = dimension;
739 value.asset_usage = asset_usage;
740 value.resize(size);
741
742 debug_assert_eq!(
743 pixel.len() % format.pixel_size(),
744 0,
745 "Must not have incomplete pixel data (pixel size is {}B).",
746 format.pixel_size(),
747 );
748 debug_assert!(
749 pixel.len() <= value.data.len(),
750 "Fill data must fit within pixel buffer (expected {}B).",
751 value.data.len(),
752 );
753
754 for current_pixel in value.data.chunks_exact_mut(pixel.len()) {
755 current_pixel.copy_from_slice(pixel);
756 }
757 value
758 }
759
760 #[inline]
762 pub fn width(&self) -> u32 {
763 self.texture_descriptor.size.width
764 }
765
766 #[inline]
768 pub fn height(&self) -> u32 {
769 self.texture_descriptor.size.height
770 }
771
772 #[inline]
774 pub fn aspect_ratio(&self) -> AspectRatio {
775 AspectRatio::try_from_pixels(self.width(), self.height()).expect(
776 "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
777 )
778 }
779
780 #[inline]
782 pub fn size_f32(&self) -> Vec2 {
783 Vec2::new(self.width() as f32, self.height() as f32)
784 }
785
786 #[inline]
788 pub fn size(&self) -> UVec2 {
789 UVec2::new(self.width(), self.height())
790 }
791
792 pub fn resize(&mut self, size: Extent3d) {
795 self.texture_descriptor.size = size;
796 self.data.resize(
797 size.volume() * self.texture_descriptor.format.pixel_size(),
798 0,
799 );
800 }
801
802 pub fn reinterpret_size(&mut self, new_size: Extent3d) {
808 assert_eq!(
809 new_size.volume(),
810 self.texture_descriptor.size.volume(),
811 "Incompatible sizes: old = {:?} new = {:?}",
812 self.texture_descriptor.size,
813 new_size
814 );
815
816 self.texture_descriptor.size = new_size;
817 }
818
819 pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
827 assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2);
829 assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1);
830 assert_eq!(self.height() % layers, 0);
831
832 self.reinterpret_size(Extent3d {
833 width: self.width(),
834 height: self.height() / layers,
835 depth_or_array_layers: layers,
836 });
837 }
838
839 pub fn convert(&self, new_format: TextureFormat) -> Option<Self> {
848 self.clone()
849 .try_into_dynamic()
850 .ok()
851 .and_then(|img| match new_format {
852 TextureFormat::R8Unorm => {
853 Some((image::DynamicImage::ImageLuma8(img.into_luma8()), false))
854 }
855 TextureFormat::Rg8Unorm => Some((
856 image::DynamicImage::ImageLumaA8(img.into_luma_alpha8()),
857 false,
858 )),
859 TextureFormat::Rgba8UnormSrgb => {
860 Some((image::DynamicImage::ImageRgba8(img.into_rgba8()), true))
861 }
862 _ => None,
863 })
864 .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb, self.asset_usage))
865 }
866
867 pub fn from_buffer(
870 #[cfg(all(debug_assertions, feature = "dds"))] name: String,
871 buffer: &[u8],
872 image_type: ImageType,
873 #[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats,
874 is_srgb: bool,
875 image_sampler: ImageSampler,
876 asset_usage: RenderAssetUsages,
877 ) -> Result<Image, TextureError> {
878 let format = image_type.to_image_format()?;
879
880 let mut image = match format {
887 #[cfg(feature = "basis-universal")]
888 ImageFormat::Basis => {
889 basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
890 }
891 #[cfg(feature = "dds")]
892 ImageFormat::Dds => dds_buffer_to_image(
893 #[cfg(debug_assertions)]
894 name,
895 buffer,
896 supported_compressed_formats,
897 is_srgb,
898 )?,
899 #[cfg(feature = "ktx2")]
900 ImageFormat::Ktx2 => {
901 ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?
902 }
903 #[allow(unreachable_patterns)]
904 _ => {
905 let image_crate_format = format
906 .as_image_crate_format()
907 .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?;
908 let mut reader = image::ImageReader::new(std::io::Cursor::new(buffer));
909 reader.set_format(image_crate_format);
910 reader.no_limits();
911 let dyn_img = reader.decode()?;
912 Self::from_dynamic(dyn_img, is_srgb, asset_usage)
913 }
914 };
915 image.sampler = image_sampler;
916 Ok(image)
917 }
918
919 pub fn is_compressed(&self) -> bool {
921 let format_description = self.texture_descriptor.format;
922 format_description
923 .required_features()
924 .contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC)
925 || format_description
926 .required_features()
927 .contains(wgpu::Features::TEXTURE_COMPRESSION_BC)
928 || format_description
929 .required_features()
930 .contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2)
931 }
932
933 #[inline(always)]
939 pub fn pixel_data_offset(&self, coords: UVec3) -> Option<usize> {
940 let width = self.texture_descriptor.size.width;
941 let height = self.texture_descriptor.size.height;
942 let depth = self.texture_descriptor.size.depth_or_array_layers;
943
944 let pixel_size = self.texture_descriptor.format.pixel_size();
945 let pixel_offset = match self.texture_descriptor.dimension {
946 TextureDimension::D3 => {
947 if coords.x >= width || coords.y >= height || coords.z >= depth {
948 return None;
949 }
950 coords.z * height * width + coords.y * width + coords.x
951 }
952 TextureDimension::D2 => {
953 if coords.x >= width || coords.y >= height {
954 return None;
955 }
956 coords.y * width + coords.x
957 }
958 TextureDimension::D1 => {
959 if coords.x >= width {
960 return None;
961 }
962 coords.x
963 }
964 };
965
966 Some(pixel_offset as usize * pixel_size)
967 }
968
969 #[inline(always)]
971 pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> {
972 let len = self.texture_descriptor.format.pixel_size();
973 self.pixel_data_offset(coords)
974 .map(|start| &self.data[start..(start + len)])
975 }
976
977 #[inline(always)]
979 pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> {
980 let len = self.texture_descriptor.format.pixel_size();
981 self.pixel_data_offset(coords)
982 .map(|start| &mut self.data[start..(start + len)])
983 }
984
985 #[inline(always)]
989 pub fn get_color_at_1d(&self, x: u32) -> Result<Color, TextureAccessError> {
990 if self.texture_descriptor.dimension != TextureDimension::D1 {
991 return Err(TextureAccessError::WrongDimension);
992 }
993 self.get_color_at_internal(UVec3::new(x, 0, 0))
994 }
995
996 #[inline(always)]
1021 pub fn get_color_at(&self, x: u32, y: u32) -> Result<Color, TextureAccessError> {
1022 if self.texture_descriptor.dimension != TextureDimension::D2 {
1023 return Err(TextureAccessError::WrongDimension);
1024 }
1025 self.get_color_at_internal(UVec3::new(x, y, 0))
1026 }
1027
1028 #[inline(always)]
1032 pub fn get_color_at_3d(&self, x: u32, y: u32, z: u32) -> Result<Color, TextureAccessError> {
1033 if self.texture_descriptor.dimension != TextureDimension::D3 {
1034 return Err(TextureAccessError::WrongDimension);
1035 }
1036 self.get_color_at_internal(UVec3::new(x, y, z))
1037 }
1038
1039 #[inline(always)]
1043 pub fn set_color_at_1d(&mut self, x: u32, color: Color) -> Result<(), TextureAccessError> {
1044 if self.texture_descriptor.dimension != TextureDimension::D1 {
1045 return Err(TextureAccessError::WrongDimension);
1046 }
1047 self.set_color_at_internal(UVec3::new(x, 0, 0), color)
1048 }
1049
1050 #[inline(always)]
1073 pub fn set_color_at(&mut self, x: u32, y: u32, color: Color) -> Result<(), TextureAccessError> {
1074 if self.texture_descriptor.dimension != TextureDimension::D2 {
1075 return Err(TextureAccessError::WrongDimension);
1076 }
1077 self.set_color_at_internal(UVec3::new(x, y, 0), color)
1078 }
1079
1080 #[inline(always)]
1084 pub fn set_color_at_3d(
1085 &mut self,
1086 x: u32,
1087 y: u32,
1088 z: u32,
1089 color: Color,
1090 ) -> Result<(), TextureAccessError> {
1091 if self.texture_descriptor.dimension != TextureDimension::D3 {
1092 return Err(TextureAccessError::WrongDimension);
1093 }
1094 self.set_color_at_internal(UVec3::new(x, y, z), color)
1095 }
1096
1097 #[inline(always)]
1098 fn get_color_at_internal(&self, coords: UVec3) -> Result<Color, TextureAccessError> {
1099 let Some(bytes) = self.pixel_bytes(coords) else {
1100 return Err(TextureAccessError::OutOfBounds {
1101 x: coords.x,
1102 y: coords.y,
1103 z: coords.z,
1104 });
1105 };
1106
1107 match self.texture_descriptor.format {
1110 TextureFormat::Rgba8UnormSrgb => Ok(Color::srgba(
1111 bytes[0] as f32 / u8::MAX as f32,
1112 bytes[1] as f32 / u8::MAX as f32,
1113 bytes[2] as f32 / u8::MAX as f32,
1114 bytes[3] as f32 / u8::MAX as f32,
1115 )),
1116 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => Ok(Color::linear_rgba(
1117 bytes[0] as f32 / u8::MAX as f32,
1118 bytes[1] as f32 / u8::MAX as f32,
1119 bytes[2] as f32 / u8::MAX as f32,
1120 bytes[3] as f32 / u8::MAX as f32,
1121 )),
1122 TextureFormat::Bgra8UnormSrgb => Ok(Color::srgba(
1123 bytes[2] as f32 / u8::MAX as f32,
1124 bytes[1] as f32 / u8::MAX as f32,
1125 bytes[0] as f32 / u8::MAX as f32,
1126 bytes[3] as f32 / u8::MAX as f32,
1127 )),
1128 TextureFormat::Bgra8Unorm => Ok(Color::linear_rgba(
1129 bytes[2] as f32 / u8::MAX as f32,
1130 bytes[1] as f32 / u8::MAX as f32,
1131 bytes[0] as f32 / u8::MAX as f32,
1132 bytes[3] as f32 / u8::MAX as f32,
1133 )),
1134 TextureFormat::Rgba32Float => Ok(Color::linear_rgba(
1135 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1136 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1137 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1138 f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1139 )),
1140 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1141 let (r, g, b, a) = (
1142 u16::from_le_bytes([bytes[0], bytes[1]]),
1143 u16::from_le_bytes([bytes[2], bytes[3]]),
1144 u16::from_le_bytes([bytes[4], bytes[5]]),
1145 u16::from_le_bytes([bytes[6], bytes[7]]),
1146 );
1147 Ok(Color::linear_rgba(
1148 (r as f64 / u16::MAX as f64) as f32,
1150 (g as f64 / u16::MAX as f64) as f32,
1151 (b as f64 / u16::MAX as f64) as f32,
1152 (a as f64 / u16::MAX as f64) as f32,
1153 ))
1154 }
1155 TextureFormat::Rgba32Uint => {
1156 let (r, g, b, a) = (
1157 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
1158 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
1159 u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
1160 u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
1161 );
1162 Ok(Color::linear_rgba(
1163 (r as f64 / u32::MAX as f64) as f32,
1165 (g as f64 / u32::MAX as f64) as f32,
1166 (b as f64 / u32::MAX as f64) as f32,
1167 (a as f64 / u32::MAX as f64) as f32,
1168 ))
1169 }
1170 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1173 let x = bytes[0] as f32 / u8::MAX as f32;
1174 Ok(Color::linear_rgb(x, x, x))
1175 }
1176 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1177 let x = u16::from_le_bytes([bytes[0], bytes[1]]);
1178 let x = (x as f64 / u16::MAX as f64) as f32;
1180 Ok(Color::linear_rgb(x, x, x))
1181 }
1182 TextureFormat::R32Uint => {
1183 let x = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1184 let x = (x as f64 / u32::MAX as f64) as f32;
1186 Ok(Color::linear_rgb(x, x, x))
1187 }
1188 TextureFormat::R32Float => {
1189 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1190 Ok(Color::linear_rgb(x, x, x))
1191 }
1192 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1193 let r = bytes[0] as f32 / u8::MAX as f32;
1194 let g = bytes[1] as f32 / u8::MAX as f32;
1195 Ok(Color::linear_rgb(r, g, 0.0))
1196 }
1197 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1198 let r = u16::from_le_bytes([bytes[0], bytes[1]]);
1199 let g = u16::from_le_bytes([bytes[2], bytes[3]]);
1200 let r = (r as f64 / u16::MAX as f64) as f32;
1202 let g = (g as f64 / u16::MAX as f64) as f32;
1203 Ok(Color::linear_rgb(r, g, 0.0))
1204 }
1205 TextureFormat::Rg32Uint => {
1206 let r = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1207 let g = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1208 let r = (r as f64 / u32::MAX as f64) as f32;
1210 let g = (g as f64 / u32::MAX as f64) as f32;
1211 Ok(Color::linear_rgb(r, g, 0.0))
1212 }
1213 TextureFormat::Rg32Float => {
1214 let r = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1215 let g = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1216 Ok(Color::linear_rgb(r, g, 0.0))
1217 }
1218 _ => Err(TextureAccessError::UnsupportedTextureFormat(
1219 self.texture_descriptor.format,
1220 )),
1221 }
1222 }
1223
1224 #[inline(always)]
1225 fn set_color_at_internal(
1226 &mut self,
1227 coords: UVec3,
1228 color: Color,
1229 ) -> Result<(), TextureAccessError> {
1230 let format = self.texture_descriptor.format;
1231
1232 let Some(bytes) = self.pixel_bytes_mut(coords) else {
1233 return Err(TextureAccessError::OutOfBounds {
1234 x: coords.x,
1235 y: coords.y,
1236 z: coords.z,
1237 });
1238 };
1239
1240 match format {
1243 TextureFormat::Rgba8UnormSrgb => {
1244 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1245 bytes[0] = (r * u8::MAX as f32) as u8;
1246 bytes[1] = (g * u8::MAX as f32) as u8;
1247 bytes[2] = (b * u8::MAX as f32) as u8;
1248 bytes[3] = (a * u8::MAX as f32) as u8;
1249 }
1250 TextureFormat::Rgba8Unorm | TextureFormat::Rgba8Uint => {
1251 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1252 bytes[0] = (r * u8::MAX as f32) as u8;
1253 bytes[1] = (g * u8::MAX as f32) as u8;
1254 bytes[2] = (b * u8::MAX as f32) as u8;
1255 bytes[3] = (a * u8::MAX as f32) as u8;
1256 }
1257 TextureFormat::Bgra8UnormSrgb => {
1258 let [r, g, b, a] = Srgba::from(color).to_f32_array();
1259 bytes[0] = (b * u8::MAX as f32) as u8;
1260 bytes[1] = (g * u8::MAX as f32) as u8;
1261 bytes[2] = (r * u8::MAX as f32) as u8;
1262 bytes[3] = (a * u8::MAX as f32) as u8;
1263 }
1264 TextureFormat::Bgra8Unorm => {
1265 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1266 bytes[0] = (b * u8::MAX as f32) as u8;
1267 bytes[1] = (g * u8::MAX as f32) as u8;
1268 bytes[2] = (r * u8::MAX as f32) as u8;
1269 bytes[3] = (a * u8::MAX as f32) as u8;
1270 }
1271 TextureFormat::Rgba32Float => {
1272 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1273 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1274 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1275 bytes[8..12].copy_from_slice(&f32::to_le_bytes(b));
1276 bytes[12..16].copy_from_slice(&f32::to_le_bytes(a));
1277 }
1278 TextureFormat::Rgba16Unorm | TextureFormat::Rgba16Uint => {
1279 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1280 let [r, g, b, a] = [
1281 (r * u16::MAX as f32) as u16,
1282 (g * u16::MAX as f32) as u16,
1283 (b * u16::MAX as f32) as u16,
1284 (a * u16::MAX as f32) as u16,
1285 ];
1286 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1287 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1288 bytes[4..6].copy_from_slice(&u16::to_le_bytes(b));
1289 bytes[6..8].copy_from_slice(&u16::to_le_bytes(a));
1290 }
1291 TextureFormat::Rgba32Uint => {
1292 let [r, g, b, a] = LinearRgba::from(color).to_f32_array();
1293 let [r, g, b, a] = [
1294 (r * u32::MAX as f32) as u32,
1295 (g * u32::MAX as f32) as u32,
1296 (b * u32::MAX as f32) as u32,
1297 (a * u32::MAX as f32) as u32,
1298 ];
1299 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1300 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1301 bytes[8..12].copy_from_slice(&u32::to_le_bytes(b));
1302 bytes[12..16].copy_from_slice(&u32::to_le_bytes(a));
1303 }
1304 TextureFormat::R8Unorm | TextureFormat::R8Uint => {
1305 let linear = LinearRgba::from(color);
1307 let luminance = Xyza::from(linear).y;
1308 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1309 bytes[0] = (r * u8::MAX as f32) as u8;
1310 }
1311 TextureFormat::R16Unorm | TextureFormat::R16Uint => {
1312 let linear = LinearRgba::from(color);
1314 let luminance = Xyza::from(linear).y;
1315 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1316 let r = (r * u16::MAX as f32) as u16;
1317 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1318 }
1319 TextureFormat::R32Uint => {
1320 let linear = LinearRgba::from(color);
1322 let luminance = Xyza::from(linear).y;
1323 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1324 let r = (r as f64 * u32::MAX as f64) as u32;
1326 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1327 }
1328 TextureFormat::R32Float => {
1329 let linear = LinearRgba::from(color);
1331 let luminance = Xyza::from(linear).y;
1332 let [r, _, _, _] = LinearRgba::gray(luminance).to_f32_array();
1333 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1334 }
1335 TextureFormat::Rg8Unorm | TextureFormat::Rg8Uint => {
1336 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1337 bytes[0] = (r * u8::MAX as f32) as u8;
1338 bytes[1] = (g * u8::MAX as f32) as u8;
1339 }
1340 TextureFormat::Rg16Unorm | TextureFormat::Rg16Uint => {
1341 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1342 let r = (r * u16::MAX as f32) as u16;
1343 let g = (g * u16::MAX as f32) as u16;
1344 bytes[0..2].copy_from_slice(&u16::to_le_bytes(r));
1345 bytes[2..4].copy_from_slice(&u16::to_le_bytes(g));
1346 }
1347 TextureFormat::Rg32Uint => {
1348 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1349 let r = (r as f64 * u32::MAX as f64) as u32;
1351 let g = (g as f64 * u32::MAX as f64) as u32;
1352 bytes[0..4].copy_from_slice(&u32::to_le_bytes(r));
1353 bytes[4..8].copy_from_slice(&u32::to_le_bytes(g));
1354 }
1355 TextureFormat::Rg32Float => {
1356 let [r, g, _, _] = LinearRgba::from(color).to_f32_array();
1357 bytes[0..4].copy_from_slice(&f32::to_le_bytes(r));
1358 bytes[4..8].copy_from_slice(&f32::to_le_bytes(g));
1359 }
1360 _ => {
1361 return Err(TextureAccessError::UnsupportedTextureFormat(
1362 self.texture_descriptor.format,
1363 ));
1364 }
1365 }
1366 Ok(())
1367 }
1368}
1369
1370#[derive(Clone, Copy, Debug)]
1371pub enum DataFormat {
1372 Rgb,
1373 Rgba,
1374 Rrr,
1375 Rrrg,
1376 Rg,
1377}
1378
1379#[derive(Clone, Copy, Debug)]
1380pub enum TranscodeFormat {
1381 Etc1s,
1382 Uastc(DataFormat),
1383 R8UnormSrgb,
1385 Rg8UnormSrgb,
1387 Rgb8,
1389}
1390
1391#[derive(Error, Display, Debug)]
1393pub enum TextureAccessError {
1394 #[display("out of bounds (x: {x}, y: {y}, z: {z})")]
1395 OutOfBounds { x: u32, y: u32, z: u32 },
1396 #[display("unsupported texture format: {_0:?}")]
1397 #[error(ignore)]
1398 UnsupportedTextureFormat(TextureFormat),
1399 #[display("attempt to access texture with different dimension")]
1400 WrongDimension,
1401}
1402
1403#[derive(Error, Display, Debug, From)]
1405#[error(ignore)]
1406pub enum TextureError {
1407 #[display("invalid image mime type: {_0}")]
1408 #[from(ignore)]
1409 InvalidImageMimeType(String),
1410 #[display("invalid image extension: {_0}")]
1411 #[from(ignore)]
1412 InvalidImageExtension(String),
1413 #[display("failed to load an image: {_0}")]
1414 ImageError(image::ImageError),
1415 #[display("unsupported texture format: {_0}")]
1416 #[from(ignore)]
1417 UnsupportedTextureFormat(String),
1418 #[display("supercompression not supported: {_0}")]
1419 #[from(ignore)]
1420 SuperCompressionNotSupported(String),
1421 #[display("failed to load an image: {_0}")]
1422 #[from(ignore)]
1423 SuperDecompressionError(String),
1424 #[display("invalid data: {_0}")]
1425 #[from(ignore)]
1426 InvalidData(String),
1427 #[display("transcode error: {_0}")]
1428 #[from(ignore)]
1429 TranscodeError(String),
1430 #[display("format requires transcoding: {_0:?}")]
1431 FormatRequiresTranscodingError(TranscodeFormat),
1432 #[display("only cubemaps with six faces are supported")]
1434 IncompleteCubemap,
1435}
1436
1437#[derive(Debug)]
1439pub enum ImageType<'a> {
1440 MimeType(&'a str),
1442 Extension(&'a str),
1444 Format(ImageFormat),
1446}
1447
1448impl<'a> ImageType<'a> {
1449 pub fn to_image_format(&self) -> Result<ImageFormat, TextureError> {
1450 match self {
1451 ImageType::MimeType(mime_type) => ImageFormat::from_mime_type(mime_type)
1452 .ok_or_else(|| TextureError::InvalidImageMimeType(mime_type.to_string())),
1453 ImageType::Extension(extension) => ImageFormat::from_extension(extension)
1454 .ok_or_else(|| TextureError::InvalidImageExtension(extension.to_string())),
1455 ImageType::Format(format) => Ok(*format),
1456 }
1457 }
1458}
1459
1460pub trait Volume {
1462 fn volume(&self) -> usize;
1463}
1464
1465impl Volume for Extent3d {
1466 fn volume(&self) -> usize {
1468 (self.width * self.height * self.depth_or_array_layers) as usize
1469 }
1470}
1471
1472pub trait TextureFormatPixelInfo {
1474 fn pixel_size(&self) -> usize;
1476}
1477
1478impl TextureFormatPixelInfo for TextureFormat {
1479 fn pixel_size(&self) -> usize {
1480 let info = self;
1481 match info.block_dimensions() {
1482 (1, 1) => info.block_copy_size(None).unwrap() as usize,
1483 _ => panic!("Using pixel_size for compressed textures is invalid"),
1484 }
1485 }
1486}
1487
1488bitflags::bitflags! {
1489 #[derive(Default, Clone, Copy, Eq, PartialEq, Debug)]
1490 #[repr(transparent)]
1491 pub struct CompressedImageFormats: u32 {
1492 const NONE = 0;
1493 const ASTC_LDR = 1 << 0;
1494 const BC = 1 << 1;
1495 const ETC2 = 1 << 2;
1496 }
1497}
1498
1499impl CompressedImageFormats {
1500 pub fn from_features(features: wgpu::Features) -> Self {
1501 let mut supported_compressed_formats = Self::default();
1502 if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) {
1503 supported_compressed_formats |= Self::ASTC_LDR;
1504 }
1505 if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
1506 supported_compressed_formats |= Self::BC;
1507 }
1508 if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) {
1509 supported_compressed_formats |= Self::ETC2;
1510 }
1511 supported_compressed_formats
1512 }
1513
1514 pub fn supports(&self, format: TextureFormat) -> bool {
1515 match format {
1516 TextureFormat::Bc1RgbaUnorm
1517 | TextureFormat::Bc1RgbaUnormSrgb
1518 | TextureFormat::Bc2RgbaUnorm
1519 | TextureFormat::Bc2RgbaUnormSrgb
1520 | TextureFormat::Bc3RgbaUnorm
1521 | TextureFormat::Bc3RgbaUnormSrgb
1522 | TextureFormat::Bc4RUnorm
1523 | TextureFormat::Bc4RSnorm
1524 | TextureFormat::Bc5RgUnorm
1525 | TextureFormat::Bc5RgSnorm
1526 | TextureFormat::Bc6hRgbUfloat
1527 | TextureFormat::Bc6hRgbFloat
1528 | TextureFormat::Bc7RgbaUnorm
1529 | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC),
1530 TextureFormat::Etc2Rgb8Unorm
1531 | TextureFormat::Etc2Rgb8UnormSrgb
1532 | TextureFormat::Etc2Rgb8A1Unorm
1533 | TextureFormat::Etc2Rgb8A1UnormSrgb
1534 | TextureFormat::Etc2Rgba8Unorm
1535 | TextureFormat::Etc2Rgba8UnormSrgb
1536 | TextureFormat::EacR11Unorm
1537 | TextureFormat::EacR11Snorm
1538 | TextureFormat::EacRg11Unorm
1539 | TextureFormat::EacRg11Snorm => self.contains(CompressedImageFormats::ETC2),
1540 TextureFormat::Astc { .. } => self.contains(CompressedImageFormats::ASTC_LDR),
1541 _ => true,
1542 }
1543 }
1544}
1545
1546#[cfg(test)]
1547mod test {
1548 use super::*;
1549
1550 #[test]
1551 fn image_size() {
1552 let size = Extent3d {
1553 width: 200,
1554 height: 100,
1555 depth_or_array_layers: 1,
1556 };
1557 let image = Image::new_fill(
1558 size,
1559 TextureDimension::D2,
1560 &[0, 0, 0, 255],
1561 TextureFormat::Rgba8Unorm,
1562 RenderAssetUsages::MAIN_WORLD,
1563 );
1564 assert_eq!(
1565 Vec2::new(size.width as f32, size.height as f32),
1566 image.size_f32()
1567 );
1568 }
1569
1570 #[test]
1571 fn image_default_size() {
1572 let image = Image::default();
1573 assert_eq!(UVec2::ONE, image.size());
1574 assert_eq!(Vec2::ONE, image.size_f32());
1575 }
1576
1577 #[test]
1578 fn on_edge_pixel_is_invalid() {
1579 let image = Image::new_fill(
1580 Extent3d {
1581 width: 5,
1582 height: 10,
1583 depth_or_array_layers: 1,
1584 },
1585 TextureDimension::D2,
1586 &[0, 0, 0, 255],
1587 TextureFormat::Rgba8Unorm,
1588 RenderAssetUsages::MAIN_WORLD,
1589 );
1590 assert!(matches!(image.get_color_at(4, 9), Ok(Color::BLACK)));
1591 assert!(matches!(
1592 image.get_color_at(0, 10),
1593 Err(TextureAccessError::OutOfBounds { x: 0, y: 10, z: 0 })
1594 ));
1595 assert!(matches!(
1596 image.get_color_at(5, 10),
1597 Err(TextureAccessError::OutOfBounds { x: 5, y: 10, z: 0 })
1598 ));
1599 }
1600}