Skip to main content

bevy_image/
ktx2.rs

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