basis_universal/transcoding/
transcoder.rs

1use super::*;
2use crate::UserData;
3use basis_universal_sys as sys;
4
5/// A transcoder that can convert compressed basis-universal data to compressed GPU formats or raw
6/// color data
7pub struct Transcoder(*mut sys::Transcoder);
8
9/// Lightweight description of a mip level on a single image within basis data
10#[derive(Default, Debug, Copy, Clone)]
11pub struct ImageLevelDescription {
12    pub original_width: u32,
13    pub original_height: u32,
14    pub block_count: u32,
15}
16
17/// Info for an image within basis data
18pub type ImageInfo = sys::basist_basisu_image_info;
19
20/// Info for a mip level of a single image within basis data
21pub type ImageLevelInfo = sys::basist_basisu_image_level_info;
22
23/// Info for the complete basis file
24pub type FileInfo = sys::FileInfo;
25
26/// Extra parameters for transcoding an image
27#[derive(Default, Debug, Clone)]
28pub struct TranscodeParameters {
29    /// The image to transcode
30    pub image_index: u32,
31    /// The mip level of the image to transcode
32    pub level_index: u32,
33    /// Optional flags can affect transcoding in various ways
34    pub decode_flags: Option<DecodeFlags>,
35    /// Optional override for row pitch
36    pub output_row_pitch_in_blocks_or_pixels: Option<u32>,
37    /// Optional override for number of rows to transcode
38    pub output_rows_in_pixels: Option<u32>,
39}
40
41/// Error result from trying to transcode an image
42#[derive(Debug, Copy, Clone)]
43pub enum TranscodeError {
44    TranscodeFormatNotSupported,
45    ImageLevelNotFound,
46    TranscodeFailed,
47}
48
49impl Default for Transcoder {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Transcoder {
56    /// Create a transcoder
57    pub fn new() -> Transcoder {
58        unsafe { Transcoder(sys::transcoder_new()) }
59    }
60
61    /// Validates the .basis file. This computes a crc16 over the entire file, so it's slow.
62    pub fn validate_file_checksums(
63        &self,
64        data: &[u8],
65        full_validation: bool,
66    ) -> bool {
67        unsafe {
68            sys::transcoder_validate_file_checksums(
69                self.0,
70                data.as_ptr() as _,
71                data.len() as u32,
72                full_validation,
73            )
74        }
75    }
76
77    /// Quick header validation - no crc16 checks.
78    pub fn validate_header(
79        &self,
80        data: &[u8],
81    ) -> bool {
82        unsafe { sys::transcoder_validate_header(self.0, data.as_ptr() as _, data.len() as u32) }
83    }
84
85    /// The type of texture represented by the basis data
86    pub fn basis_texture_type(
87        &self,
88        data: &[u8],
89    ) -> BasisTextureType {
90        unsafe {
91            sys::transcoder_get_texture_type(self.0, data.as_ptr() as _, data.len() as u32).into()
92        }
93    }
94
95    /// The basis texture format of the basis data
96    pub fn basis_texture_format(
97        &self,
98        data: &[u8],
99    ) -> BasisTextureFormat {
100        unsafe {
101            sys::transcoder_get_tex_format(self.0, data.as_ptr() as _, data.len() as u32).into()
102        }
103    }
104
105    pub fn user_data(
106        &self,
107        data: &[u8],
108    ) -> Result<UserData, ()> {
109        let mut userdata = UserData::default();
110        let result = unsafe {
111            sys::transcoder_get_userdata(
112                self.0,
113                data.as_ptr() as _,
114                data.len() as u32,
115                &mut userdata.userdata0,
116                &mut userdata.userdata1,
117            )
118        };
119
120        if result {
121            Ok(userdata)
122        } else {
123            Err(())
124        }
125    }
126
127    /// Number of images in the basis data
128    pub fn image_count(
129        &self,
130        data: &[u8],
131    ) -> u32 {
132        unsafe { sys::transcoder_get_total_images(self.0, data.as_ptr() as _, data.len() as u32) }
133    }
134
135    /// Number of mipmap levels for the specified image in the basis data
136    pub fn image_level_count(
137        &self,
138        data: &[u8],
139        image_index: u32,
140    ) -> u32 {
141        unsafe {
142            sys::transcoder_get_total_image_levels(
143                self.0,
144                data.as_ptr() as _,
145                data.len() as u32,
146                image_index,
147            )
148        }
149    }
150
151    /// Returns basic information about an image. Note that orig_width/orig_height may not be a multiple of 4.
152    pub fn image_level_description(
153        &self,
154        data: &[u8],
155        image_index: u32,
156        level_index: u32,
157    ) -> Option<ImageLevelDescription> {
158        let mut description = ImageLevelDescription::default();
159        unsafe {
160            if sys::transcoder_get_image_level_desc(
161                self.0,
162                data.as_ptr() as _,
163                data.len() as u32,
164                image_index,
165                level_index,
166                &mut description.original_width,
167                &mut description.original_height,
168                &mut description.block_count,
169            ) {
170                Some(description)
171            } else {
172                None
173            }
174        }
175    }
176
177    /// Returns information about the specified image.
178    pub fn image_info(
179        &self,
180        data: &[u8],
181        image_index: u32,
182    ) -> Option<ImageInfo> {
183        let mut image_info = unsafe { std::mem::zeroed::<ImageInfo>() };
184        unsafe {
185            if sys::transcoder_get_image_info(
186                self.0,
187                data.as_ptr() as _,
188                data.len() as u32,
189                &mut image_info,
190                image_index,
191            ) {
192                Some(image_info)
193            } else {
194                None
195            }
196        }
197    }
198
199    /// Returns information about the specified image's mipmap level.
200    pub fn image_level_info(
201        &self,
202        data: &[u8],
203        image_index: u32,
204        level_index: u32,
205    ) -> Option<ImageLevelInfo> {
206        let mut image_level_info = unsafe { std::mem::zeroed::<ImageLevelInfo>() };
207        unsafe {
208            if sys::transcoder_get_image_level_info(
209                self.0,
210                data.as_ptr() as _,
211                data.len() as u32,
212                &mut image_level_info,
213                image_index,
214                level_index,
215            ) {
216                Some(image_level_info)
217            } else {
218                None
219            }
220        }
221    }
222
223    /// Get a description of the basis file and low-level information about each slice.
224    pub fn file_info(
225        &self,
226        data: &[u8],
227    ) -> Option<FileInfo> {
228        let mut file_info = unsafe { std::mem::zeroed::<FileInfo>() };
229        unsafe {
230            if sys::transcoder_get_file_info(
231                self.0,
232                data.as_ptr() as _,
233                data.len() as u32,
234                &mut file_info,
235            ) {
236                Some(file_info)
237            } else {
238                None
239            }
240        }
241    }
242
243    /// prepare_transcoding() must be called before calling transcode_slice() or transcode_image_level().
244    /// This is `start_transcoding` in the original library
245    /// For ETC1S files, this call decompresses the selector/endpoint codebooks, so ideally you would only call this once per .basis file (not each image/mipmap level).
246    pub fn prepare_transcoding(
247        &mut self,
248        data: &[u8],
249    ) -> Result<(), ()> {
250        transcoder_init();
251        unsafe {
252            if sys::transcoder_start_transcoding(self.0, data.as_ptr() as _, data.len() as u32) {
253                Ok(())
254            } else {
255                Err(())
256            }
257        }
258    }
259
260    /// Parallel with `prepare_transcoding()`, named `stop_transcoding` in the original library
261    pub fn end_transcoding(&mut self) {
262        unsafe {
263            let result = sys::transcoder_stop_transcoding(self.0);
264            // I think this function is actually infallible, so don't return a result
265            debug_assert!(result);
266        }
267    }
268
269    /// Returns true if prepare_transcoding() has been called.
270    pub fn is_prepared_to_transcode(&self) -> bool {
271        unsafe { sys::transcoder_get_ready_to_transcode(self.0) }
272    }
273
274    /// transcode_image_level() decodes a single mipmap level from the .basis file to any of the supported output texture formats.
275    /// It'll first find the slice(s) to transcode, then call transcode_slice() one or two times to decode both the color and alpha texture data (or RG texture data from two slices for BC5).
276    /// If the .basis file doesn't have alpha slices, the output alpha blocks will be set to fully opaque (all 255's).
277    /// Currently, to decode to PVRTC1 the basis texture's dimensions in pixels must be a power of 2, due to PVRTC1 format requirements.
278    /// output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32.
279    /// output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling).
280    /// output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4).
281    /// Notes:
282    /// - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function.
283    /// - This method assumes the output texture buffer is readable. In some cases to handle alpha, the transcoder will write temporary data to the output texture in
284    /// a first pass, which will be read in a second pass.
285    pub fn transcode_image_level(
286        &self,
287        data: &[u8],
288        transcode_format: TranscoderTextureFormat,
289        transcode_parameters: TranscodeParameters,
290    ) -> Result<Vec<u8>, TranscodeError> {
291        let image_index = transcode_parameters.image_index;
292        let level_index = transcode_parameters.level_index;
293
294        //
295        // Check that the transcode format is supported for the stored texture's basis format
296        //
297        let basis_format = self.basis_texture_format(data);
298        if !basis_format.can_transcode_to_format(transcode_format) {
299            return Err(TranscodeError::TranscodeFormatNotSupported);
300        }
301
302        //
303        // Determine required size for the buffer
304        //
305        let description = self
306            .image_level_description(data, image_index, level_index)
307            .ok_or(TranscodeError::ImageLevelNotFound)?;
308        let required_buffer_bytes = transcode_format.calculate_minimum_output_buffer_bytes(
309            description.original_width,
310            description.original_height,
311            description.block_count,
312            transcode_parameters.output_row_pitch_in_blocks_or_pixels,
313            transcode_parameters.output_rows_in_pixels,
314        ) as usize;
315
316        //
317        // unwrap_or() the optional parameters
318        //
319        let decode_flags = transcode_parameters
320            .decode_flags
321            .unwrap_or_else(DecodeFlags::empty);
322        let output_row_pitch_in_blocks_or_pixels = transcode_parameters
323            .output_row_pitch_in_blocks_or_pixels
324            .unwrap_or(0);
325        let output_rows_in_pixels = transcode_parameters.output_rows_in_pixels.unwrap_or(0);
326        let transcoder_state = std::ptr::null_mut();
327
328        //
329        // Transcode
330        //
331        let mut output = vec![0_u8; required_buffer_bytes];
332        let success = unsafe {
333            sys::transcoder_transcode_image_level(
334                self.0,
335                data.as_ptr() as _,
336                data.len() as u32,
337                image_index,
338                level_index,
339                output.as_mut_ptr() as _,
340                output.len() as u32,
341                transcode_format.into(),
342                decode_flags.bits(),
343                output_row_pitch_in_blocks_or_pixels,
344                transcoder_state,
345                output_rows_in_pixels,
346            )
347        };
348
349        if success {
350            Ok(output)
351        } else {
352            Err(TranscodeError::TranscodeFailed)
353        }
354    }
355
356    // Not implemented
357    //
358    //    // Finds the basis slice corresponding to the specified image/level/alpha params, or -1 if the slice can't be found.
359    //    int find_slice(const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index, bool alpha_data) const;
360    //
361    //    // transcode_slice() decodes a single slice from the .basis file. It's a low-level API - most likely you want to use transcode_image_level().
362    //    // This is a low-level API, and will be needed to be called multiple times to decode some texture formats (like BC3, BC5, or ETC2).
363    //    // output_blocks_buf_size_in_blocks_or_pixels is just used for verification to make sure the output buffer is large enough.
364    //    // output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32.
365    //    // output_block_stride_in_bytes: Number of bytes between each output block.
366    //    // output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling).
367    //    // output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4).
368    //    // Notes:
369    //    // - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function.
370    //    bool transcode_slice(const void *pData, uint32_t data_size, uint32_t slice_index,
371    //                         void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
372    //                         block_format fmt, uint32_t output_block_stride_in_bytes, uint32_t decode_flags = 0, uint32_t output_row_pitch_in_blocks_or_pixels = 0, basisu_transcoder_state * pState = nullptr, void* pAlpha_blocks = nullptr,
373    //                         uint32_t output_rows_in_pixels = 0, int channel0 = -1, int channel1 = -1) const;
374    //
375    //    static void write_opaque_alpha_blocks(
376    //            uint32_t num_blocks_x, uint32_t num_blocks_y,
377    //            void* pOutput_blocks, block_format fmt,
378    //            uint32_t block_stride_in_bytes, uint32_t output_row_pitch_in_blocks_or_pixels);
379}
380
381impl Drop for Transcoder {
382    fn drop(&mut self) {
383        unsafe {
384            sys::transcoder_delete(self.0);
385        }
386    }
387}
388
389pub struct LowLevelUastcTranscoder(*mut sys::LowLevelUastcTranscoder);
390
391impl Default for LowLevelUastcTranscoder {
392    fn default() -> Self {
393        Self::new()
394    }
395}
396
397#[derive(Debug)]
398pub struct SliceParametersUastc {
399    pub num_blocks_x: u32,
400    pub num_blocks_y: u32,
401    pub has_alpha: bool,
402    pub original_width: u32,
403    pub original_height: u32,
404}
405
406impl LowLevelUastcTranscoder {
407    /// Create a LowLevelUastcTranscoder
408    pub fn new() -> LowLevelUastcTranscoder {
409        transcoder_init();
410        unsafe { LowLevelUastcTranscoder(sys::low_level_uastc_transcoder_new()) }
411    }
412
413    pub fn transcode_slice(
414        &self,
415        data: &[u8],
416        slice_parameters: SliceParametersUastc,
417        decode_flags: DecodeFlags,
418        transcode_block_format: TranscoderBlockFormat,
419    ) -> Result<Vec<u8>, TranscodeError> {
420        let bc1_allow_threecolor_blocks = false;
421        let transcoder_state = std::ptr::null_mut();
422        let channel0 = 0;
423        let channel1 = 3;
424
425        let output_row_pitch_in_blocks_or_pixels =
426            (slice_parameters.original_width + transcode_block_format.block_width() - 1)
427                / transcode_block_format.block_width();
428        let output_rows_in_pixels = slice_parameters.original_height;
429        let total_slice_blocks = slice_parameters.num_blocks_x * slice_parameters.num_blocks_y;
430        let required_buffer_bytes = transcode_block_format.calculate_minimum_output_buffer_bytes(
431            slice_parameters.original_width,
432            slice_parameters.original_height,
433            total_slice_blocks,
434            Some(output_row_pitch_in_blocks_or_pixels),
435            Some(output_rows_in_pixels),
436        ) as usize;
437
438        let output_block_or_pixel_stride_in_bytes =
439            transcode_block_format.bytes_per_block_or_pixel();
440
441        let mut output = vec![0_u8; required_buffer_bytes];
442        let success = unsafe {
443            sys::low_level_uastc_transcoder_transcode_slice(
444                self.0,
445                output.as_mut_ptr() as _,
446                slice_parameters.num_blocks_x,
447                slice_parameters.num_blocks_y,
448                data.as_ptr() as _,
449                data.len() as u32,
450                transcode_block_format.into(),
451                output_block_or_pixel_stride_in_bytes,
452                bc1_allow_threecolor_blocks,
453                slice_parameters.has_alpha,
454                slice_parameters.original_width,
455                slice_parameters.original_height,
456                output_row_pitch_in_blocks_or_pixels,
457                transcoder_state,
458                output_rows_in_pixels,
459                channel0,
460                channel1,
461                decode_flags.bits(),
462            )
463        };
464
465        if success {
466            Ok(output)
467        } else {
468            Err(TranscodeError::TranscodeFailed)
469        }
470    }
471}
472
473impl Drop for LowLevelUastcTranscoder {
474    fn drop(&mut self) {
475        unsafe {
476            sys::low_level_uastc_transcoder_delete(self.0);
477        }
478    }
479}