Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/basis.rs
6595 views
1
use basis_universal::{
2
BasisTextureType, DecodeFlags, TranscodeParameters, Transcoder, TranscoderTextureFormat,
3
};
4
use wgpu_types::{AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat};
5
6
use super::{CompressedImageFormats, Image, TextureError};
7
8
pub fn basis_buffer_to_image(
9
buffer: &[u8],
10
supported_compressed_formats: CompressedImageFormats,
11
is_srgb: bool,
12
) -> Result<Image, TextureError> {
13
let mut transcoder = Transcoder::new();
14
15
#[cfg(debug_assertions)]
16
if !transcoder.validate_file_checksums(buffer, true) {
17
return Err(TextureError::InvalidData("Invalid checksum".to_string()));
18
}
19
if !transcoder.validate_header(buffer) {
20
return Err(TextureError::InvalidData("Invalid header".to_string()));
21
}
22
23
let Some(image0_info) = transcoder.image_info(buffer, 0) else {
24
return Err(TextureError::InvalidData(
25
"Failed to get image info".to_string(),
26
));
27
};
28
29
// First deal with transcoding to the desired format
30
// FIXME: Use external metadata to transcode to more appropriate formats for 1- or 2-component sources
31
let (transcode_format, texture_format) =
32
get_transcoded_formats(supported_compressed_formats, is_srgb);
33
let basis_texture_format = transcoder.basis_texture_format(buffer);
34
if !basis_texture_format.can_transcode_to_format(transcode_format) {
35
return Err(TextureError::UnsupportedTextureFormat(format!(
36
"{basis_texture_format:?} cannot be transcoded to {transcode_format:?}",
37
)));
38
}
39
transcoder.prepare_transcoding(buffer).map_err(|_| {
40
TextureError::TranscodeError(format!(
41
"Failed to prepare for transcoding from {basis_texture_format:?}",
42
))
43
})?;
44
let mut transcoded = Vec::new();
45
46
let image_count = transcoder.image_count(buffer);
47
let texture_type = transcoder.basis_texture_type(buffer);
48
if texture_type == BasisTextureType::TextureTypeCubemapArray && !image_count.is_multiple_of(6) {
49
return Err(TextureError::InvalidData(format!(
50
"Basis file with cube map array texture with non-modulo 6 number of images: {image_count}",
51
)));
52
}
53
54
let image0_mip_level_count = transcoder.image_level_count(buffer, 0);
55
for image_index in 0..image_count {
56
if let Some(image_info) = transcoder.image_info(buffer, image_index)
57
&& texture_type == BasisTextureType::TextureType2D
58
&& (image_info.m_orig_width != image0_info.m_orig_width
59
|| image_info.m_orig_height != image0_info.m_orig_height)
60
{
61
return Err(TextureError::UnsupportedTextureFormat(format!(
62
"Basis file with multiple 2D textures with different sizes not supported. Image {} {}x{}, image 0 {}x{}",
63
image_index,
64
image_info.m_orig_width,
65
image_info.m_orig_height,
66
image0_info.m_orig_width,
67
image0_info.m_orig_height,
68
)));
69
}
70
let mip_level_count = transcoder.image_level_count(buffer, image_index);
71
if mip_level_count != image0_mip_level_count {
72
return Err(TextureError::InvalidData(format!(
73
"Array or volume texture has inconsistent number of mip levels. Image {image_index} has {mip_level_count} but image 0 has {image0_mip_level_count}",
74
)));
75
}
76
for level_index in 0..mip_level_count {
77
let mut data = transcoder
78
.transcode_image_level(
79
buffer,
80
transcode_format,
81
TranscodeParameters {
82
image_index,
83
level_index,
84
decode_flags: Some(DecodeFlags::HIGH_QUALITY),
85
..Default::default()
86
},
87
)
88
.map_err(|error| {
89
TextureError::TranscodeError(format!(
90
"Failed to transcode mip level {level_index} from {basis_texture_format:?} to {transcode_format:?}: {error:?}",
91
))
92
})?;
93
transcoded.append(&mut data);
94
}
95
}
96
97
// Then prepare the Image
98
let mut image = Image::default();
99
image.texture_descriptor.size = Extent3d {
100
width: image0_info.m_orig_width,
101
height: image0_info.m_orig_height,
102
depth_or_array_layers: image_count,
103
}
104
.physical_size(texture_format);
105
image.texture_descriptor.mip_level_count = image0_mip_level_count;
106
image.texture_descriptor.format = texture_format;
107
image.texture_descriptor.dimension = match texture_type {
108
BasisTextureType::TextureType2D
109
| BasisTextureType::TextureType2DArray
110
| BasisTextureType::TextureTypeCubemapArray => TextureDimension::D2,
111
BasisTextureType::TextureTypeVolume => TextureDimension::D3,
112
basis_texture_type => {
113
return Err(TextureError::UnsupportedTextureFormat(format!(
114
"{basis_texture_type:?}",
115
)))
116
}
117
};
118
image.data = Some(transcoded);
119
Ok(image)
120
}
121
122
pub fn get_transcoded_formats(
123
supported_compressed_formats: CompressedImageFormats,
124
is_srgb: bool,
125
) -> (TranscoderTextureFormat, TextureFormat) {
126
// NOTE: UASTC can be losslessly transcoded to ASTC4x4 and ASTC uses the same
127
// space as BC7 (128-bits per 4x4 texel block) so prefer ASTC over BC for
128
// transcoding speed and quality.
129
if supported_compressed_formats.contains(CompressedImageFormats::ASTC_LDR) {
130
(
131
TranscoderTextureFormat::ASTC_4x4_RGBA,
132
TextureFormat::Astc {
133
block: AstcBlock::B4x4,
134
channel: if is_srgb {
135
AstcChannel::UnormSrgb
136
} else {
137
AstcChannel::Unorm
138
},
139
},
140
)
141
} else if supported_compressed_formats.contains(CompressedImageFormats::BC) {
142
(
143
TranscoderTextureFormat::BC7_RGBA,
144
if is_srgb {
145
TextureFormat::Bc7RgbaUnormSrgb
146
} else {
147
TextureFormat::Bc7RgbaUnorm
148
},
149
)
150
} else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) {
151
(
152
TranscoderTextureFormat::ETC2_RGBA,
153
if is_srgb {
154
TextureFormat::Etc2Rgba8UnormSrgb
155
} else {
156
TextureFormat::Etc2Rgba8Unorm
157
},
158
)
159
} else {
160
(
161
TranscoderTextureFormat::RGBA32,
162
if is_srgb {
163
TextureFormat::Rgba8UnormSrgb
164
} else {
165
TextureFormat::Rgba8Unorm
166
},
167
)
168
}
169
}
170
171