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 pub texture_descriptor: wgpu::TextureDescriptor<'static>,
140 pub sampler: ImageSampler,
142 pub texture_view_descriptor: Option<TextureViewDescriptor<'static>>,
143 pub asset_usage: RenderAssetUsages,
144}
145
146#[derive(Debug, Default, Clone, Serialize, Deserialize)]
150pub enum ImageSampler {
151 #[default]
153 Default,
154 Descriptor(ImageSamplerDescriptor),
156}
157
158impl ImageSampler {
159 #[inline]
161 pub fn linear() -> ImageSampler {
162 ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
163 }
164
165 #[inline]
167 pub fn nearest() -> ImageSampler {
168 ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
169 }
170}
171
172#[derive(Resource, Debug, Clone, Deref, DerefMut)]
178pub struct DefaultImageSampler(pub(crate) Sampler);
179
180#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
186pub enum ImageAddressMode {
187 #[default]
192 ClampToEdge,
193 Repeat,
198 MirrorRepeat,
203 ClampToBorder,
209}
210
211#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
215pub enum ImageFilterMode {
216 #[default]
220 Nearest,
221 Linear,
225}
226
227#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
231pub enum ImageCompareFunction {
232 Never,
234 Less,
236 Equal,
240 LessEqual,
242 Greater,
244 NotEqual,
248 GreaterEqual,
250 Always,
252}
253
254#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
258pub enum ImageSamplerBorderColor {
259 TransparentBlack,
261 OpaqueBlack,
263 OpaqueWhite,
265 Zero,
271}
272
273#[derive(Clone, Debug, Serialize, Deserialize)]
280pub struct ImageSamplerDescriptor {
281 pub label: Option<String>,
282 pub address_mode_u: ImageAddressMode,
284 pub address_mode_v: ImageAddressMode,
286 pub address_mode_w: ImageAddressMode,
288 pub mag_filter: ImageFilterMode,
290 pub min_filter: ImageFilterMode,
292 pub mipmap_filter: ImageFilterMode,
294 pub lod_min_clamp: f32,
296 pub lod_max_clamp: f32,
298 pub compare: Option<ImageCompareFunction>,
300 pub anisotropy_clamp: u16,
302 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 #[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 #[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 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 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 pub fn transparent() -> Image {
538 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 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 #[inline]
604 pub fn width(&self) -> u32 {
605 self.texture_descriptor.size.width
606 }
607
608 #[inline]
610 pub fn height(&self) -> u32 {
611 self.texture_descriptor.size.height
612 }
613
614 #[inline]
616 pub fn aspect_ratio(&self) -> AspectRatio {
617 AspectRatio::from_pixels(self.width(), self.height())
618 }
619
620 #[inline]
622 pub fn size_f32(&self) -> Vec2 {
623 Vec2::new(self.width() as f32, self.height() as f32)
624 }
625
626 #[inline]
628 pub fn size(&self) -> UVec2 {
629 UVec2::new(self.width(), self.height())
630 }
631
632 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 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 pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
667 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 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 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 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 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 R8UnormSrgb,
788 Rg8UnormSrgb,
790 Rgb8,
792}
793
794#[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 #[error("only cubemaps with six faces are supported")]
817 IncompleteCubemap,
818}
819
820#[derive(Debug)]
822pub enum ImageType<'a> {
823 MimeType(&'a str),
825 Extension(&'a str),
827 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
843pub trait Volume {
845 fn volume(&self) -> usize;
846}
847
848impl Volume for Extent3d {
849 fn volume(&self) -> usize {
851 (self.width * self.height * self.depth_or_array_layers) as usize
852 }
853}
854
855pub trait TextureFormatPixelInfo {
857 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#[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 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 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}