bevy_render/texture/
ktx2.rs

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