1 | /* -*- tab-width: 4; -*- */ |
2 | /* vi: set sw=2 ts=4 expandtab: */ |
3 | |
4 | /* |
5 | * Copyright 2018-2020 Mark Callow. |
6 | * SPDX-License-Identifier: Apache-2.0 |
7 | */ |
8 | |
9 | /** |
10 | * @internal |
11 | * @file writer.c |
12 | * @~English |
13 | * |
14 | * @brief ktxTexture implementation. |
15 | * |
16 | * @author Mark Callow, www.edgewise-consulting.com |
17 | */ |
18 | |
19 | #if defined(_WIN32) |
20 | #define _CRT_SECURE_NO_WARNINGS |
21 | #ifndef __cplusplus |
22 | #undef inline |
23 | #define inline __inline |
24 | #endif // __cplusplus |
25 | #endif |
26 | |
27 | #include <assert.h> |
28 | #include <math.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | |
32 | #include "ktx.h" |
33 | #include "ktxint.h" |
34 | #include "formatsize.h" |
35 | #include "filestream.h" |
36 | #include "memstream.h" |
37 | #include "texture1.h" |
38 | #include "texture2.h" |
39 | #include "unused.h" |
40 | |
41 | ktx_size_t ktxTexture_GetDataSize(ktxTexture* This); |
42 | |
43 | static ktx_uint32_t padRow(ktx_uint32_t* rowBytes); |
44 | |
45 | /** |
46 | * @memberof ktxTexture @private |
47 | * @~English |
48 | * @brief Construct (initialize) a ktxTexture base class instance. |
49 | * |
50 | * @param[in] This pointer to a ktxTexture-sized block of memory to |
51 | * initialize. |
52 | * @param[in] createInfo pointer to a ktxTextureCreateInfo struct with |
53 | * information describing the texture. |
54 | * @param[in] formatSize pointer to a ktxFormatSize giving size information |
55 | * about the texture's elements. |
56 | * |
57 | * @return KTX_SUCCESS on success, other KTX_* enum values on error. |
58 | * |
59 | * @exception KTX_INVALID_VALUE @c glInternalFormat in @p createInfo is not a |
60 | * valid OpenGL internal format value. |
61 | * @exception KTX_INVALID_VALUE @c numDimensions in @p createInfo is not 1, 2 |
62 | * or 3. |
63 | * @exception KTX_INVALID_VALUE One of <tt>base{Width,Height,Depth}</tt> in |
64 | * @p createInfo is 0. |
65 | * @exception KTX_INVALID_VALUE @c numFaces in @p createInfo is not 1 or 6. |
66 | * @exception KTX_INVALID_VALUE @c numLevels in @p createInfo is 0. |
67 | * @exception KTX_INVALID_OPERATION |
68 | * The <tt>base{Width,Height,Depth}</tt> specified |
69 | * in @p createInfo are inconsistent with |
70 | * @c numDimensions. |
71 | * @exception KTX_INVALID_OPERATION |
72 | * @p createInfo is requesting a 3D array or |
73 | * 3D cubemap texture. |
74 | * @exception KTX_INVALID_OPERATION |
75 | * @p createInfo is requesting a cubemap with |
76 | * non-square or non-2D images. |
77 | * @exception KTX_INVALID_OPERATION |
78 | * @p createInfo is requesting more mip levels |
79 | * than needed for the specified |
80 | * <tt>base{Width,Height,Depth}</tt>. |
81 | * @exception KTX_OUT_OF_MEMORY Not enough memory for the texture. |
82 | */ |
83 | KTX_error_code |
84 | ktxTexture_construct(ktxTexture* This, ktxTextureCreateInfo* createInfo, |
85 | ktxFormatSize* formatSize) |
86 | { |
87 | DECLARE_PROTECTED(ktxTexture); |
88 | |
89 | memset(This, 0, sizeof(*This)); |
90 | This->_protected = (struct ktxTexture_protected*)malloc(sizeof(*prtctd)); |
91 | if (!This->_protected) |
92 | return KTX_OUT_OF_MEMORY; |
93 | prtctd = This->_protected; |
94 | memset(prtctd, 0, sizeof(*prtctd)); |
95 | memcpy(&prtctd->_formatSize, formatSize, sizeof(prtctd->_formatSize)); |
96 | |
97 | This->isCompressed = (formatSize->flags & KTX_FORMAT_SIZE_COMPRESSED_BIT); |
98 | |
99 | This->orientation.x = KTX_ORIENT_X_RIGHT; |
100 | This->orientation.y = KTX_ORIENT_Y_DOWN; |
101 | This->orientation.z = KTX_ORIENT_Z_OUT; |
102 | |
103 | /* Check texture dimensions. KTX files can store 8 types of textures: |
104 | * 1D, 2D, 3D, cube, and array variants of these. |
105 | */ |
106 | if (createInfo->numDimensions < 1 || createInfo->numDimensions > 3) |
107 | return KTX_INVALID_VALUE; |
108 | |
109 | if (createInfo->baseWidth == 0 || createInfo->baseHeight == 0 |
110 | || createInfo->baseDepth == 0) |
111 | return KTX_INVALID_VALUE; |
112 | |
113 | switch (createInfo->numDimensions) { |
114 | case 1: |
115 | if (createInfo->baseHeight > 1 || createInfo->baseDepth > 1) |
116 | return KTX_INVALID_OPERATION; |
117 | break; |
118 | |
119 | case 2: |
120 | if (createInfo->baseDepth > 1) |
121 | return KTX_INVALID_OPERATION; |
122 | break; |
123 | |
124 | case 3: |
125 | /* 3D array textures and 3D cubemaps are not supported by either |
126 | * OpenGL or Vulkan. |
127 | */ |
128 | if (createInfo->isArray || createInfo->numFaces != 1 |
129 | || createInfo->numLayers != 1) |
130 | return KTX_INVALID_OPERATION; |
131 | break; |
132 | } |
133 | This->numDimensions = createInfo->numDimensions; |
134 | This->baseWidth = createInfo->baseWidth; |
135 | This->baseDepth = createInfo->baseDepth; |
136 | This->baseHeight = createInfo->baseHeight; |
137 | |
138 | if (createInfo->numLayers == 0) |
139 | return KTX_INVALID_VALUE; |
140 | This->numLayers = createInfo->numLayers; |
141 | This->isArray = createInfo->isArray; |
142 | |
143 | if (createInfo->numFaces == 6) { |
144 | if (This->numDimensions != 2) { |
145 | /* cube map needs 2D faces */ |
146 | return KTX_INVALID_OPERATION; |
147 | } |
148 | if (createInfo->baseWidth != createInfo->baseHeight) { |
149 | /* cube maps require square images */ |
150 | return KTX_INVALID_OPERATION; |
151 | } |
152 | This->isCubemap = KTX_TRUE; |
153 | } else if (createInfo->numFaces != 1) { |
154 | /* numFaces must be either 1 or 6 */ |
155 | return KTX_INVALID_VALUE; |
156 | } |
157 | This->numFaces = createInfo->numFaces; |
158 | |
159 | /* Check number of mipmap levels */ |
160 | if (createInfo->numLevels == 0) |
161 | return KTX_INVALID_VALUE; |
162 | This->numLevels = createInfo->numLevels; |
163 | This->generateMipmaps = createInfo->generateMipmaps; |
164 | |
165 | if (createInfo->numLevels > 1) { |
166 | GLuint max_dim = MAX(MAX(createInfo->baseWidth, createInfo->baseHeight), |
167 | createInfo->baseDepth); |
168 | if (max_dim < ((GLuint)1 << (This->numLevels - 1))) |
169 | { |
170 | /* Can't have more mip levels than 1 + log2(max(width, height, depth)) */ |
171 | return KTX_INVALID_OPERATION; |
172 | } |
173 | } |
174 | |
175 | ktxHashList_Construct(&This->kvDataHead); |
176 | return KTX_SUCCESS; |
177 | } |
178 | |
179 | /** |
180 | * @memberof ktxTexture @private |
181 | * @~English |
182 | * @brief Construct (initialize) the part of a ktxTexture base class that is |
183 | * not related to the stream contents. |
184 | * |
185 | * @param[in] This pointer to a ktxTexture-sized block of memory to |
186 | * initialize. |
187 | * |
188 | * @return KTX_SUCCESS on success, other KTX_* enum values on error. |
189 | */ |
190 | KTX_error_code |
191 | ktxTexture_constructFromStream(ktxTexture* This, ktxStream* pStream, |
192 | ktxTextureCreateFlags createFlags) |
193 | { |
194 | ktxStream* stream; |
195 | UNUSED(createFlags); // Reference to keep compiler happy. |
196 | |
197 | assert(This != NULL); |
198 | assert(pStream->data.mem != NULL); |
199 | assert(pStream->type == eStreamTypeFile |
200 | || pStream->type == eStreamTypeMemory |
201 | || pStream->type == eStreamTypeCustom); |
202 | |
203 | This->_protected = (struct ktxTexture_protected *) |
204 | malloc(sizeof(struct ktxTexture_protected)); |
205 | stream = ktxTexture_getStream(This); |
206 | // Copy stream info into struct for later use. |
207 | *stream = *pStream; |
208 | |
209 | This->orientation.x = KTX_ORIENT_X_RIGHT; |
210 | This->orientation.y = KTX_ORIENT_Y_DOWN; |
211 | This->orientation.z = KTX_ORIENT_Z_OUT; |
212 | |
213 | return KTX_SUCCESS; |
214 | } |
215 | |
216 | |
217 | /** |
218 | * @memberof ktxTexture @private |
219 | * @~English |
220 | * @brief Free the memory associated with the texture contents |
221 | * |
222 | * @param[in] This pointer to the ktxTextureInt whose texture contents are |
223 | * to be freed. |
224 | */ |
225 | void |
226 | ktxTexture_destruct(ktxTexture* This) |
227 | { |
228 | ktxStream stream = *(ktxTexture_getStream(This)); |
229 | |
230 | if (stream.data.file != NULL) |
231 | stream.destruct(&stream); |
232 | if (This->kvDataHead != NULL) |
233 | ktxHashList_Destruct(&This->kvDataHead); |
234 | if (This->kvData != NULL) |
235 | free(This->kvData); |
236 | if (This->pData != NULL) |
237 | free(This->pData); |
238 | free(This->_protected); |
239 | } |
240 | |
241 | |
242 | /** |
243 | * @defgroup reader Reader |
244 | * @brief Read KTX-formatted data. |
245 | * @{ |
246 | */ |
247 | |
248 | typedef enum { KTX1, KTX2 } ktxFileType_; |
249 | typedef union { |
250 | KTX_header ktx; |
251 | KTX_header2 ktx2; |
252 | } ; |
253 | |
254 | /** |
255 | * @memberof ktxTexture @private |
256 | * @~English |
257 | * @brief Determine if stream data is KTX1 or KTX2. |
258 | * |
259 | * @param pStream pointer to the ktxStream to examine. |
260 | * @param pFileType pointer to a ktxFileType enum where the type of the data |
261 | * will be written. |
262 | * @param pHeader pointer to a ktxHeaderUnion where the header info. will be |
263 | * written. |
264 | */ |
265 | static KTX_error_code |
266 | ktxDetermineFileType_(ktxStream* pStream, ktxFileType_* pFileType, |
267 | ktxHeaderUnion_* ) |
268 | { |
269 | ktx_uint8_t ktx_ident_ref[12] = KTX_IDENTIFIER_REF; |
270 | ktx_uint8_t ktx2_ident_ref[12] = KTX2_IDENTIFIER_REF; |
271 | KTX_error_code result; |
272 | |
273 | assert(pStream != NULL && pFileType != NULL); |
274 | assert(pStream->data.mem != NULL); |
275 | assert(pStream->type == eStreamTypeFile |
276 | || pStream->type == eStreamTypeMemory |
277 | || pStream->type == eStreamTypeCustom); |
278 | |
279 | result = pStream->read(pStream, pHeader, sizeof(ktx2_ident_ref)); |
280 | if (result == KTX_SUCCESS) { |
281 | #if BIG_ENDIAN |
282 | // byte swap the heaader fields |
283 | #endif |
284 | // Compare identifier, is this a KTX or KTX2 file? |
285 | if (!memcmp(pHeader->ktx.identifier, ktx_ident_ref, 12)) { |
286 | *pFileType = KTX1; |
287 | } else if (!memcmp(pHeader->ktx2.identifier, ktx2_ident_ref, 12)) { |
288 | *pFileType = KTX2; |
289 | } else { |
290 | return KTX_UNKNOWN_FILE_FORMAT; |
291 | } |
292 | // Read rest of header. |
293 | if (*pFileType == KTX1) { |
294 | // Read rest of header. |
295 | result = pStream->read(pStream, &pHeader->ktx.endianness, |
296 | KTX_HEADER_SIZE - sizeof(ktx_ident_ref)); |
297 | } else { |
298 | result = pStream->read(pStream, &pHeader->ktx2.vkFormat, |
299 | KTX2_HEADER_SIZE - sizeof(ktx2_ident_ref)); |
300 | } |
301 | } |
302 | return result; |
303 | } |
304 | |
305 | /** |
306 | * @memberof ktxTexture |
307 | * @~English |
308 | * @brief Construct (initialize) a ktx1 or ktx2 texture according to the stream |
309 | * data. |
310 | * |
311 | * @copydetails ktxTexture_CreateFromStdioStream |
312 | */ |
313 | KTX_error_code |
314 | ktxTexture_CreateFromStream(ktxStream* pStream, |
315 | ktxTextureCreateFlags createFlags, |
316 | ktxTexture** newTex) |
317 | { |
318 | ktxHeaderUnion_ ; |
319 | ktxFileType_ fileType; |
320 | KTX_error_code result; |
321 | ktxTexture* tex; |
322 | |
323 | result = ktxDetermineFileType_(pStream, &fileType, &header); |
324 | if (result != KTX_SUCCESS) |
325 | return result; |
326 | |
327 | if (fileType == KTX1) { |
328 | ktxTexture1* tex1 = (ktxTexture1*)malloc(sizeof(ktxTexture1)); |
329 | if (tex1 == NULL) |
330 | return KTX_OUT_OF_MEMORY; |
331 | memset(tex1, 0, sizeof(ktxTexture1)); |
332 | result = ktxTexture1_constructFromStreamAndHeader(tex1, pStream, |
333 | &header.ktx, |
334 | createFlags); |
335 | tex = ktxTexture(tex1); |
336 | } else { |
337 | ktxTexture2* tex2 = (ktxTexture2*)malloc(sizeof(ktxTexture2)); |
338 | if (tex2 == NULL) |
339 | return KTX_OUT_OF_MEMORY; |
340 | memset(tex2, 0, sizeof(ktxTexture2)); |
341 | result = ktxTexture2_constructFromStreamAndHeader(tex2, pStream, |
342 | &header.ktx2, |
343 | createFlags); |
344 | tex = ktxTexture(tex2); |
345 | } |
346 | |
347 | if (result == KTX_SUCCESS) |
348 | *newTex = (ktxTexture*)tex; |
349 | else { |
350 | free(tex); |
351 | *newTex = NULL; |
352 | } |
353 | return result; |
354 | } |
355 | |
356 | /** |
357 | * @memberof ktxTexture |
358 | * @~English |
359 | * @brief Create a ktxTexture1 or ktxTexture2 from a stdio stream according |
360 | * to the stream data. |
361 | * |
362 | * @copydetails ktxTexture1_CreateFromStdioStream() |
363 | */ |
364 | KTX_error_code |
365 | ktxTexture_CreateFromStdioStream(FILE* stdioStream, |
366 | ktxTextureCreateFlags createFlags, |
367 | ktxTexture** newTex) |
368 | { |
369 | ktxStream stream; |
370 | KTX_error_code result; |
371 | |
372 | if (stdioStream == NULL || newTex == NULL) |
373 | return KTX_INVALID_VALUE; |
374 | |
375 | result = ktxFileStream_construct(&stream, stdioStream, KTX_FALSE); |
376 | if (result == KTX_SUCCESS) { |
377 | result = ktxTexture_CreateFromStream(&stream, createFlags, newTex); |
378 | } |
379 | return result; |
380 | } |
381 | |
382 | /** |
383 | * @memberof ktxTexture |
384 | * @~English |
385 | * @brief Create a ktxTexture1 or ktxTexture2 from a named KTX file according |
386 | * to the file contents. |
387 | * |
388 | * The address of a newly created ktxTexture reflecting the contents of the |
389 | * file is written to the location pointed at by @p newTex. |
390 | * |
391 | * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set, |
392 | * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This |
393 | * will minimize memory usage by allowing, for example, loading the images |
394 | * directly from the source into a Vulkan staging buffer. |
395 | * |
396 | * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is |
397 | * provided solely to enable implementation of the @e libktx v1 API on top of |
398 | * ktxTexture. |
399 | * |
400 | * @param[in] filename pointer to a char array containing the file name. |
401 | * @param[in] createFlags bitmask requesting specific actions during creation. |
402 | * @param[in,out] newTex pointer to a location in which store the address of |
403 | * the newly created texture. |
404 | * |
405 | * @return KTX_SUCCESS on success, other KTX_* enum values on error. |
406 | |
407 | * @exception KTX_FILE_OPEN_FAILED The file could not be opened. |
408 | * @exception KTX_INVALID_VALUE @p filename is @c NULL. |
409 | * |
410 | * For other exceptions, see ktxTexture_CreateFromStdioStream(). |
411 | */ |
412 | KTX_error_code |
413 | ktxTexture_CreateFromNamedFile(const char* const filename, |
414 | ktxTextureCreateFlags createFlags, |
415 | ktxTexture** newTex) |
416 | { |
417 | KTX_error_code result; |
418 | ktxStream stream; |
419 | FILE* file; |
420 | |
421 | if (filename == NULL || newTex == NULL) |
422 | return KTX_INVALID_VALUE; |
423 | |
424 | file = fopen(filename, "rb" ); |
425 | if (!file) |
426 | return KTX_FILE_OPEN_FAILED; |
427 | |
428 | result = ktxFileStream_construct(&stream, file, KTX_TRUE); |
429 | if (result == KTX_SUCCESS) { |
430 | result = ktxTexture_CreateFromStream(&stream, createFlags, newTex); |
431 | } |
432 | return result; |
433 | } |
434 | |
435 | /** |
436 | * @memberof ktxTexture |
437 | * @~English |
438 | * @brief Create a ktxTexture1 or ktxTexture2 from KTX-formatted data in memory |
439 | * according to the data contents. |
440 | * |
441 | * The address of a newly created ktxTexture reflecting the contents of the |
442 | * serialized KTX data is written to the location pointed at by @p newTex. |
443 | * |
444 | * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set, |
445 | * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This |
446 | * will minimize memory usage by allowing, for example, loading the images |
447 | * directly from the source into a Vulkan staging buffer. |
448 | * |
449 | * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is |
450 | * provided solely to enable implementation of the @e libktx v1 API on top of |
451 | * ktxTexture. |
452 | * |
453 | * @param[in] bytes pointer to the memory containing the serialized KTX data. |
454 | * @param[in] size length of the KTX data in bytes. |
455 | * @param[in] createFlags bitmask requesting specific actions during creation. |
456 | * @param[in,out] newTex pointer to a location in which store the address of |
457 | * the newly created texture. |
458 | * |
459 | * @return KTX_SUCCESS on success, other KTX_* enum values on error. |
460 | * |
461 | * @exception KTX_INVALID_VALUE Either @p bytes is NULL or @p size is 0. |
462 | * |
463 | * For other exceptions, see ktxTexture_CreateFromStdioStream(). |
464 | */ |
465 | KTX_error_code |
466 | ktxTexture_CreateFromMemory(const ktx_uint8_t* bytes, ktx_size_t size, |
467 | ktxTextureCreateFlags createFlags, |
468 | ktxTexture** newTex) |
469 | { |
470 | KTX_error_code result; |
471 | ktxStream stream; |
472 | |
473 | if (bytes == NULL || newTex == NULL || size == 0) |
474 | return KTX_INVALID_VALUE; |
475 | |
476 | result = ktxMemStream_construct_ro(&stream, bytes, size); |
477 | if (result == KTX_SUCCESS) { |
478 | result = ktxTexture_CreateFromStream(&stream, createFlags, newTex); |
479 | } |
480 | return result;} |
481 | |
482 | |
483 | /** |
484 | * @memberof ktxTexture |
485 | * @~English |
486 | * @brief Return a pointer to the texture image data. |
487 | * |
488 | * @param[in] This pointer to the ktxTexture object of interest. |
489 | */ |
490 | ktx_uint8_t* |
491 | ktxTexture_GetData(ktxTexture* This) |
492 | { |
493 | return This->pData; |
494 | } |
495 | |
496 | /** |
497 | * @memberof ktxTexture |
498 | * @~English |
499 | * @brief Return the total size of the texture image data in bytes. |
500 | * |
501 | * For a ktxTexture2 with supercompressionScheme != KTX_SS_NONE this will |
502 | * return the deflated size of the data. |
503 | * |
504 | * @param[in] This pointer to the ktxTexture object of interest. |
505 | */ |
506 | ktx_size_t |
507 | ktxTexture_GetDataSize(ktxTexture* This) |
508 | { |
509 | assert(This != NULL); |
510 | return This->dataSize; |
511 | } |
512 | |
513 | /** |
514 | * @memberof ktxTexture |
515 | * @~English |
516 | * @brief Return the size in bytes of an elements of a texture's |
517 | * images. |
518 | * |
519 | * For uncompressed textures an element is one texel. For compressed |
520 | * textures it is one block. |
521 | * |
522 | * @param[in] This pointer to the ktxTexture object of interest. |
523 | */ |
524 | ktx_uint32_t |
525 | ktxTexture_GetElementSize(ktxTexture* This) |
526 | { |
527 | assert (This != NULL); |
528 | |
529 | return (This->_protected->_formatSize.blockSizeInBits / 8); |
530 | } |
531 | |
532 | /** |
533 | * @memberof ktxTexture @private |
534 | * @~English |
535 | * @brief Calculate & return the size in bytes of an image at the specified |
536 | * mip level. |
537 | * |
538 | * For arrays, this is the size of layer, for cubemaps, the size of a face |
539 | * and for 3D textures, the size of a depth slice. |
540 | * |
541 | * The size reflects the padding of each row to KTX_GL_UNPACK_ALIGNMENT. |
542 | * |
543 | * @param[in] This pointer to the ktxTexture object of interest. |
544 | * @param[in] level level of interest. |
545 | * @param[in] fv enum specifying format version for which to calculate |
546 | * image size. |
547 | */ |
548 | ktx_size_t |
549 | ktxTexture_calcImageSize(ktxTexture* This, ktx_uint32_t level, |
550 | ktxFormatVersionEnum fv) |
551 | { |
552 | DECLARE_PROTECTED(ktxTexture); |
553 | struct blockCount { |
554 | ktx_uint32_t x, y; |
555 | } blockCount; |
556 | ktx_uint32_t blockSizeInBytes; |
557 | ktx_uint32_t rowBytes; |
558 | |
559 | assert (This != NULL); |
560 | |
561 | float levelWidth = (float)(This->baseWidth >> level); |
562 | float levelHeight = (float)(This->baseHeight >> level); |
563 | // Round up to next whole block. We can't use KTX_PADN because some of |
564 | // the block sizes are not powers of 2. |
565 | blockCount.x |
566 | = (ktx_uint32_t)ceilf(levelWidth / prtctd->_formatSize.blockWidth); |
567 | blockCount.y |
568 | = (ktx_uint32_t)ceilf(levelHeight / prtctd->_formatSize.blockHeight); |
569 | blockCount.x = MAX(prtctd->_formatSize.minBlocksX, blockCount.x); |
570 | blockCount.y = MAX(prtctd->_formatSize.minBlocksX, blockCount.y); |
571 | |
572 | blockSizeInBytes = prtctd->_formatSize.blockSizeInBits / 8; |
573 | |
574 | if (prtctd->_formatSize.flags & KTX_FORMAT_SIZE_COMPRESSED_BIT) { |
575 | assert(This->isCompressed); |
576 | return blockCount.x * blockCount.y * blockSizeInBytes; |
577 | } else { |
578 | assert(prtctd->_formatSize.blockWidth == 1U |
579 | && prtctd->_formatSize.blockHeight == 1U |
580 | && prtctd->_formatSize.blockDepth == 1U); |
581 | rowBytes = blockCount.x * blockSizeInBytes; |
582 | if (fv == KTX_FORMAT_VERSION_ONE) |
583 | (void)padRow(&rowBytes); |
584 | return rowBytes * blockCount.y; |
585 | } |
586 | } |
587 | |
588 | /** |
589 | * @memberof ktxTexture |
590 | * @~English |
591 | * @brief Iterate over the levels or faces in a ktxTexture object. |
592 | * |
593 | * Blocks of image data are passed to an application-supplied callback |
594 | * function. This is not a strict per-image iteration. Rather it reflects how |
595 | * OpenGL needs the images. For most textures the block of data includes all |
596 | * images of a mip level which implies all layers of an array. However, for |
597 | * non-array cube map textures the block is a single face of the mip level, |
598 | * i.e the callback is called once for each face. |
599 | * |
600 | * This function works even if @p This->pData == 0 so it can be used to |
601 | * obtain offsets and sizes for each level by callers who have loaded the data |
602 | * externally. |
603 | * |
604 | * @param[in] This pointer to the ktxTexture object of interest. |
605 | * @param[in,out] iterCb the address of a callback function which is called |
606 | * with the data for each image block. |
607 | * @param[in,out] userdata the address of application-specific data which is |
608 | * passed to the callback along with the image data. |
609 | * |
610 | * @return KTX_SUCCESS on success, other KTX_* enum values on error. The |
611 | * following are returned directly by this function. @p iterCb may |
612 | * return these for other causes or may return additional errors. |
613 | * |
614 | * @exception KTX_FILE_DATA_ERROR Mip level sizes are increasing not |
615 | * decreasing |
616 | * @exception KTX_INVALID_VALUE @p This is @c NULL or @p iterCb is @c NULL. |
617 | * |
618 | */ |
619 | KTX_error_code |
620 | ktxTexture_IterateLevelFaces(ktxTexture* This, PFNKTXITERCB iterCb, |
621 | void* userdata) |
622 | { |
623 | ktx_uint32_t miplevel; |
624 | KTX_error_code result = KTX_SUCCESS; |
625 | |
626 | if (This == NULL) |
627 | return KTX_INVALID_VALUE; |
628 | |
629 | if (iterCb == NULL) |
630 | return KTX_INVALID_VALUE; |
631 | |
632 | for (miplevel = 0; miplevel < This->numLevels; ++miplevel) |
633 | { |
634 | ktx_uint32_t faceLodSize; |
635 | ktx_uint32_t face; |
636 | ktx_uint32_t innerIterations; |
637 | GLsizei width, height, depth; |
638 | |
639 | /* Array textures have the same number of layers at each mip level. */ |
640 | width = MAX(1, This->baseWidth >> miplevel); |
641 | height = MAX(1, This->baseHeight >> miplevel); |
642 | depth = MAX(1, This->baseDepth >> miplevel); |
643 | |
644 | faceLodSize = (ktx_uint32_t)ktxTexture_calcFaceLodSize( |
645 | This, miplevel); |
646 | |
647 | /* All array layers are passed in a group because that is how |
648 | * GL & Vulkan need them. Hence no |
649 | * for (layer = 0; layer < This->numLayers) |
650 | */ |
651 | if (This->isCubemap && !This->isArray) |
652 | innerIterations = This->numFaces; |
653 | else |
654 | innerIterations = 1; |
655 | for (face = 0; face < innerIterations; ++face) |
656 | { |
657 | /* And all z_slices are also passed as a group hence no |
658 | * for (slice = 0; slice < This->depth) |
659 | */ |
660 | ktx_size_t offset; |
661 | |
662 | ktxTexture_GetImageOffset(This, miplevel, 0, face, &offset); |
663 | result = iterCb(miplevel, face, |
664 | width, height, depth, |
665 | faceLodSize, This->pData + offset, userdata); |
666 | |
667 | if (result != KTX_SUCCESS) |
668 | break; |
669 | } |
670 | } |
671 | |
672 | return result; |
673 | } |
674 | |
675 | /** |
676 | * @internal |
677 | * @brief Calculate and apply the padding needed to comply with |
678 | * KTX_GL_UNPACK_ALIGNMENT. |
679 | * |
680 | * For uncompressed textures, KTX format specifies KTX_GL_UNPACK_ALIGNMENT = 4. |
681 | * |
682 | * @param[in,out] rowBytes pointer to variable containing the packed no. of |
683 | * bytes in a row. The no. of bytes after padding |
684 | * is written into this location. |
685 | * @return the no. of bytes of padding. |
686 | */ |
687 | static ktx_uint32_t |
688 | padRow(ktx_uint32_t* rowBytes) |
689 | { |
690 | ktx_uint32_t rowPadding; |
691 | |
692 | assert (rowBytes != NULL); |
693 | |
694 | rowPadding = _KTX_PAD_UNPACK_ALIGN_LEN(*rowBytes); |
695 | *rowBytes += rowPadding; |
696 | return rowPadding; |
697 | } |
698 | |
699 | /** |
700 | * @memberof ktxTexture @private |
701 | * @~English |
702 | * @brief Calculate the size of an array layer at the specified mip level. |
703 | * |
704 | * The size of a layer is the size of an image * either the number of faces |
705 | * or the number of depth slices. This is the size of a layer as needed to |
706 | * find the offset within the array of images of a level and layer so the size |
707 | * reflects any @c cubePadding. |
708 | * |
709 | * @param[in] This pointer to the ktxTexture object of interest. |
710 | * @param[in] level level whose layer size to return. |
711 | * |
712 | * @return the layer size in bytes. |
713 | */ |
714 | ktx_size_t |
715 | ktxTexture_layerSize(ktxTexture* This, ktx_uint32_t level, |
716 | ktxFormatVersionEnum fv) |
717 | { |
718 | /* |
719 | * As there are no 3D cubemaps, the image's z block count will always be |
720 | * 1 for cubemaps and numFaces will always be 1 for 3D textures so the |
721 | * multiply is safe. 3D cubemaps, if they existed, would require |
722 | * imageSize * (blockCount.z + This->numFaces); |
723 | */ |
724 | DECLARE_PROTECTED(ktxTexture); |
725 | ktx_uint32_t blockCountZ; |
726 | ktx_size_t imageSize, layerSize; |
727 | |
728 | assert (This != NULL); |
729 | |
730 | blockCountZ = MAX(1, (This->baseDepth / prtctd->_formatSize.blockDepth) >> level); |
731 | imageSize = ktxTexture_calcImageSize(This, level, fv); |
732 | layerSize = imageSize * blockCountZ; |
733 | if (fv == KTX_FORMAT_VERSION_ONE && KTX_GL_UNPACK_ALIGNMENT != 4) { |
734 | if (This->isCubemap && !This->isArray) { |
735 | /* cubePadding. NOTE: this adds padding after the last face too. */ |
736 | layerSize += _KTX_PAD4(layerSize); |
737 | } |
738 | } |
739 | return layerSize * This->numFaces; |
740 | } |
741 | |
742 | /** |
743 | * @memberof ktxTexture @private |
744 | * @~English |
745 | * @brief Calculate the size of the specified mip level. |
746 | * |
747 | * The size of a level is the size of a layer * the number of layers. |
748 | * |
749 | * @param[in] This pointer to the ktxTexture object of interest. |
750 | * @param[in] level level whose layer size to return. |
751 | * |
752 | * @return the level size in bytes. |
753 | */ |
754 | ktx_size_t |
755 | ktxTexture_calcLevelSize(ktxTexture* This, ktx_uint32_t level, |
756 | ktxFormatVersionEnum fv) |
757 | { |
758 | assert (This != NULL); |
759 | assert (level < This->numLevels); |
760 | return ktxTexture_layerSize(This, level, fv) * This->numLayers; |
761 | } |
762 | |
763 | /** |
764 | * @memberof ktxTexture @private |
765 | * @~English |
766 | * @brief Calculate the faceLodSize of the specified mip level. |
767 | * |
768 | * The faceLodSize of a level for most textures is the size of a level. For |
769 | * non-array cube map textures is the size of a face. This is the size that |
770 | * must be provided to OpenGL when uploading textures. Faces get uploaded 1 |
771 | * at a time while all layers of an array or all slices of a 3D texture are |
772 | * uploaded together. |
773 | * |
774 | * @param[in] This pointer to the ktxTexture object of interest. |
775 | * @param[in] level level whose layer size to return. |
776 | * |
777 | * @return the faceLodSize size in bytes. |
778 | */ |
779 | ktx_size_t |
780 | ktxTexture_doCalcFaceLodSize(ktxTexture* This, ktx_uint32_t level, |
781 | ktxFormatVersionEnum fv) |
782 | { |
783 | /* |
784 | * For non-array cubemaps this is the size of a face. For everything |
785 | * else it is the size of the level. |
786 | */ |
787 | if (This->isCubemap && !This->isArray) |
788 | return ktxTexture_calcImageSize(This, level, fv); |
789 | else |
790 | return ktxTexture_calcLevelSize(This, level, fv); |
791 | } |
792 | |
793 | |
794 | /** |
795 | * @memberof ktxTexture @private |
796 | * @~English |
797 | * @brief Return the number of bytes needed to store all the image data for |
798 | * a ktxTexture. |
799 | * |
800 | * The caclulated size does not include space for storing the @c imageSize |
801 | * fields of each mip level. |
802 | * |
803 | * @param[in] This pointer to the ktxTexture object of interest. |
804 | * @param[in] fv enum specifying format version for which to calculate |
805 | * image size. |
806 | * |
807 | * @return the data size in bytes. |
808 | */ |
809 | ktx_size_t |
810 | ktxTexture_calcDataSizeTexture(ktxTexture* This) |
811 | { |
812 | assert (This != NULL); |
813 | return ktxTexture_calcDataSizeLevels(This, This->numLevels); |
814 | } |
815 | |
816 | /** |
817 | * @memberof ktxTexture @private |
818 | * @~English |
819 | * @brief Get information about rows of an uncompresssed texture image at a |
820 | * specified level. |
821 | * |
822 | * For an image at @p level of a ktxTexture provide the number of rows, the |
823 | * packed (unpadded) number of bytes in a row and the padding necessary to |
824 | * comply with KTX_GL_UNPACK_ALIGNMENT. |
825 | * |
826 | * @param[in] This pointer to the ktxTexture object of interest. |
827 | * @param[in] level level of interest. |
828 | * @param[in,out] numRows pointer to location to store the number of rows. |
829 | * @param[in,out] pRowLengthBytes pointer to location to store number of bytes |
830 | * in a row. |
831 | * @param[in.out] pRowPadding pointer to location to store the number of bytes |
832 | * of padding. |
833 | */ |
834 | void |
835 | ktxTexture_rowInfo(ktxTexture* This, ktx_uint32_t level, |
836 | ktx_uint32_t* numRows, ktx_uint32_t* pRowLengthBytes, |
837 | ktx_uint32_t* pRowPadding) |
838 | { |
839 | DECLARE_PROTECTED(ktxTexture); |
840 | struct blockCount { |
841 | ktx_uint32_t x; |
842 | } blockCount; |
843 | |
844 | assert (This != NULL); |
845 | |
846 | assert(!This->isCompressed); |
847 | assert(prtctd->_formatSize.blockWidth == 1U |
848 | && prtctd->_formatSize.blockHeight == 1U |
849 | && prtctd->_formatSize.blockDepth == 1U); |
850 | |
851 | blockCount.x = MAX(1, (This->baseWidth / prtctd->_formatSize.blockWidth) >> level); |
852 | *numRows = MAX(1, (This->baseHeight / prtctd->_formatSize.blockHeight) >> level); |
853 | |
854 | *pRowLengthBytes = blockCount.x * prtctd->_formatSize.blockSizeInBits / 8; |
855 | *pRowPadding = padRow(pRowLengthBytes); |
856 | } |
857 | |
858 | /** |
859 | * @memberof ktxTexture |
860 | * @~English |
861 | * @brief Return pitch betweeb rows of a texture image level in bytes. |
862 | * |
863 | * For uncompressed textures the pitch is the number of bytes between |
864 | * rows of texels. For compressed textures it is the number of bytes |
865 | * between rows of blocks. The value is padded to GL_UNPACK_ALIGNMENT, |
866 | * if necessary. For all currently known compressed formats padding |
867 | * will not be necessary. |
868 | * |
869 | * @param[in] This pointer to the ktxTexture object of interest. |
870 | * @param[in] level level of interest. |
871 | * |
872 | * @return the row pitch in bytes. |
873 | */ |
874 | ktx_uint32_t |
875 | ktxTexture_GetRowPitch(ktxTexture* This, ktx_uint32_t level) |
876 | { |
877 | DECLARE_PROTECTED(ktxTexture) |
878 | struct blockCount { |
879 | ktx_uint32_t x; |
880 | } blockCount; |
881 | ktx_uint32_t pitch; |
882 | |
883 | blockCount.x = MAX(1, (This->baseWidth / prtctd->_formatSize.blockWidth) >> level); |
884 | pitch = blockCount.x * prtctd->_formatSize.blockSizeInBits / 8; |
885 | (void)padRow(&pitch); |
886 | |
887 | return pitch; |
888 | } |
889 | |
890 | /** |
891 | * @memberof ktxTexture @private |
892 | * @~English |
893 | * @brief Query if a ktxTexture has an active stream. |
894 | * |
895 | * Tests if a ktxTexture has unread image data. The internal stream is closed |
896 | * once all the images have been read. |
897 | * |
898 | * @param[in] This pointer to the ktxTexture object of interest. |
899 | * |
900 | * @return KTX_TRUE if there is an active stream, KTX_FALSE otherwise. |
901 | */ |
902 | ktx_bool_t |
903 | ktxTexture_isActiveStream(ktxTexture* This) |
904 | { |
905 | assert(This != NULL); |
906 | ktxStream* stream = ktxTexture_getStream(This); |
907 | return stream->data.file != NULL; |
908 | } |
909 | |
910 | /** @} */ |
911 | |
912 | |