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