912 lines
30 KiB
C
912 lines
30 KiB
C
|
/* -*- tab-width: 4; -*- */
|
||
|
/* vi: set sw=2 ts=4 expandtab: */
|
||
|
|
||
|
/*
|
||
|
* Copyright 2018-2020 Mark Callow.
|
||
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @internal
|
||
|
* @file writer.c
|
||
|
* @~English
|
||
|
*
|
||
|
* @brief ktxTexture implementation.
|
||
|
*
|
||
|
* @author Mark Callow, www.edgewise-consulting.com
|
||
|
*/
|
||
|
|
||
|
#if defined(_WIN32)
|
||
|
#define _CRT_SECURE_NO_WARNINGS
|
||
|
#ifndef __cplusplus
|
||
|
#undef inline
|
||
|
#define inline __inline
|
||
|
#endif // __cplusplus
|
||
|
#endif
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <math.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "ktx.h"
|
||
|
#include "ktxint.h"
|
||
|
#include "formatsize.h"
|
||
|
#include "filestream.h"
|
||
|
#include "memstream.h"
|
||
|
#include "texture1.h"
|
||
|
#include "texture2.h"
|
||
|
#include "unused.h"
|
||
|
|
||
|
ktx_size_t ktxTexture_GetDataSize(ktxTexture* This);
|
||
|
|
||
|
static ktx_uint32_t padRow(ktx_uint32_t* rowBytes);
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Construct (initialize) a ktxTexture base class instance.
|
||
|
*
|
||
|
* @param[in] This pointer to a ktxTexture-sized block of memory to
|
||
|
* initialize.
|
||
|
* @param[in] createInfo pointer to a ktxTextureCreateInfo struct with
|
||
|
* information describing the texture.
|
||
|
* @param[in] formatSize pointer to a ktxFormatSize giving size information
|
||
|
* about the texture's elements.
|
||
|
*
|
||
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||
|
*
|
||
|
* @exception KTX_INVALID_VALUE @c glInternalFormat in @p createInfo is not a
|
||
|
* valid OpenGL internal format value.
|
||
|
* @exception KTX_INVALID_VALUE @c numDimensions in @p createInfo is not 1, 2
|
||
|
* or 3.
|
||
|
* @exception KTX_INVALID_VALUE One of <tt>base{Width,Height,Depth}</tt> in
|
||
|
* @p createInfo is 0.
|
||
|
* @exception KTX_INVALID_VALUE @c numFaces in @p createInfo is not 1 or 6.
|
||
|
* @exception KTX_INVALID_VALUE @c numLevels in @p createInfo is 0.
|
||
|
* @exception KTX_INVALID_OPERATION
|
||
|
* The <tt>base{Width,Height,Depth}</tt> specified
|
||
|
* in @p createInfo are inconsistent with
|
||
|
* @c numDimensions.
|
||
|
* @exception KTX_INVALID_OPERATION
|
||
|
* @p createInfo is requesting a 3D array or
|
||
|
* 3D cubemap texture.
|
||
|
* @exception KTX_INVALID_OPERATION
|
||
|
* @p createInfo is requesting a cubemap with
|
||
|
* non-square or non-2D images.
|
||
|
* @exception KTX_INVALID_OPERATION
|
||
|
* @p createInfo is requesting more mip levels
|
||
|
* than needed for the specified
|
||
|
* <tt>base{Width,Height,Depth}</tt>.
|
||
|
* @exception KTX_OUT_OF_MEMORY Not enough memory for the texture.
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_construct(ktxTexture* This, ktxTextureCreateInfo* createInfo,
|
||
|
ktxFormatSize* formatSize)
|
||
|
{
|
||
|
DECLARE_PROTECTED(ktxTexture);
|
||
|
|
||
|
memset(This, 0, sizeof(*This));
|
||
|
This->_protected = (struct ktxTexture_protected*)malloc(sizeof(*prtctd));
|
||
|
if (!This->_protected)
|
||
|
return KTX_OUT_OF_MEMORY;
|
||
|
prtctd = This->_protected;
|
||
|
memset(prtctd, 0, sizeof(*prtctd));
|
||
|
memcpy(&prtctd->_formatSize, formatSize, sizeof(prtctd->_formatSize));
|
||
|
|
||
|
This->isCompressed = (formatSize->flags & KTX_FORMAT_SIZE_COMPRESSED_BIT);
|
||
|
|
||
|
This->orientation.x = KTX_ORIENT_X_RIGHT;
|
||
|
This->orientation.y = KTX_ORIENT_Y_DOWN;
|
||
|
This->orientation.z = KTX_ORIENT_Z_OUT;
|
||
|
|
||
|
/* Check texture dimensions. KTX files can store 8 types of textures:
|
||
|
* 1D, 2D, 3D, cube, and array variants of these.
|
||
|
*/
|
||
|
if (createInfo->numDimensions < 1 || createInfo->numDimensions > 3)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
if (createInfo->baseWidth == 0 || createInfo->baseHeight == 0
|
||
|
|| createInfo->baseDepth == 0)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
switch (createInfo->numDimensions) {
|
||
|
case 1:
|
||
|
if (createInfo->baseHeight > 1 || createInfo->baseDepth > 1)
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
if (createInfo->baseDepth > 1)
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
/* 3D array textures and 3D cubemaps are not supported by either
|
||
|
* OpenGL or Vulkan.
|
||
|
*/
|
||
|
if (createInfo->isArray || createInfo->numFaces != 1
|
||
|
|| createInfo->numLayers != 1)
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
break;
|
||
|
}
|
||
|
This->numDimensions = createInfo->numDimensions;
|
||
|
This->baseWidth = createInfo->baseWidth;
|
||
|
This->baseDepth = createInfo->baseDepth;
|
||
|
This->baseHeight = createInfo->baseHeight;
|
||
|
|
||
|
if (createInfo->numLayers == 0)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
This->numLayers = createInfo->numLayers;
|
||
|
This->isArray = createInfo->isArray;
|
||
|
|
||
|
if (createInfo->numFaces == 6) {
|
||
|
if (This->numDimensions != 2) {
|
||
|
/* cube map needs 2D faces */
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
}
|
||
|
if (createInfo->baseWidth != createInfo->baseHeight) {
|
||
|
/* cube maps require square images */
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
}
|
||
|
This->isCubemap = KTX_TRUE;
|
||
|
} else if (createInfo->numFaces != 1) {
|
||
|
/* numFaces must be either 1 or 6 */
|
||
|
return KTX_INVALID_VALUE;
|
||
|
}
|
||
|
This->numFaces = createInfo->numFaces;
|
||
|
|
||
|
/* Check number of mipmap levels */
|
||
|
if (createInfo->numLevels == 0)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
This->numLevels = createInfo->numLevels;
|
||
|
This->generateMipmaps = createInfo->generateMipmaps;
|
||
|
|
||
|
if (createInfo->numLevels > 1) {
|
||
|
GLuint max_dim = MAX(MAX(createInfo->baseWidth, createInfo->baseHeight),
|
||
|
createInfo->baseDepth);
|
||
|
if (max_dim < ((GLuint)1 << (This->numLevels - 1)))
|
||
|
{
|
||
|
/* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
|
||
|
return KTX_INVALID_OPERATION;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ktxHashList_Construct(&This->kvDataHead);
|
||
|
return KTX_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Construct (initialize) the part of a ktxTexture base class that is
|
||
|
* not related to the stream contents.
|
||
|
*
|
||
|
* @param[in] This pointer to a ktxTexture-sized block of memory to
|
||
|
* initialize.
|
||
|
*
|
||
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_constructFromStream(ktxTexture* This, ktxStream* pStream,
|
||
|
ktxTextureCreateFlags createFlags)
|
||
|
{
|
||
|
ktxStream* stream;
|
||
|
UNUSED(createFlags); // Reference to keep compiler happy.
|
||
|
|
||
|
assert(This != NULL);
|
||
|
assert(pStream->data.mem != NULL);
|
||
|
assert(pStream->type == eStreamTypeFile
|
||
|
|| pStream->type == eStreamTypeMemory
|
||
|
|| pStream->type == eStreamTypeCustom);
|
||
|
|
||
|
This->_protected = (struct ktxTexture_protected *)
|
||
|
malloc(sizeof(struct ktxTexture_protected));
|
||
|
stream = ktxTexture_getStream(This);
|
||
|
// Copy stream info into struct for later use.
|
||
|
*stream = *pStream;
|
||
|
|
||
|
This->orientation.x = KTX_ORIENT_X_RIGHT;
|
||
|
This->orientation.y = KTX_ORIENT_Y_DOWN;
|
||
|
This->orientation.z = KTX_ORIENT_Z_OUT;
|
||
|
|
||
|
return KTX_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Free the memory associated with the texture contents
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTextureInt whose texture contents are
|
||
|
* to be freed.
|
||
|
*/
|
||
|
void
|
||
|
ktxTexture_destruct(ktxTexture* This)
|
||
|
{
|
||
|
ktxStream stream = *(ktxTexture_getStream(This));
|
||
|
|
||
|
if (stream.data.file != NULL)
|
||
|
stream.destruct(&stream);
|
||
|
if (This->kvDataHead != NULL)
|
||
|
ktxHashList_Destruct(&This->kvDataHead);
|
||
|
if (This->kvData != NULL)
|
||
|
free(This->kvData);
|
||
|
if (This->pData != NULL)
|
||
|
free(This->pData);
|
||
|
free(This->_protected);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @defgroup reader Reader
|
||
|
* @brief Read KTX-formatted data.
|
||
|
* @{
|
||
|
*/
|
||
|
|
||
|
typedef enum { KTX1, KTX2 } ktxFileType_;
|
||
|
typedef union {
|
||
|
KTX_header ktx;
|
||
|
KTX_header2 ktx2;
|
||
|
} ktxHeaderUnion_;
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Determine if stream data is KTX1 or KTX2.
|
||
|
*
|
||
|
* @param pStream pointer to the ktxStream to examine.
|
||
|
* @param pFileType pointer to a ktxFileType enum where the type of the data
|
||
|
* will be written.
|
||
|
* @param pHeader pointer to a ktxHeaderUnion where the header info. will be
|
||
|
* written.
|
||
|
*/
|
||
|
static KTX_error_code
|
||
|
ktxDetermineFileType_(ktxStream* pStream, ktxFileType_* pFileType,
|
||
|
ktxHeaderUnion_* pHeader)
|
||
|
{
|
||
|
ktx_uint8_t ktx_ident_ref[12] = KTX_IDENTIFIER_REF;
|
||
|
ktx_uint8_t ktx2_ident_ref[12] = KTX2_IDENTIFIER_REF;
|
||
|
KTX_error_code result;
|
||
|
|
||
|
assert(pStream != NULL && pFileType != NULL);
|
||
|
assert(pStream->data.mem != NULL);
|
||
|
assert(pStream->type == eStreamTypeFile
|
||
|
|| pStream->type == eStreamTypeMemory
|
||
|
|| pStream->type == eStreamTypeCustom);
|
||
|
|
||
|
result = pStream->read(pStream, pHeader, sizeof(ktx2_ident_ref));
|
||
|
if (result == KTX_SUCCESS) {
|
||
|
#if BIG_ENDIAN
|
||
|
// byte swap the heaader fields
|
||
|
#endif
|
||
|
// Compare identifier, is this a KTX or KTX2 file?
|
||
|
if (!memcmp(pHeader->ktx.identifier, ktx_ident_ref, 12)) {
|
||
|
*pFileType = KTX1;
|
||
|
} else if (!memcmp(pHeader->ktx2.identifier, ktx2_ident_ref, 12)) {
|
||
|
*pFileType = KTX2;
|
||
|
} else {
|
||
|
return KTX_UNKNOWN_FILE_FORMAT;
|
||
|
}
|
||
|
// Read rest of header.
|
||
|
if (*pFileType == KTX1) {
|
||
|
// Read rest of header.
|
||
|
result = pStream->read(pStream, &pHeader->ktx.endianness,
|
||
|
KTX_HEADER_SIZE - sizeof(ktx_ident_ref));
|
||
|
} else {
|
||
|
result = pStream->read(pStream, &pHeader->ktx2.vkFormat,
|
||
|
KTX2_HEADER_SIZE - sizeof(ktx2_ident_ref));
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Construct (initialize) a ktx1 or ktx2 texture according to the stream
|
||
|
* data.
|
||
|
*
|
||
|
* @copydetails ktxTexture_CreateFromStdioStream
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_CreateFromStream(ktxStream* pStream,
|
||
|
ktxTextureCreateFlags createFlags,
|
||
|
ktxTexture** newTex)
|
||
|
{
|
||
|
ktxHeaderUnion_ header;
|
||
|
ktxFileType_ fileType;
|
||
|
KTX_error_code result;
|
||
|
ktxTexture* tex;
|
||
|
|
||
|
result = ktxDetermineFileType_(pStream, &fileType, &header);
|
||
|
if (result != KTX_SUCCESS)
|
||
|
return result;
|
||
|
|
||
|
if (fileType == KTX1) {
|
||
|
ktxTexture1* tex1 = (ktxTexture1*)malloc(sizeof(ktxTexture1));
|
||
|
if (tex1 == NULL)
|
||
|
return KTX_OUT_OF_MEMORY;
|
||
|
memset(tex1, 0, sizeof(ktxTexture1));
|
||
|
result = ktxTexture1_constructFromStreamAndHeader(tex1, pStream,
|
||
|
&header.ktx,
|
||
|
createFlags);
|
||
|
tex = ktxTexture(tex1);
|
||
|
} else {
|
||
|
ktxTexture2* tex2 = (ktxTexture2*)malloc(sizeof(ktxTexture2));
|
||
|
if (tex2 == NULL)
|
||
|
return KTX_OUT_OF_MEMORY;
|
||
|
memset(tex2, 0, sizeof(ktxTexture2));
|
||
|
result = ktxTexture2_constructFromStreamAndHeader(tex2, pStream,
|
||
|
&header.ktx2,
|
||
|
createFlags);
|
||
|
tex = ktxTexture(tex2);
|
||
|
}
|
||
|
|
||
|
if (result == KTX_SUCCESS)
|
||
|
*newTex = (ktxTexture*)tex;
|
||
|
else {
|
||
|
free(tex);
|
||
|
*newTex = NULL;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Create a ktxTexture1 or ktxTexture2 from a stdio stream according
|
||
|
* to the stream data.
|
||
|
*
|
||
|
* @copydetails ktxTexture1_CreateFromStdioStream()
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_CreateFromStdioStream(FILE* stdioStream,
|
||
|
ktxTextureCreateFlags createFlags,
|
||
|
ktxTexture** newTex)
|
||
|
{
|
||
|
ktxStream stream;
|
||
|
KTX_error_code result;
|
||
|
|
||
|
if (stdioStream == NULL || newTex == NULL)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
result = ktxFileStream_construct(&stream, stdioStream, KTX_FALSE);
|
||
|
if (result == KTX_SUCCESS) {
|
||
|
result = ktxTexture_CreateFromStream(&stream, createFlags, newTex);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Create a ktxTexture1 or ktxTexture2 from a named KTX file according
|
||
|
* to the file contents.
|
||
|
*
|
||
|
* The address of a newly created ktxTexture reflecting the contents of the
|
||
|
* file is written to the location pointed at by @p newTex.
|
||
|
*
|
||
|
* The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
|
||
|
* if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
|
||
|
* will minimize memory usage by allowing, for example, loading the images
|
||
|
* directly from the source into a Vulkan staging buffer.
|
||
|
*
|
||
|
* The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
|
||
|
* provided solely to enable implementation of the @e libktx v1 API on top of
|
||
|
* ktxTexture.
|
||
|
*
|
||
|
* @param[in] filename pointer to a char array containing the file name.
|
||
|
* @param[in] createFlags bitmask requesting specific actions during creation.
|
||
|
* @param[in,out] newTex pointer to a location in which store the address of
|
||
|
* the newly created texture.
|
||
|
*
|
||
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||
|
|
||
|
* @exception KTX_FILE_OPEN_FAILED The file could not be opened.
|
||
|
* @exception KTX_INVALID_VALUE @p filename is @c NULL.
|
||
|
*
|
||
|
* For other exceptions, see ktxTexture_CreateFromStdioStream().
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_CreateFromNamedFile(const char* const filename,
|
||
|
ktxTextureCreateFlags createFlags,
|
||
|
ktxTexture** newTex)
|
||
|
{
|
||
|
KTX_error_code result;
|
||
|
ktxStream stream;
|
||
|
FILE* file;
|
||
|
|
||
|
if (filename == NULL || newTex == NULL)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
file = fopen(filename, "rb");
|
||
|
if (!file)
|
||
|
return KTX_FILE_OPEN_FAILED;
|
||
|
|
||
|
result = ktxFileStream_construct(&stream, file, KTX_TRUE);
|
||
|
if (result == KTX_SUCCESS) {
|
||
|
result = ktxTexture_CreateFromStream(&stream, createFlags, newTex);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Create a ktxTexture1 or ktxTexture2 from KTX-formatted data in memory
|
||
|
* according to the data contents.
|
||
|
*
|
||
|
* The address of a newly created ktxTexture reflecting the contents of the
|
||
|
* serialized KTX data is written to the location pointed at by @p newTex.
|
||
|
*
|
||
|
* The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
|
||
|
* if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
|
||
|
* will minimize memory usage by allowing, for example, loading the images
|
||
|
* directly from the source into a Vulkan staging buffer.
|
||
|
*
|
||
|
* The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
|
||
|
* provided solely to enable implementation of the @e libktx v1 API on top of
|
||
|
* ktxTexture.
|
||
|
*
|
||
|
* @param[in] bytes pointer to the memory containing the serialized KTX data.
|
||
|
* @param[in] size length of the KTX data in bytes.
|
||
|
* @param[in] createFlags bitmask requesting specific actions during creation.
|
||
|
* @param[in,out] newTex pointer to a location in which store the address of
|
||
|
* the newly created texture.
|
||
|
*
|
||
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||
|
*
|
||
|
* @exception KTX_INVALID_VALUE Either @p bytes is NULL or @p size is 0.
|
||
|
*
|
||
|
* For other exceptions, see ktxTexture_CreateFromStdioStream().
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_CreateFromMemory(const ktx_uint8_t* bytes, ktx_size_t size,
|
||
|
ktxTextureCreateFlags createFlags,
|
||
|
ktxTexture** newTex)
|
||
|
{
|
||
|
KTX_error_code result;
|
||
|
ktxStream stream;
|
||
|
|
||
|
if (bytes == NULL || newTex == NULL || size == 0)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
result = ktxMemStream_construct_ro(&stream, bytes, size);
|
||
|
if (result == KTX_SUCCESS) {
|
||
|
result = ktxTexture_CreateFromStream(&stream, createFlags, newTex);
|
||
|
}
|
||
|
return result;}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Return a pointer to the texture image data.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
*/
|
||
|
ktx_uint8_t*
|
||
|
ktxTexture_GetData(ktxTexture* This)
|
||
|
{
|
||
|
return This->pData;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Return the total size of the texture image data in bytes.
|
||
|
*
|
||
|
* For a ktxTexture2 with supercompressionScheme != KTX_SS_NONE this will
|
||
|
* return the deflated size of the data.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_GetDataSize(ktxTexture* This)
|
||
|
{
|
||
|
assert(This != NULL);
|
||
|
return This->dataSize;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Return the size in bytes of an elements of a texture's
|
||
|
* images.
|
||
|
*
|
||
|
* For uncompressed textures an element is one texel. For compressed
|
||
|
* textures it is one block.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
*/
|
||
|
ktx_uint32_t
|
||
|
ktxTexture_GetElementSize(ktxTexture* This)
|
||
|
{
|
||
|
assert (This != NULL);
|
||
|
|
||
|
return (This->_protected->_formatSize.blockSizeInBits / 8);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Calculate & return the size in bytes of an image at the specified
|
||
|
* mip level.
|
||
|
*
|
||
|
* For arrays, this is the size of layer, for cubemaps, the size of a face
|
||
|
* and for 3D textures, the size of a depth slice.
|
||
|
*
|
||
|
* The size reflects the padding of each row to KTX_GL_UNPACK_ALIGNMENT.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level of interest.
|
||
|
* @param[in] fv enum specifying format version for which to calculate
|
||
|
* image size.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_calcImageSize(ktxTexture* This, ktx_uint32_t level,
|
||
|
ktxFormatVersionEnum fv)
|
||
|
{
|
||
|
DECLARE_PROTECTED(ktxTexture);
|
||
|
struct blockCount {
|
||
|
ktx_uint32_t x, y;
|
||
|
} blockCount;
|
||
|
ktx_uint32_t blockSizeInBytes;
|
||
|
ktx_uint32_t rowBytes;
|
||
|
|
||
|
assert (This != NULL);
|
||
|
|
||
|
float levelWidth = (float)(This->baseWidth >> level);
|
||
|
float levelHeight = (float)(This->baseHeight >> level);
|
||
|
// Round up to next whole block. We can't use KTX_PADN because some of
|
||
|
// the block sizes are not powers of 2.
|
||
|
blockCount.x
|
||
|
= (ktx_uint32_t)ceilf(levelWidth / prtctd->_formatSize.blockWidth);
|
||
|
blockCount.y
|
||
|
= (ktx_uint32_t)ceilf(levelHeight / prtctd->_formatSize.blockHeight);
|
||
|
blockCount.x = MAX(prtctd->_formatSize.minBlocksX, blockCount.x);
|
||
|
blockCount.y = MAX(prtctd->_formatSize.minBlocksX, blockCount.y);
|
||
|
|
||
|
blockSizeInBytes = prtctd->_formatSize.blockSizeInBits / 8;
|
||
|
|
||
|
if (prtctd->_formatSize.flags & KTX_FORMAT_SIZE_COMPRESSED_BIT) {
|
||
|
assert(This->isCompressed);
|
||
|
return blockCount.x * blockCount.y * blockSizeInBytes;
|
||
|
} else {
|
||
|
assert(prtctd->_formatSize.blockWidth == 1U
|
||
|
&& prtctd->_formatSize.blockHeight == 1U
|
||
|
&& prtctd->_formatSize.blockDepth == 1U);
|
||
|
rowBytes = blockCount.x * blockSizeInBytes;
|
||
|
if (fv == KTX_FORMAT_VERSION_ONE)
|
||
|
(void)padRow(&rowBytes);
|
||
|
return rowBytes * blockCount.y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Iterate over the levels or faces in a ktxTexture object.
|
||
|
*
|
||
|
* Blocks of image data are passed to an application-supplied callback
|
||
|
* function. This is not a strict per-image iteration. Rather it reflects how
|
||
|
* OpenGL needs the images. For most textures the block of data includes all
|
||
|
* images of a mip level which implies all layers of an array. However, for
|
||
|
* non-array cube map textures the block is a single face of the mip level,
|
||
|
* i.e the callback is called once for each face.
|
||
|
*
|
||
|
* This function works even if @p This->pData == 0 so it can be used to
|
||
|
* obtain offsets and sizes for each level by callers who have loaded the data
|
||
|
* externally.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in,out] iterCb the address of a callback function which is called
|
||
|
* with the data for each image block.
|
||
|
* @param[in,out] userdata the address of application-specific data which is
|
||
|
* passed to the callback along with the image data.
|
||
|
*
|
||
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error. The
|
||
|
* following are returned directly by this function. @p iterCb may
|
||
|
* return these for other causes or may return additional errors.
|
||
|
*
|
||
|
* @exception KTX_FILE_DATA_ERROR Mip level sizes are increasing not
|
||
|
* decreasing
|
||
|
* @exception KTX_INVALID_VALUE @p This is @c NULL or @p iterCb is @c NULL.
|
||
|
*
|
||
|
*/
|
||
|
KTX_error_code
|
||
|
ktxTexture_IterateLevelFaces(ktxTexture* This, PFNKTXITERCB iterCb,
|
||
|
void* userdata)
|
||
|
{
|
||
|
ktx_uint32_t miplevel;
|
||
|
KTX_error_code result = KTX_SUCCESS;
|
||
|
|
||
|
if (This == NULL)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
if (iterCb == NULL)
|
||
|
return KTX_INVALID_VALUE;
|
||
|
|
||
|
for (miplevel = 0; miplevel < This->numLevels; ++miplevel)
|
||
|
{
|
||
|
ktx_uint32_t faceLodSize;
|
||
|
ktx_uint32_t face;
|
||
|
ktx_uint32_t innerIterations;
|
||
|
GLsizei width, height, depth;
|
||
|
|
||
|
/* Array textures have the same number of layers at each mip level. */
|
||
|
width = MAX(1, This->baseWidth >> miplevel);
|
||
|
height = MAX(1, This->baseHeight >> miplevel);
|
||
|
depth = MAX(1, This->baseDepth >> miplevel);
|
||
|
|
||
|
faceLodSize = (ktx_uint32_t)ktxTexture_calcFaceLodSize(
|
||
|
This, miplevel);
|
||
|
|
||
|
/* All array layers are passed in a group because that is how
|
||
|
* GL & Vulkan need them. Hence no
|
||
|
* for (layer = 0; layer < This->numLayers)
|
||
|
*/
|
||
|
if (This->isCubemap && !This->isArray)
|
||
|
innerIterations = This->numFaces;
|
||
|
else
|
||
|
innerIterations = 1;
|
||
|
for (face = 0; face < innerIterations; ++face)
|
||
|
{
|
||
|
/* And all z_slices are also passed as a group hence no
|
||
|
* for (slice = 0; slice < This->depth)
|
||
|
*/
|
||
|
ktx_size_t offset;
|
||
|
|
||
|
ktxTexture_GetImageOffset(This, miplevel, 0, face, &offset);
|
||
|
result = iterCb(miplevel, face,
|
||
|
width, height, depth,
|
||
|
faceLodSize, This->pData + offset, userdata);
|
||
|
|
||
|
if (result != KTX_SUCCESS)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @internal
|
||
|
* @brief Calculate and apply the padding needed to comply with
|
||
|
* KTX_GL_UNPACK_ALIGNMENT.
|
||
|
*
|
||
|
* For uncompressed textures, KTX format specifies KTX_GL_UNPACK_ALIGNMENT = 4.
|
||
|
*
|
||
|
* @param[in,out] rowBytes pointer to variable containing the packed no. of
|
||
|
* bytes in a row. The no. of bytes after padding
|
||
|
* is written into this location.
|
||
|
* @return the no. of bytes of padding.
|
||
|
*/
|
||
|
static ktx_uint32_t
|
||
|
padRow(ktx_uint32_t* rowBytes)
|
||
|
{
|
||
|
ktx_uint32_t rowPadding;
|
||
|
|
||
|
assert (rowBytes != NULL);
|
||
|
|
||
|
rowPadding = _KTX_PAD_UNPACK_ALIGN_LEN(*rowBytes);
|
||
|
*rowBytes += rowPadding;
|
||
|
return rowPadding;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Calculate the size of an array layer at the specified mip level.
|
||
|
*
|
||
|
* The size of a layer is the size of an image * either the number of faces
|
||
|
* or the number of depth slices. This is the size of a layer as needed to
|
||
|
* find the offset within the array of images of a level and layer so the size
|
||
|
* reflects any @c cubePadding.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level whose layer size to return.
|
||
|
*
|
||
|
* @return the layer size in bytes.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_layerSize(ktxTexture* This, ktx_uint32_t level,
|
||
|
ktxFormatVersionEnum fv)
|
||
|
{
|
||
|
/*
|
||
|
* As there are no 3D cubemaps, the image's z block count will always be
|
||
|
* 1 for cubemaps and numFaces will always be 1 for 3D textures so the
|
||
|
* multiply is safe. 3D cubemaps, if they existed, would require
|
||
|
* imageSize * (blockCount.z + This->numFaces);
|
||
|
*/
|
||
|
DECLARE_PROTECTED(ktxTexture);
|
||
|
ktx_uint32_t blockCountZ;
|
||
|
ktx_size_t imageSize, layerSize;
|
||
|
|
||
|
assert (This != NULL);
|
||
|
|
||
|
blockCountZ = MAX(1, (This->baseDepth / prtctd->_formatSize.blockDepth) >> level);
|
||
|
imageSize = ktxTexture_calcImageSize(This, level, fv);
|
||
|
layerSize = imageSize * blockCountZ;
|
||
|
if (fv == KTX_FORMAT_VERSION_ONE && KTX_GL_UNPACK_ALIGNMENT != 4) {
|
||
|
if (This->isCubemap && !This->isArray) {
|
||
|
/* cubePadding. NOTE: this adds padding after the last face too. */
|
||
|
layerSize += _KTX_PAD4(layerSize);
|
||
|
}
|
||
|
}
|
||
|
return layerSize * This->numFaces;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Calculate the size of the specified mip level.
|
||
|
*
|
||
|
* The size of a level is the size of a layer * the number of layers.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level whose layer size to return.
|
||
|
*
|
||
|
* @return the level size in bytes.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_calcLevelSize(ktxTexture* This, ktx_uint32_t level,
|
||
|
ktxFormatVersionEnum fv)
|
||
|
{
|
||
|
assert (This != NULL);
|
||
|
assert (level < This->numLevels);
|
||
|
return ktxTexture_layerSize(This, level, fv) * This->numLayers;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Calculate the faceLodSize of the specified mip level.
|
||
|
*
|
||
|
* The faceLodSize of a level for most textures is the size of a level. For
|
||
|
* non-array cube map textures is the size of a face. This is the size that
|
||
|
* must be provided to OpenGL when uploading textures. Faces get uploaded 1
|
||
|
* at a time while all layers of an array or all slices of a 3D texture are
|
||
|
* uploaded together.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level whose layer size to return.
|
||
|
*
|
||
|
* @return the faceLodSize size in bytes.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_doCalcFaceLodSize(ktxTexture* This, ktx_uint32_t level,
|
||
|
ktxFormatVersionEnum fv)
|
||
|
{
|
||
|
/*
|
||
|
* For non-array cubemaps this is the size of a face. For everything
|
||
|
* else it is the size of the level.
|
||
|
*/
|
||
|
if (This->isCubemap && !This->isArray)
|
||
|
return ktxTexture_calcImageSize(This, level, fv);
|
||
|
else
|
||
|
return ktxTexture_calcLevelSize(This, level, fv);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Return the number of bytes needed to store all the image data for
|
||
|
* a ktxTexture.
|
||
|
*
|
||
|
* The caclulated size does not include space for storing the @c imageSize
|
||
|
* fields of each mip level.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] fv enum specifying format version for which to calculate
|
||
|
* image size.
|
||
|
*
|
||
|
* @return the data size in bytes.
|
||
|
*/
|
||
|
ktx_size_t
|
||
|
ktxTexture_calcDataSizeTexture(ktxTexture* This)
|
||
|
{
|
||
|
assert (This != NULL);
|
||
|
return ktxTexture_calcDataSizeLevels(This, This->numLevels);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Get information about rows of an uncompresssed texture image at a
|
||
|
* specified level.
|
||
|
*
|
||
|
* For an image at @p level of a ktxTexture provide the number of rows, the
|
||
|
* packed (unpadded) number of bytes in a row and the padding necessary to
|
||
|
* comply with KTX_GL_UNPACK_ALIGNMENT.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level of interest.
|
||
|
* @param[in,out] numRows pointer to location to store the number of rows.
|
||
|
* @param[in,out] pRowLengthBytes pointer to location to store number of bytes
|
||
|
* in a row.
|
||
|
* @param[in.out] pRowPadding pointer to location to store the number of bytes
|
||
|
* of padding.
|
||
|
*/
|
||
|
void
|
||
|
ktxTexture_rowInfo(ktxTexture* This, ktx_uint32_t level,
|
||
|
ktx_uint32_t* numRows, ktx_uint32_t* pRowLengthBytes,
|
||
|
ktx_uint32_t* pRowPadding)
|
||
|
{
|
||
|
DECLARE_PROTECTED(ktxTexture);
|
||
|
struct blockCount {
|
||
|
ktx_uint32_t x;
|
||
|
} blockCount;
|
||
|
|
||
|
assert (This != NULL);
|
||
|
|
||
|
assert(!This->isCompressed);
|
||
|
assert(prtctd->_formatSize.blockWidth == 1U
|
||
|
&& prtctd->_formatSize.blockHeight == 1U
|
||
|
&& prtctd->_formatSize.blockDepth == 1U);
|
||
|
|
||
|
blockCount.x = MAX(1, (This->baseWidth / prtctd->_formatSize.blockWidth) >> level);
|
||
|
*numRows = MAX(1, (This->baseHeight / prtctd->_formatSize.blockHeight) >> level);
|
||
|
|
||
|
*pRowLengthBytes = blockCount.x * prtctd->_formatSize.blockSizeInBits / 8;
|
||
|
*pRowPadding = padRow(pRowLengthBytes);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture
|
||
|
* @~English
|
||
|
* @brief Return pitch betweeb rows of a texture image level in bytes.
|
||
|
*
|
||
|
* For uncompressed textures the pitch is the number of bytes between
|
||
|
* rows of texels. For compressed textures it is the number of bytes
|
||
|
* between rows of blocks. The value is padded to GL_UNPACK_ALIGNMENT,
|
||
|
* if necessary. For all currently known compressed formats padding
|
||
|
* will not be necessary.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
* @param[in] level level of interest.
|
||
|
*
|
||
|
* @return the row pitch in bytes.
|
||
|
*/
|
||
|
ktx_uint32_t
|
||
|
ktxTexture_GetRowPitch(ktxTexture* This, ktx_uint32_t level)
|
||
|
{
|
||
|
DECLARE_PROTECTED(ktxTexture)
|
||
|
struct blockCount {
|
||
|
ktx_uint32_t x;
|
||
|
} blockCount;
|
||
|
ktx_uint32_t pitch;
|
||
|
|
||
|
blockCount.x = MAX(1, (This->baseWidth / prtctd->_formatSize.blockWidth) >> level);
|
||
|
pitch = blockCount.x * prtctd->_formatSize.blockSizeInBits / 8;
|
||
|
(void)padRow(&pitch);
|
||
|
|
||
|
return pitch;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @memberof ktxTexture @private
|
||
|
* @~English
|
||
|
* @brief Query if a ktxTexture has an active stream.
|
||
|
*
|
||
|
* Tests if a ktxTexture has unread image data. The internal stream is closed
|
||
|
* once all the images have been read.
|
||
|
*
|
||
|
* @param[in] This pointer to the ktxTexture object of interest.
|
||
|
*
|
||
|
* @return KTX_TRUE if there is an active stream, KTX_FALSE otherwise.
|
||
|
*/
|
||
|
ktx_bool_t
|
||
|
ktxTexture_isActiveStream(ktxTexture* This)
|
||
|
{
|
||
|
assert(This != NULL);
|
||
|
ktxStream* stream = ktxTexture_getStream(This);
|
||
|
return stream->data.file != NULL;
|
||
|
}
|
||
|
|
||
|
/** @} */
|
||
|
|