libktx_rs/
texture.rs

1// Copyright (C) 2021 Paolo Jovon <paolo.jovon@gmail.com>
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core types involving KTX [`Texture`]s.
5
6use crate::{
7    enums::{
8        ktx_result, PackAstcBlockDimension, PackAstcEncoderFunction, PackAstcEncoderMode,
9        PackAstcQualityLevel, SuperCompressionScheme, TranscodeFlags, TranscodeFormat,
10    },
11    sys, KtxError,
12};
13use std::marker::PhantomData;
14
15/// A source of [`Texture`]s.
16pub trait TextureSource<'a> {
17    /// Attempts to create a new texture by consuming `self`.  
18    fn create_texture(self) -> Result<Texture<'a>, KtxError>;
19}
20
21/// A sink of [`Texture`]s, e.g. something they can be written to.
22#[cfg(feature = "write")]
23pub trait TextureSink {
24    /// Attempts to write `texture` to `self`.
25    fn write_texture(&mut self, texture: &Texture) -> Result<(), KtxError>;
26}
27
28/// Parameters for ASTC compression.
29///
30/// This only applies to Arm's ASTC encoder, which is in `libktx-rs-sys/build/KTX-Software/lib/astc-encoder`.  
31/// See [`sys::ktxAstcParams`] for information on the various fields.
32pub struct AstcParams {
33    pub verbose: bool,
34    pub thread_count: u32,
35    pub block_dimension: PackAstcBlockDimension,
36    pub function: PackAstcEncoderFunction,
37    pub mode: PackAstcEncoderMode,
38    pub quality_level: PackAstcQualityLevel,
39    pub normal_map: bool,
40    pub input_swizzle: [char; 4],
41}
42
43/// A KTX (1 or 2) texture.
44///
45/// This wraps both a [`sys::ktxTexture`] handle, and the [`TextureSource`] it was created from.
46pub struct Texture<'a> {
47    // Not actually dead - there most likely are raw pointers referencing this!!
48    #[allow(dead_code)]
49    pub(crate) source: Box<dyn TextureSource<'a> + 'a>,
50    pub(crate) handle: *mut sys::ktxTexture,
51    pub(crate) handle_phantom: PhantomData<&'a sys::ktxTexture>,
52}
53
54impl<'a> Texture<'a> {
55    /// Attempts to create a new texture, consuming the given [`TextureSource`].
56    pub fn new<S>(source: S) -> Result<Self, KtxError>
57    where
58        S: TextureSource<'a>,
59    {
60        source.create_texture()
61    }
62
63    /// Attempts to write the texture (in its native format, either KTX1 or KTX2) to `sink`.
64    #[cfg(feature = "write")]
65    pub fn write_to<T: TextureSink>(&self, sink: &mut T) -> Result<(), KtxError> {
66        sink.write_texture(self)
67    }
68
69    /// Returns the pointer to the (C-allocated) underlying [`sys::ktxTexture`].
70    ///
71    /// **SAFETY**: Pointers are harmless. Dereferencing them is not!
72    pub fn handle(&self) -> *mut sys::ktxTexture {
73        self.handle
74    }
75
76    /// Returns the total size of image data, in bytes.
77    pub fn data_size(&self) -> usize {
78        // SAFETY: Safe if `self.handle` is sane.
79        unsafe { sys::ktxTexture_GetDataSize(self.handle) as usize }
80    }
81
82    /// Returns a read-only view on the image data.
83    pub fn data(&self) -> &[u8] {
84        let data = unsafe { sys::ktxTexture_GetData(self.handle) };
85        // SAFETY: Safe if `self.handle` is sane.
86        unsafe { std::slice::from_raw_parts(data, self.data_size()) }
87    }
88
89    /// Returns a read-write view on the image data.
90    pub fn data_mut(&mut self) -> &mut [u8] {
91        let data = unsafe { sys::ktxTexture_GetData(self.handle) };
92        // SAFETY: Safe if `self.handle` is sane.
93        unsafe { std::slice::from_raw_parts_mut(data, self.data_size()) }
94    }
95
96    /// Returns the pitch (in bytes) of an image row at the specified image level.  
97    /// This is rounded up to 1 if needed.
98    pub fn row_pitch(&self, level: u32) -> usize {
99        // SAFETY: Safe if `self.handle` is sane.
100        //         `level` is not used for indexing internally; no bounds-checking required.
101        unsafe { sys::ktxTexture_GetRowPitch(self.handle, level) as usize }
102    }
103
104    /// Returns the size (in bytes) of an element of the image.
105    pub fn element_size(&self) -> usize {
106        // SAFETY: Safe if `self.handle` is sane.
107        unsafe { sys::ktxTexture_GetElementSize(self.handle) as usize }
108    }
109
110    /// Attempts to return the offset (in bytes) into [`Self::data`] for the image
111    /// at the given mip level, array layer, and slice.  
112    /// `slice` is either a cubemap's face or a 3D texture's depth slice.
113    pub fn get_image_offset(&self, level: u32, layer: u32, slice: u32) -> Result<usize, KtxError> {
114        // SAFETY: Safe if `self.handle` is sane.
115        unsafe {
116            let vtbl = (*self.handle).vtbl;
117            if let Some(get_image_offset_fn) = (*vtbl).GetImageOffset {
118                let mut offset = 0usize;
119                let err = get_image_offset_fn(self.handle, level, layer, slice, &mut offset);
120                ktx_result(err, offset)
121            } else {
122                Err(KtxError::InvalidValue)
123            }
124        }
125    }
126
127    /// Attempts to return the size (in bytes) of the uncompressed image data.
128    pub fn get_data_size_uncompressed(&self) -> Result<usize, KtxError> {
129        // SAFETY: Safe if `self.handle` is sane.
130        unsafe {
131            let vtbl = (*self.handle).vtbl;
132            if let Some(get_data_size_fn) = (*vtbl).GetDataSizeUncompressed {
133                Ok((get_data_size_fn)(self.handle))
134            } else {
135                Err(KtxError::InvalidValue)
136            }
137        }
138    }
139
140    /// Attempts to return the size (in bytes) of a certain mip level.
141    pub fn get_image_size(&self, level: u32) -> Result<usize, KtxError> {
142        // SAFETY: Safe if `self.handle` is sane.
143        unsafe {
144            let vtbl = (*self.handle).vtbl;
145            if let Some(get_image_size_fn) = (*vtbl).GetImageSize {
146                Ok((get_image_size_fn)(self.handle, level))
147            } else {
148                Err(KtxError::InvalidValue)
149            }
150        }
151    }
152
153    /// Attempts to [re]load this image's data to its internal buffer.
154    /// Also see [`Self::data()`].
155    ///
156    /// Creating the image with [`enums::TextureCreateFlags::LOAD_IMAGE_DATA`] performs this step automatically on load.
157    pub fn load_image_data(&self) -> Result<(), KtxError> {
158        // SAFETY: Safe if `self.handle` is sane.
159        unsafe {
160            let vtbl = (*self.handle).vtbl;
161            if let Some(load_image_data_fn) = (*vtbl).LoadImageData {
162                let err = (load_image_data_fn)(self.handle, std::ptr::null_mut(), 0usize);
163                ktx_result(err, ())
164            } else {
165                Err(KtxError::InvalidValue)
166            }
167        }
168    }
169
170    /// Attempts to iterate all mip levels of the image, and all faces of cubemaps.
171    /// This calls
172    /// ```rust,ignore
173    /// callback(miplevel: i32, face: i32, width: i32, height: i32, depth: i32, pixel_data: &[u8]) -> Result<(), KtxError>
174    /// ```
175    /// for each level/face. The image data passed to the callback is immutable.
176    /// Note that image data should already have been loaded (see [`Self::load_image_data()`]).
177    pub fn iterate_levels<F>(&self, mut callback: F) -> Result<(), KtxError>
178    where
179        F: FnMut(i32, i32, i32, i32, i32, &[u8]) -> Result<(), KtxError>,
180    {
181        unsafe extern "C" fn c_iterator_fn<F>(
182            mip: i32,
183            face: i32,
184            width: i32,
185            height: i32,
186            depth: i32,
187            pixels_size: u64,
188            pixels: *mut std::ffi::c_void,
189            closure_ptr: *mut std::ffi::c_void,
190        ) -> sys::ktx_error_code_e
191        where
192            F: FnMut(i32, i32, i32, i32, i32, &[u8]) -> Result<(), KtxError>,
193        {
194            let closure = closure_ptr as *mut F;
195            let pixels_slice =
196                std::slice::from_raw_parts(pixels as *const u8, pixels_size as usize);
197            match (*closure)(mip, face, width, height, depth, pixels_slice) {
198                Ok(_) => sys::ktx_error_code_e_KTX_SUCCESS,
199                Err(code) => code as u32,
200            }
201        }
202
203        // SAFETY: Safe if `self.handle` is sane.
204        unsafe {
205            if (*self.handle).pData.is_null() {
206                // Data was not loaded
207                return Err(KtxError::InvalidValue);
208            }
209
210            let vtbl = (*self.handle).vtbl;
211            if let Some(iterate_levels_fn) = (*vtbl).IterateLevels {
212                let closure_ptr = &mut callback as *mut F as *mut std::ffi::c_void;
213                let err = (iterate_levels_fn)(self.handle, Some(c_iterator_fn::<F>), closure_ptr);
214                ktx_result(err, ())
215            } else {
216                Err(KtxError::InvalidValue)
217            }
218        }
219    }
220
221    /// Attempts to iterate all mip levels of the image, and all faces of cubemaps.
222    /// This calls
223    /// ```rust,ignore
224    /// callback(miplevel: i32, face: i32, width: i32, height: i32, depth: i32, pixel_data: &mut [u8]) -> Result<(), KtxError>
225    /// ```
226    /// for each level/face. The image data passed to the callback is mutable.
227    /// Note that image data should already have been loaded (see [`Self::load_image_data()`]).
228    pub fn iterate_levels_mut<F>(&mut self, mut callback: F) -> Result<(), KtxError>
229    where
230        F: FnMut(i32, i32, i32, i32, i32, &mut [u8]) -> Result<(), KtxError>,
231    {
232        unsafe extern "C" fn c_iterator_fn<F>(
233            mip: i32,
234            face: i32,
235            width: i32,
236            height: i32,
237            depth: i32,
238            pixels_size: u64,
239            pixels: *mut std::ffi::c_void,
240            closure_ptr: *mut std::ffi::c_void,
241        ) -> sys::ktx_error_code_e
242        where
243            F: FnMut(i32, i32, i32, i32, i32, &mut [u8]) -> Result<(), KtxError>,
244        {
245            let closure = closure_ptr as *mut F;
246            let pixels_slice =
247                std::slice::from_raw_parts_mut(pixels as *mut u8, pixels_size as usize);
248            match (*closure)(mip, face, width, height, depth, pixels_slice) {
249                Ok(_) => sys::ktx_error_code_e_KTX_SUCCESS,
250                Err(code) => code as u32,
251            }
252        }
253
254        // SAFETY: Safe if `self.handle` is sane.
255        unsafe {
256            if (*self.handle).pData.is_null() {
257                // Data was not loaded
258                return Err(KtxError::InvalidValue);
259            }
260
261            let vtbl = (*self.handle).vtbl;
262            if let Some(iterate_levels_fn) = (*vtbl).IterateLevels {
263                let closure_ptr = &mut callback as *mut F as *mut std::ffi::c_void;
264                let err = (iterate_levels_fn)(self.handle, Some(c_iterator_fn::<F>), closure_ptr);
265                ktx_result(err, ())
266            } else {
267                Err(KtxError::InvalidValue)
268            }
269        }
270    }
271
272    /// If this [`Texture`] really is a KTX1, returns KTX1-specific functionalities for it.
273    pub fn ktx1<'b>(&'b mut self) -> Option<Ktx1<'b, 'a>> {
274        // SAFETY: Safe if `self.handle` is sane.
275        if unsafe { &*self.handle }.classId == sys::class_id_ktxTexture1_c {
276            Some(Ktx1 { texture: self })
277        } else {
278            None
279        }
280    }
281
282    /// If this [`Texture`] really is a KTX2, returns KTX2-specific functionalities for it.
283    pub fn ktx2<'b>(&'b mut self) -> Option<Ktx2<'b, 'a>> {
284        // SAFETY: Safe if `self.handle` is sane.
285        if unsafe { &*self.handle }.classId == sys::class_id_ktxTexture2_c {
286            Some(Ktx2 { texture: self })
287        } else {
288            None
289        }
290    }
291}
292
293impl<'a> Drop for Texture<'a> {
294    fn drop(&mut self) {
295        unsafe {
296            let vtbl = (*self.handle).vtbl;
297            if let Some(destroy_fn) = (*vtbl).Destroy {
298                (destroy_fn)(self.handle as *mut sys::ktxTexture);
299            }
300        }
301    }
302}
303
304/// KTX1-specific [`Texture`] functionality.
305pub struct Ktx1<'a, 'b: 'a> {
306    texture: &'a mut Texture<'b>,
307}
308
309impl<'a, 'b: 'a> Ktx1<'a, 'b> {
310    /// Returns a pointer to the underlying (C-allocated) [`sys::ktxTexture1`].
311    ///
312    /// **SAFETY**: Pointers are harmless. Dereferencing them is not!
313    pub fn handle(&self) -> *mut sys::ktxTexture1 {
314        self.texture.handle as *mut sys::ktxTexture1
315    }
316
317    /// Returns the OpenGL format of the texture's data (e.g. `GL_RGBA`).
318    ///
319    /// Also see [`Self::gl_internal_format`], [`Self::gl_base_internal_format`].
320    pub fn gl_format(&self) -> u32 {
321        let handle = self.handle();
322        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
323        unsafe { (*handle).glFormat }
324    }
325
326    /// Returns the OpenGL format of the texture's data (e.g. `GL_RGBA`).
327    ///
328    /// Also see [`Self::gl_format`], [`Self::gl_base_internal_format`].
329    pub fn gl_internal_format(&self) -> u32 {
330        let handle = self.handle();
331        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
332        unsafe { (*handle).glFormat }
333    }
334
335    /// Returns the OpenGL base internal format of the texture's data (e.g. `GL_RGBA`).
336    ///
337    /// Also see [`Self::gl_format`], [`Self::gl_internal_format`].
338    pub fn gl_base_internal_format(&self) -> u32 {
339        let handle = self.handle();
340        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
341        unsafe { (*handle).glBaseInternalformat }
342    }
343
344    /// Returns the OpenGL datatype of the texture's data (e.g. `GL_UNSIGNED_BYTE`).
345    pub fn gl_type(&self) -> u32 {
346        let handle = self.handle();
347        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
348        unsafe { (*handle).glType }
349    }
350
351    /// Will this KTX1 need transcoding?
352    pub fn needs_transcoding(&self) -> bool {
353        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
354        unsafe { sys::ktxTexture1_NeedsTranscoding(self.handle()) }
355    }
356
357    // TODO: WriteKTX2ToStream with a Rust stream (and to a memory slice?)
358    //       Probably needs a TextureSink trait
359}
360
361/// KTX2-specific [`Texture`] functionality.
362pub struct Ktx2<'a, 'b: 'a> {
363    texture: &'a mut Texture<'b>,
364}
365
366impl<'a, 'b: 'a> Ktx2<'a, 'b> {
367    /// Returns a pointer to the underlying (C-allocated) [`sys::ktxTexture2`].
368    ///
369    /// **SAFETY**: Pointers are harmless. Dereferencing them is not!
370    pub fn handle(&self) -> *mut sys::ktxTexture2 {
371        self.texture.handle as *mut sys::ktxTexture2
372    }
373
374    /// Returns the Vulkan format of the texture's data (e.g. `VK_R8G8B8A8_UNORM`).
375    pub fn vk_format(&self) -> u32 {
376        let handle = self.handle();
377        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
378        unsafe { (*handle).vkFormat }
379    }
380
381    /// Returns the supercompression scheme in use for this texture's data.
382    pub fn supercompression_scheme(&self) -> SuperCompressionScheme {
383        let handle = self.handle();
384        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
385        unsafe { (*handle).supercompressionScheme.into() }
386    }
387
388    /// Is this a video texture?
389    pub fn is_video(&self) -> bool {
390        let handle = self.handle();
391        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
392        unsafe { (*handle).isVideo }
393    }
394
395    /// Returns the duration of the video texture (if [`Self::is_video`]).
396    pub fn duration(&self) -> u32 {
397        let handle = self.handle();
398        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
399        unsafe { (*handle).duration }
400    }
401
402    /// Returns the timescale of the video texture (if [`Self::is_video`]).
403    pub fn timescale(&self) -> u32 {
404        let handle = self.handle();
405        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
406        unsafe { (*handle).timescale }
407    }
408
409    /// Returns the loop count of the video texture (if [`Self::is_video`]).
410    pub fn loop_count(&self) -> u32 {
411        let handle = self.handle();
412        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX1
413        unsafe { (*handle).loopcount }
414    }
415
416    /// Will this KTX2 need transcoding?
417    pub fn needs_transcoding(&self) -> bool {
418        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
419        unsafe { sys::ktxTexture2_NeedsTranscoding(self.handle()) }
420    }
421
422    /// Compresses a uncompressed KTX2 texture with Basis Universal.  
423    /// `quality` is 1-255; 0 -> the default quality, 128. **Lower `quality` means better (but slower) compression**.
424    pub fn compress_basis(&mut self, quality: u32) -> Result<(), KtxError> {
425        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
426        let errcode = unsafe { sys::ktxTexture2_CompressBasis(self.handle(), quality as u32) };
427        ktx_result(errcode, ())
428    }
429
430    /// Compresses the KTX2 texture's data with ZStandard compression.  
431    /// `level` is 1-22; lower is faster (hence, worse compression).  
432    /// Values over 20 may consume significant memory.
433    pub fn deflate_zstd(&mut self, level: u32) -> Result<(), KtxError> {
434        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
435        let errcode = unsafe { sys::ktxTexture2_DeflateZstd(self.handle(), level as u32) };
436        ktx_result(errcode, ())
437    }
438
439    /// Compresses the KTX2's image data with ASTC.  
440    /// This is a simplified version of [`Ktx2::compress_astc_ex`].
441    pub fn compress_astc(&mut self, quality: u32) -> Result<(), KtxError> {
442        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
443        let errcode = unsafe { sys::ktxTexture2_CompressAstc(self.handle(), quality) };
444        ktx_result(errcode, ())
445    }
446
447    /// Compresses the KTX2's image data with ASTC.   
448    /// This is an extended version of [`Ktx2::compress_astc`].
449    pub fn compress_astc_ex(&mut self, params: AstcParams) -> Result<(), KtxError> {
450        let mut c_input_swizzle: [std::os::raw::c_char; 4] = [0, 0, 0, 0];
451        for (ch, c_ch) in params.input_swizzle.iter().zip(c_input_swizzle.iter_mut()) {
452            *c_ch = *ch as _;
453        }
454        let mut c_params = sys::ktxAstcParams {
455            structSize: std::mem::size_of::<sys::ktxAstcParams>() as u32,
456            verbose: params.verbose,
457            threadCount: params.thread_count,
458            blockDimension: params.block_dimension as u32,
459            function: params.function as u32,
460            mode: params.mode as u32,
461            qualityLevel: params.quality_level as u32,
462            normalMap: params.normal_map,
463            inputSwizzle: c_input_swizzle,
464        };
465
466        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
467        let errcode = unsafe { sys::ktxTexture2_CompressAstcEx(self.handle(), &mut c_params) };
468        ktx_result(errcode, ())
469    }
470
471    /// Returns the number of components of the KTX2 and the size in bytes of each components.
472    pub fn component_info(&self) -> (u32, u32) {
473        let mut num_components: u32 = 0;
474        let mut component_size: u32 = 0;
475        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
476        unsafe {
477            sys::ktxTexture2_GetComponentInfo(
478                self.handle(),
479                &mut num_components,
480                &mut component_size,
481            );
482        }
483        (num_components, component_size)
484    }
485
486    /// Returns the number of components of the KTX2, also considering compression.  
487    ///
488    /// **This may differ from values returned by [`Self::component_info`]:**
489    /// - For uncompressed formats: this is the number of image components, as from [`Self::component_info`].
490    /// - For block-compressed formats: 1 or 2, according to the DFD color model.
491    /// - For Basis Universal-compressed textures: obtained by parsing channel IDs before any encoding and deflation.
492    ///
493    /// See [`sys::ktxTexture2_GetNumComponents`].
494    pub fn num_components(&self) -> u32 {
495        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
496        unsafe { sys::ktxTexture2_GetNumComponents(self.handle()) }
497    }
498
499    /// Returns the Opto-Electrical Transfer Function (OETF) for this KTX2, in KHR_DF format.  
500    /// See <https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#_emphasis_role_strong_emphasis_transferfunction_emphasis_emphasis>.
501    pub fn oetf(&self) -> u32 {
502        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
503        unsafe { sys::ktxTexture2_GetOETF(self.handle()) }
504    }
505
506    /// Does this KTX2 have premultiplied alpha?
507    pub fn premultiplied_alpha(&self) -> bool {
508        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
509        unsafe { sys::ktxTexture2_GetPremultipliedAlpha(self.handle()) }
510    }
511
512    /// Transcodes this KTX2 to the given format by using ETC1S (from Basis Universal) or UASTC.
513    ///
514    /// - BasisLZ supercompressed textures are turned back to ETC1S, then transcoded.
515    /// - UASTC-compressed images are inflated (possibly, even deflating any ZStandard supercompression), then transcoded.
516    /// - **All internal data of the texture may change, including the
517    /// [DFD](https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#_anchor_id_dataformatdescriptor_xreflabel_dataformatdescriptor_khronos_data_format_descriptor)**!
518    pub fn transcode_basis(
519        &mut self,
520        format: TranscodeFormat,
521        flags: TranscodeFlags,
522    ) -> Result<(), KtxError> {
523        // SAFETY: Safe if `self.texture.handle` is sane + actually a KTX2
524        let errcode =
525            unsafe { sys::ktxTexture2_TranscodeBasis(self.handle(), format as u32, flags.bits()) };
526        ktx_result(errcode, ())
527    }
528}