plumageRender/external/ktx/lib/writer_v1.c

709 lines
26 KiB
C

/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/* $Id: 687889ad2b1bee58a6d439ef4d6c10830a733418 $ */
/*
* ©2010-2018 Mark Callow.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @internal
* @file writer.c
* @~English
*
* @brief V1 API functions for creating KTX-format files from a set of images.
*
* Keep the v1 API implementation as is because reimplementing it in terms
* of the v2 API would use too much memory. This is because this API expects
* all the images to already be loaded in memory and the v2 api would load
* them into another memory buffer prior to writing the file.
*
* @author Mark Callow, when at HI Corporation
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <limits.h>
#include "GL/glcorearb.h"
#include "ktx.h"
#include "ktxint.h"
#include "stream.h"
#include "filestream.h"
#include "memstream.h"
#include "gl_format.h"
static GLenum getFormatFromInternalFormatLegacy(GLenum internalFormat);
static GLenum getTypeFromInternalFormatLegacy(GLenum internalFormat);
static void getFormatSizeLegacy(GLenum internalFormat,
GlFormatSize* formatSize);
/**
* @internal
* @ingroup writer
* @~English
* @deprecated Use ktxTexture_writeToStream().
* @brief Write image(s) in a KTX-format to a ktxStream.
*
* @param [in] stream pointer to the ktxStream from which to load.
* @param [in] textureInfo pointer to a KTX_texture_info structure providing
* information about the images to be included in
* the KTX file.
* @param [in] bytesOfKeyValueData
* specifies the number of bytes of key-value data.
* @param [in] keyValueData a pointer to the keyValue data.
* @param [in] numImages number of images in the following array
* @param [in] images array of KTX_image_info providing image size and
* data.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
* 4 or is different from the size of the type
* specified in @c glType.
* @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
* pixelDepth != 0 && pixelHeight == 0.
* @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
* numberOfFaces != 6 or numberOfArrayElements
* or numberOfMipmapLevels are < 0.
* @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
* type.
* @exception KTX_INVALID_OPERATION
* In @p textureInfo, numberOfFaces == 6 and
* images are either not 2D or are not square.
* @exception KTX_INVALID_OPERATION
* @p numImages is insufficient for the specified
* number of mipmap levels and faces.
* @exception KTX_INVALID_OPERATION
* The size of a provided image is different than
* that required for the specified width, height
* or depth or for the mipmap level being
* processed.
* @exception KTX_INVALID_OPERATION
* @c glType and @c glFormat in @p textureInfo are
* mismatched. See OpenGL 4.4 specification
* section 8.4.4 and table 8.5.
* @exception KTX_FILE_WRITE_ERROR
* A system error occurred while writing the file.
* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
*/
static
KTX_error_code
ktxWriteKTXS(struct ktxStream *stream, const KTX_texture_info* textureInfo,
GLsizei bytesOfKeyValueData, const void* keyValueData,
GLuint numImages, KTX_image_info images[])
{
KTX_header header = KTX_IDENTIFIER_REF;
GLuint i, level, dimension, cubemap = 0;
GLuint numMipmapLevels, numArrayElements;
GLbyte pad[4] = { 0, 0, 0, 0 };
KTX_error_code result = KTX_SUCCESS;
GLboolean compressed = GL_FALSE;
GLuint groupBytes;
if (!stream) {
return KTX_INVALID_VALUE;
}
/* endianess int.. if this comes out reversed, all of the other ints will
* too.
*/
header.endianness = KTX_ENDIAN_REF;
header.glType = textureInfo->glType;
header.glTypeSize = textureInfo->glTypeSize;
header.glFormat = textureInfo->glFormat;
header.glInternalformat = textureInfo->glInternalFormat;
header.glBaseInternalformat = textureInfo->glBaseInternalFormat;
header.pixelWidth = textureInfo->pixelWidth;
header.pixelHeight = textureInfo->pixelHeight;
header.pixelDepth = textureInfo->pixelDepth;
header.numberOfArrayElements = textureInfo->numberOfArrayElements;
header.numberOfFaces = textureInfo->numberOfFaces;
header.numberOfMipmapLevels = textureInfo->numberOfMipmapLevels;
header.bytesOfKeyValueData = bytesOfKeyValueData;
/* Do some sanity checking */
if (header.glTypeSize != 1 &&
header.glTypeSize != 2 &&
header.glTypeSize != 4)
{
/* Only 8, 16, and 32-bit types are supported for byte-swapping.
* See UNPACK_SWAP_BYTES & table 8.4 in the OpenGL 4.4 spec.
*/
return KTX_INVALID_VALUE;
}
if (header.glType == 0 || header.glFormat == 0)
{
if (header.glType + header.glFormat != 0) {
/* either both or neither of glType & glFormat must be zero */
return KTX_INVALID_VALUE;
} else
compressed = GL_TRUE;
}
else
{
GlFormatSize formatInfo;
GLenum expectedFormat, expectedType;
expectedFormat = getFormatFromInternalFormatLegacy(header.glInternalformat);
if (expectedFormat == GL_INVALID_VALUE
|| expectedFormat != header.glFormat)
return KTX_INVALID_OPERATION;
expectedType = getTypeFromInternalFormatLegacy(header.glInternalformat);
if (expectedType == GL_INVALID_VALUE
|| expectedType != header.glType)
return KTX_INVALID_OPERATION;
getFormatSizeLegacy(header.glInternalformat, &formatInfo);
groupBytes = formatInfo.blockSizeInBits / CHAR_BIT;
}
/* Check texture dimensions. KTX files can store 8 types of textures:
* 1D, 2D, 3D, cube, and array variants of these. There is currently
* no GL extension that would accept 3D array array textures but we'll
* let such files be created.
*/
if ((header.pixelWidth == 0) ||
(header.pixelDepth > 0 && header.pixelHeight == 0))
{
/* texture must have width */
/* texture must have height if it has depth */
return KTX_INVALID_VALUE;
}
if (header.pixelHeight > 0 && header.pixelDepth > 0)
dimension = 3;
else if (header.pixelHeight > 0)
dimension = 2;
else
dimension = 1;
if (header.numberOfFaces != 1 && header.pixelDepth != 0)
{
/* No 3D cubemaps so either faces or depth must be 1. */
return KTX_INVALID_OPERATION;
}
if (header.numberOfFaces == 6)
{
if (dimension != 2)
{
/* cube map needs 2D faces */
return KTX_INVALID_OPERATION;
}
if (header.pixelWidth != header.pixelHeight)
{
/* cube maps require square images */
return KTX_INVALID_OPERATION;
}
}
else if (header.numberOfFaces != 1)
{
/* numberOfFaces must be either 1 or 6 */
return KTX_INVALID_VALUE;
}
if (header.numberOfArrayElements == 0)
numArrayElements = 1;
else
numArrayElements = header.numberOfArrayElements;
if (header.numberOfFaces == 6)
cubemap = 1;
/* Check number of mipmap levels */
if (header.numberOfMipmapLevels == 0)
{
numMipmapLevels = 1;
}
else
numMipmapLevels = header.numberOfMipmapLevels;
if (numMipmapLevels > 1) {
GLuint max_dim = MAX(MAX(header.pixelWidth, header.pixelHeight), header.pixelDepth);
if (max_dim < ((GLuint)1 << (header.numberOfMipmapLevels - 1)))
{
/* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
return KTX_INVALID_VALUE;
}
}
if (numImages < numMipmapLevels * header.numberOfFaces)
{
/* Not enough images */
return KTX_INVALID_OPERATION;
}
//write header
result = stream->write(stream, &header, sizeof(KTX_header), 1);
if (result != KTX_SUCCESS)
return result;
//write keyValueData
if (bytesOfKeyValueData != 0) {
if (keyValueData == NULL)
return KTX_INVALID_OPERATION;
result = stream->write(stream, keyValueData, 1, bytesOfKeyValueData);
if (result != KTX_SUCCESS)
return result;
}
/* Write the image data */
for (level = 0, i = 0; level < numMipmapLevels; ++level)
{
GLuint faceSlice, faceLodSize;
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
GLuint faceLodPadding;
#endif
GLuint pixelWidth, pixelHeight, pixelDepth;
GLsizei imageBytes, packedImageBytes;
GLsizei packedRowBytes, rowBytes, rowPadding;
GLuint numImages;
pixelWidth = MAX(1, header.pixelWidth >> level);
pixelHeight = MAX(1, header.pixelHeight >> level);
pixelDepth = MAX(1, header.pixelDepth >> level);
/* Calculate face sizes for this LoD based on glType, glFormat, width & height */
packedImageBytes = groupBytes
* pixelWidth
* pixelHeight;
rowPadding = 0;
packedRowBytes = groupBytes * pixelWidth;
/* KTX format specifies UNPACK_ALIGNMENT==4 */
/* GL spec: rows are not to be padded when elementBytes != 1, 2, 4 or 8.
* As GL currently has no such elements, no test is necessary.
*/
if (!compressed) {
rowBytes = _KTX_PAD_UNPACK_ALIGN(packedRowBytes);
rowPadding = rowBytes - packedRowBytes;
}
if (rowPadding == 0) {
imageBytes = packedImageBytes;
} else {
/* Need to pad the rows to meet the required UNPACK_ALIGNMENT */
imageBytes = rowBytes * pixelHeight;
}
if (textureInfo->numberOfArrayElements == 0 && cubemap) {
/* Non-array cubemap. */
numImages = 6;
faceLodSize = imageBytes;
} else {
numImages = cubemap ? 6 : pixelDepth;
numImages *= numArrayElements;
faceLodSize = imageBytes * numImages;
}
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
faceLodPadding = _KTX_PAD4_LEN(faceLodSize);
#endif
result = stream->write(stream, &faceLodSize, sizeof(faceLodSize), 1);
if (result != KTX_SUCCESS)
goto cleanup;
for (faceSlice = 0; faceSlice < numImages; ++faceSlice, ++i) {
if (!compressed) {
/* Sanity check. */
if (images[i].size != packedImageBytes) {
result = KTX_INVALID_OPERATION;
goto cleanup;
}
}
if (rowPadding == 0) {
/* Can write whole face at once */
result = stream->write(stream, images[i].data, images[i].size,
1);
if (result != KTX_SUCCESS)
goto cleanup;
} else {
/* Write the rows individually, padding each one */
GLuint row;
GLuint numRows = pixelHeight;
for (row = 0; row < numRows; row++) {
result = stream->write(stream,
&images[i].data[row*packedRowBytes],
packedRowBytes, 1);
if (result != KTX_SUCCESS)
goto cleanup;
result = stream->write(stream, pad, sizeof(GLbyte),
rowPadding);
if (result != KTX_SUCCESS)
goto cleanup;
}
}
#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) {
result = stream->write(stream, pad, sizeof(GLbyte),
faceLodPadding);
if (result != KTX_SUCCESS)
goto cleanup;
}
#endif
}
}
cleanup:
return result;
}
/**
* @~English
* @ingroup writer
* @deprecated Use ktxTexture_WriteToStdioStream().
* @brief Write image(s) in KTX format to a stdio FILE stream.
*
* @note textureInfo directly reflects what is written to the KTX file
* header. That is @c numberOfArrayElements should be 0 for non arrays;
* @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
* @c format & @c typesize should be 0 for compressed textures.
*
* @param[in] file pointer to the FILE stream to write to.
* @param[in] textureInfo pointer to a KTX_texture_info structure providing
* information about the images to be included in
* the KTX file.
* @param[in] bytesOfKeyValueData
* specifies the number of bytes of key-value data.
* @param[in] keyValueData a pointer to the keyValue data.
* @param[in] numImages number of images in the following array
* @param[in] images array of KTX_image_info providing image size and
* data.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
* 4 or is different from the size of the type
* specified in @c glType.
* @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
* pixelDepth != 0 && pixelHeight == 0.
* @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
* numberOfFaces != 6 or numberOfArrayElements
* or numberOfMipmapLevels are < 0.
* @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
* type.
* @exception KTX_INVALID_OPERATION
* In @p textureInfo, numberOfFaces == 6 and
* images are either not 2D or are not square.
* @exception KTX_INVALID_OPERATION
* @p numImages is insufficient for the specified
* number of mipmap levels and faces.
* @exception KTX_INVALID_OPERATION
* The size of a provided image is different than
* that required for the specified width, height
* or depth or for the mipmap level being
* processed.
* @exception KTX_INVALID_OPERATION
* @c glType and @c glFormat in @p textureInfo are
* mismatched. See OpenGL 4.4 specification
* section 8.4.4 and table 8.5.
* @exception KTX_FILE_WRITE_ERROR
* A system error occurred while writing the file.
* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
*/
KTX_error_code
ktxWriteKTXF(FILE *file, const KTX_texture_info* textureInfo,
GLsizei bytesOfKeyValueData, const void* keyValueData,
GLuint numImages, KTX_image_info images[])
{
struct ktxStream stream;
KTX_error_code result;
result = ktxFileStream_construct(&stream, file, KTX_FALSE);
if (result != KTX_SUCCESS)
return result;
result = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData,
numImages, images);
stream.destruct(&stream);
return result;
}
/**
* @~English
* @ingroup writer
* @deprecated Use ktxTexture_WriteToNamedFile().
* @brief Write image(s) in KTX format to a file on disk.
*
* @note textureInfo directly reflects what is written to the KTX file
* header. That is @c numberOfArrayElements should be 0 for non arrays;
* @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
* @c format & @c typesize should be 0 for compressed textures.
*
* @param[in] dstname pointer to a C string that contains the path of
* the file to load.
* @param[in] textureInfo pointer to a KTX_texture_info structure providing
* information about the images to be included in
* the KTX file.
* @param[in] bytesOfKeyValueData
* specifies the number of bytes of key-value data.
* @param[in] keyValueData a pointer to the keyValue data.
* @param[in] numImages number of images in the following array.
* @param[in] images array of KTX_image_info providing image size and
* data.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_FILE_OPEN_FAILED Unable to open the specified file for
* writing.
*
* For other exceptions, see ktxWriteKTXF().
*/
KTX_error_code
ktxWriteKTXN(const char* dstname, const KTX_texture_info* textureInfo,
GLsizei bytesOfKeyValueData, const void* keyValueData,
GLuint numImages, KTX_image_info images[])
{
struct ktxStream stream;
KTX_error_code result;
FILE* dst = fopen(dstname, "wb");
if (dst) {
result = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData,
keyValueData, numImages, images);
fclose(dst);
} else
result = KTX_FILE_OPEN_FAILED;
return result;
}
/**
* @~English
* @ingroup writer
* @deprecated Use ktxTexture_WriteToMemory().
* @brief Write image(s) in KTX format to memory.
*
* Memory is allocated by the function and the caller is responsible for
* freeing it.
*
* @note textureInfo directly reflects what is written to the KTX file
* header. That is @c numberOfArrayElements should be 0 for non arrays;
* @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
* @c format & @c typesize should be 0 for compressed textures.
*
* @param[out] ppDstBytes pointer to location to write the address of
* the destination memory. The Application is
responsible for freeing this memory.
* @param[out] pSize pointer to location to write the size in bytes of
* the KTX data.
* @param[in] textureInfo pointer to a KTX_texture_info structure providing
* information about the images to be included in
* the KTX file.
* @param[in] bytesOfKeyValueData
* specifies the number of bytes of key-value data.
* @param[in] keyValueData a pointer to the keyValue data.
* @param[in] numImages number of images in the following array.
* @param[in] images array of KTX_image_info providing image size and
* data.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_VALUE @p dst or @p size is NULL.
* @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
* 4 or is different from the size of the type
* specified in @c glType.
* @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
* pixelDepth != 0 && pixelHeight == 0.
* @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
* numberOfFaces != 6 or numberOfArrayElements
* or numberOfMipmapLevels are < 0.
* @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
* type.
* @exception KTX_INVALID_OPERATION
* In @p textureInfo, numberOfFaces == 6 and
* images are either not 2D or are not square.
* @exception KTX_INVALID_OPERATION
* @p numImages is insufficient for the specified
* number of mipmap levels and faces.
* @exception KTX_INVALID_OPERATION
* The size of a provided image is different than
* that required for the specified width, height
* or depth or for the mipmap level being
* processed.
* @exception KTX_INVALID_OPERATION
* @c glType and @c glFormat in @p textureInfo are
* mismatched. See OpenGL 4.4 specification
* section 8.4.4 and table 8.5.
* @exception KTX_FILE_WRITE_ERROR
* A system error occurred while writing the file.
* @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
*/
KTX_error_code
ktxWriteKTXM(unsigned char** ppDstBytes, GLsizei* pSize,
const KTX_texture_info* textureInfo,
GLsizei bytesOfKeyValueData, const void* keyValueData,
GLuint numImages, KTX_image_info images[])
{
struct ktxStream stream;
KTX_error_code rc;
ktx_size_t strSize;
if (ppDstBytes == NULL || pSize == NULL)
return KTX_INVALID_VALUE;
*ppDstBytes = NULL;
rc = ktxMemStream_construct(&stream, KTX_FALSE);
if (rc != KTX_SUCCESS)
return rc;
rc = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData,
numImages, images);
if(rc != KTX_SUCCESS)
{
ktxMemStream_destruct(&stream);
return rc;
}
ktxMemStream_getdata(&stream, ppDstBytes);
stream.getsize(&stream, &strSize);
*pSize = (GLsizei)strSize;
/* This function does not free the memory pointed at by the
* value obtained from ktxMemStream_getdata().
*/
stream.destruct(&stream);
return KTX_SUCCESS;
}
/**
* @internal
* @~English
* @brief Get the matching format for an internalformat.
*
* Adds support for deprecated legacy formats needed to create textures for
* use with OpenGL ES 1 & 2 to glGetFormatFromInternalFormat.
*
* @param[in] internalFormat the internal format of the image data
*
* @return the matching glFormat enum or GL_INVALID_VALUE if format is
* unrecognized.
*/
GLenum
getFormatFromInternalFormatLegacy(GLenum internalFormat)
{
switch (internalFormat) {
case GL_LUMINANCE8:
case GL_LUMINANCE16:
return GL_LUMINANCE;
case GL_ALPHA8:
case GL_ALPHA16:
return GL_ALPHA;
case GL_LUMINANCE8_ALPHA8:
case GL_LUMINANCE16_ALPHA16:
return GL_LUMINANCE_ALPHA;
default:
return glGetFormatFromInternalFormat(internalFormat);
}
}
/**
* @internal
* @~English
* @brief Get the GL data type for an internalformat.
*
* Adds support for deprecated legacy formats needed to create textures for
* use with OpenGL ES 1 & 2 to glGetTypeFromInternalFormat.
*
* @param[in] internalFormat the internal format of the image data
*
* @return the matching glFormat enum or GL_INVALID_VALUE if format is
* unrecognized.
*/
GLenum
getTypeFromInternalFormatLegacy(GLenum internalFormat)
{
switch (internalFormat) {
case GL_LUMINANCE8:
case GL_ALPHA8:
return GL_UNSIGNED_BYTE;
case GL_LUMINANCE16:
case GL_ALPHA16:
case GL_LUMINANCE8_ALPHA8:
return GL_UNSIGNED_SHORT;
case GL_LUMINANCE16_ALPHA16:
return GL_UNSIGNED_INT;
default:
return glGetTypeFromInternalFormat(internalFormat);
}
}
/**
* @internal
* @~English
* @brief Get size information for an internalformat.
*
* Adds support for deprecated legacy formats needed to create textures for
* use with OpenGL ES 1 & 2 to glGetTypeFromInternalFormat.
*
* @param[in] internalFormat the internal format of the image data
* @param[in,out] formatSize pointer to a formatSize struct in which the
* information is returned.
*/
void
getFormatSizeLegacy(GLenum internalFormat, GlFormatSize* pFormatSize)
{
switch (internalFormat) {
case GL_LUMINANCE8:
case GL_ALPHA8:
pFormatSize->flags = 0;
pFormatSize->paletteSizeInBits = 0;
pFormatSize->blockSizeInBits = 1 * 8;
pFormatSize->blockWidth = 1;
pFormatSize->blockHeight = 1;
pFormatSize->blockDepth = 1;
break;
case GL_LUMINANCE16:
case GL_ALPHA16:
case GL_LUMINANCE8_ALPHA8:
pFormatSize->flags = 0;
pFormatSize->paletteSizeInBits = 0;
pFormatSize->blockSizeInBits = 2 * 8;
pFormatSize->blockWidth = 1;
pFormatSize->blockHeight = 1;
pFormatSize->blockDepth = 1;
break;
case GL_LUMINANCE16_ALPHA16:
pFormatSize->flags = 0;
pFormatSize->paletteSizeInBits = 0;
pFormatSize->blockSizeInBits = 4 * 8;
pFormatSize->blockWidth = 1;
pFormatSize->blockHeight = 1;
pFormatSize->blockDepth = 1;
break;
default:
glGetFormatSize(internalFormat, pFormatSize);
}
}