bevy_image/
ktx2.rs

1#[cfg(any(feature = "flate2", feature = "ruzstd"))]
2use std::io::Read;
3
4#[cfg(feature = "basis-universal")]
5use basis_universal::{
6    DecodeFlags, LowLevelUastcTranscoder, SliceParametersUastc, TranscoderBlockFormat,
7};
8use bevy_color::Srgba;
9use bevy_utils::default;
10#[cfg(any(feature = "flate2", feature = "ruzstd"))]
11use ktx2::SupercompressionScheme;
12use ktx2::{
13    BasicDataFormatDescriptor, ChannelTypeQualifiers, ColorModel, DataFormatDescriptorHeader,
14    Header, SampleInformation,
15};
16use wgpu::{
17    AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor,
18    TextureViewDimension,
19};
20
21use super::{CompressedImageFormats, DataFormat, Image, TextureError, TranscodeFormat};
22
23#[cfg(feature = "ktx2")]
24pub fn ktx2_buffer_to_image(
25    buffer: &[u8],
26    supported_compressed_formats: CompressedImageFormats,
27    is_srgb: bool,
28) -> Result<Image, TextureError> {
29    let ktx2 = ktx2::Reader::new(buffer)
30        .map_err(|err| TextureError::InvalidData(format!("Failed to parse ktx2 file: {err:?}")))?;
31    let Header {
32        pixel_width: width,
33        pixel_height: height,
34        pixel_depth: depth,
35        layer_count,
36        face_count,
37        level_count,
38        supercompression_scheme,
39        ..
40    } = ktx2.header();
41    let layer_count = layer_count.max(1);
42    let face_count = face_count.max(1);
43    let depth = depth.max(1);
44
45    // Handle supercompression
46    let mut levels = Vec::new();
47    if let Some(supercompression_scheme) = supercompression_scheme {
48        for (_level, _level_data) in ktx2.levels().enumerate() {
49            match supercompression_scheme {
50                #[cfg(feature = "flate2")]
51                SupercompressionScheme::ZLIB => {
52                    let mut decoder = flate2::bufread::ZlibDecoder::new(_level_data);
53                    let mut decompressed = Vec::new();
54                    decoder.read_to_end(&mut decompressed).map_err(|err| {
55                        TextureError::SuperDecompressionError(format!(
56                            "Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}",
57                        ))
58                    })?;
59                    levels.push(decompressed);
60                }
61                #[cfg(feature = "ruzstd")]
62                SupercompressionScheme::Zstandard => {
63                    let mut cursor = std::io::Cursor::new(_level_data);
64                    let mut decoder = ruzstd::StreamingDecoder::new(&mut cursor)
65                        .map_err(|err| TextureError::SuperDecompressionError(err.to_string()))?;
66                    let mut decompressed = Vec::new();
67                    decoder.read_to_end(&mut decompressed).map_err(|err| {
68                        TextureError::SuperDecompressionError(format!(
69                            "Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}",
70                        ))
71                    })?;
72                    levels.push(decompressed);
73                }
74                _ => {
75                    return Err(TextureError::SuperDecompressionError(format!(
76                        "Unsupported supercompression scheme: {supercompression_scheme:?}",
77                    )));
78                }
79            }
80        }
81    } else {
82        levels = ktx2.levels().map(<[u8]>::to_vec).collect();
83    }
84
85    // Identify the format
86    let texture_format = ktx2_get_texture_format(&ktx2, is_srgb).or_else(|error| match error {
87        // Transcode if needed and supported
88        TextureError::FormatRequiresTranscodingError(transcode_format) => {
89            let mut transcoded = vec![Vec::default(); levels.len()];
90            let texture_format = match transcode_format {
91                TranscodeFormat::R8UnormSrgb => {
92                    let (mut original_width, mut original_height) = (width, height);
93
94                    for (level, level_data) in levels.iter().enumerate() {
95                        transcoded[level] = level_data
96                            .iter()
97                            .copied()
98                            .map(|v| (Srgba::gamma_function(v as f32 / 255.) * 255.).floor() as u8)
99                            .collect::<Vec<u8>>();
100
101                        // Next mip dimensions are half the current, minimum 1x1
102                        original_width = (original_width / 2).max(1);
103                        original_height = (original_height / 2).max(1);
104                    }
105
106                    TextureFormat::R8Unorm
107                }
108                TranscodeFormat::Rg8UnormSrgb => {
109                    let (mut original_width, mut original_height) = (width, height);
110
111                    for (level, level_data) in levels.iter().enumerate() {
112                        transcoded[level] = level_data
113                            .iter()
114                            .copied()
115                            .map(|v| (Srgba::gamma_function(v as f32 / 255.) * 255.).floor() as u8)
116                            .collect::<Vec<u8>>();
117
118                        // Next mip dimensions are half the current, minimum 1x1
119                        original_width = (original_width / 2).max(1);
120                        original_height = (original_height / 2).max(1);
121                    }
122
123                    TextureFormat::Rg8Unorm
124                }
125                TranscodeFormat::Rgb8 => {
126                    let mut rgba = vec![255u8; width as usize * height as usize * 4];
127                    for (level, level_data) in levels.iter().enumerate() {
128                        let n_pixels = (width as usize >> level).max(1) * (height as usize >> level).max(1);
129
130                        let mut offset = 0;
131                        for _layer in 0..layer_count {
132                            for _face in 0..face_count {
133                                for i in 0..n_pixels {
134                                    rgba[i * 4] = level_data[offset];
135                                    rgba[i * 4 + 1] = level_data[offset + 1];
136                                    rgba[i * 4 + 2] = level_data[offset + 2];
137                                    offset += 3;
138                                }
139                                transcoded[level].extend_from_slice(&rgba[0..n_pixels * 4]);
140                            }
141                        }
142                    }
143
144                    if is_srgb {
145                        TextureFormat::Rgba8UnormSrgb
146                    } else {
147                        TextureFormat::Rgba8Unorm
148                    }
149                }
150                #[cfg(feature = "basis-universal")]
151                TranscodeFormat::Uastc(data_format) => {
152                    let (transcode_block_format, texture_format) =
153                        get_transcoded_formats(supported_compressed_formats, data_format, is_srgb);
154                    let texture_format_info = texture_format;
155                    let (block_width_pixels, block_height_pixels) = (
156                        texture_format_info.block_dimensions().0,
157                        texture_format_info.block_dimensions().1,
158                    );
159                    // Texture is not a depth or stencil format, it is possible to pass `None` and unwrap
160                    let block_bytes = texture_format_info.block_copy_size(None).unwrap();
161
162                    let transcoder = LowLevelUastcTranscoder::new();
163                    for (level, level_data) in levels.iter().enumerate() {
164                        let (level_width, level_height) = (
165                            (width >> level as u32).max(1),
166                            (height >> level as u32).max(1),
167                        );
168                        let (num_blocks_x, num_blocks_y) = (
169                            level_width.div_ceil(block_width_pixels) .max(1),
170                            level_height.div_ceil(block_height_pixels) .max(1),
171                        );
172                        let level_bytes = (num_blocks_x * num_blocks_y * block_bytes) as usize;
173
174                        let mut offset = 0;
175                        for _layer in 0..layer_count {
176                            for _face in 0..face_count {
177                                // NOTE: SliceParametersUastc does not implement Clone nor Copy so
178                                // it has to be created per use
179                                let slice_parameters = SliceParametersUastc {
180                                    num_blocks_x,
181                                    num_blocks_y,
182                                    has_alpha: false,
183                                    original_width: level_width,
184                                    original_height: level_height,
185                                };
186                                transcoder
187                                    .transcode_slice(
188                                        &level_data[offset..(offset + level_bytes)],
189                                        slice_parameters,
190                                        DecodeFlags::HIGH_QUALITY,
191                                        transcode_block_format,
192                                    )
193                                    .map(|mut transcoded_level| transcoded[level].append(&mut transcoded_level))
194                                    .map_err(|error| {
195                                        TextureError::SuperDecompressionError(format!(
196                                            "Failed to transcode mip level {level} from UASTC to {transcode_block_format:?}: {error:?}",
197                                        ))
198                                    })?;
199                                offset += level_bytes;
200                            }
201                        }
202                    }
203                    texture_format
204                }
205                // ETC1S is a subset of ETC1 which is a subset of ETC2
206                // TODO: Implement transcoding
207                TranscodeFormat::Etc1s => {
208                    let texture_format = if is_srgb {
209                        TextureFormat::Etc2Rgb8UnormSrgb
210                    } else {
211                        TextureFormat::Etc2Rgb8Unorm
212                    };
213                    if !supported_compressed_formats.supports(texture_format) {
214                        return Err(error);
215                    }
216                    transcoded = levels.to_vec();
217                    texture_format
218                }
219                #[cfg(not(feature = "basis-universal"))]
220                _ => return Err(error),
221            };
222            levels = transcoded;
223            Ok(texture_format)
224        }
225        _ => Err(error),
226    })?;
227    if !supported_compressed_formats.supports(texture_format) {
228        return Err(TextureError::UnsupportedTextureFormat(format!(
229            "Format not supported by this GPU: {texture_format:?}",
230        )));
231    }
232
233    // Reorder data from KTX2 MipXLayerYFaceZ to wgpu LayerYFaceZMipX
234    let texture_format_info = texture_format;
235    let (block_width_pixels, block_height_pixels) = (
236        texture_format_info.block_dimensions().0 as usize,
237        texture_format_info.block_dimensions().1 as usize,
238    );
239    // Texture is not a depth or stencil format, it is possible to pass `None` and unwrap
240    let block_bytes = texture_format_info.block_copy_size(None).unwrap() as usize;
241
242    let mut wgpu_data = vec![Vec::default(); (layer_count * face_count) as usize];
243    for (level, level_data) in levels.iter().enumerate() {
244        let (level_width, level_height, level_depth) = (
245            (width as usize >> level).max(1),
246            (height as usize >> level).max(1),
247            (depth as usize >> level).max(1),
248        );
249        let (num_blocks_x, num_blocks_y) = (
250            level_width.div_ceil(block_width_pixels).max(1),
251            level_height.div_ceil(block_height_pixels).max(1),
252        );
253        let level_bytes = num_blocks_x * num_blocks_y * level_depth * block_bytes;
254
255        let mut index = 0;
256        for _layer in 0..layer_count {
257            for _face in 0..face_count {
258                let offset = index * level_bytes;
259                wgpu_data[index].extend_from_slice(&level_data[offset..(offset + level_bytes)]);
260                index += 1;
261            }
262        }
263    }
264
265    // Assign the data and fill in the rest of the metadata now the possible
266    // error cases have been handled
267    let mut image = Image::default();
268    image.texture_descriptor.format = texture_format;
269    image.data = wgpu_data.into_iter().flatten().collect::<Vec<_>>();
270    image.texture_descriptor.size = Extent3d {
271        width,
272        height,
273        depth_or_array_layers: if layer_count > 1 || face_count > 1 {
274            layer_count * face_count
275        } else {
276            depth
277        }
278        .max(1),
279    }
280    .physical_size(texture_format);
281    image.texture_descriptor.mip_level_count = level_count;
282    image.texture_descriptor.dimension = if depth > 1 {
283        TextureDimension::D3
284    } else if image.is_compressed() || height > 1 {
285        TextureDimension::D2
286    } else {
287        TextureDimension::D1
288    };
289    let mut dimension = None;
290    if face_count == 6 {
291        dimension = Some(if layer_count > 1 {
292            TextureViewDimension::CubeArray
293        } else {
294            TextureViewDimension::Cube
295        });
296    } else if layer_count > 1 {
297        dimension = Some(TextureViewDimension::D2Array);
298    } else if depth > 1 {
299        dimension = Some(TextureViewDimension::D3);
300    }
301    if dimension.is_some() {
302        image.texture_view_descriptor = Some(TextureViewDescriptor {
303            dimension,
304            ..default()
305        });
306    }
307    Ok(image)
308}
309
310#[cfg(feature = "basis-universal")]
311pub fn get_transcoded_formats(
312    supported_compressed_formats: CompressedImageFormats,
313    data_format: DataFormat,
314    is_srgb: bool,
315) -> (TranscoderBlockFormat, TextureFormat) {
316    match data_format {
317        DataFormat::Rrr => {
318            if supported_compressed_formats.contains(CompressedImageFormats::BC) {
319                (TranscoderBlockFormat::BC4, TextureFormat::Bc4RUnorm)
320            } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) {
321                (
322                    TranscoderBlockFormat::ETC2_EAC_R11,
323                    TextureFormat::EacR11Unorm,
324                )
325            } else {
326                (TranscoderBlockFormat::RGBA32, TextureFormat::R8Unorm)
327            }
328        }
329        DataFormat::Rrrg | DataFormat::Rg => {
330            if supported_compressed_formats.contains(CompressedImageFormats::BC) {
331                (TranscoderBlockFormat::BC5, TextureFormat::Bc5RgUnorm)
332            } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) {
333                (
334                    TranscoderBlockFormat::ETC2_EAC_RG11,
335                    TextureFormat::EacRg11Unorm,
336                )
337            } else {
338                (TranscoderBlockFormat::RGBA32, TextureFormat::Rg8Unorm)
339            }
340        }
341        // NOTE: Rgba16Float should be transcoded to BC6H/ASTC_HDR. Neither are supported by
342        // basis-universal, nor is ASTC_HDR supported by wgpu
343        DataFormat::Rgb | DataFormat::Rgba => {
344            // NOTE: UASTC can be losslessly transcoded to ASTC4x4 and ASTC uses the same
345            // space as BC7 (128-bits per 4x4 texel block) so prefer ASTC over BC for
346            // transcoding speed and quality.
347            if supported_compressed_formats.contains(CompressedImageFormats::ASTC_LDR) {
348                (
349                    TranscoderBlockFormat::ASTC_4x4,
350                    TextureFormat::Astc {
351                        block: AstcBlock::B4x4,
352                        channel: if is_srgb {
353                            AstcChannel::UnormSrgb
354                        } else {
355                            AstcChannel::Unorm
356                        },
357                    },
358                )
359            } else if supported_compressed_formats.contains(CompressedImageFormats::BC) {
360                (
361                    TranscoderBlockFormat::BC7,
362                    if is_srgb {
363                        TextureFormat::Bc7RgbaUnormSrgb
364                    } else {
365                        TextureFormat::Bc7RgbaUnorm
366                    },
367                )
368            } else if supported_compressed_formats.contains(CompressedImageFormats::ETC2) {
369                (
370                    TranscoderBlockFormat::ETC2_RGBA,
371                    if is_srgb {
372                        TextureFormat::Etc2Rgba8UnormSrgb
373                    } else {
374                        TextureFormat::Etc2Rgba8Unorm
375                    },
376                )
377            } else {
378                (
379                    TranscoderBlockFormat::RGBA32,
380                    if is_srgb {
381                        TextureFormat::Rgba8UnormSrgb
382                    } else {
383                        TextureFormat::Rgba8Unorm
384                    },
385                )
386            }
387        }
388    }
389}
390
391#[cfg(feature = "ktx2")]
392pub fn ktx2_get_texture_format<Data: AsRef<[u8]>>(
393    ktx2: &ktx2::Reader<Data>,
394    is_srgb: bool,
395) -> Result<TextureFormat, TextureError> {
396    if let Some(format) = ktx2.header().format {
397        return ktx2_format_to_texture_format(format, is_srgb);
398    }
399
400    for data_format_descriptor in ktx2.data_format_descriptors() {
401        if data_format_descriptor.header == DataFormatDescriptorHeader::BASIC {
402            let basic_data_format_descriptor =
403                BasicDataFormatDescriptor::parse(data_format_descriptor.data)
404                    .map_err(|err| TextureError::InvalidData(format!("KTX2: {err:?}")))?;
405            let sample_information = basic_data_format_descriptor
406                .sample_information()
407                .collect::<Vec<_>>();
408            return ktx2_dfd_to_texture_format(
409                &basic_data_format_descriptor,
410                &sample_information,
411                is_srgb,
412            );
413        }
414    }
415
416    Err(TextureError::UnsupportedTextureFormat(
417        "Unknown".to_string(),
418    ))
419}
420
421enum DataType {
422    Unorm,
423    UnormSrgb,
424    Snorm,
425    Float,
426    Uint,
427    Sint,
428}
429
430// This can be obtained from core::mem::transmute::<f32, u32>(1.0f32). It is used for identifying
431// normalized sample types as in Unorm or Snorm.
432const F32_1_AS_U32: u32 = 1065353216;
433
434fn sample_information_to_data_type(
435    sample: &SampleInformation,
436    is_srgb: bool,
437) -> Result<DataType, TextureError> {
438    // Exponent flag not supported
439    if sample
440        .channel_type_qualifiers
441        .contains(ChannelTypeQualifiers::EXPONENT)
442    {
443        return Err(TextureError::UnsupportedTextureFormat(
444            "Unsupported KTX2 channel type qualifier: exponent".to_string(),
445        ));
446    }
447    Ok(
448        if sample
449            .channel_type_qualifiers
450            .contains(ChannelTypeQualifiers::FLOAT)
451        {
452            // If lower bound of range is 0 then unorm, else if upper bound is 1.0f32 as u32
453            if sample
454                .channel_type_qualifiers
455                .contains(ChannelTypeQualifiers::SIGNED)
456            {
457                if sample.upper == F32_1_AS_U32 {
458                    DataType::Snorm
459                } else {
460                    DataType::Float
461                }
462            } else if is_srgb {
463                DataType::UnormSrgb
464            } else {
465                DataType::Unorm
466            }
467        } else if sample
468            .channel_type_qualifiers
469            .contains(ChannelTypeQualifiers::SIGNED)
470        {
471            DataType::Sint
472        } else {
473            DataType::Uint
474        },
475    )
476}
477
478#[cfg(feature = "ktx2")]
479pub fn ktx2_dfd_to_texture_format(
480    data_format_descriptor: &BasicDataFormatDescriptor,
481    sample_information: &[SampleInformation],
482    is_srgb: bool,
483) -> Result<TextureFormat, TextureError> {
484    Ok(match data_format_descriptor.color_model {
485        Some(ColorModel::RGBSDA) => {
486            match sample_information.len() {
487                1 => {
488                    // Only red channel allowed
489                    if sample_information[0].channel_type != 0 {
490                        return Err(TextureError::UnsupportedTextureFormat(
491                            "Only red-component single-component KTX2 RGBSDA formats supported"
492                                .to_string(),
493                        ));
494                    }
495
496                    let sample = &sample_information[0];
497                    let data_type = sample_information_to_data_type(sample, false)?;
498                    match sample.bit_length {
499                        8 => match data_type {
500                            DataType::Unorm => TextureFormat::R8Unorm,
501                            DataType::UnormSrgb => {
502                                return Err(TextureError::UnsupportedTextureFormat(
503                                    "UnormSrgb not supported for R8".to_string(),
504                                ));
505                            }
506                            DataType::Snorm => TextureFormat::R8Snorm,
507                            DataType::Float => {
508                                return Err(TextureError::UnsupportedTextureFormat(
509                                    "Float not supported for R8".to_string(),
510                                ));
511                            }
512                            DataType::Uint => TextureFormat::R8Uint,
513                            DataType::Sint => TextureFormat::R8Sint,
514                        },
515                        16 => match data_type {
516                            DataType::Unorm => TextureFormat::R16Unorm,
517                            DataType::UnormSrgb => {
518                                return Err(TextureError::UnsupportedTextureFormat(
519                                    "UnormSrgb not supported for R16".to_string(),
520                                ));
521                            }
522                            DataType::Snorm => TextureFormat::R16Snorm,
523                            DataType::Float => TextureFormat::R16Float,
524                            DataType::Uint => TextureFormat::R16Uint,
525                            DataType::Sint => TextureFormat::R16Sint,
526                        },
527                        32 => match data_type {
528                            DataType::Unorm => {
529                                return Err(TextureError::UnsupportedTextureFormat(
530                                    "Unorm not supported for R32".to_string(),
531                                ));
532                            }
533                            DataType::UnormSrgb => {
534                                return Err(TextureError::UnsupportedTextureFormat(
535                                    "UnormSrgb not supported for R32".to_string(),
536                                ));
537                            }
538                            DataType::Snorm => {
539                                return Err(TextureError::UnsupportedTextureFormat(
540                                    "Snorm not supported for R32".to_string(),
541                                ));
542                            }
543                            DataType::Float => TextureFormat::R32Float,
544                            DataType::Uint => TextureFormat::R32Uint,
545                            DataType::Sint => TextureFormat::R32Sint,
546                        },
547                        v => {
548                            return Err(TextureError::UnsupportedTextureFormat(format!(
549                                "Unsupported sample bit length for RGBSDA 1-channel format: {v}",
550                            )));
551                        }
552                    }
553                }
554                2 => {
555                    // Only red and green channels allowed
556                    if sample_information[0].channel_type != 0
557                        || sample_information[1].channel_type != 1
558                    {
559                        return Err(TextureError::UnsupportedTextureFormat(
560                            "Only red-green-component two-component KTX2 RGBSDA formats supported"
561                                .to_string(),
562                        ));
563                    }
564                    // Only same bit length for all channels
565                    assert_eq!(
566                        sample_information[0].bit_length,
567                        sample_information[1].bit_length
568                    );
569                    // Only same channel type qualifiers for all channels
570                    assert_eq!(
571                        sample_information[0].channel_type_qualifiers,
572                        sample_information[1].channel_type_qualifiers
573                    );
574                    // Only same sample range for all channels
575                    assert_eq!(sample_information[0].lower, sample_information[1].lower);
576                    assert_eq!(sample_information[0].upper, sample_information[1].upper);
577
578                    let sample = &sample_information[0];
579                    let data_type = sample_information_to_data_type(sample, false)?;
580                    match sample.bit_length {
581                        8 => match data_type {
582                            DataType::Unorm => TextureFormat::Rg8Unorm,
583                            DataType::UnormSrgb => {
584                                return Err(TextureError::UnsupportedTextureFormat(
585                                    "UnormSrgb not supported for Rg8".to_string(),
586                                ));
587                            }
588                            DataType::Snorm => TextureFormat::Rg8Snorm,
589                            DataType::Float => {
590                                return Err(TextureError::UnsupportedTextureFormat(
591                                    "Float not supported for Rg8".to_string(),
592                                ));
593                            }
594                            DataType::Uint => TextureFormat::Rg8Uint,
595                            DataType::Sint => TextureFormat::Rg8Sint,
596                        },
597                        16 => match data_type {
598                            DataType::Unorm => TextureFormat::Rg16Unorm,
599                            DataType::UnormSrgb => {
600                                return Err(TextureError::UnsupportedTextureFormat(
601                                    "UnormSrgb not supported for Rg16".to_string(),
602                                ));
603                            }
604                            DataType::Snorm => TextureFormat::Rg16Snorm,
605                            DataType::Float => TextureFormat::Rg16Float,
606                            DataType::Uint => TextureFormat::Rg16Uint,
607                            DataType::Sint => TextureFormat::Rg16Sint,
608                        },
609                        32 => match data_type {
610                            DataType::Unorm => {
611                                return Err(TextureError::UnsupportedTextureFormat(
612                                    "Unorm not supported for Rg32".to_string(),
613                                ));
614                            }
615                            DataType::UnormSrgb => {
616                                return Err(TextureError::UnsupportedTextureFormat(
617                                    "UnormSrgb not supported for Rg32".to_string(),
618                                ));
619                            }
620                            DataType::Snorm => {
621                                return Err(TextureError::UnsupportedTextureFormat(
622                                    "Snorm not supported for Rg32".to_string(),
623                                ));
624                            }
625                            DataType::Float => TextureFormat::Rg32Float,
626                            DataType::Uint => TextureFormat::Rg32Uint,
627                            DataType::Sint => TextureFormat::Rg32Sint,
628                        },
629                        v => {
630                            return Err(TextureError::UnsupportedTextureFormat(format!(
631                                "Unsupported sample bit length for RGBSDA 2-channel format: {v}",
632                            )));
633                        }
634                    }
635                }
636                3 => {
637                    if sample_information[0].channel_type == 0
638                        && sample_information[0].bit_length == 11
639                        && sample_information[1].channel_type == 1
640                        && sample_information[1].bit_length == 11
641                        && sample_information[2].channel_type == 2
642                        && sample_information[2].bit_length == 10
643                    {
644                        TextureFormat::Rg11b10Ufloat
645                    } else if sample_information[0].channel_type == 0
646                        && sample_information[0].bit_length == 9
647                        && sample_information[1].channel_type == 1
648                        && sample_information[1].bit_length == 9
649                        && sample_information[2].channel_type == 2
650                        && sample_information[2].bit_length == 9
651                    {
652                        TextureFormat::Rgb9e5Ufloat
653                    } else if sample_information[0].channel_type == 0
654                        && sample_information[0].bit_length == 8
655                        && sample_information[1].channel_type == 1
656                        && sample_information[1].bit_length == 8
657                        && sample_information[2].channel_type == 2
658                        && sample_information[2].bit_length == 8
659                    {
660                        return Err(TextureError::FormatRequiresTranscodingError(
661                            TranscodeFormat::Rgb8,
662                        ));
663                    } else {
664                        return Err(TextureError::UnsupportedTextureFormat(
665                            "3-component formats not supported".to_string(),
666                        ));
667                    }
668                }
669                4 => {
670                    // Only RGBA or BGRA channels allowed
671                    let is_rgba = sample_information[0].channel_type == 0;
672                    assert!(
673                        sample_information[0].channel_type == 0
674                            || sample_information[0].channel_type == 2
675                    );
676                    assert_eq!(sample_information[1].channel_type, 1);
677                    assert_eq!(
678                        sample_information[2].channel_type,
679                        if is_rgba { 2 } else { 0 }
680                    );
681                    assert_eq!(sample_information[3].channel_type, 15);
682
683                    // Handle one special packed format
684                    if sample_information[0].bit_length == 10
685                        && sample_information[1].bit_length == 10
686                        && sample_information[2].bit_length == 10
687                        && sample_information[3].bit_length == 2
688                    {
689                        return Ok(TextureFormat::Rgb10a2Unorm);
690                    }
691
692                    // Only same bit length for all channels
693                    assert!(
694                        sample_information[0].bit_length == sample_information[1].bit_length
695                            && sample_information[0].bit_length == sample_information[2].bit_length
696                            && sample_information[0].bit_length == sample_information[3].bit_length
697                    );
698                    assert!(
699                        sample_information[0].lower == sample_information[1].lower
700                            && sample_information[0].lower == sample_information[2].lower
701                            && sample_information[0].lower == sample_information[3].lower
702                    );
703                    assert!(
704                        sample_information[0].upper == sample_information[1].upper
705                            && sample_information[0].upper == sample_information[2].upper
706                            && sample_information[0].upper == sample_information[3].upper
707                    );
708
709                    let sample = &sample_information[0];
710                    let data_type = sample_information_to_data_type(sample, is_srgb)?;
711                    match sample.bit_length {
712                        8 => match data_type {
713                            DataType::Unorm => {
714                                if is_rgba {
715                                    TextureFormat::Rgba8Unorm
716                                } else {
717                                    TextureFormat::Bgra8Unorm
718                                }
719                            }
720                            DataType::UnormSrgb => {
721                                if is_rgba {
722                                    TextureFormat::Rgba8UnormSrgb
723                                } else {
724                                    TextureFormat::Bgra8UnormSrgb
725                                }
726                            }
727                            DataType::Snorm => {
728                                if is_rgba {
729                                    TextureFormat::Rgba8Snorm
730                                } else {
731                                    return Err(TextureError::UnsupportedTextureFormat(
732                                        "Bgra8 not supported for Snorm".to_string(),
733                                    ));
734                                }
735                            }
736                            DataType::Float => {
737                                return Err(TextureError::UnsupportedTextureFormat(
738                                    "Float not supported for Rgba8/Bgra8".to_string(),
739                                ));
740                            }
741                            DataType::Uint => {
742                                if is_rgba {
743                                    // NOTE: This is more about how you want to use the data so
744                                    // TextureFormat::Rgba8Uint is incorrect here
745                                    if is_srgb {
746                                        TextureFormat::Rgba8UnormSrgb
747                                    } else {
748                                        TextureFormat::Rgba8Unorm
749                                    }
750                                } else {
751                                    return Err(TextureError::UnsupportedTextureFormat(
752                                        "Bgra8 not supported for Uint".to_string(),
753                                    ));
754                                }
755                            }
756                            DataType::Sint => {
757                                if is_rgba {
758                                    // NOTE: This is more about how you want to use the data so
759                                    // TextureFormat::Rgba8Sint is incorrect here
760                                    TextureFormat::Rgba8Snorm
761                                } else {
762                                    return Err(TextureError::UnsupportedTextureFormat(
763                                        "Bgra8 not supported for Sint".to_string(),
764                                    ));
765                                }
766                            }
767                        },
768                        16 => match data_type {
769                            DataType::Unorm => {
770                                if is_rgba {
771                                    TextureFormat::Rgba16Unorm
772                                } else {
773                                    return Err(TextureError::UnsupportedTextureFormat(
774                                        "Bgra16 not supported for Unorm".to_string(),
775                                    ));
776                                }
777                            }
778                            DataType::UnormSrgb => {
779                                return Err(TextureError::UnsupportedTextureFormat(
780                                    "UnormSrgb not supported for Rgba16/Bgra16".to_string(),
781                                ));
782                            }
783                            DataType::Snorm => {
784                                if is_rgba {
785                                    TextureFormat::Rgba16Snorm
786                                } else {
787                                    return Err(TextureError::UnsupportedTextureFormat(
788                                        "Bgra16 not supported for Snorm".to_string(),
789                                    ));
790                                }
791                            }
792                            DataType::Float => {
793                                if is_rgba {
794                                    TextureFormat::Rgba16Float
795                                } else {
796                                    return Err(TextureError::UnsupportedTextureFormat(
797                                        "Bgra16 not supported for Float".to_string(),
798                                    ));
799                                }
800                            }
801                            DataType::Uint => {
802                                if is_rgba {
803                                    TextureFormat::Rgba16Uint
804                                } else {
805                                    return Err(TextureError::UnsupportedTextureFormat(
806                                        "Bgra16 not supported for Uint".to_string(),
807                                    ));
808                                }
809                            }
810                            DataType::Sint => {
811                                if is_rgba {
812                                    TextureFormat::Rgba16Sint
813                                } else {
814                                    return Err(TextureError::UnsupportedTextureFormat(
815                                        "Bgra16 not supported for Sint".to_string(),
816                                    ));
817                                }
818                            }
819                        },
820                        32 => match data_type {
821                            DataType::Unorm => {
822                                return Err(TextureError::UnsupportedTextureFormat(
823                                    "Unorm not supported for Rgba32/Bgra32".to_string(),
824                                ));
825                            }
826                            DataType::UnormSrgb => {
827                                return Err(TextureError::UnsupportedTextureFormat(
828                                    "UnormSrgb not supported for Rgba32/Bgra32".to_string(),
829                                ));
830                            }
831                            DataType::Snorm => {
832                                return Err(TextureError::UnsupportedTextureFormat(
833                                    "Snorm not supported for Rgba32/Bgra32".to_string(),
834                                ));
835                            }
836                            DataType::Float => {
837                                if is_rgba {
838                                    TextureFormat::Rgba32Float
839                                } else {
840                                    return Err(TextureError::UnsupportedTextureFormat(
841                                        "Bgra32 not supported for Float".to_string(),
842                                    ));
843                                }
844                            }
845                            DataType::Uint => {
846                                if is_rgba {
847                                    TextureFormat::Rgba32Uint
848                                } else {
849                                    return Err(TextureError::UnsupportedTextureFormat(
850                                        "Bgra32 not supported for Uint".to_string(),
851                                    ));
852                                }
853                            }
854                            DataType::Sint => {
855                                if is_rgba {
856                                    TextureFormat::Rgba32Sint
857                                } else {
858                                    return Err(TextureError::UnsupportedTextureFormat(
859                                        "Bgra32 not supported for Sint".to_string(),
860                                    ));
861                                }
862                            }
863                        },
864                        v => {
865                            return Err(TextureError::UnsupportedTextureFormat(format!(
866                                "Unsupported sample bit length for RGBSDA 4-channel format: {v}",
867                            )));
868                        }
869                    }
870                }
871                v => {
872                    return Err(TextureError::UnsupportedTextureFormat(format!(
873                        "Unsupported channel count for RGBSDA format: {v}",
874                    )));
875                }
876            }
877        }
878        Some(ColorModel::YUVSDA)
879        | Some(ColorModel::YIQSDA)
880        | Some(ColorModel::LabSDA)
881        | Some(ColorModel::CMYKA)
882        | Some(ColorModel::HSVAAng)
883        | Some(ColorModel::HSLAAng)
884        | Some(ColorModel::HSVAHex)
885        | Some(ColorModel::HSLAHex)
886        | Some(ColorModel::YCgCoA)
887        | Some(ColorModel::YcCbcCrc)
888        | Some(ColorModel::ICtCp)
889        | Some(ColorModel::CIEXYZ)
890        | Some(ColorModel::CIEXYY) => {
891            return Err(TextureError::UnsupportedTextureFormat(format!(
892                "{:?}",
893                data_format_descriptor.color_model
894            )));
895        }
896        Some(ColorModel::XYZW) => {
897            // Same number of channels in both texel block dimensions and sample info descriptions
898            assert_eq!(
899                data_format_descriptor.texel_block_dimensions[0] as usize,
900                sample_information.len()
901            );
902            match sample_information.len() {
903                4 => {
904                    // Only RGBA or BGRA channels allowed
905                    assert_eq!(sample_information[0].channel_type, 0);
906                    assert_eq!(sample_information[1].channel_type, 1);
907                    assert_eq!(sample_information[2].channel_type, 2);
908                    assert_eq!(sample_information[3].channel_type, 3);
909                    // Only same bit length for all channels
910                    assert!(
911                        sample_information[0].bit_length == sample_information[1].bit_length
912                            && sample_information[0].bit_length == sample_information[2].bit_length
913                            && sample_information[0].bit_length == sample_information[3].bit_length
914                    );
915                    // Only same channel type qualifiers for all channels
916                    assert!(
917                        sample_information[0].channel_type_qualifiers
918                            == sample_information[1].channel_type_qualifiers
919                            && sample_information[0].channel_type_qualifiers
920                                == sample_information[2].channel_type_qualifiers
921                            && sample_information[0].channel_type_qualifiers
922                                == sample_information[3].channel_type_qualifiers
923                    );
924                    // Only same sample range for all channels
925                    assert!(
926                        sample_information[0].lower == sample_information[1].lower
927                            && sample_information[0].lower == sample_information[2].lower
928                            && sample_information[0].lower == sample_information[3].lower
929                    );
930                    assert!(
931                        sample_information[0].upper == sample_information[1].upper
932                            && sample_information[0].upper == sample_information[2].upper
933                            && sample_information[0].upper == sample_information[3].upper
934                    );
935
936                    let sample = &sample_information[0];
937                    let data_type = sample_information_to_data_type(sample, false)?;
938                    match sample.bit_length {
939                        8 => match data_type {
940                            DataType::Unorm => TextureFormat::Rgba8Unorm,
941                            DataType::UnormSrgb => {
942                                return Err(TextureError::UnsupportedTextureFormat(
943                                    "UnormSrgb not supported for XYZW".to_string(),
944                                ));
945                            }
946                            DataType::Snorm => TextureFormat::Rgba8Snorm,
947                            DataType::Float => {
948                                return Err(TextureError::UnsupportedTextureFormat(
949                                    "Float not supported for Rgba8/Bgra8".to_string(),
950                                ));
951                            }
952                            DataType::Uint => TextureFormat::Rgba8Uint,
953                            DataType::Sint => TextureFormat::Rgba8Sint,
954                        },
955                        16 => match data_type {
956                            DataType::Unorm => TextureFormat::Rgba16Unorm,
957                            DataType::UnormSrgb => {
958                                return Err(TextureError::UnsupportedTextureFormat(
959                                    "UnormSrgb not supported for Rgba16/Bgra16".to_string(),
960                                ));
961                            }
962                            DataType::Snorm => TextureFormat::Rgba16Snorm,
963                            DataType::Float => TextureFormat::Rgba16Float,
964                            DataType::Uint => TextureFormat::Rgba16Uint,
965                            DataType::Sint => TextureFormat::Rgba16Sint,
966                        },
967                        32 => match data_type {
968                            DataType::Unorm => {
969                                return Err(TextureError::UnsupportedTextureFormat(
970                                    "Unorm not supported for Rgba32/Bgra32".to_string(),
971                                ));
972                            }
973                            DataType::UnormSrgb => {
974                                return Err(TextureError::UnsupportedTextureFormat(
975                                    "UnormSrgb not supported for Rgba32/Bgra32".to_string(),
976                                ));
977                            }
978                            DataType::Snorm => {
979                                return Err(TextureError::UnsupportedTextureFormat(
980                                    "Snorm not supported for Rgba32/Bgra32".to_string(),
981                                ));
982                            }
983                            DataType::Float => TextureFormat::Rgba32Float,
984                            DataType::Uint => TextureFormat::Rgba32Uint,
985                            DataType::Sint => TextureFormat::Rgba32Sint,
986                        },
987                        v => {
988                            return Err(TextureError::UnsupportedTextureFormat(format!(
989                                "Unsupported sample bit length for XYZW 4-channel format: {v}",
990                            )));
991                        }
992                    }
993                }
994                v => {
995                    return Err(TextureError::UnsupportedTextureFormat(format!(
996                        "Unsupported channel count for XYZW format: {v}",
997                    )));
998                }
999            }
1000        }
1001        Some(ColorModel::BC1A) => {
1002            if is_srgb {
1003                TextureFormat::Bc1RgbaUnormSrgb
1004            } else {
1005                TextureFormat::Bc1RgbaUnorm
1006            }
1007        }
1008        Some(ColorModel::BC2) => {
1009            if is_srgb {
1010                TextureFormat::Bc2RgbaUnormSrgb
1011            } else {
1012                TextureFormat::Bc2RgbaUnorm
1013            }
1014        }
1015        Some(ColorModel::BC3) => {
1016            if is_srgb {
1017                TextureFormat::Bc3RgbaUnormSrgb
1018            } else {
1019                TextureFormat::Bc3RgbaUnorm
1020            }
1021        }
1022        Some(ColorModel::BC4) => {
1023            if sample_information[0].lower == 0 {
1024                TextureFormat::Bc4RUnorm
1025            } else {
1026                TextureFormat::Bc4RSnorm
1027            }
1028        }
1029        // FIXME: Red and green channels can be swapped for ATI2n/3Dc
1030        Some(ColorModel::BC5) => {
1031            if sample_information[0].lower == 0 {
1032                TextureFormat::Bc5RgUnorm
1033            } else {
1034                TextureFormat::Bc5RgSnorm
1035            }
1036        }
1037        Some(ColorModel::BC6H) => {
1038            if sample_information[0].lower == 0 {
1039                TextureFormat::Bc6hRgbUfloat
1040            } else {
1041                TextureFormat::Bc6hRgbFloat
1042            }
1043        }
1044        Some(ColorModel::BC7) => {
1045            if is_srgb {
1046                TextureFormat::Bc7RgbaUnormSrgb
1047            } else {
1048                TextureFormat::Bc7RgbaUnorm
1049            }
1050        }
1051        // ETC1 a subset of ETC2 only supporting Rgb8
1052        Some(ColorModel::ETC1) => {
1053            if is_srgb {
1054                TextureFormat::Etc2Rgb8UnormSrgb
1055            } else {
1056                TextureFormat::Etc2Rgb8Unorm
1057            }
1058        }
1059        Some(ColorModel::ETC2) => match sample_information.len() {
1060            1 => {
1061                let sample = &sample_information[0];
1062                match sample.channel_type {
1063                    0 => {
1064                        if sample_information[0]
1065                            .channel_type_qualifiers
1066                            .contains(ChannelTypeQualifiers::SIGNED)
1067                        {
1068                            TextureFormat::EacR11Snorm
1069                        } else {
1070                            TextureFormat::EacR11Unorm
1071                        }
1072                    }
1073                    2 => {
1074                        if is_srgb {
1075                            TextureFormat::Etc2Rgb8UnormSrgb
1076                        } else {
1077                            TextureFormat::Etc2Rgb8Unorm
1078                        }
1079                    }
1080                    _ => {
1081                        return Err(TextureError::UnsupportedTextureFormat(format!(
1082                            "Invalid ETC2 sample channel type: {}",
1083                            sample.channel_type
1084                        )))
1085                    }
1086                }
1087            }
1088            2 => {
1089                let sample0 = &sample_information[0];
1090                let sample1 = &sample_information[1];
1091                if sample0.channel_type == 0 && sample1.channel_type == 1 {
1092                    if sample0
1093                        .channel_type_qualifiers
1094                        .contains(ChannelTypeQualifiers::SIGNED)
1095                    {
1096                        TextureFormat::EacRg11Snorm
1097                    } else {
1098                        TextureFormat::EacRg11Unorm
1099                    }
1100                } else if sample0.channel_type == 2 && sample1.channel_type == 15 {
1101                    if is_srgb {
1102                        TextureFormat::Etc2Rgb8A1UnormSrgb
1103                    } else {
1104                        TextureFormat::Etc2Rgb8A1Unorm
1105                    }
1106                } else if sample0.channel_type == 15 && sample1.channel_type == 2 {
1107                    if is_srgb {
1108                        TextureFormat::Etc2Rgba8UnormSrgb
1109                    } else {
1110                        TextureFormat::Etc2Rgba8Unorm
1111                    }
1112                } else {
1113                    return Err(TextureError::UnsupportedTextureFormat(format!(
1114                        "Invalid ETC2 2-sample channel types: {} {}",
1115                        sample0.channel_type, sample1.channel_type
1116                    )));
1117                }
1118            }
1119            v => {
1120                return Err(TextureError::UnsupportedTextureFormat(format!(
1121                    "Unsupported channel count for ETC2 format: {v}",
1122                )));
1123            }
1124        },
1125        Some(ColorModel::ASTC) => TextureFormat::Astc {
1126            block: match (
1127                data_format_descriptor.texel_block_dimensions[0],
1128                data_format_descriptor.texel_block_dimensions[1],
1129            ) {
1130                (4, 4) => AstcBlock::B4x4,
1131                (5, 4) => AstcBlock::B5x4,
1132                (5, 5) => AstcBlock::B5x5,
1133                (6, 5) => AstcBlock::B6x5,
1134                (8, 5) => AstcBlock::B8x5,
1135                (8, 8) => AstcBlock::B8x8,
1136                (10, 5) => AstcBlock::B10x5,
1137                (10, 6) => AstcBlock::B10x6,
1138                (10, 8) => AstcBlock::B10x8,
1139                (10, 10) => AstcBlock::B10x10,
1140                (12, 10) => AstcBlock::B12x10,
1141                (12, 12) => AstcBlock::B12x12,
1142                d => {
1143                    return Err(TextureError::UnsupportedTextureFormat(format!(
1144                        "Invalid ASTC dimension: {} x {}",
1145                        d.0, d.1
1146                    )))
1147                }
1148            },
1149            channel: if is_srgb {
1150                AstcChannel::UnormSrgb
1151            } else {
1152                AstcChannel::Unorm
1153            },
1154        },
1155        Some(ColorModel::ETC1S) => {
1156            return Err(TextureError::FormatRequiresTranscodingError(
1157                TranscodeFormat::Etc1s,
1158            ));
1159        }
1160        Some(ColorModel::PVRTC) => {
1161            return Err(TextureError::UnsupportedTextureFormat(
1162                "PVRTC is not supported".to_string(),
1163            ));
1164        }
1165        Some(ColorModel::PVRTC2) => {
1166            return Err(TextureError::UnsupportedTextureFormat(
1167                "PVRTC2 is not supported".to_string(),
1168            ));
1169        }
1170        Some(ColorModel::UASTC) => {
1171            return Err(TextureError::FormatRequiresTranscodingError(
1172                TranscodeFormat::Uastc(match sample_information[0].channel_type {
1173                    0 => DataFormat::Rgb,
1174                    3 => DataFormat::Rgba,
1175                    4 => DataFormat::Rrr,
1176                    5 => DataFormat::Rrrg,
1177                    6 => DataFormat::Rg,
1178                    channel_type => {
1179                        return Err(TextureError::UnsupportedTextureFormat(format!(
1180                            "Invalid KTX2 UASTC channel type: {channel_type}",
1181                        )))
1182                    }
1183                }),
1184            ));
1185        }
1186        None => {
1187            return Err(TextureError::UnsupportedTextureFormat(
1188                "Unspecified KTX2 color model".to_string(),
1189            ));
1190        }
1191        _ => {
1192            return Err(TextureError::UnsupportedTextureFormat(format!(
1193                "Unknown KTX2 color model: {:?}",
1194                data_format_descriptor.color_model
1195            )));
1196        }
1197    })
1198}
1199
1200#[cfg(feature = "ktx2")]
1201pub fn ktx2_format_to_texture_format(
1202    ktx2_format: ktx2::Format,
1203    is_srgb: bool,
1204) -> Result<TextureFormat, TextureError> {
1205    Ok(match ktx2_format {
1206        ktx2::Format::R8_UNORM | ktx2::Format::R8_SRGB => {
1207            if is_srgb {
1208                return Err(TextureError::FormatRequiresTranscodingError(
1209                    TranscodeFormat::R8UnormSrgb,
1210                ));
1211            }
1212            TextureFormat::R8Unorm
1213        }
1214        ktx2::Format::R8_SNORM => TextureFormat::R8Snorm,
1215        ktx2::Format::R8_UINT => TextureFormat::R8Uint,
1216        ktx2::Format::R8_SINT => TextureFormat::R8Sint,
1217        ktx2::Format::R8G8_UNORM | ktx2::Format::R8G8_SRGB => {
1218            if is_srgb {
1219                return Err(TextureError::FormatRequiresTranscodingError(
1220                    TranscodeFormat::Rg8UnormSrgb,
1221                ));
1222            }
1223            TextureFormat::Rg8Unorm
1224        }
1225        ktx2::Format::R8G8_SNORM => TextureFormat::Rg8Snorm,
1226        ktx2::Format::R8G8_UINT => TextureFormat::Rg8Uint,
1227        ktx2::Format::R8G8_SINT => TextureFormat::Rg8Sint,
1228        ktx2::Format::R8G8B8_UNORM | ktx2::Format::R8G8B8_SRGB => {
1229            return Err(TextureError::FormatRequiresTranscodingError(
1230                TranscodeFormat::Rgb8,
1231            ));
1232        }
1233        ktx2::Format::R8G8B8A8_UNORM | ktx2::Format::R8G8B8A8_SRGB => {
1234            if is_srgb {
1235                TextureFormat::Rgba8UnormSrgb
1236            } else {
1237                TextureFormat::Rgba8Unorm
1238            }
1239        }
1240        ktx2::Format::R8G8B8A8_SNORM => TextureFormat::Rgba8Snorm,
1241        ktx2::Format::R8G8B8A8_UINT => TextureFormat::Rgba8Uint,
1242        ktx2::Format::R8G8B8A8_SINT => TextureFormat::Rgba8Sint,
1243        ktx2::Format::B8G8R8A8_UNORM | ktx2::Format::B8G8R8A8_SRGB => {
1244            if is_srgb {
1245                TextureFormat::Bgra8UnormSrgb
1246            } else {
1247                TextureFormat::Bgra8Unorm
1248            }
1249        }
1250        ktx2::Format::A2R10G10B10_UNORM_PACK32 => TextureFormat::Rgb10a2Unorm,
1251
1252        ktx2::Format::R16_UNORM => TextureFormat::R16Unorm,
1253        ktx2::Format::R16_SNORM => TextureFormat::R16Snorm,
1254        ktx2::Format::R16_UINT => TextureFormat::R16Uint,
1255        ktx2::Format::R16_SINT => TextureFormat::R16Sint,
1256        ktx2::Format::R16_SFLOAT => TextureFormat::R16Float,
1257        ktx2::Format::R16G16_UNORM => TextureFormat::Rg16Unorm,
1258        ktx2::Format::R16G16_SNORM => TextureFormat::Rg16Snorm,
1259        ktx2::Format::R16G16_UINT => TextureFormat::Rg16Uint,
1260        ktx2::Format::R16G16_SINT => TextureFormat::Rg16Sint,
1261        ktx2::Format::R16G16_SFLOAT => TextureFormat::Rg16Float,
1262
1263        ktx2::Format::R16G16B16A16_UNORM => TextureFormat::Rgba16Unorm,
1264        ktx2::Format::R16G16B16A16_SNORM => TextureFormat::Rgba16Snorm,
1265        ktx2::Format::R16G16B16A16_UINT => TextureFormat::Rgba16Uint,
1266        ktx2::Format::R16G16B16A16_SINT => TextureFormat::Rgba16Sint,
1267        ktx2::Format::R16G16B16A16_SFLOAT => TextureFormat::Rgba16Float,
1268        ktx2::Format::R32_UINT => TextureFormat::R32Uint,
1269        ktx2::Format::R32_SINT => TextureFormat::R32Sint,
1270        ktx2::Format::R32_SFLOAT => TextureFormat::R32Float,
1271        ktx2::Format::R32G32_UINT => TextureFormat::Rg32Uint,
1272        ktx2::Format::R32G32_SINT => TextureFormat::Rg32Sint,
1273        ktx2::Format::R32G32_SFLOAT => TextureFormat::Rg32Float,
1274
1275        ktx2::Format::R32G32B32A32_UINT => TextureFormat::Rgba32Uint,
1276        ktx2::Format::R32G32B32A32_SINT => TextureFormat::Rgba32Sint,
1277        ktx2::Format::R32G32B32A32_SFLOAT => TextureFormat::Rgba32Float,
1278
1279        ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Ufloat,
1280        ktx2::Format::E5B9G9R9_UFLOAT_PACK32 => TextureFormat::Rgb9e5Ufloat,
1281
1282        ktx2::Format::X8_D24_UNORM_PACK32 => TextureFormat::Depth24Plus,
1283        ktx2::Format::D32_SFLOAT => TextureFormat::Depth32Float,
1284
1285        ktx2::Format::D24_UNORM_S8_UINT => TextureFormat::Depth24PlusStencil8,
1286
1287        ktx2::Format::BC1_RGB_UNORM_BLOCK
1288        | ktx2::Format::BC1_RGB_SRGB_BLOCK
1289        | ktx2::Format::BC1_RGBA_UNORM_BLOCK
1290        | ktx2::Format::BC1_RGBA_SRGB_BLOCK => {
1291            if is_srgb {
1292                TextureFormat::Bc1RgbaUnormSrgb
1293            } else {
1294                TextureFormat::Bc1RgbaUnorm
1295            }
1296        }
1297        ktx2::Format::BC2_UNORM_BLOCK | ktx2::Format::BC2_SRGB_BLOCK => {
1298            if is_srgb {
1299                TextureFormat::Bc2RgbaUnormSrgb
1300            } else {
1301                TextureFormat::Bc2RgbaUnorm
1302            }
1303        }
1304        ktx2::Format::BC3_UNORM_BLOCK | ktx2::Format::BC3_SRGB_BLOCK => {
1305            if is_srgb {
1306                TextureFormat::Bc3RgbaUnormSrgb
1307            } else {
1308                TextureFormat::Bc3RgbaUnorm
1309            }
1310        }
1311        ktx2::Format::BC4_UNORM_BLOCK => TextureFormat::Bc4RUnorm,
1312        ktx2::Format::BC4_SNORM_BLOCK => TextureFormat::Bc4RSnorm,
1313        ktx2::Format::BC5_UNORM_BLOCK => TextureFormat::Bc5RgUnorm,
1314        ktx2::Format::BC5_SNORM_BLOCK => TextureFormat::Bc5RgSnorm,
1315        ktx2::Format::BC6H_UFLOAT_BLOCK => TextureFormat::Bc6hRgbUfloat,
1316        ktx2::Format::BC6H_SFLOAT_BLOCK => TextureFormat::Bc6hRgbFloat,
1317        ktx2::Format::BC7_UNORM_BLOCK | ktx2::Format::BC7_SRGB_BLOCK => {
1318            if is_srgb {
1319                TextureFormat::Bc7RgbaUnormSrgb
1320            } else {
1321                TextureFormat::Bc7RgbaUnorm
1322            }
1323        }
1324        ktx2::Format::ETC2_R8G8B8_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8_SRGB_BLOCK => {
1325            if is_srgb {
1326                TextureFormat::Etc2Rgb8UnormSrgb
1327            } else {
1328                TextureFormat::Etc2Rgb8Unorm
1329            }
1330        }
1331        ktx2::Format::ETC2_R8G8B8A1_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8A1_SRGB_BLOCK => {
1332            if is_srgb {
1333                TextureFormat::Etc2Rgb8A1UnormSrgb
1334            } else {
1335                TextureFormat::Etc2Rgb8A1Unorm
1336            }
1337        }
1338        ktx2::Format::ETC2_R8G8B8A8_UNORM_BLOCK | ktx2::Format::ETC2_R8G8B8A8_SRGB_BLOCK => {
1339            if is_srgb {
1340                TextureFormat::Etc2Rgba8UnormSrgb
1341            } else {
1342                TextureFormat::Etc2Rgba8Unorm
1343            }
1344        }
1345        ktx2::Format::EAC_R11_UNORM_BLOCK => TextureFormat::EacR11Unorm,
1346        ktx2::Format::EAC_R11_SNORM_BLOCK => TextureFormat::EacR11Snorm,
1347        ktx2::Format::EAC_R11G11_UNORM_BLOCK => TextureFormat::EacRg11Unorm,
1348        ktx2::Format::EAC_R11G11_SNORM_BLOCK => TextureFormat::EacRg11Snorm,
1349        ktx2::Format::ASTC_4x4_UNORM_BLOCK | ktx2::Format::ASTC_4x4_SRGB_BLOCK => {
1350            TextureFormat::Astc {
1351                block: AstcBlock::B4x4,
1352                channel: if is_srgb {
1353                    AstcChannel::UnormSrgb
1354                } else {
1355                    AstcChannel::Unorm
1356                },
1357            }
1358        }
1359        ktx2::Format::ASTC_5x4_UNORM_BLOCK | ktx2::Format::ASTC_5x4_SRGB_BLOCK => {
1360            TextureFormat::Astc {
1361                block: AstcBlock::B5x4,
1362                channel: if is_srgb {
1363                    AstcChannel::UnormSrgb
1364                } else {
1365                    AstcChannel::Unorm
1366                },
1367            }
1368        }
1369        ktx2::Format::ASTC_5x5_UNORM_BLOCK | ktx2::Format::ASTC_5x5_SRGB_BLOCK => {
1370            TextureFormat::Astc {
1371                block: AstcBlock::B5x5,
1372                channel: if is_srgb {
1373                    AstcChannel::UnormSrgb
1374                } else {
1375                    AstcChannel::Unorm
1376                },
1377            }
1378        }
1379        ktx2::Format::ASTC_6x5_UNORM_BLOCK | ktx2::Format::ASTC_6x5_SRGB_BLOCK => {
1380            TextureFormat::Astc {
1381                block: AstcBlock::B6x5,
1382                channel: if is_srgb {
1383                    AstcChannel::UnormSrgb
1384                } else {
1385                    AstcChannel::Unorm
1386                },
1387            }
1388        }
1389        ktx2::Format::ASTC_6x6_UNORM_BLOCK | ktx2::Format::ASTC_6x6_SRGB_BLOCK => {
1390            TextureFormat::Astc {
1391                block: AstcBlock::B6x6,
1392                channel: if is_srgb {
1393                    AstcChannel::UnormSrgb
1394                } else {
1395                    AstcChannel::Unorm
1396                },
1397            }
1398        }
1399        ktx2::Format::ASTC_8x5_UNORM_BLOCK | ktx2::Format::ASTC_8x5_SRGB_BLOCK => {
1400            TextureFormat::Astc {
1401                block: AstcBlock::B8x5,
1402                channel: if is_srgb {
1403                    AstcChannel::UnormSrgb
1404                } else {
1405                    AstcChannel::Unorm
1406                },
1407            }
1408        }
1409        ktx2::Format::ASTC_8x6_UNORM_BLOCK | ktx2::Format::ASTC_8x6_SRGB_BLOCK => {
1410            TextureFormat::Astc {
1411                block: AstcBlock::B8x6,
1412                channel: if is_srgb {
1413                    AstcChannel::UnormSrgb
1414                } else {
1415                    AstcChannel::Unorm
1416                },
1417            }
1418        }
1419        ktx2::Format::ASTC_8x8_UNORM_BLOCK | ktx2::Format::ASTC_8x8_SRGB_BLOCK => {
1420            TextureFormat::Astc {
1421                block: AstcBlock::B8x8,
1422                channel: if is_srgb {
1423                    AstcChannel::UnormSrgb
1424                } else {
1425                    AstcChannel::Unorm
1426                },
1427            }
1428        }
1429        ktx2::Format::ASTC_10x5_UNORM_BLOCK | ktx2::Format::ASTC_10x5_SRGB_BLOCK => {
1430            TextureFormat::Astc {
1431                block: AstcBlock::B10x5,
1432                channel: if is_srgb {
1433                    AstcChannel::UnormSrgb
1434                } else {
1435                    AstcChannel::Unorm
1436                },
1437            }
1438        }
1439        ktx2::Format::ASTC_10x6_UNORM_BLOCK | ktx2::Format::ASTC_10x6_SRGB_BLOCK => {
1440            TextureFormat::Astc {
1441                block: AstcBlock::B10x6,
1442                channel: if is_srgb {
1443                    AstcChannel::UnormSrgb
1444                } else {
1445                    AstcChannel::Unorm
1446                },
1447            }
1448        }
1449        ktx2::Format::ASTC_10x8_UNORM_BLOCK | ktx2::Format::ASTC_10x8_SRGB_BLOCK => {
1450            TextureFormat::Astc {
1451                block: AstcBlock::B10x8,
1452                channel: if is_srgb {
1453                    AstcChannel::UnormSrgb
1454                } else {
1455                    AstcChannel::Unorm
1456                },
1457            }
1458        }
1459        ktx2::Format::ASTC_10x10_UNORM_BLOCK | ktx2::Format::ASTC_10x10_SRGB_BLOCK => {
1460            TextureFormat::Astc {
1461                block: AstcBlock::B10x10,
1462                channel: if is_srgb {
1463                    AstcChannel::UnormSrgb
1464                } else {
1465                    AstcChannel::Unorm
1466                },
1467            }
1468        }
1469        ktx2::Format::ASTC_12x10_UNORM_BLOCK | ktx2::Format::ASTC_12x10_SRGB_BLOCK => {
1470            TextureFormat::Astc {
1471                block: AstcBlock::B12x10,
1472                channel: if is_srgb {
1473                    AstcChannel::UnormSrgb
1474                } else {
1475                    AstcChannel::Unorm
1476                },
1477            }
1478        }
1479        ktx2::Format::ASTC_12x12_UNORM_BLOCK | ktx2::Format::ASTC_12x12_SRGB_BLOCK => {
1480            TextureFormat::Astc {
1481                block: AstcBlock::B12x12,
1482                channel: if is_srgb {
1483                    AstcChannel::UnormSrgb
1484                } else {
1485                    AstcChannel::Unorm
1486                },
1487            }
1488        }
1489        _ => {
1490            return Err(TextureError::UnsupportedTextureFormat(format!(
1491                "{ktx2_format:?}"
1492            )))
1493        }
1494    })
1495}
1496
1497#[cfg(test)]
1498mod tests {
1499    use crate::CompressedImageFormats;
1500
1501    use super::ktx2_buffer_to_image;
1502
1503    #[test]
1504    fn test_ktx_levels() {
1505        // R8UnormSrgb textture with 4x4 pixels data and 3 levels of mipmaps
1506        let buffer = vec![
1507            0xab, 0x4b, 0x54, 0x58, 0x20, 0x32, 0x30, 0xbb, 0x0d, 10, 0x1a, 10, 0x0f, 0, 0, 0, 1,
1508            0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0,
1509            0, 0, 0x98, 0, 0, 0, 0x2c, 0, 0, 0, 0xc4, 0, 0, 0, 0x5c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1510            0, 0, 0, 0, 0, 0, 0, 0, 0, 0x28, 1, 0, 0, 0, 0, 0, 0, 0x10, 0, 0, 0, 0, 0, 0, 0, 0x10,
1511            0, 0, 0, 0, 0, 0, 0, 0x24, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0,
1512            0, 0, 0, 0x20, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1513            0x2c, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0x28, 0, 1, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
1514            0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0x12, 0, 0, 0, 0x4b, 0x54, 0x58,
1515            0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0, 0x72, 0x64, 0, 0,
1516            0, 0x10, 0, 0, 0, 0x4b, 0x54, 0x58, 0x73, 0x77, 0x69, 0x7a, 0x7a, 0x6c, 0x65, 0, 0x72,
1517            0x72, 0x72, 0x31, 0, 0x2c, 0, 0, 0, 0x4b, 0x54, 0x58, 0x77, 0x72, 0x69, 0x74, 0x65,
1518            0x72, 0, 0x74, 0x6f, 0x6b, 0x74, 0x78, 0x20, 0x76, 0x34, 0x2e, 0x33, 0x2e, 0x30, 0x7e,
1519            0x32, 0x38, 0x20, 0x2f, 0x20, 0x6c, 0x69, 0x62, 0x6b, 0x74, 0x78, 0x20, 0x76, 0x34,
1520            0x2e, 0x33, 0x2e, 0x30, 0x7e, 0x31, 0, 0x4a, 0, 0, 0, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
1521            0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
1522            0x4a,
1523        ];
1524        let supported_compressed_formats = CompressedImageFormats::empty();
1525        let result = ktx2_buffer_to_image(&buffer, supported_compressed_formats, true);
1526        assert!(result.is_ok());
1527    }
1528}