Skip to main content

basisu_c_sys/extra/
transcoder.rs

1use crate::{
2    extra::BuHeap,
3    transcoder as trans_sys,
4    utils::{BasisTextureFormat, TranscodeTargetFormat},
5};
6use alloc::vec::Vec;
7use async_lock::OnceCell;
8use core::num::NonZero;
9use wgpu_types::{
10    AstcBlock, AstcChannel, Extent3d, TextureDataOrder, TextureFormat, TextureViewDimension,
11};
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct TranscodedImage {
15    /// The output data of image pixels.
16    pub data: Vec<u8>,
17    /// The data order. Currently it's always [`TextureDataOrder::MipMajor`].
18    pub data_order: TextureDataOrder,
19    /// The size of transcoded image.
20    pub size: Extent3d,
21    /// The format of transcoded image.
22    pub format: TextureFormat,
23    /// The mip level count of transcoded image.
24    pub mip_level_count: u32,
25    /// The texture view dimension of transcoded image. It can be D2, D2Array, Cube or CubeArray.
26    pub view_dimension: TextureViewDimension,
27}
28
29static BASISU_TRANSCODER_INITIALIZED: OnceCell<()> = OnceCell::new();
30
31/// Init global data of transcoder ([`trans_sys::bt_init`]), and basisu wasm if on web.
32pub async fn basisu_transcoder_init() {
33    BASISU_TRANSCODER_INITIALIZED
34        .get_or_init(async || {
35            #[cfg(all(
36                target_arch = "wasm32",
37                target_vendor = "unknown",
38                target_os = "unknown",
39            ))]
40            crate::instantiate_basisu_wasm().await;
41            unsafe { trans_sys::bt_init() };
42        })
43        .await;
44}
45
46/// A wrapper of [`trans_sys::bt_enable_debug_printf`].
47pub fn basisu_transcoder_enable_debug_printf(enable: bool) {
48    unsafe { trans_sys::bt_enable_debug_printf(enable as u32) };
49}
50
51#[derive(Debug, thiserror::Error, PartialEq)]
52#[non_exhaustive]
53pub enum BasisuTranscodeError {
54    #[error("Input data is empty")]
55    EmptyInputData,
56    #[error("Failed to load ktx2 data, likely the input data isn't valid")]
57    LoadKtx2DataFailed,
58    #[error("Invalid ktx2 face count. It must be 1 or 6, got {0}")]
59    InvalidFaceCount(u32),
60    #[error("`BasisuTranscoder::prepare` isn't called before transcoding")]
61    UnsupportedTranscodeTarget,
62    #[error("`bt_ktx2_start_transcoding` failed")]
63    BtStartTranscodingFailed,
64    #[error("`bt_ktx2_transcode_image_level` failed")]
65    BtTranscodeImageLevelFailed,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct TranscodeInfo {
70    pub width: u32,
71    pub height: u32,
72    pub levels: u32,
73    pub layers: u32,
74    pub faces: u32,
75    pub is_srgb: bool,
76    pub basis_format: BasisTextureFormat,
77    pub preferred_target: TranscodeTargetFormat,
78}
79
80pub struct BasisuTranscoder {
81    ktx2_data: Ktx2Data,
82    info: TranscodeInfo,
83}
84
85struct Ktx2Data {
86    #[expect(
87        unused,
88        reason = "This is kept to remain memory, which is referenced by ktx2 handle"
89    )]
90    data: BuHeap,
91    ktx2_handle: NonZero<u64>,
92}
93
94impl Ktx2Data {
95    fn new(data: BuHeap) -> Option<Self> {
96        NonZero::try_from(unsafe {
97            trans_sys::bt_ktx2_open(
98                data.ptr().into(),
99                u32::try_from(u64::from(data.capacity())).unwrap(),
100            )
101        })
102        .ok()
103        .map(|ktx2_handle| Self { data, ktx2_handle })
104    }
105    #[inline]
106    fn ktx2_handle(&self) -> NonZero<u64> {
107        self.ktx2_handle
108    }
109}
110
111impl Drop for Ktx2Data {
112    fn drop(&mut self) {
113        unsafe { trans_sys::bt_ktx2_close(self.ktx2_handle().into()) };
114    }
115}
116
117impl BasisuTranscoder {
118    /// Create a transcoder and set the input data of ktx2 basisu file in bytes.
119    ///
120    /// Panic if [`basisu_transcoder_init`] hasn't been called.
121    ///
122    /// `supported_compressed_formats` and `channel_type_hint` affect the preferred transcode target.
123    ///
124    /// `channel_type_hint` only has effect if the basisu texture is ETC1S, or device only supports ETC2.  If it's [`ChannelType::Auto`], it will be determined automatically according to ktx2 dfd channel ids.
125    ///
126    /// Default transcode target selection:
127    ///
128    /// | BasisU format                  | Target selection                                               |
129    /// | ------------------------------ | -------------------------------------------------------------- |
130    /// | ETC1S                          | Bc7Rgba/Bc5Rg/Bc4R > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8 |
131    /// | UASTC_LDR, ASTC_LDR, XUASTC_LDR| Astc > Bc7Rgba > Etc2Rgba8/Etc2Rgb8/EacRg11/EacR11 > Rgba8     |
132    /// | UASTC_HDR, ASTC_HDR            | Astc > Bc6hRgbUfloat > Rgba16Float                             |
133    pub fn new(
134        input: &[u8],
135        supported_compressed_formats: SupportedTextureCompression,
136        channel_type_hint: ChannelType,
137    ) -> Result<Self, BasisuTranscodeError> {
138        if !BASISU_TRANSCODER_INITIALIZED.is_initialized() {
139            panic!("`basisu_transcoder_init` must be called before create transcoder");
140        }
141        unsafe {
142            let Some(input_data) = BuHeap::new(input) else {
143                return Err(BasisuTranscodeError::EmptyInputData);
144            };
145            let Some(ktx2_data) = Ktx2Data::new(input_data) else {
146                return Err(BasisuTranscodeError::LoadKtx2DataFailed);
147            };
148            let ktx2_handle_ptr = u64::from(ktx2_data.ktx2_handle());
149            if trans_sys::bt_ktx2_start_transcoding(ktx2_handle_ptr).is_err() {
150                return Err(BasisuTranscodeError::BtStartTranscodingFailed);
151            }
152            let faces = trans_sys::bt_ktx2_get_faces(ktx2_handle_ptr);
153            if faces != 1 && faces != 6 {
154                return Err(BasisuTranscodeError::InvalidFaceCount(faces));
155            }
156            let width = trans_sys::bt_ktx2_get_width(ktx2_handle_ptr);
157            let height = trans_sys::bt_ktx2_get_height(ktx2_handle_ptr);
158            let layers = trans_sys::bt_ktx2_get_layers(ktx2_handle_ptr);
159            let levels = trans_sys::bt_ktx2_get_levels(ktx2_handle_ptr);
160            let is_srgb = trans_sys::bt_ktx2_is_srgb(ktx2_handle_ptr).is_ok();
161            let basis_format = BasisTextureFormat::try_from(
162                trans_sys::bt_ktx2_get_basis_tex_format(ktx2_handle_ptr),
163            )
164            .unwrap();
165            let channel_id0 = trans_sys::bt_ktx2_get_dfd_channel_id0(ktx2_handle_ptr);
166            let channel_id1 = trans_sys::bt_ktx2_get_dfd_channel_id1(ktx2_handle_ptr);
167            let is_uastc = trans_sys::bt_ktx2_is_uastc_ldr_4x4(ktx2_handle_ptr).is_ok();
168            let channel_type = if channel_type_hint != ChannelType::Auto {
169                channel_type_hint
170            } else {
171                channel_id_to_type(is_uastc, channel_id0, channel_id1)
172            };
173            let preferred_target = select_preferred_transcode_target(
174                basis_format,
175                channel_type,
176                supported_compressed_formats,
177            );
178
179            let info = TranscodeInfo {
180                width,
181                height,
182                levels,
183                layers,
184                faces,
185                is_srgb,
186                basis_format,
187                preferred_target,
188            };
189            Ok(Self { ktx2_data, info })
190        }
191    }
192
193    /// Get the input info [`TranscodeInfo`].
194    pub fn get_info(&self) -> TranscodeInfo {
195        self.info
196    }
197
198    /// Transcode the input data and return the image. Return error if it's not prepared or transcoding failed.
199    ///
200    /// If `transcode_target`/`is_srgb` is None, it will use [`TranscodeInfo::preferred_target`]/[`TranscodeInfo::is_srgb`].
201    pub fn transcode(
202        &self,
203        transcode_target: Option<TranscodeTargetFormat>,
204        is_srgb: Option<bool>,
205    ) -> Result<TranscodedImage, BasisuTranscodeError> {
206        let info = self.info;
207        let transcode_target = transcode_target.unwrap_or(info.preferred_target);
208        if unsafe {
209            trans_sys::bt_basis_is_format_supported(
210                transcode_target as u32,
211                info.basis_format as u32,
212            )
213            .is_err()
214        } {
215            return Err(BasisuTranscodeError::UnsupportedTranscodeTarget);
216        }
217        let out_format = transcode_target_to_wgpu_format(transcode_target)
218            .ok_or(BasisuTranscodeError::UnsupportedTranscodeTarget)?;
219        let total_layers = info.layers.max(1);
220        let mut total_bytes = 0;
221        let ktx2_handle_ptr = u64::from(self.ktx2_data.ktx2_handle());
222        let data = unsafe {
223            for level_index in 0..info.levels {
224                for layer_index in 0..total_layers {
225                    for face_index in 0..info.faces {
226                        let orig_width = trans_sys::bt_ktx2_get_level_orig_width(
227                            ktx2_handle_ptr,
228                            level_index,
229                            layer_index,
230                            face_index,
231                        );
232                        let orig_height = trans_sys::bt_ktx2_get_level_orig_height(
233                            ktx2_handle_ptr,
234                            level_index,
235                            layer_index,
236                            face_index,
237                        );
238                        let bytes = trans_sys::bt_basis_compute_transcoded_image_size_in_bytes(
239                            transcode_target as u32,
240                            orig_width,
241                            orig_height,
242                        );
243                        total_bytes += bytes;
244                    }
245                }
246            }
247
248            let basisu_heap = BuHeap::new_uninit(NonZero::new(total_bytes.into()).unwrap());
249            let basisu_ptr = u64::from(basisu_heap.ptr());
250            let mut offset = 0u64;
251            for level_index in 0..info.levels {
252                for layer_index in 0..total_layers {
253                    for face_index in 0..info.faces {
254                        let bytes_per_block_or_pixel =
255                            trans_sys::bt_basis_get_bytes_per_block_or_pixel(
256                                transcode_target as u32,
257                            );
258                        let orig_width = trans_sys::bt_ktx2_get_level_orig_width(
259                            ktx2_handle_ptr,
260                            level_index,
261                            layer_index,
262                            face_index,
263                        );
264                        let orig_height = trans_sys::bt_ktx2_get_level_orig_height(
265                            ktx2_handle_ptr,
266                            level_index,
267                            layer_index,
268                            face_index,
269                        );
270                        let bytes = trans_sys::bt_basis_compute_transcoded_image_size_in_bytes(
271                            transcode_target as u32,
272                            orig_width,
273                            orig_height,
274                        );
275                        let blocks = bytes / bytes_per_block_or_pixel;
276                        if trans_sys::bt_ktx2_transcode_image_level(
277                            ktx2_handle_ptr,
278                            level_index,
279                            layer_index,
280                            face_index,
281                            basisu_ptr + offset,
282                            blocks,
283                            transcode_target as u32,
284                            0,
285                            0,
286                            0,
287                            -1,
288                            -1,
289                            0,
290                        )
291                        .is_err()
292                        {
293                            return Err(BasisuTranscodeError::BtTranscodeImageLevelFailed);
294                        }
295                        offset += bytes as u64;
296                    }
297                }
298            }
299            basisu_heap.try_read(..).unwrap()
300        };
301
302        let view_dimension = if info.layers == 0 {
303            if info.faces == 1 {
304                TextureViewDimension::D2
305            } else if info.faces == 6 {
306                TextureViewDimension::Cube
307            } else {
308                return Err(BasisuTranscodeError::InvalidFaceCount(info.faces));
309            }
310        } else if info.faces == 1 {
311            TextureViewDimension::D2Array
312        } else if info.faces == 6 {
313            TextureViewDimension::CubeArray
314        } else {
315            return Err(BasisuTranscodeError::InvalidFaceCount(info.faces));
316        };
317        let extent = Extent3d {
318            width: info.width,
319            height: info.height,
320            depth_or_array_layers: total_layers * info.faces,
321        };
322        let out_format = if is_srgb.unwrap_or(info.is_srgb) {
323            out_format.add_srgb_suffix()
324        } else {
325            out_format.remove_srgb_suffix()
326        };
327        Ok(TranscodedImage {
328            data,
329            data_order: TextureDataOrder::MipMajor,
330            // Note: we must give wgpu the logical texture dimensions, so it can correctly compute mip sizes.
331            // However this currently causes wgpu to panic if the dimensions aren't a multiple of blocksize.
332            // See https://github.com/gfx-rs/wgpu/issues/7677 for more context.
333            size: extent,
334            format: out_format,
335            mip_level_count: info.levels,
336            view_dimension,
337        })
338    }
339}
340
341#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
342#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
343pub enum ChannelType {
344    #[default]
345    Auto,
346    Rgba,
347    Rgb,
348    Rg,
349    R,
350}
351
352bitflags::bitflags! {
353    #[derive(Clone, Copy, Debug)]
354    pub struct SupportedTextureCompression: u8 {
355        const ETC2 = 1;
356        const BC = 1 << 1;
357        const ASTC_LDR = 1 << 2;
358        const ASTC_HDR = 1 << 3;
359    }
360}
361
362const KTX2_DF_CHANNEL_ETC1S_RGB: u32 = 0;
363const KTX2_DF_CHANNEL_ETC1S_RRR: u32 = 3;
364const KTX2_DF_CHANNEL_ETC1S_GGG: u32 = 4;
365const KTX2_DF_CHANNEL_ETC1S_AAA: u32 = 15;
366
367const KTX2_DF_CHANNEL_UASTC_RGB: u32 = 0;
368const KTX2_DF_CHANNEL_UASTC_RGBA: u32 = 3;
369const KTX2_DF_CHANNEL_UASTC_RRR: u32 = 4;
370const KTX2_DF_CHANNEL_UASTC_RRRG: u32 = 5;
371const KTX2_DF_CHANNEL_UASTC_RG: u32 = 6;
372
373fn channel_id_to_type(is_uastc: bool, channel_id0: u32, channel_id1: u32) -> ChannelType {
374    if is_uastc {
375        match channel_id0 {
376            KTX2_DF_CHANNEL_UASTC_RGB => ChannelType::Rgb,
377            KTX2_DF_CHANNEL_UASTC_RGBA => ChannelType::Rgba,
378            KTX2_DF_CHANNEL_UASTC_RRR => ChannelType::R,
379            KTX2_DF_CHANNEL_UASTC_RRRG => ChannelType::Rg,
380            KTX2_DF_CHANNEL_UASTC_RG => ChannelType::Rg,
381            _ => ChannelType::Rgba,
382        }
383    } else {
384        if channel_id0 == KTX2_DF_CHANNEL_ETC1S_RGB && channel_id1 != KTX2_DF_CHANNEL_ETC1S_AAA {
385            ChannelType::Rgb
386        } else if channel_id0 == KTX2_DF_CHANNEL_ETC1S_RGB
387            && channel_id1 == KTX2_DF_CHANNEL_ETC1S_AAA
388        {
389            ChannelType::Rgba
390        } else if channel_id0 == KTX2_DF_CHANNEL_ETC1S_RRR
391            && channel_id1 != KTX2_DF_CHANNEL_ETC1S_GGG
392        {
393            ChannelType::R
394        } else if channel_id0 == KTX2_DF_CHANNEL_ETC1S_RRR
395            && channel_id1 == KTX2_DF_CHANNEL_ETC1S_GGG
396        {
397            ChannelType::Rg
398        } else {
399            ChannelType::Rgba
400        }
401    }
402}
403
404// Select target format according to https://github.com/KhronosGroup/3D-Formats-Guidelines/blob/main/KTXDeveloperGuide.md.
405fn select_preferred_transcode_target(
406    basis_format: BasisTextureFormat,
407    channel_type: ChannelType,
408    supported_compressed_formats: SupportedTextureCompression,
409) -> TranscodeTargetFormat {
410    let select_hdr_4x4 = || {
411        if supported_compressed_formats.contains(SupportedTextureCompression::ASTC_HDR) {
412            TranscodeTargetFormat::AstcHdr4x4Rgba
413        } else if supported_compressed_formats.contains(SupportedTextureCompression::BC) {
414            TranscodeTargetFormat::Bc6H
415        } else {
416            TranscodeTargetFormat::RgbaHalf
417        }
418    };
419    let select_hdr_6x6 = || {
420        if supported_compressed_formats.contains(SupportedTextureCompression::ASTC_HDR) {
421            TranscodeTargetFormat::AstcHdr6x6Rgba
422        } else if supported_compressed_formats.contains(SupportedTextureCompression::BC) {
423            TranscodeTargetFormat::Bc6H
424        } else {
425            TranscodeTargetFormat::RgbaHalf
426        }
427    };
428    let select_astc_ldr = || {
429        if supported_compressed_formats.contains(SupportedTextureCompression::ASTC_LDR) {
430            TranscodeTargetFormat::try_from(unsafe {
431                trans_sys::bt_basis_get_transcoder_texture_format_from_basis_tex_format(
432                    basis_format as u32,
433                )
434            })
435            .unwrap()
436        } else if supported_compressed_formats.contains(SupportedTextureCompression::BC) {
437            TranscodeTargetFormat::Bc7Rgba
438        } else if supported_compressed_formats.contains(SupportedTextureCompression::ETC2) {
439            match channel_type {
440                ChannelType::Rgb => TranscodeTargetFormat::Etc1Rgb,
441                ChannelType::Rgba | ChannelType::Auto => TranscodeTargetFormat::Etc2Rgba,
442                ChannelType::R => TranscodeTargetFormat::Etc2EacR11,
443                ChannelType::Rg => TranscodeTargetFormat::Etc2EacRg11,
444            }
445        } else {
446            TranscodeTargetFormat::RGBA32
447        }
448    };
449    let select_etc1s = || {
450        if supported_compressed_formats.contains(SupportedTextureCompression::ETC2) {
451            match channel_type {
452                ChannelType::Rgb => TranscodeTargetFormat::Etc1Rgb,
453                ChannelType::Rgba | ChannelType::Auto => TranscodeTargetFormat::Etc2Rgba,
454                ChannelType::R => TranscodeTargetFormat::Etc2EacR11,
455                ChannelType::Rg => TranscodeTargetFormat::Etc2EacRg11,
456            }
457        } else if supported_compressed_formats.contains(SupportedTextureCompression::BC) {
458            match channel_type {
459                ChannelType::Rgb => TranscodeTargetFormat::Bc7Rgba,
460                ChannelType::Rgba | ChannelType::Auto => TranscodeTargetFormat::Bc7Rgba,
461                ChannelType::R => TranscodeTargetFormat::Bc4R,
462                ChannelType::Rg => TranscodeTargetFormat::Bc5Rg,
463            }
464        } else {
465            TranscodeTargetFormat::RGBA32
466        }
467    };
468    match basis_format {
469        BasisTextureFormat::Etc1s => select_etc1s(),
470        BasisTextureFormat::UastcLdr4x4 => select_astc_ldr(),
471        BasisTextureFormat::UastcHdr4x4 => select_hdr_4x4(),
472        BasisTextureFormat::AstcHdr6x6 => select_hdr_6x6(),
473        BasisTextureFormat::UastcHdr6x6 => select_hdr_6x6(),
474        BasisTextureFormat::XuastcLdr4x4
475        | BasisTextureFormat::XuastcLdr5x4
476        | BasisTextureFormat::XuastcLdr5x5
477        | BasisTextureFormat::XuastcLdr6x5
478        | BasisTextureFormat::XuastcLdr6x6
479        | BasisTextureFormat::XuastcLdr8x5
480        | BasisTextureFormat::XuastcLdr8x6
481        | BasisTextureFormat::XuastcLdr10x5
482        | BasisTextureFormat::XuastcLdr10x6
483        | BasisTextureFormat::XuastcLdr8x8
484        | BasisTextureFormat::XuastcLdr10x8
485        | BasisTextureFormat::XuastcLdr10x10
486        | BasisTextureFormat::XuastcLdr12x10
487        | BasisTextureFormat::XuastcLdr12x12
488        | BasisTextureFormat::AstcLdr4x4
489        | BasisTextureFormat::AstcLdr5x4
490        | BasisTextureFormat::AstcLdr5x5
491        | BasisTextureFormat::AstcLdr6x5
492        | BasisTextureFormat::AstcLdr6x6
493        | BasisTextureFormat::AstcLdr8x5
494        | BasisTextureFormat::AstcLdr8x6
495        | BasisTextureFormat::AstcLdr10x5
496        | BasisTextureFormat::AstcLdr10x6
497        | BasisTextureFormat::AstcLdr8x8
498        | BasisTextureFormat::AstcLdr10x8
499        | BasisTextureFormat::AstcLdr10x10
500        | BasisTextureFormat::AstcLdr12x10
501        | BasisTextureFormat::AstcLdr12x12 => select_astc_ldr(),
502    }
503}
504
505pub fn transcode_target_to_wgpu_format(transcode: TranscodeTargetFormat) -> Option<TextureFormat> {
506    Some(match transcode {
507        TranscodeTargetFormat::Etc1Rgb => TextureFormat::Etc2Rgb8Unorm,
508        TranscodeTargetFormat::Etc2Rgba => TextureFormat::Etc2Rgba8Unorm,
509        TranscodeTargetFormat::Bc1Rgb => TextureFormat::Bc1RgbaUnorm,
510        TranscodeTargetFormat::Bc3Rgba => TextureFormat::Bc3RgbaUnorm,
511        TranscodeTargetFormat::Bc4R => TextureFormat::Bc4RUnorm,
512        TranscodeTargetFormat::Bc5Rg => TextureFormat::Bc5RgUnorm,
513        TranscodeTargetFormat::Bc7Rgba => TextureFormat::Bc7RgbaUnorm,
514        TranscodeTargetFormat::Pvrtc1_4Rgb => return None,
515        TranscodeTargetFormat::Pvrtc1_4Rgba => return None,
516        TranscodeTargetFormat::AstcLdr4x4Rgba => TextureFormat::Astc {
517            block: AstcBlock::B4x4,
518            channel: AstcChannel::Unorm,
519        },
520        TranscodeTargetFormat::AtcRgb => return None,
521        TranscodeTargetFormat::AtcRgba => return None,
522        TranscodeTargetFormat::Fxt1Rgb => return None,
523        TranscodeTargetFormat::Pvrtc2_4Rgb => return None,
524        TranscodeTargetFormat::Pvrtc2_4Rgba => return None,
525        TranscodeTargetFormat::Etc2EacR11 => TextureFormat::EacR11Unorm,
526        TranscodeTargetFormat::Etc2EacRg11 => TextureFormat::EacRg11Unorm,
527        TranscodeTargetFormat::Bc6H => TextureFormat::Bc6hRgbUfloat,
528        TranscodeTargetFormat::AstcHdr4x4Rgba => TextureFormat::Astc {
529            block: AstcBlock::B4x4,
530            channel: AstcChannel::Hdr,
531        },
532        TranscodeTargetFormat::RGBA32 => TextureFormat::Rgba8Unorm,
533        TranscodeTargetFormat::RGB565 => return None,
534        TranscodeTargetFormat::BGR565 => return None,
535        TranscodeTargetFormat::RGBA4444 => return None,
536        TranscodeTargetFormat::RgbHalf => return None,
537        TranscodeTargetFormat::RgbaHalf => TextureFormat::Rgba16Float,
538        TranscodeTargetFormat::Rgb9e5 => TextureFormat::Rgb9e5Ufloat,
539        TranscodeTargetFormat::AstcHdr6x6Rgba => TextureFormat::Astc {
540            block: AstcBlock::B6x6,
541            channel: AstcChannel::Hdr,
542        },
543        TranscodeTargetFormat::AstcLdr5x4Rgba => TextureFormat::Astc {
544            block: AstcBlock::B5x4,
545            channel: AstcChannel::Unorm,
546        },
547        TranscodeTargetFormat::AstcLdr5x5Rgba => TextureFormat::Astc {
548            block: AstcBlock::B5x5,
549            channel: AstcChannel::Unorm,
550        },
551        TranscodeTargetFormat::AstcLdr6x5Rgba => TextureFormat::Astc {
552            block: AstcBlock::B6x5,
553            channel: AstcChannel::Unorm,
554        },
555        TranscodeTargetFormat::AstcLdr6x6Rgba => TextureFormat::Astc {
556            block: AstcBlock::B6x6,
557            channel: AstcChannel::Unorm,
558        },
559        TranscodeTargetFormat::AstcLdr8x5Rgba => TextureFormat::Astc {
560            block: AstcBlock::B8x5,
561            channel: AstcChannel::Unorm,
562        },
563        TranscodeTargetFormat::AstcLdr8x6Rgba => TextureFormat::Astc {
564            block: AstcBlock::B8x6,
565            channel: AstcChannel::Unorm,
566        },
567        TranscodeTargetFormat::AstcLdr10x5Rgba => TextureFormat::Astc {
568            block: AstcBlock::B10x5,
569            channel: AstcChannel::Unorm,
570        },
571        TranscodeTargetFormat::AstcLdr10x6Rgba => TextureFormat::Astc {
572            block: AstcBlock::B10x6,
573            channel: AstcChannel::Unorm,
574        },
575        TranscodeTargetFormat::AstcLdr8x8Rgba => TextureFormat::Astc {
576            block: AstcBlock::B8x8,
577            channel: AstcChannel::Unorm,
578        },
579        TranscodeTargetFormat::AstcLdr10x8Rgba => TextureFormat::Astc {
580            block: AstcBlock::B10x8,
581            channel: AstcChannel::Unorm,
582        },
583        TranscodeTargetFormat::AstcLdr10x10Rgba => TextureFormat::Astc {
584            block: AstcBlock::B10x10,
585            channel: AstcChannel::Unorm,
586        },
587        TranscodeTargetFormat::AstcLdr12x10Rgba => TextureFormat::Astc {
588            block: AstcBlock::B12x10,
589            channel: AstcChannel::Unorm,
590        },
591        TranscodeTargetFormat::AstcLdr12x12Rgba => TextureFormat::Astc {
592            block: AstcBlock::B12x12,
593            channel: AstcChannel::Unorm,
594        },
595    })
596}
597
598#[cfg(test)]
599mod tests {
600
601    #[test]
602    #[should_panic]
603    fn transcoder_create_before_init() {
604        if super::BASISU_TRANSCODER_INITIALIZED.is_initialized() {
605            panic!("Basisu is already initialized, panic to skip this test");
606        } else {
607            let _ = super::BasisuTranscoder::new(
608                &[],
609                super::SupportedTextureCompression::empty(),
610                super::ChannelType::Auto,
611            );
612        }
613    }
614}