virtualx-engine/thirdparty/libktx/lib/texture.c
acazuc a00cf02241 Add support for KTX & KTX2 image format
Add support glTF KHR_texture_basisu extension
2023-08-19 10:27:29 +02:00

911 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;
}
/** @} */