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}