Add ktx
This commit is contained in:
@@ -0,0 +1,978 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @file
|
||||
* @~English
|
||||
*
|
||||
* @brief Functions for creating KTX-format files from a set of images.
|
||||
*
|
||||
* @author Mark Callow, HI Corporation
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2018-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#if defined(__GNUC__)
|
||||
#include <strings.h> // For strncasecmp on GNU/Linux
|
||||
#endif
|
||||
|
||||
#include "ktx.h"
|
||||
#include "ktxint.h"
|
||||
#include "filestream.h"
|
||||
#include "memstream.h"
|
||||
#include "texture1.h"
|
||||
|
||||
#include "dfdutils/dfd.h"
|
||||
#include "vkformat_enum.h"
|
||||
#include "vk_format.h"
|
||||
#include "version.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup writer Writer
|
||||
* @brief Write KTX-formatted data.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture @private
|
||||
* @~English
|
||||
* @brief Set image for level, layer, faceSlice from a ktxStream source.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] level mip level of the image to set.
|
||||
* @param[in] layer array layer of the image to set.
|
||||
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
||||
* @param[in] src ktxStream pointer to the source.
|
||||
* @param[in] srcSize size of the source image in bytes.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
||||
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
||||
* specified level, layer & faceSlice.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* No storage was allocated when the texture was
|
||||
* created.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_setImageFromStream(ktxTexture1* This, ktx_uint32_t level,
|
||||
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
||||
ktxStream* src, ktx_size_t srcSize)
|
||||
{
|
||||
ktx_uint32_t packedRowBytes, rowBytes, rowPadding, numRows = 0;
|
||||
ktx_size_t packedBytes, unpackedBytes;
|
||||
ktx_size_t imageOffset;
|
||||
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
||||
ktx_uint32_t faceLodPadding;
|
||||
#endif
|
||||
|
||||
if (!This || !src)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
if (!This->pData)
|
||||
return KTX_INVALID_OPERATION;
|
||||
|
||||
ktxTexture_GetImageOffset(ktxTexture(This), level, layer, faceSlice, &imageOffset);
|
||||
|
||||
if (This->isCompressed) {
|
||||
packedBytes = ktxTexture_GetImageSize(ktxTexture(This), level);
|
||||
rowPadding = 0;
|
||||
// These 2 are not used when rowPadding == 0. Quiets compiler warning.
|
||||
packedRowBytes = 0;
|
||||
rowBytes = 0;
|
||||
} else {
|
||||
ktxTexture_rowInfo(ktxTexture(This), level, &numRows, &rowBytes, &rowPadding);
|
||||
unpackedBytes = rowBytes * numRows;
|
||||
if (rowPadding) {
|
||||
packedRowBytes = rowBytes - rowPadding;
|
||||
packedBytes = packedRowBytes * numRows;
|
||||
} else {
|
||||
packedRowBytes = rowBytes;
|
||||
packedBytes = unpackedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcSize != packedBytes)
|
||||
return KTX_INVALID_OPERATION;
|
||||
// The above will catch a flagrantly invalid srcSize. This is an
|
||||
// additional check of the internal calculations.
|
||||
assert (imageOffset + srcSize <= This->dataSize);
|
||||
|
||||
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
||||
faceLodPadding = _KTX_PAD4_LEN(faceLodSize);
|
||||
#endif
|
||||
|
||||
if (rowPadding == 0) {
|
||||
/* Can copy whole image at once */
|
||||
src->read(src, This->pData + imageOffset, srcSize);
|
||||
} else {
|
||||
/* Copy the rows individually, padding each one */
|
||||
ktx_uint32_t row;
|
||||
ktx_uint8_t* dst = This->pData + imageOffset;
|
||||
ktx_uint8_t pad[4] = { 0, 0, 0, 0 };
|
||||
for (row = 0; row < numRows; row++) {
|
||||
ktx_uint32_t rowOffset = rowBytes * row;
|
||||
src->read(src, dst + rowOffset, packedRowBytes);
|
||||
memcpy(dst + rowOffset + packedRowBytes, pad, rowPadding);
|
||||
}
|
||||
}
|
||||
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
||||
/*
|
||||
* When KTX_GL_UNPACK_ALIGNMENT == 4, rows, and therefore everything else,
|
||||
* are always 4-byte aligned and faceLodPadding is always 0. It is always
|
||||
* 0 for compressed formats too because they all have multiple-of-4 block
|
||||
* sizes.
|
||||
*/
|
||||
if (faceLodPadding)
|
||||
memcpy(This->pData + faceLodSize, pad, faceLodPadding);
|
||||
#endif
|
||||
return KTX_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Set image for level, layer, faceSlice from a stdio stream source.
|
||||
*
|
||||
* Uncompressed images read from the stream are expected to have their rows
|
||||
* tightly packed as is the norm for most image file formats. The copied image
|
||||
* is padded as necessary to achieve the KTX-specified row alignment. No
|
||||
* padding is done if the ktxTexture's @c isCompressed field is @c KTX_TRUE.
|
||||
*
|
||||
* Level, layer, faceSlice rather than offset are specified to enable some
|
||||
* validation.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] level mip level of the image to set.
|
||||
* @param[in] layer array layer of the image to set.
|
||||
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
||||
* @param[in] src stdio stream pointer to the source.
|
||||
* @param[in] srcSize size of the source image in bytes.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
||||
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
||||
* specified level, layer & faceSlice.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* No storage was allocated when the texture was
|
||||
* created.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_SetImageFromStdioStream(ktxTexture1* This, ktx_uint32_t level,
|
||||
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
||||
FILE* src, ktx_size_t srcSize)
|
||||
{
|
||||
ktxStream srcstr;
|
||||
KTX_error_code result;
|
||||
|
||||
result = ktxFileStream_construct(&srcstr, src, KTX_FALSE);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
result = ktxTexture1_setImageFromStream(This, level, layer, faceSlice,
|
||||
&srcstr, srcSize);
|
||||
ktxFileStream_destruct(&srcstr);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Set image for level, layer, faceSlice from an image in memory.
|
||||
*
|
||||
* Uncompressed images in memory are expected to have their rows tightly packed
|
||||
* as is the norm for most image file formats. The copied image is padded as
|
||||
* necessary to achieve the KTX-specified row alignment. No padding is done if
|
||||
* the ktxTexture's @c isCompressed field is @c KTX_TRUE.
|
||||
*
|
||||
* Level, layer, faceSlice rather than offset are specified to enable some
|
||||
* validation.
|
||||
*
|
||||
* @warning Do not use @c memcpy for this as it will not pad when necessary.
|
||||
*
|
||||
* @note The caller is responsible for freeing the original image memory
|
||||
* referred to by @p src.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] level mip level of the image to set.
|
||||
* @param[in] layer array layer of the image to set.
|
||||
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
||||
* @param[in] src pointer to the image source in memory.
|
||||
* @param[in] srcSize size of the source image in bytes.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
||||
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
||||
* specified level, layer & faceSlice.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* No storage was allocated when the texture was
|
||||
* created.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_SetImageFromMemory(ktxTexture1* This, ktx_uint32_t level,
|
||||
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
||||
const ktx_uint8_t* src, ktx_size_t srcSize)
|
||||
{
|
||||
ktxStream srcstr;
|
||||
KTX_error_code result;
|
||||
|
||||
result = ktxMemStream_construct_ro(&srcstr, src, srcSize);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
result = ktxTexture1_setImageFromStream(This, level, layer, faceSlice,
|
||||
&srcstr, srcSize);
|
||||
ktxMemStream_destruct(&srcstr);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a ktxStream in KTX format.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dststr destination ktxStream.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dststr is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* Both kvDataHead and kvData are set in the
|
||||
* ktxTexture
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteToStream(ktxTexture1* This, ktxStream* dststr)
|
||||
{
|
||||
KTX_header header = { .identifier = KTX_IDENTIFIER_REF };
|
||||
KTX_error_code result = KTX_SUCCESS;
|
||||
ktx_uint8_t* pKvd = 0;
|
||||
ktx_uint32_t level, levelOffset;
|
||||
|
||||
if (!dststr) {
|
||||
return KTX_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (This->pData == NULL)
|
||||
return KTX_INVALID_OPERATION;
|
||||
|
||||
if (This->kvDataHead && This->kvData)
|
||||
return KTX_INVALID_OPERATION;
|
||||
|
||||
//endianess int.. if this comes out reversed, all of the other ints will too.
|
||||
header.endianness = KTX_ENDIAN_REF;
|
||||
header.glInternalformat = This->glInternalformat;
|
||||
header.glFormat = This->glFormat;
|
||||
header.glBaseInternalformat = This->glBaseInternalformat;
|
||||
header.glType = This->glType;
|
||||
header.glTypeSize = ktxTexture1_glTypeSize(This);
|
||||
header.pixelWidth = This->baseWidth;
|
||||
header.pixelHeight = This->numDimensions > 1 ? This->baseHeight : 0;
|
||||
header.pixelDepth = This->numDimensions > 2 ? This->baseDepth : 0;
|
||||
header.numberOfArrayElements = This->isArray ? This->numLayers : 0;
|
||||
assert (This->isCubemap ? This->numFaces == 6 : This->numFaces == 1);
|
||||
header.numberOfFaces = This->numFaces;
|
||||
assert (This->generateMipmaps ? This->numLevels == 1 : This->numLevels >= 1);
|
||||
header.numberOfMipLevels = This->generateMipmaps ? 0 : This->numLevels;
|
||||
|
||||
if (This->kvDataHead != NULL) {
|
||||
ktxHashList_Serialize(&This->kvDataHead,
|
||||
&header.bytesOfKeyValueData, &pKvd);
|
||||
} else if (This->kvData) {
|
||||
pKvd = This->kvData;
|
||||
header.bytesOfKeyValueData = This->kvDataLen;
|
||||
} else {
|
||||
header.bytesOfKeyValueData = 0;
|
||||
}
|
||||
|
||||
//write header
|
||||
result = dststr->write(dststr, &header, sizeof(KTX_header), 1);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
|
||||
//write keyValueData
|
||||
if (header.bytesOfKeyValueData != 0) {
|
||||
assert(pKvd != NULL);
|
||||
|
||||
result = dststr->write(dststr, pKvd, 1, header.bytesOfKeyValueData);
|
||||
if (This->kvDataHead != NULL)
|
||||
free(pKvd);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Write the image data */
|
||||
for (level = 0, levelOffset=0; level < This->numLevels; ++level)
|
||||
{
|
||||
ktx_uint32_t faceLodSize, layer, levelDepth, numImages;
|
||||
ktx_size_t imageSize;
|
||||
|
||||
faceLodSize = (ktx_uint32_t)ktxTexture_doCalcFaceLodSize(ktxTexture(This),
|
||||
level,
|
||||
KTX_FORMAT_VERSION_ONE);
|
||||
imageSize = ktxTexture_GetImageSize(ktxTexture(This), level);
|
||||
levelDepth = MAX(1, This->baseDepth >> level);
|
||||
if (This->isCubemap && !This->isArray)
|
||||
numImages = This->numFaces;
|
||||
else
|
||||
numImages = This->isCubemap ? This->numFaces : levelDepth;
|
||||
|
||||
result = dststr->write(dststr, &faceLodSize, sizeof(faceLodSize), 1);
|
||||
if (result != KTX_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
for (layer = 0; layer < This->numLayers; layer++) {
|
||||
ktx_uint32_t faceSlice;
|
||||
|
||||
for (faceSlice = 0; faceSlice < numImages; faceSlice++) {
|
||||
result = dststr->write(dststr, This->pData + levelOffset,
|
||||
imageSize, 1);
|
||||
levelOffset += (ktx_uint32_t)imageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a stdio stream in KTX format.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dstsstr destination stdio stream.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dstsstr is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* Both kvDataHead and kvData are set in the
|
||||
* ktxTexture
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteToStdioStream(ktxTexture1* This, FILE* dstsstr)
|
||||
{
|
||||
ktxStream stream;
|
||||
KTX_error_code result = KTX_SUCCESS;
|
||||
|
||||
if (!This)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
result = ktxFileStream_construct(&stream, dstsstr, KTX_FALSE);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
|
||||
return ktxTexture1_WriteToStream(This, &stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a named file in KTX format.
|
||||
*
|
||||
* The file name must be encoded in utf-8. On Windows convert unicode names
|
||||
* to utf-8 with @c WideCharToMultiByte(CP_UTF8, ...) before calling.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dstname destination file name.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dstname is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* Both kvDataHead and kvData are set in the
|
||||
* ktxTexture
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteToNamedFile(ktxTexture1* This, const char* const dstname)
|
||||
{
|
||||
KTX_error_code result;
|
||||
FILE* dst;
|
||||
|
||||
if (!This)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
dst = ktxFOpenUTF8(dstname, "wb");
|
||||
if (dst) {
|
||||
result = ktxTexture1_WriteToStdioStream(This, dst);
|
||||
fclose(dst);
|
||||
} else
|
||||
result = KTX_FILE_OPEN_FAILED;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to block of memory in KTX format.
|
||||
*
|
||||
* Memory is allocated by the function and the caller is responsible for
|
||||
* freeing it.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in,out] ppDstBytes pointer to location to write the address of
|
||||
* the destination memory. The Application is
|
||||
* responsible for freeing this memory.
|
||||
* @param[in,out] pSize pointer to location to write the size in bytes of
|
||||
* the KTX data.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This, @p ppDstBytes or @p pSize is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* Both kvDataHead and kvData are set in the
|
||||
* ktxTexture
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteToMemory(ktxTexture1* This,
|
||||
ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
|
||||
{
|
||||
struct ktxStream dststr;
|
||||
KTX_error_code result;
|
||||
ktx_size_t strSize;
|
||||
|
||||
if (!This || !ppDstBytes || !pSize)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
*ppDstBytes = NULL;
|
||||
|
||||
result = ktxMemStream_construct(&dststr, KTX_FALSE);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
|
||||
result = ktxTexture1_WriteToStream(This, &dststr);
|
||||
if(result != KTX_SUCCESS)
|
||||
{
|
||||
ktxMemStream_destruct(&dststr);
|
||||
return result;
|
||||
}
|
||||
|
||||
ktxMemStream_getdata(&dststr, ppDstBytes);
|
||||
dststr.getsize(&dststr, &strSize);
|
||||
*pSize = (GLsizei)strSize;
|
||||
/* This function does not free the memory pointed at by the
|
||||
* value obtained from ktxMemStream_getdata() thanks to the
|
||||
* KTX_FALSE passed to the constructor above.
|
||||
*/
|
||||
ktxMemStream_destruct(&dststr);
|
||||
return KTX_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
ktx_uint32_t lcm4(uint32_t a);
|
||||
KTX_error_code appendLibId(ktxHashList* head,
|
||||
ktxHashListEntry* writerEntry);
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a ktxStream in KTX 2 format.
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dststr destination ktxStream.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dststr is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture contains unknownY KTX- or ktx-
|
||||
* prefixed metadata keys.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The length of the already set writerId metadata
|
||||
* plus the library's version id exceeds the
|
||||
* maximum allowed.
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteKTX2ToStream(ktxTexture1* This, ktxStream* dststr)
|
||||
{
|
||||
KTX_header2 header = { .identifier = KTX2_IDENTIFIER_REF };
|
||||
KTX_error_code result;
|
||||
ktx_uint32_t kvdLen;
|
||||
ktx_uint8_t* pKvd;
|
||||
ktx_uint32_t initialLevelPadLen;
|
||||
ktxLevelIndexEntry* levelIndex = NULL;
|
||||
ktx_uint32_t levelIndexSize;
|
||||
ktx_uint32_t offset;
|
||||
ktx_uint32_t requiredLevelAlignment;
|
||||
|
||||
if (!dststr) {
|
||||
return KTX_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (This->pData == NULL)
|
||||
return KTX_INVALID_OPERATION;
|
||||
|
||||
header.vkFormat
|
||||
= vkGetFormatFromOpenGLInternalFormat(This->glInternalformat);
|
||||
// The above function does not return any formats in the prohibited list.
|
||||
if (header.vkFormat == VK_FORMAT_UNDEFINED) {
|
||||
// XXX TODO. Handle ASTC HDR & 3D.
|
||||
return KTX_UNSUPPORTED_TEXTURE_TYPE;
|
||||
}
|
||||
header.typeSize = ktxTexture1_glTypeSize(This);
|
||||
header.pixelWidth = This->baseWidth;
|
||||
header.pixelHeight = This->numDimensions > 1 ? This->baseHeight : 0;
|
||||
header.pixelDepth = This->numDimensions > 2 ? This->baseDepth : 0;
|
||||
header.layerCount = This->isArray ? This->numLayers : 0;
|
||||
assert (This->isCubemap ? This->numFaces == 6 : This->numFaces == 1);
|
||||
header.faceCount = This->numFaces;
|
||||
assert (This->generateMipmaps? This->numLevels == 1 : This->numLevels >= 1);
|
||||
header.levelCount = This->generateMipmaps ? 0 : This->numLevels;
|
||||
|
||||
levelIndexSize = sizeof(ktxLevelIndexEntry) * This->numLevels;
|
||||
levelIndex = (ktxLevelIndexEntry*) malloc(levelIndexSize);
|
||||
|
||||
offset = sizeof(header) + levelIndexSize;
|
||||
|
||||
ktx_uint32_t* dfd = vk2dfd(header.vkFormat);
|
||||
if (!dfd) {
|
||||
result = KTX_UNSUPPORTED_TEXTURE_TYPE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
header.dataFormatDescriptor.byteOffset = offset;
|
||||
header.dataFormatDescriptor.byteLength = *dfd;
|
||||
offset += header.dataFormatDescriptor.byteLength;
|
||||
|
||||
ktxHashListEntry* pEntry;
|
||||
// Check for invalid metadata.
|
||||
for (pEntry = This->kvDataHead; pEntry != NULL; pEntry = ktxHashList_Next(pEntry)) {
|
||||
unsigned int keyLen;
|
||||
char* key;
|
||||
|
||||
ktxHashListEntry_GetKey(pEntry, &keyLen, &key);
|
||||
if (strncasecmp(key, "KTX", 3) == 0) {
|
||||
// N.B. Writer metadata is not legal in a KTX v1 file but we know
|
||||
// we're going to write this out as a v2 file so we allow it so
|
||||
// conversion apps can identify themselves.
|
||||
if (strcmp(key, KTX_ORIENTATION_KEY) && strcmp(key, KTX_WRITER_KEY)) {
|
||||
result = KTX_INVALID_OPERATION;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = ktxHashList_FindEntry(&This->kvDataHead, KTX_ORIENTATION_KEY,
|
||||
&pEntry);
|
||||
// Rewrite the orientation value in the KTX2 form.
|
||||
if (result == KTX_SUCCESS) {
|
||||
unsigned int count;
|
||||
char* orientation;
|
||||
ktx_uint32_t orientationLen;
|
||||
char newOrient[4] = {0, 0, 0, 0};
|
||||
|
||||
result = ktxHashListEntry_GetValue(pEntry,
|
||||
&orientationLen, (void**)&orientation);
|
||||
count = sscanf(orientation, "S=%c,T=%c,R=%c",
|
||||
&newOrient[0],
|
||||
&newOrient[1],
|
||||
&newOrient[2]);
|
||||
|
||||
if (count < This->numDimensions) {
|
||||
// There needs to be an entry for each dimension of the texture.
|
||||
result = KTX_FILE_DATA_ERROR;
|
||||
goto cleanup;
|
||||
} else if (count > This->numDimensions) {
|
||||
// KTX 1 is less strict than KTX 2 so there is a chance of having
|
||||
// more dimensions than needed.
|
||||
count = This->numDimensions;
|
||||
newOrient[count] = '\0';
|
||||
}
|
||||
|
||||
ktxHashList_DeleteEntry(&This->kvDataHead, pEntry);
|
||||
ktxHashList_AddKVPair(&This->kvDataHead, KTX_ORIENTATION_KEY,
|
||||
count+1, newOrient);
|
||||
}
|
||||
pEntry = NULL;
|
||||
// See comment at valid metadata check at line 582.
|
||||
result = ktxHashList_FindEntry(&This->kvDataHead, KTX_WRITER_KEY,
|
||||
&pEntry);
|
||||
result = appendLibId(&This->kvDataHead, pEntry);
|
||||
if (result != KTX_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
ktxHashList_Sort(&This->kvDataHead); // KTX2 requires sorted metadata.
|
||||
ktxHashList_Serialize(&This->kvDataHead, &kvdLen, &pKvd);
|
||||
header.keyValueData.byteOffset = kvdLen != 0 ? offset : 0;
|
||||
header.keyValueData.byteLength = kvdLen;
|
||||
offset += kvdLen;
|
||||
|
||||
header.supercompressionGlobalData.byteOffset = 0;
|
||||
header.supercompressionGlobalData.byteLength = 0;
|
||||
|
||||
requiredLevelAlignment
|
||||
= lcm4(This->_protected->_formatSize.blockSizeInBits / 8);
|
||||
initialLevelPadLen = _KTX_PADN_LEN(requiredLevelAlignment, offset);
|
||||
offset += initialLevelPadLen;
|
||||
|
||||
for (ktx_int32_t level = This->numLevels - 1; level >= 0; level--) {
|
||||
ktx_size_t levelSize =
|
||||
ktxTexture_calcLevelSize(ktxTexture(This), level,
|
||||
KTX_FORMAT_VERSION_TWO);
|
||||
|
||||
levelIndex[level].uncompressedByteLength = levelSize;
|
||||
levelIndex[level].byteLength = levelSize;
|
||||
levelIndex[level].byteOffset = offset;
|
||||
offset += _KTX_PADN(requiredLevelAlignment, levelSize);
|
||||
}
|
||||
|
||||
// write header and indices
|
||||
result = dststr->write(dststr, &header, sizeof(header), 1);
|
||||
if (result != KTX_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
// write level index
|
||||
result = dststr->write(dststr, levelIndex, levelIndexSize, 1);
|
||||
if (result != KTX_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
// write data format descriptor
|
||||
result = dststr->write(dststr, dfd, 1, *dfd);
|
||||
|
||||
// write keyValueData
|
||||
if (kvdLen != 0) {
|
||||
assert(pKvd != NULL);
|
||||
|
||||
result = dststr->write(dststr, pKvd, 1, kvdLen);
|
||||
free(pKvd);
|
||||
if (result != KTX_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
char padding[32] = { 0 };
|
||||
// write supercompressionGlobalData & sgdPadding
|
||||
|
||||
if (initialLevelPadLen) {
|
||||
result = dststr->write(dststr, padding, 1, initialLevelPadLen);
|
||||
if (result != KTX_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the image data
|
||||
for (ktx_int32_t level = This->numLevels - 1;
|
||||
level >= 0 && result == KTX_SUCCESS; --level)
|
||||
{
|
||||
//ktx_uint64_t faceLodSize;
|
||||
ktx_uint32_t layer, levelDepth, numImages;
|
||||
ktx_uint32_t srcLevelOffset, srcOffset;
|
||||
ktx_size_t imageSize, dstLevelSize = 0;
|
||||
#define DUMP_IMAGE 0
|
||||
#if defined(DEBUG) || DUMP_IMAGE
|
||||
ktx_size_t pos;
|
||||
#endif
|
||||
imageSize = ktxTexture_calcImageSize(ktxTexture(This), level,
|
||||
KTX_FORMAT_VERSION_TWO);
|
||||
#if defined(DEBUG)
|
||||
result = dststr->getpos(dststr, (ktx_off_t*)&pos);
|
||||
// Could fail if stdout is a pipe
|
||||
if (result == KTX_SUCCESS)
|
||||
assert(pos == levelIndex[level].byteOffset);
|
||||
else
|
||||
assert(result == KTX_FILE_ISPIPE);
|
||||
#endif
|
||||
|
||||
levelDepth = MAX(1, This->baseDepth >> level);
|
||||
if (This->isCubemap && !This->isArray)
|
||||
numImages = This->numFaces;
|
||||
else
|
||||
numImages = This->isCubemap ? This->numFaces : levelDepth;
|
||||
|
||||
ktx_uint32_t numRows = 0, rowBytes = 0, rowPadding = 0;
|
||||
if (!This->isCompressed) {
|
||||
ktxTexture_rowInfo(ktxTexture(This), level, &numRows, &rowBytes,
|
||||
&rowPadding);
|
||||
}
|
||||
srcLevelOffset = (ktx_uint32_t)ktxTexture_calcLevelOffset(
|
||||
ktxTexture(This),
|
||||
level);
|
||||
srcOffset = srcLevelOffset;
|
||||
for (layer = 0; layer < This->numLayers; layer++) {
|
||||
ktx_uint32_t faceSlice;
|
||||
|
||||
for (faceSlice = 0; faceSlice < numImages; faceSlice++) {
|
||||
#if DUMP_IMAGE
|
||||
dststr->getsize(dststr, &pos);
|
||||
fprintf(stdout, "Writing level %d, layer %d, faceSlice %d to offset %#zx\n",
|
||||
level, layer, faceSlice, pos);
|
||||
#endif
|
||||
if (rowPadding == 0) {
|
||||
#if DUMP_IMAGE
|
||||
if (!This->isCompressed)
|
||||
for (uint32_t y = 0; y < (This->baseHeight >> level); y++) {
|
||||
for (uint32_t x = 0; x < rowBytes; x++) {
|
||||
fprintf(stdout, "%#x, ",
|
||||
*(This->pData + srcOffset + y * rowBytes + x));
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
#endif
|
||||
// Write entire image.
|
||||
result = dststr->write(dststr, This->pData + srcOffset,
|
||||
imageSize, 1);
|
||||
dstLevelSize += imageSize;
|
||||
} else {
|
||||
/* Copy the rows individually, removing padding. */
|
||||
ktx_uint32_t row;
|
||||
ktx_uint8_t* src = This->pData + srcOffset;
|
||||
ktx_uint32_t packedRowBytes = rowBytes - rowPadding;
|
||||
for (row = 0; row < numRows; row++) {
|
||||
ktx_uint32_t rowOffset = rowBytes * row;
|
||||
#if DUMP_IMAGE
|
||||
for (uint32_t i = 0; i < packedRowBytes; i++)
|
||||
fprintf(stdout, "%#x, ", *(src + rowOffset + i));
|
||||
#endif
|
||||
result = dststr->write(dststr, src + rowOffset,
|
||||
packedRowBytes, 1);
|
||||
dstLevelSize += packedRowBytes;
|
||||
}
|
||||
}
|
||||
#if DUMP_IMAGE
|
||||
fprintf(stdout, "\n");
|
||||
#endif
|
||||
srcOffset += (ktx_uint32_t)imageSize;
|
||||
}
|
||||
}
|
||||
if (result == KTX_SUCCESS && level != 0) {
|
||||
uint32_t levelPadLen = _KTX_PADN_LEN(requiredLevelAlignment,
|
||||
dstLevelSize);
|
||||
if (levelPadLen)
|
||||
result = dststr->write(dststr, padding, 1, levelPadLen);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free(dfd);
|
||||
free(levelIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a stdio stream in KTX2 format.
|
||||
*
|
||||
* Callers are strongly urged to include a KTXwriter item in the texture's metadata.
|
||||
* It can be added by code, similar to the following, prior to calling this
|
||||
* function.
|
||||
* @code
|
||||
* char writer[100];
|
||||
* snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
|
||||
* ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
|
||||
* (ktx_uint32_t)strlen(writer) + 1,
|
||||
* writer);
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dstsstr destination stdio stream.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dstsstr is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture contains unknownY KTX- or ktx-
|
||||
* prefixed metadata keys.
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteKTX2ToStdioStream(ktxTexture1* This, FILE* dstsstr)
|
||||
{
|
||||
ktxStream stream;
|
||||
KTX_error_code result = KTX_SUCCESS;
|
||||
|
||||
if (!This)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
result = ktxFileStream_construct(&stream, dstsstr, KTX_FALSE);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
|
||||
return ktxTexture1_WriteKTX2ToStream(This, &stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to a named file in KTX2 format.
|
||||
*
|
||||
* The file name must be encoded in utf-8. On Windows convert unicode names
|
||||
* to utf-8 with @c WideCharToMultiByte(CP_UTF8, ...) before calling.
|
||||
*
|
||||
* Callers are strongly urged to include a KTXwriter item in the texture's
|
||||
* metadata. It can be added by code, similar to the following, prior to
|
||||
* calling this function.
|
||||
* @code
|
||||
* char writer[100];
|
||||
* snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
|
||||
* ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
|
||||
* (ktx_uint32_t)strlen(writer) + 1,
|
||||
* writer);
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in] dstname destination file name.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This or @p dstname is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture contains unknownY KTX- or ktx-
|
||||
* prefixed metadata keys.
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteKTX2ToNamedFile(ktxTexture1* This, const char* const dstname)
|
||||
{
|
||||
KTX_error_code result;
|
||||
FILE* dst;
|
||||
|
||||
if (!This)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
dst = ktxFOpenUTF8(dstname, "wb");
|
||||
if (dst) {
|
||||
result = ktxTexture1_WriteKTX2ToStdioStream(This, dst);
|
||||
fclose(dst);
|
||||
} else
|
||||
result = KTX_FILE_OPEN_FAILED;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof ktxTexture1
|
||||
* @~English
|
||||
* @brief Write a ktxTexture object to block of memory in KTX2 format.
|
||||
*
|
||||
* Memory is allocated by the function and the caller is responsible for
|
||||
* freeing it.
|
||||
*
|
||||
* Callers are strongly urged to include a KTXwriter item in the texture's metadata.
|
||||
* It can be added by code, similar to the following, prior to calling this
|
||||
* function.
|
||||
* @code
|
||||
* char writer[100];
|
||||
* snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
|
||||
* ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
|
||||
* (ktx_uint32_t)strlen(writer) + 1,
|
||||
* writer);
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] This pointer to the target ktxTexture object.
|
||||
* @param[in,out] ppDstBytes pointer to location to write the address of
|
||||
* the destination memory. The Application is
|
||||
* responsible for freeing this memory.
|
||||
* @param[in,out] pSize pointer to location to write the size in bytes of
|
||||
* the KTX data.
|
||||
*
|
||||
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
||||
*
|
||||
* @exception KTX_INVALID_VALUE @p This, @p ppDstBytes or @p pSize is NULL.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture does not contain any image data.
|
||||
* @exception KTX_INVALID_OPERATION
|
||||
* The ktxTexture contains unknownY KTX- or ktx-
|
||||
* prefixed metadata keys.
|
||||
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
||||
* the system.
|
||||
* @exception KTX_FILE_WRITE_ERROR
|
||||
* An error occurred while writing the file.
|
||||
*/
|
||||
KTX_error_code
|
||||
ktxTexture1_WriteKTX2ToMemory(ktxTexture1* This,
|
||||
ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
|
||||
{
|
||||
struct ktxStream dststr;
|
||||
KTX_error_code result;
|
||||
ktx_size_t strSize;
|
||||
|
||||
if (!This || !ppDstBytes || !pSize)
|
||||
return KTX_INVALID_VALUE;
|
||||
|
||||
*ppDstBytes = NULL;
|
||||
|
||||
result = ktxMemStream_construct(&dststr, KTX_FALSE);
|
||||
if (result != KTX_SUCCESS)
|
||||
return result;
|
||||
|
||||
result = ktxTexture1_WriteKTX2ToStream(This, &dststr);
|
||||
if(result != KTX_SUCCESS)
|
||||
{
|
||||
ktxMemStream_destruct(&dststr);
|
||||
return result;
|
||||
}
|
||||
|
||||
ktxMemStream_getdata(&dststr, ppDstBytes);
|
||||
dststr.getsize(&dststr, &strSize);
|
||||
*pSize = (GLsizei)strSize;
|
||||
/* This function does not free the memory pointed at by the
|
||||
* value obtained from ktxMemStream_getdata() thanks to the
|
||||
* KTX_FALSE passed to the constructor above.
|
||||
*/
|
||||
ktxMemStream_destruct(&dststr);
|
||||
return KTX_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
Reference in New Issue
Block a user