a00cf02241
Add support glTF KHR_texture_basisu extension
2525 lines
88 KiB
C
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,
|
|
};
|
|
|
|
/** @} */
|
|
|