godot/thirdparty/libktx/lib/texture2.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

2525 lines
88 KiB
C

/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2019-2020 The Khronos Group Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file texture2.c
* @~English
*
* @brief ktxTexture2 implementation. Support for KTX2 format.
*
* @author Mark Callow, www.edgewise-consulting.com
*/
#if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdlib.h>
#include <string.h>
#include <zstd.h>
#include <zstd_errors.h>
#include <KHR/khr_df.h>
#include "dfdutils/dfd.h"
#include "ktx.h"
#include "ktxint.h"
#include "filestream.h"
#include "memstream.h"
#include "texture2.h"
#include "unused.h"
#include "vk_format.h"
// FIXME: Test this #define and put it in a header somewhere.
//#define IS_BIG_ENDIAN (1 == *(unsigned char *)&(const int){0x01000000ul})
#define IS_BIG_ENDIAN 0
struct ktxTexture_vtbl ktxTexture2_vtbl;
struct ktxTexture_vtblInt ktxTexture2_vtblInt;
#if !defined(BITFIELD_ORDER_FROM_MSB)
// Most compilers, including all those tested so far, including clang, gcc
// and msvc, order bitfields from the lsb so these struct declarations work.
// Could this be because I've only tested on little-endian machines?
// These are preferred as they are much easier to manually initialize
// and verify.
struct sampleType {
uint32_t bitOffset: 16;
uint32_t bitLength: 8;
uint32_t channelType: 8; // Includes qualifiers
uint32_t samplePosition0: 8;
uint32_t samplePosition1: 8;
uint32_t samplePosition2: 8;
uint32_t samplePosition3: 8;
uint32_t lower;
uint32_t upper;
};
struct BDFD {
uint32_t vendorId: 17;
uint32_t descriptorType: 15;
uint32_t versionNumber: 16;
uint32_t descriptorBlockSize: 16;
uint32_t model: 8;
uint32_t primaries: 8;
uint32_t transfer: 8;
uint32_t flags: 8;
uint32_t texelBlockDimension0: 8;
uint32_t texelBlockDimension1: 8;
uint32_t texelBlockDimension2: 8;
uint32_t texelBlockDimension3: 8;
uint32_t bytesPlane0: 8;
uint32_t bytesPlane1: 8;
uint32_t bytesPlane2: 8;
uint32_t bytesPlane3: 8;
uint32_t bytesPlane4: 8;
uint32_t bytesPlane5: 8;
uint32_t bytesPlane6: 8;
uint32_t bytesPlane7: 8;
struct sampleType samples[6];
};
struct BDFD e5b9g9r9_ufloat_comparator = {
.vendorId = 0,
.descriptorType = 0,
.versionNumber = 2,
.descriptorBlockSize = sizeof(struct BDFD),
.model = KHR_DF_MODEL_RGBSDA,
.primaries = KHR_DF_PRIMARIES_BT709,
.transfer = KHR_DF_TRANSFER_LINEAR,
.flags = KHR_DF_FLAG_ALPHA_STRAIGHT,
.texelBlockDimension0 = 0,
.texelBlockDimension1 = 0,
.texelBlockDimension2 = 0,
.texelBlockDimension3 = 0,
.bytesPlane0 = 4,
.bytesPlane1 = 0,
.bytesPlane2 = 0,
.bytesPlane3 = 0,
.bytesPlane4 = 0,
.bytesPlane5 = 0,
.bytesPlane6 = 0,
.bytesPlane7 = 0,
// gcc likes this way. It does not like, e.g.,
// .samples[0].bitOffset = 0, etc. which is accepted by both clang & msvc.
// I find the standards docs impenetrable so I don't know which is correct.
.samples[0] = {
.bitOffset = 0,
.bitLength = 8,
.channelType = KHR_DF_CHANNEL_RGBSDA_RED,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 0,
.upper = 8448,
},
.samples[1] = {
.bitOffset = 27,
.bitLength = 4,
.channelType = KHR_DF_CHANNEL_RGBSDA_RED | KHR_DF_SAMPLE_DATATYPE_EXPONENT,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 15,
.upper = 31,
},
.samples[2] = {
.bitOffset = 9,
.bitLength = 8,
.channelType = KHR_DF_CHANNEL_RGBSDA_GREEN,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 0,
.upper = 8448,
},
.samples[3] = {
.bitOffset = 27,
.bitLength = 4,
.channelType = KHR_DF_CHANNEL_RGBSDA_GREEN | KHR_DF_SAMPLE_DATATYPE_EXPONENT,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 15,
.upper = 31,
},
.samples[4] = {
.bitOffset = 18,
.bitLength = 8,
.channelType = KHR_DF_CHANNEL_RGBSDA_BLUE,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 0,
.upper = 8448,
},
.samples[5] = {
.bitOffset = 27,
.bitLength = 4,
.channelType = KHR_DF_CHANNEL_RGBSDA_BLUE | KHR_DF_SAMPLE_DATATYPE_EXPONENT,
.samplePosition0 = 0,
.samplePosition1 = 0,
.samplePosition2 = 0,
.samplePosition3 = 0,
.lower = 15,
.upper = 31,
}
};
#else
// For compilers which order bitfields from the msb rather than lsb.
#define shift(x,val) ((val) << KHR_DF_SHIFT_ ## x)
#define sampleshift(x,val) ((val) << KHR_DF_SAMPLESHIFT_ ## x)
#define e5b9g9r9_bdbwordcount KHR_DFDSIZEWORDS(6)
ktx_uint32_t e5b9g9r9_ufloat_comparator[e5b9g9r9_bdbwordcount] = {
0, // descriptorType & vendorId
shift(DESCRIPTORBLOCKSIZE, e5b9g9r9_bdbwordcount * sizeof(ktx_uint32_t)) | shift(VERSIONNUMBER, 2),
// N.B. Allow various values of primaries, transfer & flags
shift(FLAGS, KHR_DF_FLAG_ALPHA_STRAIGHT) | shift(TRANSFER, KHR_DF_TRANSFER_LINEAR) | shift(PRIMARIES, KHR_DF_PRIMARIES_BT709) | shift(MODEL, KHR_DF_MODEL_RGBSDA),
0, // texelBlockDimension3~0
shift(BYTESPLANE0, 4), // All other bytesPlane fields are 0.
0, // bytesPlane7~4
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_RED) | sampleshift(BITLENGTH, 8) | sampleshift(BITOFFSET, 0),
0, // samplePosition3~0
0, // sampleLower
8448, // sampleUpper
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_RED | KHR_DF_SAMPLE_DATATYPE_EXPONENT) | sampleshift(BITLENGTH, 4) | sampleshift(BITOFFSET, 27),
0, // samplePosition3~0
15, // sampleLower
31, // sampleUpper
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_GREEN) | sampleshift(BITLENGTH, 8) | sampleshift(BITOFFSET, 9),
0, // samplePosition3~0
0, // sampleLower
8448, // sampleUpper
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_GREEN | KHR_DF_SAMPLE_DATATYPE_EXPONENT) | sampleshift(BITLENGTH, 4) | sampleshift(BITOFFSET, 27),
0, // samplePosition3~0
15, // sampleLower
31, // sampleUpper
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_BLUE) | sampleshift(BITLENGTH, 8) | sampleshift(BITOFFSET, 18),
0, // samplePosition3~0
0, // sampleLower
8448, // sampleUpper
sampleshift(CHANNELID, KHR_DF_CHANNEL_RGBSDA_BLUE | KHR_DF_SAMPLE_DATATYPE_EXPONENT) | sampleshift(BITLENGTH, 4) | sampleshift(BITOFFSET, 27),
0, // samplePosition3~0
15, // sampleLower
31, // sampleUpper
};
#endif
/**
* @private
* @~English
* @brief Initialize a ktxFormatSize object from the info in a DFD.
*
* This is used instead of referring to the DFD directly so code dealing
* with format info can be common to KTX 1 & 2.
*
* @param[in] This pointer the ktxTexture2 whose DFD to use.
* @param[in] fi pointer to the ktxFormatSize object to initialize.
*
* @return KTX_TRUE on success, otherwise KTX_FALSE.
*/
bool
ktxFormatSize_initFromDfd(ktxFormatSize* This, ktx_uint32_t* pDfd)
{
uint32_t* pBdb = pDfd + 1;
// Check the DFD is of the expected type and version.
if (*pBdb != 0) {
// Either decriptorType or vendorId is not 0
return false;
}
if (KHR_DFDVAL(pBdb, VERSIONNUMBER) != KHR_DF_VERSIONNUMBER_1_3) {
return false;
}
// DFD has supported type and version. Process it.
This->blockWidth = KHR_DFDVAL(pBdb, TEXELBLOCKDIMENSION0) + 1;
This->blockHeight = KHR_DFDVAL(pBdb, TEXELBLOCKDIMENSION1) + 1;
This->blockDepth = KHR_DFDVAL(pBdb, TEXELBLOCKDIMENSION2) + 1;
This->blockSizeInBits = KHR_DFDVAL(pBdb, BYTESPLANE0) * 8;
This->paletteSizeInBits = 0; // No paletted formats in ktx v2.
This->flags = 0;
This->minBlocksX = This->minBlocksY = 1;
if (KHR_DFDVAL(pBdb, MODEL) >= KHR_DF_MODEL_DXT1A) {
// A block compressed format. Entire block is a single sample.
This->flags |= KTX_FORMAT_SIZE_COMPRESSED_BIT;
if (KHR_DFDVAL(pBdb, MODEL) == KHR_DF_MODEL_PVRTC) {
This->minBlocksX = This->minBlocksY = 2;
}
} else {
// An uncompressed format.
// Special case depth & depth stencil formats
if (KHR_DFDSVAL(pBdb, 0, CHANNELID) == KHR_DF_CHANNEL_RGBSDA_DEPTH) {
if (KHR_DFDSAMPLECOUNT(pBdb) == 1) {
This->flags |= KTX_FORMAT_SIZE_DEPTH_BIT;
} else if (KHR_DFDSAMPLECOUNT(pBdb) == 2) {
This->flags |= KTX_FORMAT_SIZE_STENCIL_BIT;
This->flags |= KTX_FORMAT_SIZE_DEPTH_BIT;
This->flags |= KTX_FORMAT_SIZE_PACKED_BIT;
} else {
return false;
}
} else if (KHR_DFDSVAL(pBdb, 0, CHANNELID) == KHR_DF_CHANNEL_RGBSDA_STENCIL) {
This->flags |= KTX_FORMAT_SIZE_STENCIL_BIT;
} else if (KHR_DFDSAMPLECOUNT(pBdb) == 6
#if !defined(BITFIELD_ORDER_FROM_MSB)
&& !memcmp(((uint32_t*)&e5b9g9r9_ufloat_comparator) + KHR_DF_WORD_TEXELBLOCKDIMENSION0, &pBdb[KHR_DF_WORD_TEXELBLOCKDIMENSION0], sizeof(e5b9g9r9_ufloat_comparator)-(KHR_DF_WORD_TEXELBLOCKDIMENSION0)*sizeof(uint32_t))) {
#else
&& !memcmp(&e5b9g9r9_ufloat_comparator[KHR_DF_WORD_TEXELBLOCKDIMENSION0], &pBdb[KHR_DF_WORD_TEXELBLOCKDIMENSION0], sizeof(e5b9g9r9_ufloat_comparator)-(KHR_DF_WORD_TEXELBLOCKDIMENSION0)*sizeof(uint32_t))) {
#endif
// Special case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 as interpretDFD
// only handles "simple formats", i.e. where channels are described
// in contiguous bits.
This->flags |= KTX_FORMAT_SIZE_PACKED_BIT;
} else {
InterpretedDFDChannel rgba[4];
uint32_t wordBytes;
enum InterpretDFDResult result;
result = interpretDFD(pDfd, &rgba[0], &rgba[1], &rgba[2], &rgba[3],
&wordBytes);
if (result >= i_UNSUPPORTED_ERROR_BIT)
return false;
if (result & i_PACKED_FORMAT_BIT)
This->flags |= KTX_FORMAT_SIZE_PACKED_BIT;
}
}
if (This->blockSizeInBits == 0) {
// The DFD shows a supercompressed texture. Complete the ktxFormatSize
// struct by figuring out the post inflation value for bytesPlane0.
// Setting it here simplifies stuff later in this file. Setting the
// post inflation block size here will not cause any problems for
// the following reasons. (1) in v2 files levelIndex is always used to
// calculate data size and, of course, for the level offsets. (2) Finer
// grain access to supercompressed data than levels is not possible.
uint32_t blockByteLength;
recreateBytesPlane0FromSampleInfo(pDfd, &blockByteLength);
This->blockSizeInBits = blockByteLength * 8;
}
return true;
}
/**
* @private
* @~English
* @brief Create a DFD for a VkFormat.
*
* This KTX-specific function adds support for combined depth stencil formats
* which are not supported by @e dfdutils' @c vk2dfd function because they
* are not seen outside a Vulkan device. KTX has its own definitions for
* these that enable uploading, with some effort.
*
* @param[in] vkFormat the format for which to create a DFD.
*/
static uint32_t*
ktxVk2dfd(ktx_uint32_t vkFormat)
{
switch(vkFormat) {
case VK_FORMAT_D16_UNORM_S8_UINT:
// 2 16-bit words. D16 in the first. S8 in the 8 LSBs of the second.
return createDFDDepthStencil(16, 8, 4);
case VK_FORMAT_D24_UNORM_S8_UINT:
// 1 32-bit word. D24 in the MSBs. S8 in the LSBs.
return createDFDDepthStencil(24, 8, 4);
case VK_FORMAT_D32_SFLOAT_S8_UINT:
// 2 32-bit words. D32 float in the first word. S8 in LSBs of the
// second.
return createDFDDepthStencil(32, 8, 8);
default:
return vk2dfd(vkFormat);
}
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Do the part of ktxTexture2 construction that is common to
* new textures and those constructed from a stream.
*
* @param[in] This pointer to a ktxTexture2-sized block of memory to
* initialize.
* @param[in] numLevels the number of levels the texture must have.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
* @exception KTX_OUT_OF_MEMORY Not enough memory for the texture data.
*/
static KTX_error_code
ktxTexture2_constructCommon(ktxTexture2* This, ktx_uint32_t numLevels)
{
assert(This != NULL);
ktx_size_t privateSize;
This->classId = ktxTexture2_c;
This->vtbl = &ktxTexture2_vtbl;
This->_protected->_vtbl = ktxTexture2_vtblInt;
privateSize = sizeof(ktxTexture2_private)
+ sizeof(ktxLevelIndexEntry) * (numLevels - 1);
This->_private = (ktxTexture2_private*)malloc(privateSize);
if (This->_private == NULL) {
return KTX_OUT_OF_MEMORY;
}
memset(This->_private, 0, privateSize);
return KTX_SUCCESS;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a new, empty, ktxTexture2.
*
* @param[in] This pointer to a ktxTexture2-sized block of memory to
* initialize.
* @param[in] createInfo pointer to a ktxTextureCreateInfo struct with
* information describing the texture.
* @param[in] storageAllocation
* enum indicating whether or not to allocate storage
* for the texture images.
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
* @exception KTX_OUT_OF_MEMORY Not enough memory for the texture or image data.
* @exception KTX_UNSUPPORTED_TEXTURE_TYPE
* The request VkFormat is one of the
* prohibited formats.
*/
static KTX_error_code
ktxTexture2_construct(ktxTexture2* This, ktxTextureCreateInfo* createInfo,
ktxTextureCreateStorageEnum storageAllocation)
{
ktxFormatSize formatSize;
KTX_error_code result;
memset(This, 0, sizeof(*This));
if (createInfo->vkFormat != VK_FORMAT_UNDEFINED) {
This->pDfd = ktxVk2dfd(createInfo->vkFormat);
if (!This->pDfd)
return KTX_INVALID_VALUE; // Format is unknown or unsupported.
#ifdef _DEBUG
// If this fires, an unsupported format or incorrect DFD
// has crept into vk2dfd.
assert(ktxFormatSize_initFromDfd(&formatSize, This->pDfd));
#else
(void)ktxFormatSize_initFromDfd(&formatSize, This->pDfd);
#endif
} else {
// TODO: Validate createInfo->pDfd.
This->pDfd = (ktx_uint32_t*)malloc(*createInfo->pDfd);
if (!This->pDfd)
return KTX_OUT_OF_MEMORY;
memcpy(This->pDfd, createInfo->pDfd, *createInfo->pDfd);
if (ktxFormatSize_initFromDfd(&formatSize, This->pDfd)) {
result = KTX_UNSUPPORTED_TEXTURE_TYPE;
goto cleanup;
}
}
result = ktxTexture_construct(ktxTexture(This), createInfo, &formatSize);
if (result != KTX_SUCCESS)
return result;
result = ktxTexture2_constructCommon(This, createInfo->numLevels);
if (result != KTX_SUCCESS)
goto cleanup;;
This->vkFormat = createInfo->vkFormat;
// Ideally we'd set all these things in ktxFormatSize_initFromDfd
// but This->_protected is not allocated until ktxTexture_construct;
if (This->isCompressed)
This->_protected->_typeSize = 1;
else if (formatSize.flags & KTX_FORMAT_SIZE_PACKED_BIT)
This->_protected->_typeSize = formatSize.blockSizeInBits / 8;
else if (formatSize.flags & (KTX_FORMAT_SIZE_DEPTH_BIT | KTX_FORMAT_SIZE_STENCIL_BIT)) {
if (createInfo->vkFormat == VK_FORMAT_D16_UNORM_S8_UINT)
This->_protected->_typeSize = 2;
else
This->_protected->_typeSize = 4;
} else {
// Unpacked and uncompressed
uint32_t numComponents;
getDFDComponentInfoUnpacked(This->pDfd, &numComponents,
&This->_protected->_typeSize);
}
This->supercompressionScheme = KTX_SS_NONE;
This->_private->_requiredLevelAlignment
= ktxTexture2_calcRequiredLevelAlignment(This);
// Create levelIndex. Offsets are from start of the KTX2 stream.
ktxLevelIndexEntry* levelIndex = This->_private->_levelIndex;
This->_private->_firstLevelFileOffset = 0;
for (ktx_uint32_t level = 0; level < This->numLevels; level++) {
levelIndex[level].uncompressedByteLength =
ktxTexture_calcLevelSize(ktxTexture(This), level,
KTX_FORMAT_VERSION_TWO);
levelIndex[level].byteLength =
levelIndex[level].uncompressedByteLength;
levelIndex[level].byteOffset =
ktxTexture_calcLevelOffset(ktxTexture(This), level);
}
// Allocate storage, if requested.
if (storageAllocation == KTX_TEXTURE_CREATE_ALLOC_STORAGE) {
This->dataSize
= ktxTexture_calcDataSizeTexture(ktxTexture(This));
This->pData = malloc(This->dataSize);
if (This->pData == NULL) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
}
return result;
cleanup:
ktxTexture2_destruct(This);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture by copying a source ktxTexture.
*
* @param[in] This pointer to a ktxTexture2-sized block of memory to
* initialize.
* @param[in] orig pointer to the source texture to copy.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_OUT_OF_MEMORY Not enough memory for the texture data.
*/
static KTX_error_code
ktxTexture2_constructCopy(ktxTexture2* This, ktxTexture2* orig)
{
KTX_error_code result;
memcpy(This, orig, sizeof(ktxTexture2));
// Zero all the pointers to make error handling easier
This->_protected = NULL;
This->_private = NULL;
This->pDfd = NULL;
This->kvData = NULL;
This->kvDataHead = NULL;
This->pData = NULL;
This->_protected =
(ktxTexture_protected*)malloc(sizeof(ktxTexture_protected));
if (!This->_protected)
return KTX_OUT_OF_MEMORY;
// Must come before memcpy of _protected so as to close an active stream.
if (!orig->pData && ktxTexture_isActiveStream((ktxTexture*)orig))
ktxTexture2_LoadImageData(orig, NULL, 0);
memcpy(This->_protected, orig->_protected, sizeof(ktxTexture_protected));
ktx_size_t privateSize = sizeof(ktxTexture2_private)
+ sizeof(ktxLevelIndexEntry) * (orig->numLevels - 1);
This->_private = (ktxTexture2_private*)malloc(privateSize);
if (This->_private == NULL) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
memcpy(This->_private, orig->_private, privateSize);
if (orig->_private->_sgdByteLength > 0) {
This->_private->_supercompressionGlobalData
= (ktx_uint8_t*)malloc(orig->_private->_sgdByteLength);
if (!This->_private->_supercompressionGlobalData) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
memcpy(This->_private->_supercompressionGlobalData,
orig->_private->_supercompressionGlobalData,
orig->_private->_sgdByteLength);
}
This->pDfd = (ktx_uint32_t*)malloc(*orig->pDfd);
if (!This->pDfd) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
memcpy(This->pDfd, orig->pDfd, *orig->pDfd);
if (orig->kvDataHead) {
ktxHashList_ConstructCopy(&This->kvDataHead, orig->kvDataHead);
} else if (orig->kvData) {
This->kvData = (ktx_uint8_t*)malloc(orig->kvDataLen);
if (!This->kvData) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
memcpy(This->kvData, orig->kvData, orig->kvDataLen);
}
// Can't share the image data as the data pointer is exposed in the
// ktxTexture2 structure. Changing it to a ref-counted pointer would
// break code. Maybe that's okay as we're still pre-release. But,
// since this constructor will be mostly be used when transcoding
// supercompressed images, it is probably not too big a deal to make
// a copy of the data.
This->pData = (ktx_uint8_t*)malloc(This->dataSize);
if (This->pData == NULL) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
memcpy(This->pData, orig->pData, orig->dataSize);
return KTX_SUCCESS;
cleanup:
if (This->_protected) free(This->_protected);
if (This->_private) {
if (This->_private->_supercompressionGlobalData)
free(This->_private->_supercompressionGlobalData);
free(This->_private);
}
if (This->pDfd) free (This->pDfd);
if (This->kvDataHead) ktxHashList_Destruct(&This->kvDataHead);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture from a ktxStream reading from a KTX source.
*
* The KTX header, which must have been read prior to calling this, is passed
* to the function.
*
* The stream object is copied into the constructed ktxTexture2.
*
* 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.
*
* If either KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT or
* KTX_TEXTURE_CREATE_RAW_KVDATA_BIT is set then the ktxTexture's orientation
* fields will be set to defaults even if the KTX source contains
* KTXorientation metadata.
*
* @param[in] This pointer to a ktxTexture2-sized block of memory to
* initialize.
* @param[in] pStream pointer to the stream to read.
* @param[in] pHeader pointer to a KTX header that has already been read from
* the stream.
* @param[in] createFlags bitmask requesting specific actions during creation.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_FILE_DATA_ERROR
* Source data is inconsistent with the KTX
* specification.
* @exception KTX_FILE_READ_ERROR
* An error occurred while reading the source.
* @exception KTX_FILE_UNEXPECTED_EOF
* Not enough data in the source.
* @exception KTX_OUT_OF_MEMORY Not enough memory to load either the images or
* the key-value data.
* @exception KTX_UNKNOWN_FILE_FORMAT
* The source is not in KTX format.
* @exception KTX_UNSUPPORTED_TEXTURE_TYPE
* The source describes a texture type not
* supported by OpenGL or Vulkan, e.g, a 3D array.
*/
KTX_error_code
ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream,
KTX_header2* pHeader,
ktxTextureCreateFlags createFlags)
{
ktxTexture2_private* private;
KTX_error_code result;
KTX_supplemental_info suppInfo;
ktxStream* stream;
ktx_size_t levelIndexSize;
assert(pHeader != NULL && pStream != NULL);
memset(This, 0, sizeof(*This));
result = ktxTexture_constructFromStream(ktxTexture(This), pStream,
createFlags);
if (result != KTX_SUCCESS)
return result;
result = ktxCheckHeader2_(pHeader, &suppInfo);
if (result != KTX_SUCCESS)
goto cleanup;
// ktxCheckHeader2_ has done the max(1, levelCount) on pHeader->levelCount.
result = ktxTexture2_constructCommon(This, pHeader->levelCount);
if (result != KTX_SUCCESS)
goto cleanup;
private = This->_private;
stream = ktxTexture2_getStream(This);
/*
* Initialize from pHeader->info.
*/
This->vkFormat = pHeader->vkFormat;
This->supercompressionScheme = pHeader->supercompressionScheme;
This->_protected->_typeSize = pHeader->typeSize;
// Can these be done by a ktxTexture_constructFromStream?
This->numDimensions = suppInfo.textureDimension;
This->baseWidth = pHeader->pixelWidth;
assert(suppInfo.textureDimension > 0 && suppInfo.textureDimension < 4);
switch (suppInfo.textureDimension) {
case 1:
This->baseHeight = This->baseDepth = 1;
break;
case 2:
This->baseHeight = pHeader->pixelHeight;
This->baseDepth = 1;
break;
case 3:
This->baseHeight = pHeader->pixelHeight;
This->baseDepth = pHeader->pixelDepth;
break;
}
if (pHeader->layerCount > 0) {
This->numLayers = pHeader->layerCount;
This->isArray = KTX_TRUE;
} else {
This->numLayers = 1;
This->isArray = KTX_FALSE;
}
This->numFaces = pHeader->faceCount;
if (pHeader->faceCount == 6)
This->isCubemap = KTX_TRUE;
else
This->isCubemap = KTX_FALSE;
// ktxCheckHeader2_ does the max(1, levelCount) and sets
// suppInfo.generateMipmaps when it was originally 0.
This->numLevels = pHeader->levelCount;
This->generateMipmaps = suppInfo.generateMipmaps;
// Read level index
levelIndexSize = sizeof(ktxLevelIndexEntry) * This->numLevels;
result = stream->read(stream, &private->_levelIndex, levelIndexSize);
if (result != KTX_SUCCESS)
goto cleanup;
// Rebase index to start of data and save file offset.
private->_firstLevelFileOffset
= private->_levelIndex[This->numLevels-1].byteOffset;
for (ktx_uint32_t level = 0; level < This->numLevels; level++) {
private->_levelIndex[level].byteOffset
-= private->_firstLevelFileOffset;
}
// Read DFD
This->pDfd =
(ktx_uint32_t*)malloc(pHeader->dataFormatDescriptor.byteLength);
if (!This->pDfd) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
result = stream->read(stream, This->pDfd,
pHeader->dataFormatDescriptor.byteLength);
if (result != KTX_SUCCESS)
goto cleanup;
if (!ktxFormatSize_initFromDfd(&This->_protected->_formatSize, This->pDfd)) {
result = KTX_UNSUPPORTED_TEXTURE_TYPE;
goto cleanup;
}
This->isCompressed = (This->_protected->_formatSize.flags & KTX_FORMAT_SIZE_COMPRESSED_BIT);
if (This->supercompressionScheme == KTX_SS_BASIS_LZ
&& KHR_DFDVAL(This->pDfd + 1, MODEL) != KHR_DF_MODEL_ETC1S)
{
result = KTX_FILE_DATA_ERROR;
goto cleanup;
}
This->_private->_requiredLevelAlignment
= ktxTexture2_calcRequiredLevelAlignment(This);
// Make an empty hash list.
ktxHashList_Construct(&This->kvDataHead);
// Load KVData.
if (pHeader->keyValueData.byteLength > 0) {
if (!(createFlags & KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT)) {
ktx_uint32_t kvdLen = pHeader->keyValueData.byteLength;
ktx_uint8_t* pKvd;
pKvd = malloc(kvdLen);
if (pKvd == NULL) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
result = stream->read(stream, pKvd, kvdLen);
if (result != KTX_SUCCESS)
goto cleanup;
if (IS_BIG_ENDIAN) {
/* Swap the counts inside the key & value data. */
ktx_uint8_t* src = pKvd;
ktx_uint8_t* end = pKvd + kvdLen;
while (src < end) {
ktx_uint32_t keyAndValueByteSize = *((ktx_uint32_t*)src);
_ktxSwapEndian32(&keyAndValueByteSize, 1);
src += _KTX_PAD4(keyAndValueByteSize);
}
}
if (!(createFlags & KTX_TEXTURE_CREATE_RAW_KVDATA_BIT)) {
char* orientationStr;
ktx_uint32_t orientationLen;
ktx_uint32_t animData[3];
ktx_uint32_t animDataLen;
result = ktxHashList_Deserialize(&This->kvDataHead,
kvdLen, pKvd);
free(pKvd);
if (result != KTX_SUCCESS) {
goto cleanup;
}
result = ktxHashList_FindValue(&This->kvDataHead,
KTX_ORIENTATION_KEY,
&orientationLen,
(void**)&orientationStr);
assert(result != KTX_INVALID_VALUE);
if (result == KTX_SUCCESS) {
// Length includes the terminating NUL.
if (orientationLen != This->numDimensions + 1) {
// There needs to be an entry for each dimension of
// the texture.
result = KTX_FILE_DATA_ERROR;
goto cleanup;
} else {
switch (This->numDimensions) {
case 3:
This->orientation.z = orientationStr[2];
FALLTHROUGH;
case 2:
This->orientation.y = orientationStr[1];
FALLTHROUGH;
case 1:
This->orientation.x = orientationStr[0];
}
}
} else {
result = KTX_SUCCESS; // Not finding orientation is okay.
}
result = ktxHashList_FindValue(&This->kvDataHead,
KTX_ANIMDATA_KEY,
&animDataLen,
(void**)animData);
assert(result != KTX_INVALID_VALUE);
if (result == KTX_SUCCESS) {
if (animDataLen != sizeof(animData)) {
result = KTX_FILE_DATA_ERROR;
goto cleanup;
}
if (This->isArray) {
This->isVideo = KTX_TRUE;
This->duration = animData[0];
This->timescale = animData[1];
This->loopcount = animData[2];
} else {
// animData is only valid for array textures.
result = KTX_FILE_DATA_ERROR;
goto cleanup;
}
} else {
result = KTX_SUCCESS; // Not finding video is okay.
}
} else {
This->kvDataLen = kvdLen;
This->kvData = pKvd;
}
} else {
stream->skip(stream, pHeader->keyValueData.byteLength);
}
}
if (pHeader->supercompressionGlobalData.byteLength > 0) {
// There could be padding here so seek to the next item.
(void)stream->setpos(stream,
pHeader->supercompressionGlobalData.byteOffset);
// Read supercompressionGlobalData
private->_supercompressionGlobalData =
(ktx_uint8_t*)malloc(pHeader->supercompressionGlobalData.byteLength);
if (!private->_supercompressionGlobalData) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
private->_sgdByteLength
= pHeader->supercompressionGlobalData.byteLength;
result = stream->read(stream, private->_supercompressionGlobalData,
private->_sgdByteLength);
if (result != KTX_SUCCESS)
goto cleanup;
}
// Calculate size of the image data. Level 0 is the last level in the data.
This->dataSize = private->_levelIndex[0].byteOffset
+ private->_levelIndex[0].byteLength;
/*
* Load the images, if requested.
*/
if (createFlags & KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT) {
result = ktxTexture2_LoadImageData(This, NULL, 0);
}
if (result != KTX_SUCCESS)
goto cleanup;
return result;
cleanup:
ktxTexture2_destruct(This);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture from a ktxStream reading from a KTX source.
*
* The stream object is copied into the constructed ktxTexture2.
*
* 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] This pointer to a ktxTexture2-sized block of memory to
* initialize.
* @param[in] pStream pointer to the stream to read.
* @param[in] createFlags bitmask requesting specific actions during creation.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_FILE_READ_ERROR
* An error occurred while reading the source.
*
* For other exceptions see ktxTexture2_constructFromStreamAndHeader().
*/
static KTX_error_code
ktxTexture2_constructFromStream(ktxTexture2* This, ktxStream* pStream,
ktxTextureCreateFlags createFlags)
{
KTX_header2 header;
KTX_error_code result;
// Read header.
result = pStream->read(pStream, &header, KTX2_HEADER_SIZE);
if (result != KTX_SUCCESS)
return result;
#if IS_BIG_ENDIAN
// byte swap the header
#endif
return ktxTexture2_constructFromStreamAndHeader(This, pStream,
&header, createFlags);
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture from a stdio stream reading from a KTX source.
*
* See ktxTextureInt_constructFromStream for details.
*
* @note Do not close the stdio stream until you are finished with the texture
* object.
*
* @param[in] This pointer to a ktxTextureInt-sized block of memory to
* initialize.
* @param[in] stdioStream a stdio FILE pointer opened on the source.
* @param[in] createFlags bitmask requesting specific actions during creation.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_VALUE Either @p stdiostream or @p This is null.
*
* For other exceptions, see ktxTexture_constructFromStream().
*/
static KTX_error_code
ktxTexture2_constructFromStdioStream(ktxTexture2* This, FILE* stdioStream,
ktxTextureCreateFlags createFlags)
{
KTX_error_code result;
ktxStream stream;
if (stdioStream == NULL || This == NULL)
return KTX_INVALID_VALUE;
result = ktxFileStream_construct(&stream, stdioStream, KTX_FALSE);
if (result == KTX_SUCCESS)
result = ktxTexture2_constructFromStream(This, &stream, createFlags);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture from a named KTX file.
*
* See ktxTextureInt_constructFromStream for details.
*
* @param[in] This pointer to a ktxTextureInt-sized block of memory to
* initialize.
* @param[in] filename pointer to a char array containing the file name.
* @param[in] createFlags bitmask requesting specific actions during creation.
*
* @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_constructFromStream().
*/
static KTX_error_code
ktxTexture2_constructFromNamedFile(ktxTexture2* This,
const char* const filename,
ktxTextureCreateFlags createFlags)
{
KTX_error_code result;
ktxStream stream;
FILE* file;
if (This == NULL || filename == 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 = ktxTexture2_constructFromStream(This, &stream, createFlags);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Construct a ktxTexture from KTX-formatted data in memory.
*
* See ktxTextureInt_constructFromStream for details.
*
* @param[in] This pointer to a ktxTextureInt-sized block of memory to
* initialize.
* @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.
*
* @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_constructFromStream().
*/
static KTX_error_code
ktxTexture2_constructFromMemory(ktxTexture2* This,
const ktx_uint8_t* bytes, ktx_size_t size,
ktxTextureCreateFlags createFlags)
{
KTX_error_code result;
ktxStream stream;
if (bytes == NULL || size == 0)
return KTX_INVALID_VALUE;
result = ktxMemStream_construct_ro(&stream, bytes, size);
if (result == KTX_SUCCESS)
result = ktxTexture2_constructFromStream(This, &stream, createFlags);
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Destruct a ktxTexture2, freeing and internal memory.
*
* @param[in] This pointer to a ktxTexture2-sized block of memory to
* initialize.
*/
void
ktxTexture2_destruct(ktxTexture2* This)
{
if (This->pDfd) free(This->pDfd);
if (This->_private) {
ktx_uint8_t* sgd = This->_private->_supercompressionGlobalData;
if (sgd) free(sgd);
free(This->_private);
}
ktxTexture_destruct(ktxTexture(This));
}
/**
* @memberof ktxTexture2
* @ingroup writer
* @~English
* @brief Create a new empty ktxTexture2.
*
* The address of the newly created ktxTexture2 is written to the location
* pointed at by @p newTex.
*
* @param[in] createInfo pointer to a ktxTextureCreateInfo struct with
* information describing the texture.
* @param[in] storageAllocation
* enum indicating whether or not to allocate storage
* for the texture images.
* @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 @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's images.
*/
KTX_error_code
ktxTexture2_Create(ktxTextureCreateInfo* createInfo,
ktxTextureCreateStorageEnum storageAllocation,
ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_construct(tex, createInfo, storageAllocation);
if (result != KTX_SUCCESS) {
free(tex);
} else {
*newTex = tex;
}
return result;
}
/**
* @memberof ktxTexture2
* @ingroup writer
* @~English
* @brief Create a ktxTexture2 by making a copy of a ktxTexture2.
*
* The address of the newly created ktxTexture2 is written to the location
* pointed at by @p newTex.
*
* @param[in] orig pointer to the texture to copy.
* @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_OUT_OF_MEMORY Not enough memory for the texture data.
*/
KTX_error_code
ktxTexture2_CreateCopy(ktxTexture2* orig, ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_constructCopy(tex, orig);
if (result != KTX_SUCCESS) {
free(tex);
} else {
*newTex = tex;
}
return result;
}
/**
* @defgroup reader Reader
* @brief Read KTX-formatted data.
* @{
*/
/**
* @memberof ktxTexture2
* @~English
* @brief Create a ktxTexture2 from a stdio stream reading from a KTX source.
*
* The address of a newly created ktxTexture2 reflecting the contents of the
* stdio stream 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] stdioStream stdio FILE pointer created from the desired file.
* @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 @p newTex is @c NULL.
* @exception KTX_FILE_DATA_ERROR
* Source data is inconsistent with the KTX
* specification.
* @exception KTX_FILE_READ_ERROR
* An error occurred while reading the source.
* @exception KTX_FILE_UNEXPECTED_EOF
* Not enough data in the source.
* @exception KTX_OUT_OF_MEMORY Not enough memory to create the texture object,
* load the images or load the key-value data.
* @exception KTX_UNKNOWN_FILE_FORMAT
* The source is not in KTX format.
* @exception KTX_UNSUPPORTED_TEXTURE_TYPE
* The source describes a texture type not
* supported by OpenGL or Vulkan, e.g, a 3D array.
*/
KTX_error_code
ktxTexture2_CreateFromStdioStream(FILE* stdioStream,
ktxTextureCreateFlags createFlags,
ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_constructFromStdioStream(tex, stdioStream,
createFlags);
if (result == KTX_SUCCESS)
*newTex = (ktxTexture2*)tex;
else {
free(tex);
*newTex = NULL;
}
return result;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Create a ktxTexture2 from a named KTX file.
*
* The address of a newly created ktxTexture2 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
ktxTexture2_CreateFromNamedFile(const char* const filename,
ktxTextureCreateFlags createFlags,
ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_constructFromNamedFile(tex, filename, createFlags);
if (result == KTX_SUCCESS)
*newTex = (ktxTexture2*)tex;
else {
free(tex);
*newTex = NULL;
}
return result;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Create a ktxTexture2 from KTX-formatted data in memory.
*
* The address of a newly created ktxTexture2 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
ktxTexture2_CreateFromMemory(const ktx_uint8_t* bytes, ktx_size_t size,
ktxTextureCreateFlags createFlags,
ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_constructFromMemory(tex, bytes, size,
createFlags);
if (result == KTX_SUCCESS)
*newTex = (ktxTexture2*)tex;
else {
free(tex);
*newTex = NULL;
}
return result;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Create a ktxTexture2 from KTX-formatted data from a stream.
*
* The address of a newly created ktxTexture2 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] stream pointer to the stream to read KTX data from.
* @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
ktxTexture2_CreateFromStream(ktxStream* stream,
ktxTextureCreateFlags createFlags,
ktxTexture2** newTex)
{
KTX_error_code result;
if (newTex == NULL)
return KTX_INVALID_VALUE;
ktxTexture2* tex = (ktxTexture2*)malloc(sizeof(ktxTexture2));
if (tex == NULL)
return KTX_OUT_OF_MEMORY;
result = ktxTexture2_constructFromStream(tex, stream, createFlags);
if (result == KTX_SUCCESS)
*newTex = (ktxTexture2*)tex;
else {
free(tex);
*newTex = NULL;
}
return result;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Destroy a ktxTexture2 object.
*
* This frees the memory associated with the texture contents and the memory
* of the ktxTexture2 object. This does @e not delete any OpenGL or Vulkan
* texture objects created by ktxTexture2_GLUpload or ktxTexture2_VkUpload.
*
* @param[in] This pointer to the ktxTexture2 object to destroy
*/
void
ktxTexture2_Destroy(ktxTexture2* This)
{
ktxTexture2_destruct(This);
free(This);
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Calculate the size of the image data for the specified number
* of levels.
*
* The data size is the sum of the sizes of each level up to the number
* specified and includes any @c mipPadding between levels. It does
* not include initial @c mipPadding required in the file.
*
* @param[in] This pointer to the ktxTexture object of interest.
* @param[in] levels number of levels whose data size to return.
*
* @return the data size in bytes.
*/
ktx_size_t
ktxTexture2_calcDataSizeLevels(ktxTexture2* This, ktx_uint32_t levels)
{
ktx_size_t dataSize = 0;
assert(This != NULL);
assert(This->supercompressionScheme == KTX_SS_NONE);
assert(levels <= This->numLevels);
for (ktx_uint32_t i = levels - 1; i > 0; i--) {
ktx_size_t levelSize = ktxTexture_calcLevelSize(ktxTexture(This), i,
KTX_FORMAT_VERSION_TWO);
dataSize += _KTX_PADN(This->_private->_requiredLevelAlignment,
levelSize);
}
dataSize += ktxTexture_calcLevelSize(ktxTexture(This), 0,
KTX_FORMAT_VERSION_TWO);
return dataSize;
}
/**
* @memberof ktxTexture2 @private
* @~English
*
* @copydoc ktxTexture::ktxTexture_doCalcFaceLodSize
*/
ktx_size_t
ktxTexture2_calcFaceLodSize(ktxTexture2* This, ktx_uint32_t level)
{
assert(This != NULL);
assert(This->supercompressionScheme == KTX_SS_NONE);
/*
* 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(ktxTexture(This), level,
KTX_FORMAT_VERSION_TWO);
else
return This->_private->_levelIndex[level].uncompressedByteLength;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Return the offset of a level in bytes from the start of the image
* data in a ktxTexture.
*
* Since the offset is from the start of the image data, it does not include the initial
* @c mipPadding required in the file.
*
* @param[in] This pointer to the ktxTexture object of interest.
* @param[in] level level whose offset to return.
*
* @return the data size in bytes.
*/
ktx_size_t
ktxTexture2_calcLevelOffset(ktxTexture2* This, ktx_uint32_t level)
{
assert (This != NULL);
assert(This->supercompressionScheme == KTX_SS_NONE);
assert (level < This->numLevels);
ktx_size_t levelOffset = 0;
for (ktx_uint32_t i = This->numLevels - 1; i > level; i--) {
ktx_size_t levelSize;
levelSize = ktxTexture_calcLevelSize(ktxTexture(This), i,
KTX_FORMAT_VERSION_TWO);
levelOffset += _KTX_PADN(This->_private->_requiredLevelAlignment,
levelSize);
}
return levelOffset;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Retrieve the offset of a level's first image within the KTX2 file.
*
* @param[in] This pointer to the ktxTexture object of interest.
*/
ktx_uint64_t ktxTexture2_levelFileOffset(ktxTexture2* This, ktx_uint32_t level)
{
assert(This->_private->_firstLevelFileOffset != 0);
return This->_private->_levelIndex[level].byteOffset
+ This->_private->_firstLevelFileOffset;
}
// Recursive function to return the greatest common divisor of a and b.
static uint32_t
gcd(uint32_t a, uint32_t b) {
if (a == 0)
return b;
return gcd(b % a, a);
}
// Function to return the least common multiple of a & 4.
uint32_t
lcm4(uint32_t a)
{
if (!(a & 0x03))
return a; // a is a multiple of 4.
return (a*4) / gcd(a, 4);
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Return the required alignment for levels of this texture.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return The required alignment for levels.
*/
ktx_uint32_t
ktxTexture2_calcRequiredLevelAlignment(ktxTexture2* This)
{
ktx_uint32_t alignment;
if (This->supercompressionScheme != KTX_SS_NONE)
alignment = 1;
else
alignment = lcm4(This->_protected->_formatSize.blockSizeInBits / 8);
return alignment;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Return what the required alignment for levels of this texture will be after inflation.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return The required alignment for levels.
*/
ktx_uint32_t
ktxTexture2_calcPostInflationLevelAlignment(ktxTexture2* This)
{
ktx_uint32_t alignment;
// Should actually work for none supercompressed but don't want to
// encourage use of it.
assert(This->supercompressionScheme >= KTX_SS_ZSTD);
if (This->vkFormat != VK_FORMAT_UNDEFINED)
alignment = lcm4(This->_protected->_formatSize.blockSizeInBits / 8);
else
alignment = 16;
return alignment;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Return information about the components of an image in a texture.
*
* @param[in] This pointer to the ktxTexture object of interest.
* @param[in,out] pNumComponents pointer to location in which to write the
* number of components in the textures images.
* @param[in,out] pComponentByteLength
* pointer to the location in which to write
* byte length of a component.
*/
void
ktxTexture2_GetComponentInfo(ktxTexture2* This, uint32_t* pNumComponents,
uint32_t* pComponentByteLength)
{
// FIXME Need to handle packed case.
getDFDComponentInfoUnpacked(This->pDfd, pNumComponents,
pComponentByteLength);
}
/**
* @memberof ktxTexture2
* @~English
* @brief Return the number of components in an image of the texture.
*
* Returns the number of components indicated by the DFD's sample information
* in accordance with the color model. For uncompressed formats it will be the actual
* number of components in the image. For block-compressed formats, it will be 1 or 2
* according to the format's DFD color model. For Basis compressed textures, the
* function examines the ids of the channels indicated by the DFD and uses that
* information to determine and return the number of components in the image
* @e before encoding and deflation so it can be used to help choose a suitable
* transcode target format.
*
* @param[in] This pointer to the ktxTexture object of interest.
*
* @return the number of components.
*/
ktx_uint32_t
ktxTexture2_GetNumComponents(ktxTexture2* This)
{
uint32_t* pBdb = This->pDfd + 1;
uint32_t dfdNumComponents = getDFDNumComponents(This->pDfd);
uint32_t colorModel = KHR_DFDVAL(pBdb, MODEL);
if (colorModel < KHR_DF_MODEL_DXT1A) {
return dfdNumComponents;
} else {
switch (colorModel) {
case KHR_DF_MODEL_ETC1S:
{
uint32_t channel0Id = KHR_DFDSVAL(pBdb, 0, CHANNELID);
if (dfdNumComponents == 1) {
if (channel0Id == KHR_DF_CHANNEL_ETC1S_RGB)
return 3;
else
return 1;
} else {
uint32_t channel1Id = KHR_DFDSVAL(pBdb, 1, CHANNELID);
if (channel0Id == KHR_DF_CHANNEL_ETC1S_RGB
&& channel1Id == KHR_DF_CHANNEL_ETC1S_AAA)
return 4;
else {
// An invalid combination of channel Ids should never
// have been set during creation or should have been
// caught when the file was loaded.
assert(channel0Id == KHR_DF_CHANNEL_ETC1S_RRR
&& channel1Id == KHR_DF_CHANNEL_ETC1S_GGG);
return 2;
}
}
break;
}
case KHR_DF_MODEL_UASTC:
switch (KHR_DFDSVAL(pBdb, 0, CHANNELID)) {
case KHR_DF_CHANNEL_UASTC_RRR:
return 1;
case KHR_DF_CHANNEL_UASTC_RRRG:
return 2;
case KHR_DF_CHANNEL_UASTC_RGB:
return 3;
case KHR_DF_CHANNEL_UASTC_RGBA:
return 4;
default:
// Same comment as for the assert in the ETC1 case.
assert(false);
return 1;
}
break;
default:
return dfdNumComponents;
}
}
}
/**
* @memberof ktxTexture2
* @~English
* @brief Find the offset of an image within a ktxTexture's image data.
*
* As there is no such thing as a 3D cubemap we make the 3rd location parameter
* do double duty. Only works for non-supercompressed textures as
* there is no way to tell where an image is for a supercompressed one.
*
* @param[in] This pointer to the ktxTexture object of interest.
* @param[in] level mip level of the image.
* @param[in] layer array layer of the image.
* @param[in] faceSlice cube map face or depth slice of the image.
* @param[in,out] pOffset pointer to location to store the offset.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_OPERATION
* @p level, @p layer or @p faceSlice exceed the
* dimensions of the texture.
* @exception KTX_INVALID_OPERATION Texture is supercompressed.
* @exception KTX_INVALID_VALID @p This is NULL.
*/
KTX_error_code
ktxTexture2_GetImageOffset(ktxTexture2* This, ktx_uint32_t level,
ktx_uint32_t layer, ktx_uint32_t faceSlice,
ktx_size_t* pOffset)
{
if (This == NULL)
return KTX_INVALID_VALUE;
if (level >= This->numLevels || layer >= This->numLayers)
return KTX_INVALID_OPERATION;
if (This->supercompressionScheme != KTX_SS_NONE)
return KTX_INVALID_OPERATION;
if (This->isCubemap) {
if (faceSlice >= This->numFaces)
return KTX_INVALID_OPERATION;
} else {
ktx_uint32_t maxSlice = MAX(1, This->baseDepth >> level);
if (faceSlice >= maxSlice)
return KTX_INVALID_OPERATION;
}
// Get the offset of the start of the level.
*pOffset = ktxTexture2_levelDataOffset(This, level);
// All layers, faces & slices within a level are the same size.
if (layer != 0) {
ktx_size_t layerSize;
layerSize = ktxTexture_layerSize(ktxTexture(This), level,
KTX_FORMAT_VERSION_TWO);
*pOffset += layer * layerSize;
}
if (faceSlice != 0) {
ktx_size_t imageSize;
imageSize = ktxTexture2_GetImageSize(This, level);
*pOffset += faceSlice * imageSize;
}
return KTX_SUCCESS;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Retrieve the opto-electrical transfer function of the images.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return A @c khr_df_transfer enum value specifying the OETF.
*/
khr_df_transfer_e
ktxTexture2_GetOETF_e(ktxTexture2* This)
{
return KHR_DFDVAL(This->pDfd+1, TRANSFER);
}
/**
* @memberof ktxTexture2
* @~English
* @brief Retrieve the opto-electrical transfer function of the images.
* @deprecated Retained for backward compatibility. Use ktxTexture2\_GetOETF\_e()
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return A @c khr_df_transfer enum value specifying the OETF, returned as
* @c ktx_uint32_t.
*/
ktx_uint32_t
ktxTexture2_GetOETF(ktxTexture2* This)
{
return KHR_DFDVAL(This->pDfd+1, TRANSFER);
}
/**
* @memberof ktxTexture2
* @~English
* @brief Retrieve the DFD color model of the images.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return A @c khr_df_transfer enum value specifying the color model.
*/
khr_df_model_e
ktxTexture2_GetColorModel_e(ktxTexture2* This)
{
return KHR_DFDVAL(This->pDfd+1, MODEL);
}
/**
* @memberof ktxTexture2
* @~English
* @brief Retrieve whether the RGB components have been premultiplied by the alpha component.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*
* @return KTX\_TRUE if the components are premultiplied, KTX_FALSE otherwise.
*/
ktx_bool_t
ktxTexture2_GetPremultipliedAlpha(ktxTexture2* This)
{
return KHR_DFDVAL(This->pDfd+1, FLAGS) & KHR_DF_FLAG_ALPHA_PREMULTIPLIED;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Query if the images are in a transcodable format.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*/
ktx_bool_t
ktxTexture2_NeedsTranscoding(ktxTexture2* This)
{
if (KHR_DFDVAL(This->pDfd + 1, MODEL) == KHR_DF_MODEL_ETC1S)
return true;
else if (KHR_DFDVAL(This->pDfd + 1, MODEL) == KHR_DF_MODEL_UASTC)
return true;
else
return false;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Return the total size in bytes of the uncompressed data of a ktxTexture2.
*
* If supercompressionScheme == KTX_SS_NONE or
* KTX_SS_BASIS_LZ, returns the value of @c This->dataSize
* else if supercompressionScheme == KTX_SS_ZSTD, it returns the
* sum of the uncompressed sizes of each mip level plus space for the level padding. With no
* supercompression the data size and uncompressed data size are the same. For Basis
* supercompression the uncompressed size cannot be known until the data is transcoded
* so the compressed size is returned.
*
* @param[in] This pointer to the ktxTexture1 object of interest.
*/
ktx_size_t
ktxTexture2_GetDataSizeUncompressed(ktxTexture2* This)
{
switch (This->supercompressionScheme) {
case KTX_SS_BASIS_LZ:
case KTX_SS_NONE:
return This->dataSize;
case KTX_SS_ZSTD:
{
ktx_size_t uncompressedSize = 0;
ktx_uint32_t uncompressedLevelAlignment;
ktxLevelIndexEntry* levelIndex = This->_private->_levelIndex;
uncompressedLevelAlignment =
ktxTexture2_calcPostInflationLevelAlignment(This);
for (ktx_int32_t level = This->numLevels - 1; level >= 1; level--) {
ktx_size_t uncompressedLevelSize;
uncompressedLevelSize = levelIndex[level].uncompressedByteLength;
uncompressedLevelSize = _KTX_PADN(uncompressedLevelAlignment,
uncompressedLevelSize);
uncompressedSize += uncompressedLevelSize;
}
uncompressedSize += levelIndex[0].uncompressedByteLength;
return uncompressedSize;
}
case KTX_SS_BEGIN_VENDOR_RANGE:
case KTX_SS_END_VENDOR_RANGE:
case KTX_SS_BEGIN_RESERVED:
default:
return 0;
}
}
/**
* @memberof ktxTexture2
* @~English
* @brief Calculate & return the size in bytes of an image at the specified
* mip level.
*
* For arrays, this is the size of a 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 ktxTexture2 object of interest.
* @param[in] level level of interest. *
*/
ktx_size_t
ktxTexture2_GetImageSize(ktxTexture2* This, ktx_uint32_t level)
{
return ktxTexture_calcImageSize(ktxTexture(This), level,
KTX_FORMAT_VERSION_TWO);
}
/**
* @memberof ktxTexture2
* @~English
* @brief Iterate over the mip levels in a ktxTexture2 object.
*
* This is almost identical to ktxTexture_IterateLevelFaces(). The difference is
* that the blocks of image data for non-array cube maps include all faces of
* a mip level.
*
* 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.
*
* Intended for use only when supercompressionScheme == SUPERCOMPRESSION_NONE.
*
* @param[in] This handle of the ktxTexture opened on the data.
* @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_OPERATION supercompressionScheme != SUPERCOMPRESSION_NONE.
* @exception KTX_INVALID_VALUE @p This is @c NULL or @p iterCb is @c NULL.
*
*/
KTX_error_code
ktxTexture2_IterateLevels(ktxTexture2* This, PFNKTXITERCB iterCb, void* userdata)
{
KTX_error_code result = KTX_SUCCESS;
//ZSTD_DCtx* dctx;
//ktx_uint8_t* decompBuf;
ktxLevelIndexEntry* levelIndex = This->_private->_levelIndex;
if (This == NULL)
return KTX_INVALID_VALUE;
if (iterCb == NULL)
return KTX_INVALID_VALUE;
if (This->supercompressionScheme != KTX_SS_NONE)
return KTX_INVALID_OPERATION;
for (ktx_int32_t level = This->numLevels - 1; level >= 0; level--)
{
ktx_uint32_t width, height, depth;
ktx_uint64_t levelSize;
ktx_uint64_t offset;
/* Array textures have the same number of layers at each mip level. */
width = MAX(1, This->baseWidth >> level);
height = MAX(1, This->baseHeight >> level);
depth = MAX(1, This->baseDepth >> level);
levelSize = levelIndex[level].uncompressedByteLength;
offset = ktxTexture2_levelDataOffset(This, level);
/* All array layers are passed in a group because that is how
* GL & Vulkan need them. Hence no
* for (layer = 0; layer < This->numLayers)
*/
result = iterCb(level, 0, width, height, depth,
levelSize, This->pData + offset, userdata);
if (result != KTX_SUCCESS)
break;
}
return result;
}
/**
* @memberof ktxTexture2
* @~English
* @brief Iterate over the images in a ktxTexture2 object while loading the
* image data.
*
* This operates similarly to ktxTexture_IterateLevelFaces() except that it
* loads the images from the ktxTexture2's source to a temporary buffer
* while iterating. If supercompressionScheme == KTX_SS_ZSTD,
* it will inflate the data before passing it to the callback. The callback function
* must copy the image data if it wishes to preserve it as the temporary buffer
* is reused for each level and is freed when this function exits.
*
* This function is helpful for reducing memory usage when uploading the data
* to a graphics API.
*
* Intended for use only when supercompressionScheme == SUPERCOMPRESSION_NONE
* or SUPERCOMPRESSION_ZSTD. As there is no access to the ktxTexture's data on
* conclusion of this function, destroying the texture on completion is recommended.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
* @param[in,out] iterCb the address of a callback function which is called
* with the data for each image.
* @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_OPERATION the ktxTexture2 was not created from a
* stream, i.e there is no data to load, or
* this ktxTexture2's images have already
* been loaded.
* @exception KTX_INVALID_OPERATION
* supercompressionScheme != SUPERCOMPRESSION_NONE.
* and supercompressionScheme != SUPERCOMPRESSION_ZSTD.
* @exception KTX_INVALID_VALUE @p This is @c NULL or @p iterCb is @c NULL.
* @exception KTX_OUT_OF_MEMORY not enough memory to allocate a block to
* hold the base level image.
*/
KTX_error_code
ktxTexture2_IterateLoadLevelFaces(ktxTexture2* This, PFNKTXITERCB iterCb,
void* userdata)
{
DECLARE_PROTECTED(ktxTexture);
ktxStream* stream = (ktxStream *)&prtctd->_stream;
ktxLevelIndexEntry* levelIndex;
ktx_size_t dataSize = 0, uncompressedDataSize = 0;
KTX_error_code result = KTX_SUCCESS;
ktx_uint8_t* dataBuf = NULL;
ktx_uint8_t* uncompressedDataBuf = NULL;
ktx_uint8_t* pData;
ZSTD_DCtx* dctx = NULL;
if (This == NULL)
return KTX_INVALID_VALUE;
if (This->classId != ktxTexture2_c)
return KTX_INVALID_OPERATION;
if (This->supercompressionScheme != KTX_SS_NONE &&
This->supercompressionScheme != KTX_SS_ZSTD)
return KTX_INVALID_OPERATION;
if (iterCb == NULL)
return KTX_INVALID_VALUE;
if (prtctd->_stream.data.file == NULL)
// This Texture not created from a stream or images are already loaded.
return KTX_INVALID_OPERATION;
levelIndex = This->_private->_levelIndex;
// Allocate memory sufficient for the base level
dataSize = levelIndex[0].byteLength;
dataBuf = malloc(dataSize);
if (!dataBuf)
return KTX_OUT_OF_MEMORY;
if (This->supercompressionScheme == KTX_SS_ZSTD) {
uncompressedDataSize = levelIndex[0].uncompressedByteLength;
uncompressedDataBuf = malloc(uncompressedDataSize);
if (!uncompressedDataBuf) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
dctx = ZSTD_createDCtx();
pData = uncompressedDataBuf;
} else {
pData = dataBuf;
}
for (ktx_int32_t level = This->numLevels - 1; level >= 0; --level)
{
ktx_size_t levelSize;
GLsizei width, height, depth;
// Array textures have the same number of layers at each mip level.
width = MAX(1, This->baseWidth >> level);
height = MAX(1, This->baseHeight >> level);
depth = MAX(1, This->baseDepth >> level);
levelSize = levelIndex[level].byteLength;
if (dataSize < levelSize) {
// Levels cannot be larger than the base level
result = KTX_FILE_DATA_ERROR;
goto cleanup;
}
// Use setpos so we skip any padding.
result = stream->setpos(stream,
ktxTexture2_levelFileOffset(This, level));
if (result != KTX_SUCCESS)
goto cleanup;
result = stream->read(stream, dataBuf, levelSize);
if (result != KTX_SUCCESS)
goto cleanup;
if (This->supercompressionScheme == KTX_SS_ZSTD) {
levelSize =
ZSTD_decompressDCtx(dctx, uncompressedDataBuf,
uncompressedDataSize,
dataBuf,
levelSize);
if (ZSTD_isError(levelSize)) {
ZSTD_ErrorCode error = ZSTD_getErrorCode(levelSize);
switch(error) {
case ZSTD_error_dstSize_tooSmall:
return KTX_INVALID_VALUE; // inflatedDataCapacity too small.
case ZSTD_error_memory_allocation:
return KTX_OUT_OF_MEMORY;
default:
return KTX_FILE_DATA_ERROR;
}
}
// We don't fix up the texture's dataSize, levelIndex or
// _requiredAlignment because after this function completes there
// is no way to get at the texture's data.
//nindex[level].byteOffset = levelOffset;
//nindex[level].uncompressedByteLength = nindex[level].byteLength =
//levelByteLength;
}
#if IS_BIG_ENDIAN
switch (prtctd->_typeSize) {
case 2:
_ktxSwapEndian16((ktx_uint16_t*)pData, levelSize / 2);
break;
case 4:
_ktxSwapEndian32((ktx_uint32_t*)pDest, levelSize / 4);
break;
case 8:
_ktxSwapEndian64((ktx_uint64_t*)pDest, levelSize / 8);
break;
}
#endif
// With the exception of non-array cubemaps the entire level
// is passed at once because that is how OpenGL and Vulkan need them.
// Vulkan could take all the faces at once too but we iterate
// them separately for OpenGL.
if (This->isCubemap && !This->isArray) {
ktx_uint8_t* pFace = pData;
struct blockCount {
ktx_uint32_t x, y;
} blockCount;
ktx_size_t faceSize;
blockCount.x
= (uint32_t)ceilf((float)width / prtctd->_formatSize.blockWidth);
blockCount.y
= (uint32_t)ceilf((float)height / prtctd->_formatSize.blockHeight);
blockCount.x = MAX(prtctd->_formatSize.minBlocksX, blockCount.x);
blockCount.y = MAX(prtctd->_formatSize.minBlocksX, blockCount.y);
faceSize = blockCount.x * blockCount.y
* prtctd->_formatSize.blockSizeInBits / 8;
for (ktx_uint32_t face = 0; face < This->numFaces; ++face) {
result = iterCb(level, face,
width, height, depth,
(ktx_uint32_t)faceSize, pFace, userdata);
pFace += faceSize;
if (result != KTX_SUCCESS)
goto cleanup;
}
} else {
result = iterCb(level, 0,
width, height, depth,
(ktx_uint32_t)levelSize, pData, userdata);
if (result != KTX_SUCCESS)
goto cleanup;
}
}
// No further need for this.
stream->destruct(stream);
This->_private->_firstLevelFileOffset = 0;
cleanup:
free(dataBuf);
if (uncompressedDataBuf) free(uncompressedDataBuf);
if (dctx) ZSTD_freeDCtx(dctx);
return result;
}
KTX_error_code
ktxTexture2_inflateZstdInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData,
ktx_uint8_t* pInflatedData,
ktx_size_t inflatedDataCapacity);
/**
* @memberof ktxTexture2
* @~English
* @brief Load all the image data from the ktxTexture2's source.
*
* The data will be inflated if supercompressionScheme == SUPERCOMPRESSION_ZSTD.
* The data is loaded into the provided buffer or to an internally allocated
* buffer, if @p pBuffer is @c NULL. Callers providing their own buffer must
* ensure the buffer large enough to hold the inflated data for files deflated
* with Zstd. See ktxTexture2_GetDataSizeUncompressed().
*
* The texture's levelIndex, dataSize, DFD and supercompressionScheme will
* all be updated after successful inflation to reflect the inflated data.
*
* @param[in] This pointer to the ktxTexture object of interest.
* @param[in] pBuffer pointer to the buffer in which to load the image data.
* @param[in] bufSize size of the buffer pointed at by @p pBuffer.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_VALUE @p This is NULL.
* @exception KTX_INVALID_VALUE @p bufSize is less than the the image data size.
* @exception KTX_INVALID_OPERATION
* The data has already been loaded or the
* ktxTexture was not created from a KTX source.
* @exception KTX_OUT_OF_MEMORY Insufficient memory for the image data.
*/
KTX_error_code
ktxTexture2_LoadImageData(ktxTexture2* This,
ktx_uint8_t* pBuffer, ktx_size_t bufSize)
{
DECLARE_PROTECTED(ktxTexture);
DECLARE_PRIVATE(ktxTexture2);
ktx_uint8_t* pDest;
ktx_uint8_t* pDeflatedData = 0;
ktx_uint8_t* pReadBuf;
KTX_error_code result = KTX_SUCCESS;
ktx_size_t inflatedDataCapacity = ktxTexture2_GetDataSizeUncompressed(This);
if (This == NULL)
return KTX_INVALID_VALUE;
if (This->pData != NULL)
return KTX_INVALID_OPERATION; // Data already loaded.
if (prtctd->_stream.data.file == NULL)
// This Texture not created from a stream or images already loaded;
return KTX_INVALID_OPERATION;
if (pBuffer == NULL) {
This->pData = malloc(inflatedDataCapacity);
if (This->pData == NULL)
return KTX_OUT_OF_MEMORY;
pDest = This->pData;
} else if (bufSize < inflatedDataCapacity) {
return KTX_INVALID_VALUE;
} else {
pDest = pBuffer;
}
if (This->supercompressionScheme == KTX_SS_ZSTD) {
// Create buffer to hold deflated data.
pDeflatedData = malloc(This->dataSize);
if (pDeflatedData == NULL)
return KTX_OUT_OF_MEMORY;
pReadBuf = pDeflatedData;
} else {
pReadBuf = pDest;
}
// Seek to data for first level as there may be padding between the
// metadata/sgd and the image data.
result = prtctd->_stream.setpos(&prtctd->_stream,
private->_firstLevelFileOffset);
if (result != KTX_SUCCESS)
return result;
result = prtctd->_stream.read(&prtctd->_stream, pReadBuf,
This->dataSize);
if (result != KTX_SUCCESS)
return result;
if (This->supercompressionScheme == KTX_SS_ZSTD) {
assert(pDeflatedData != NULL);
result = ktxTexture2_inflateZstdInt(This, pDeflatedData, pDest,
inflatedDataCapacity);
free(pDeflatedData);
if (result != KTX_SUCCESS) {
if (pBuffer == NULL) {
free(This->pData);
This->pData = 0;
}
return result;
}
}
if (IS_BIG_ENDIAN) {
// Perform endianness conversion on texture data.
// To avoid mip padding, need to convert each level individually.
for (ktx_uint32_t level = 0; level < This->numLevels; ++level)
{
ktx_size_t levelOffset;
ktx_size_t levelByteLength;
levelByteLength = private->_levelIndex[level].byteLength;
levelOffset = ktxTexture2_levelDataOffset(This, level);
pDest = This->pData + levelOffset;
switch (prtctd->_typeSize) {
case 2:
_ktxSwapEndian16((ktx_uint16_t*)pDest, levelByteLength / 2);
break;
case 4:
_ktxSwapEndian32((ktx_uint32_t*)pDest, levelByteLength / 4);
break;
case 8:
_ktxSwapEndian64((ktx_uint64_t*)pDest, levelByteLength / 8);
break;
}
}
}
// No further need for stream or file offset.
prtctd->_stream.destruct(&prtctd->_stream);
private->_firstLevelFileOffset = 0;
return result;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Retrieve the offset of a level's first image within the ktxTexture2's
* image data.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
*/
ktx_uint64_t ktxTexture2_levelDataOffset(ktxTexture2* This, ktx_uint32_t level)
{
return This->_private->_levelIndex[level].byteOffset;
}
/**
* @memberof ktxTexture2 @private
* @~English
* @brief Inflate the data in a ktxTexture2 object using Zstandard.
*
* The texture's levelIndex, dataSize, DFD and supercompressionScheme will
* all be updated after successful inflation to reflect the inflated data.
*
* @param[in] This pointer to the ktxTexture2 object of interest.
* @param[in] pDeflatedData pointer to a buffer containing the deflated data
* of the entire texture.
* @param[in,out] pInflatedData pointer to a buffer in which to write the inflated
* data.
* @param[in] inflatedDataCapacity capacity of the buffer pointed at by
* @p pInflatedData.
*/
KTX_error_code
ktxTexture2_inflateZstdInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData,
ktx_uint8_t* pInflatedData,
ktx_size_t inflatedDataCapacity)
{
DECLARE_PROTECTED(ktxTexture);
ktx_uint32_t levelIndexByteLength =
This->numLevels * sizeof(ktxLevelIndexEntry);
uint64_t levelOffset = 0;
ktxLevelIndexEntry* cindex = This->_private->_levelIndex;
ktxLevelIndexEntry* nindex;
ktx_uint32_t uncompressedLevelAlignment;
ZSTD_DCtx* dctx;
if (pDeflatedData == NULL)
return KTX_INVALID_VALUE;
if (pInflatedData == NULL)
return KTX_INVALID_VALUE;
if (This->supercompressionScheme != KTX_SS_ZSTD)
return KTX_INVALID_OPERATION;
nindex = malloc(levelIndexByteLength);
if (nindex == NULL)
return KTX_OUT_OF_MEMORY;
uncompressedLevelAlignment =
ktxTexture2_calcPostInflationLevelAlignment(This);
ktx_size_t inflatedByteLength = 0;
dctx = ZSTD_createDCtx();
for (int32_t level = This->numLevels - 1; level >= 0; level--) {
size_t levelByteLength =
ZSTD_decompressDCtx(dctx, pInflatedData + levelOffset,
inflatedDataCapacity,
&pDeflatedData[cindex[level].byteOffset],
cindex[level].byteLength);
if (ZSTD_isError(levelByteLength)) {
ZSTD_ErrorCode error = ZSTD_getErrorCode(levelByteLength);
switch(error) {
case ZSTD_error_dstSize_tooSmall:
return KTX_INVALID_VALUE; // inflatedDataCapacity too small.
case ZSTD_error_memory_allocation:
return KTX_OUT_OF_MEMORY;
default:
return KTX_FILE_DATA_ERROR;
}
}
nindex[level].byteOffset = levelOffset;
nindex[level].uncompressedByteLength = nindex[level].byteLength =
levelByteLength;
ktx_size_t paddedLevelByteLength
= _KTX_PADN(uncompressedLevelAlignment, levelByteLength);
inflatedByteLength += paddedLevelByteLength;
levelOffset += paddedLevelByteLength;
inflatedDataCapacity -= paddedLevelByteLength;
}
ZSTD_freeDCtx(dctx);
// Now modify the texture.
This->dataSize = inflatedByteLength;
This->supercompressionScheme = KTX_SS_NONE;
memcpy(cindex, nindex, levelIndexByteLength); // Update level index
free(nindex);
This->_private->_requiredLevelAlignment = uncompressedLevelAlignment;
// Set bytesPlane as we're now sized.
uint32_t* bdb = This->pDfd + 1;
// blockSizeInBits was set to the inflated size on file load.
bdb[KHR_DF_WORD_BYTESPLANE0] = prtctd->_formatSize.blockSizeInBits / 8;
return KTX_SUCCESS;
}
#if !KTX_FEATURE_WRITE
/*
* Stubs for writer functions that return a proper error code
*/
KTX_error_code
ktxTexture2_SetImageFromMemory(ktxTexture2* This, ktx_uint32_t level,
ktx_uint32_t layer, ktx_uint32_t faceSlice,
const ktx_uint8_t* src, ktx_size_t srcSize)
{
UNUSED(This);
UNUSED(level);
UNUSED(layer);
UNUSED(faceSlice);
UNUSED(src);
UNUSED(srcSize);
return KTX_INVALID_OPERATION;
}
KTX_error_code
ktxTexture2_SetImageFromStdioStream(ktxTexture2* This, ktx_uint32_t level,
ktx_uint32_t layer, ktx_uint32_t faceSlice,
FILE* src, ktx_size_t srcSize)
{
UNUSED(This);
UNUSED(level);
UNUSED(layer);
UNUSED(faceSlice);
UNUSED(src);
UNUSED(srcSize);
return KTX_INVALID_OPERATION;
}
KTX_error_code
ktxTexture2_WriteToStdioStream(ktxTexture2* This, FILE* dstsstr)
{
UNUSED(This);
UNUSED(dstsstr);
return KTX_INVALID_OPERATION;
}
KTX_error_code
ktxTexture2_WriteToNamedFile(ktxTexture2* This, const char* const dstname)
{
UNUSED(This);
UNUSED(dstname);
return KTX_INVALID_OPERATION;
}
KTX_error_code
ktxTexture2_WriteToMemory(ktxTexture2* This,
ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
{
UNUSED(This);
UNUSED(ppDstBytes);
UNUSED(pSize);
return KTX_INVALID_OPERATION;
}
KTX_error_code
ktxTexture2_WriteToStream(ktxTexture2* This,
ktxStream* dststr)
{
UNUSED(This);
UNUSED(dststr);
return KTX_INVALID_OPERATION;
}
#endif
/*
* Initialized here at the end to avoid the need for multiple declarations of
* the virtual functions.
*/
struct ktxTexture_vtblInt ktxTexture2_vtblInt = {
(PFNCALCDATASIZELEVELS)ktxTexture2_calcDataSizeLevels,
(PFNCALCFACELODSIZE)ktxTexture2_calcFaceLodSize,
(PFNCALCLEVELOFFSET)ktxTexture2_calcLevelOffset
};
struct ktxTexture_vtbl ktxTexture2_vtbl = {
(PFNKTEXDESTROY)ktxTexture2_Destroy,
(PFNKTEXGETIMAGEOFFSET)ktxTexture2_GetImageOffset,
(PFNKTEXGETDATASIZEUNCOMPRESSED)ktxTexture2_GetDataSizeUncompressed,
(PFNKTEXGETIMAGESIZE)ktxTexture2_GetImageSize,
(PFNKTEXITERATELEVELS)ktxTexture2_IterateLevels,
(PFNKTEXITERATELOADLEVELFACES)ktxTexture2_IterateLoadLevelFaces,
(PFNKTEXNEEDSTRANSCODING)ktxTexture2_NeedsTranscoding,
(PFNKTEXLOADIMAGEDATA)ktxTexture2_LoadImageData,
(PFNKTEXSETIMAGEFROMMEMORY)ktxTexture2_SetImageFromMemory,
(PFNKTEXSETIMAGEFROMSTDIOSTREAM)ktxTexture2_SetImageFromStdioStream,
(PFNKTEXWRITETOSTDIOSTREAM)ktxTexture2_WriteToStdioStream,
(PFNKTEXWRITETONAMEDFILE)ktxTexture2_WriteToNamedFile,
(PFNKTEXWRITETOMEMORY)ktxTexture2_WriteToMemory,
(PFNKTEXWRITETOSTREAM)ktxTexture2_WriteToStream,
};
/** @} */