971 lines
37 KiB
C
971 lines
37 KiB
C
/* -*- tab-width: 4; -*- */
|
|
/* vi: set sw=2 ts=4: */
|
|
|
|
/* $Id: a1d73255a63f3e703c641f61bafba89453f05efd $ */
|
|
|
|
/**
|
|
* @file
|
|
* @~English
|
|
*
|
|
* @brief Functions for instantiating GL or GLES textures from KTX files.
|
|
*
|
|
* @author Georg Kolling, Imagination Technology
|
|
* @author Mark Callow, HI Corporation
|
|
*/
|
|
|
|
/*
|
|
Copyright (c) 2010 The Khronos Group Inc.
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and/or associated documentation files (the
|
|
"Materials"), to deal in the Materials without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Materials, and to
|
|
permit persons to whom the Materials are furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
unaltered in all copies or substantial portions of the Materials.
|
|
Any additions, deletions, or changes to the original source files
|
|
must be clearly indicated in accompanying documentation.
|
|
|
|
If only executable code is distributed, then the accompanying
|
|
documentation must state that "this software is based in part on the
|
|
work of the Khronos Group".
|
|
|
|
THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
|
*/
|
|
|
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "KHR/khrplatform.h"
|
|
#include "ktx.h"
|
|
#include "ktxint.h"
|
|
#include "ktxstream.h"
|
|
#include "ktxfilestream.h"
|
|
#include "ktxmemstream.h"
|
|
|
|
#include KTX_GLFUNCPTRS
|
|
|
|
DECLARE_GL_FUNCPTRS
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Additional contextProfile bit indicating an OpenGL ES context.
|
|
*
|
|
* This is the same value NVIDIA returns when using an OpenGL ES profile
|
|
* of their desktop drivers. However it is not specified in any official
|
|
* specification as OpenGL ES does not support the GL_CONTEXT_PROFILE_MASK
|
|
* query.
|
|
*/
|
|
#define _CONTEXT_ES_PROFILE_BIT 0x4
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @name Supported Sized Format Macros
|
|
*
|
|
* These macros describe values that may be used with the sizedFormats
|
|
* variable.
|
|
*/
|
|
/**@{*/
|
|
#define _NON_LEGACY_FORMATS 0x1 /*< @internal Non-legacy sized formats are supported. */
|
|
#define _LEGACY_FORMATS 0x2 /*< @internal Legacy sized formats are supported. */
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief All sized formats are supported
|
|
*/
|
|
#define _ALL_SIZED_FORMATS (_NON_LEGACY_FORMATS | _LEGACY_FORMATS)
|
|
#define _NO_SIZED_FORMATS 0 /*< @internal No sized formats are supported. */
|
|
/**@}*/
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief indicates the profile of the current context.
|
|
*/
|
|
static GLint contextProfile = 0;
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Indicates what sized texture formats are supported
|
|
* by the current context.
|
|
*/
|
|
static GLint sizedFormats = _ALL_SIZED_FORMATS;
|
|
static GLboolean supportsSwizzle = GL_TRUE;
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Indicates which R16 & RG16 formats are supported by the current context.
|
|
*/
|
|
static GLint R16Formats = _KTX_ALL_R16_FORMATS;
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Indicates if the current context supports sRGB textures.
|
|
*/
|
|
static GLboolean supportsSRGB = GL_TRUE;
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Indicates if the current context supports cube map arrays.
|
|
*/
|
|
static GLboolean supportsCubeMapArrays = GL_FALSE;
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Workaround mismatch of glGetString declaration and standard string
|
|
* function parameters.
|
|
*/
|
|
#define glGetString(x) (const char*)glGetString(x)
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Workaround mismatch of glGetStringi declaration and standard string
|
|
* function parameters.
|
|
*/
|
|
#define pfGlGetStringi(x,y) (const char*)pfGlGetStringi(x,y)
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Check for existence of OpenGL extension
|
|
*/
|
|
static GLboolean
|
|
hasExtension(const char* extension)
|
|
{
|
|
if (pfGlGetStringi == NULL) {
|
|
if (strstr(glGetString(GL_EXTENSIONS), extension) != NULL)
|
|
return GL_TRUE;
|
|
else
|
|
return GL_FALSE;
|
|
}
|
|
else {
|
|
int i, n;
|
|
|
|
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
|
for (i = 0; i < n; i++) {
|
|
if (strcmp(pfGlGetStringi(GL_EXTENSIONS, i), extension) == 0)
|
|
return GL_TRUE;
|
|
}
|
|
return GL_FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Discover the capabilities of the current GL context.
|
|
*
|
|
* Queries the context and sets several the following internal variables indicating
|
|
* the capabilities of the context:
|
|
*
|
|
* @li sizedFormats
|
|
* @li supportsSwizzle
|
|
* @li supportsSRGB
|
|
* @li b16Formats
|
|
*
|
|
*/
|
|
static void discoverContextCapabilities(void)
|
|
{
|
|
GLint majorVersion = 1;
|
|
GLint minorVersion = 0;
|
|
|
|
// Done here so things will work when GLEW, or equivalent, is being used
|
|
// and GL function names are defined as pointers. Initialization at
|
|
// declaration would happen before these pointers have been initialized.
|
|
INITIALIZE_GL_FUNCPTRS
|
|
|
|
if (strstr(glGetString(GL_VERSION), "GL ES") != NULL)
|
|
contextProfile = _CONTEXT_ES_PROFILE_BIT;
|
|
// MAJOR & MINOR only introduced in GL {,ES} 3.0
|
|
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
|
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
|
if (glGetError() != GL_NO_ERROR) {
|
|
// < v3.0; resort to the old-fashioned way.
|
|
if (contextProfile & _CONTEXT_ES_PROFILE_BIT)
|
|
sscanf(glGetString(GL_VERSION), "OpenGL ES %d.%d ",
|
|
&majorVersion, &minorVersion);
|
|
else
|
|
sscanf(glGetString(GL_VERSION), "OpenGL %d.%d ",
|
|
&majorVersion, &minorVersion);
|
|
}
|
|
if (contextProfile & _CONTEXT_ES_PROFILE_BIT) {
|
|
if (majorVersion < 3) {
|
|
supportsSwizzle = GL_FALSE;
|
|
sizedFormats = _NO_SIZED_FORMATS;
|
|
R16Formats = _KTX_NO_R16_FORMATS;
|
|
supportsSRGB = GL_FALSE;
|
|
} else {
|
|
sizedFormats = _NON_LEGACY_FORMATS;
|
|
if (hasExtension("GL_EXT_texture_cube_map_array")) {
|
|
supportsCubeMapArrays = GL_TRUE;
|
|
}
|
|
}
|
|
if (hasExtension("GL_OES_required_internalformat")) {
|
|
sizedFormats |= _ALL_SIZED_FORMATS;
|
|
}
|
|
// There are no OES extensions for sRGB textures or R16 formats.
|
|
} else {
|
|
// PROFILE_MASK was introduced in OpenGL 3.2.
|
|
// Profiles: CONTEXT_CORE_PROFILE_BIT 0x1, CONTEXT_COMPATIBILITY_PROFILE_BIT 0x2.
|
|
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &contextProfile);
|
|
if (glGetError() == GL_NO_ERROR) {
|
|
// >= 3.2
|
|
if (majorVersion == 3 && minorVersion < 3)
|
|
supportsSwizzle = GL_FALSE;
|
|
if ((contextProfile & GL_CONTEXT_CORE_PROFILE_BIT))
|
|
sizedFormats &= ~_LEGACY_FORMATS;
|
|
if (majorVersion >= 4)
|
|
supportsCubeMapArrays = GL_TRUE;
|
|
} else {
|
|
// < 3.2
|
|
contextProfile = GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
|
|
supportsSwizzle = GL_FALSE;
|
|
// sRGB textures introduced in 2.0
|
|
if (majorVersion < 2 && hasExtension("GL_EXT_texture_sRGB")) {
|
|
supportsSRGB = GL_FALSE;
|
|
}
|
|
// R{,G]16 introduced in 3.0; R{,G}16_SNORM introduced in 3.1.
|
|
if (majorVersion == 3) {
|
|
if (minorVersion == 0)
|
|
R16Formats &= ~_KTX_R16_FORMATS_SNORM;
|
|
} else if (strstr(glGetString(GL_EXTENSIONS), "GL_ARB_texture_rg") != NULL) {
|
|
R16Formats &= ~_KTX_R16_FORMATS_SNORM;
|
|
} else {
|
|
R16Formats = _KTX_NO_R16_FORMATS;
|
|
}
|
|
}
|
|
if (!supportsCubeMapArrays) {
|
|
if (hasExtension("GL_ARB_texture_cube_map_array")) {
|
|
supportsCubeMapArrays = GL_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if SUPPORT_LEGACY_FORMAT_CONVERSION
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Convert deprecated legacy-format texture to modern format.
|
|
*
|
|
* The function sets the GL_TEXTURE_SWIZZLEs necessary to get the same
|
|
* behavior as the legacy format.
|
|
*
|
|
* @param [in] target texture target on which the swizzle will
|
|
* be set.
|
|
* @param [in, out] pFormat pointer to variable holding the base format of the
|
|
* texture. The new base format is written here.
|
|
* @param [in, out] pInternalFormat pointer to variable holding the internalformat
|
|
* of the texture. The new internalformat is
|
|
* written here.
|
|
* @return void unrecognized formats will be passed on to OpenGL. Any loading error
|
|
* that arises will be handled in the usual way.
|
|
*/
|
|
static void convertFormat(GLenum target, GLenum* pFormat, GLenum* pInternalFormat) {
|
|
switch (*pFormat) {
|
|
case GL_ALPHA:
|
|
{
|
|
GLint swizzle[] = {GL_ZERO, GL_ZERO, GL_ZERO, GL_RED};
|
|
*pFormat = GL_RED;
|
|
glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
|
|
switch (*pInternalFormat) {
|
|
case GL_ALPHA:
|
|
case GL_ALPHA4:
|
|
case GL_ALPHA8:
|
|
*pInternalFormat = GL_R8;
|
|
break;
|
|
case GL_ALPHA12:
|
|
case GL_ALPHA16:
|
|
*pInternalFormat = GL_R16;
|
|
break;
|
|
}
|
|
}
|
|
case GL_LUMINANCE:
|
|
{
|
|
GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
|
|
*pFormat = GL_RED;
|
|
glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
|
|
switch (*pInternalFormat) {
|
|
case GL_LUMINANCE:
|
|
case GL_LUMINANCE4:
|
|
case GL_LUMINANCE8:
|
|
*pInternalFormat = GL_R8;
|
|
break;
|
|
case GL_LUMINANCE12:
|
|
case GL_LUMINANCE16:
|
|
*pInternalFormat = GL_R16;
|
|
break;
|
|
#if 0
|
|
// XXX Must avoid setting TEXTURE_SWIZZLE in these cases
|
|
// XXX Must manually swizzle.
|
|
case GL_SLUMINANCE:
|
|
case GL_SLUMINANCE8:
|
|
*pInternalFormat = GL_SRGB8;
|
|
break;
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
case GL_LUMINANCE_ALPHA:
|
|
{
|
|
GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_GREEN};
|
|
*pFormat = GL_RG;
|
|
glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
|
|
switch (*pInternalFormat) {
|
|
case GL_LUMINANCE_ALPHA:
|
|
case GL_LUMINANCE4_ALPHA4:
|
|
case GL_LUMINANCE6_ALPHA2:
|
|
case GL_LUMINANCE8_ALPHA8:
|
|
*pInternalFormat = GL_RG8;
|
|
break;
|
|
case GL_LUMINANCE12_ALPHA4:
|
|
case GL_LUMINANCE12_ALPHA12:
|
|
case GL_LUMINANCE16_ALPHA16:
|
|
*pInternalFormat = GL_RG16;
|
|
break;
|
|
#if 0
|
|
// XXX Must avoid setting TEXTURE_SWIZZLE in these cases
|
|
// XXX Must manually swizzle.
|
|
case GL_SLUMINANCE_ALPHA:
|
|
case GL_SLUMINANCE8_ALPHA8:
|
|
*pInternalFormat = GL_SRGB8_ALPHA8;
|
|
break;
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
case GL_INTENSITY:
|
|
{
|
|
GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_RED};
|
|
*pFormat = GL_RED;
|
|
glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
|
|
switch (*pInternalFormat) {
|
|
case GL_INTENSITY:
|
|
case GL_INTENSITY4:
|
|
case GL_INTENSITY8:
|
|
*pInternalFormat = GL_R8;
|
|
break;
|
|
case GL_INTENSITY12:
|
|
case GL_INTENSITY16:
|
|
*pInternalFormat = GL_R16;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif /* SUPPORT_LEGACY_FORMAT_CONVERSION */
|
|
|
|
|
|
/*
|
|
* @~English
|
|
* @brief Load a GL texture object from a ktxStream.
|
|
*
|
|
* The function sets the texture object's GL_TEXTURE_MAX_LEVEL parameter
|
|
* according to the number of levels in the ktxStream, provided the library
|
|
* has been compiled with a version of gl.h where GL_TEXTURE_MAX_LEVEL is
|
|
* defined.
|
|
*
|
|
* It will unpack compressed GL_ETC1_RGB8_OES and GL_ETC2_* format
|
|
* textures in software when the format is not supported by the GL context,
|
|
* provided the library has been compiled with SUPPORT_SOFTWARE_ETC_UNPACK
|
|
* defined as 1.
|
|
*
|
|
* It will also convert textures with legacy formats to their modern equivalents
|
|
* when the format is not supported by the GL context, provided the library
|
|
* has been compiled with SUPPORT_LEGACY_FORMAT_CONVERSION defined as 1.
|
|
*
|
|
* @param [in] stream pointer to the ktxStream from which to load.
|
|
* @param [in,out] pTexture name of the GL texture to load. If NULL or if
|
|
* <tt>*pTexture == 0</tt> the function will generate
|
|
* a texture name. The function binds either the
|
|
* generated name or the name given in @p *pTexture
|
|
* to the texture target returned in @p *pTarget,
|
|
* before loading the texture data. If @p pTexture
|
|
* is not NULL and a name was generated, the generated
|
|
* name will be returned in *pTexture.
|
|
* @param [out] pTarget @p *pTarget is set to the texture target used. The
|
|
* target is chosen based on the file contents.
|
|
* @param [out] pDimensions If @p pDimensions is not NULL, the width, height and
|
|
* depth of the texture's base level are returned in the
|
|
* fields of the KTX_dimensions structure to which it points.
|
|
* @param [out] pIsMipmapped
|
|
* If @p pIsMipmapped is not NULL, @p *pIsMipmapped is set
|
|
* to GL_TRUE if the KTX texture is mipmapped, GL_FALSE
|
|
* otherwise.
|
|
* @param [out] pGlerror @p *pGlerror is set to the value returned by
|
|
* glGetError when this function returns the error
|
|
* KTX_GL_ERROR. glerror can be NULL.
|
|
* @param [in,out] pKvdLen If not NULL, @p *pKvdLen is set to the number of bytes
|
|
* of key-value data pointed at by @p *ppKvd. Must not be
|
|
* NULL, if @p ppKvd is not NULL.
|
|
* @param [in,out] ppKvd If not NULL, @p *ppKvd is set to the point to a block of
|
|
* memory containing key-value data read from the file.
|
|
* The application is responsible for freeing the memory.
|
|
*
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p target is @c NULL or the size of a mip
|
|
* level is greater than the size of the
|
|
* preceding level.
|
|
* @exception KTX_INVALID_OPERATION @p ppKvd is not NULL but pKvdLen is NULL.
|
|
* @exception KTX_UNEXPECTED_END_OF_FILE the file does not contain the
|
|
* expected amount of data.
|
|
* @exception KTX_OUT_OF_MEMORY Sufficient memory could not be allocated to store
|
|
* the requested key-value data.
|
|
* @exception KTX_GL_ERROR A GL error was raised by glBindTexture,
|
|
* glGenTextures or gl*TexImage*. The GL error
|
|
* will be returned in @p *glerror, if glerror
|
|
* is not @c NULL.
|
|
*/
|
|
static
|
|
KTX_error_code
|
|
ktxLoadTextureS(struct ktxStream* stream, GLuint* pTexture, GLenum* pTarget,
|
|
KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
|
|
GLenum* pGlerror,
|
|
unsigned int* pKvdLen, unsigned char** ppKvd)
|
|
{
|
|
GLint previousUnpackAlignment;
|
|
KTX_header header;
|
|
KTX_texinfo texinfo;
|
|
void* data = NULL;
|
|
khronos_uint32_t dataSize = 0;
|
|
GLuint texname;
|
|
int texnameUser;
|
|
khronos_uint32_t faceLodSize;
|
|
khronos_uint32_t faceLodSizeRounded;
|
|
khronos_uint32_t level;
|
|
khronos_uint32_t face;
|
|
GLenum glFormat, glInternalFormat;
|
|
KTX_error_code errorCode = KTX_SUCCESS;
|
|
GLenum errorTmp;
|
|
|
|
if (pGlerror)
|
|
*pGlerror = GL_NO_ERROR;
|
|
|
|
if (ppKvd) {
|
|
*ppKvd = NULL;
|
|
}
|
|
|
|
if (!stream || !stream->read || !stream->skip) {
|
|
return KTX_INVALID_VALUE;
|
|
}
|
|
|
|
if (!pTarget) {
|
|
return KTX_INVALID_VALUE;
|
|
}
|
|
|
|
errorCode = stream->read(&header, KTX_HEADER_SIZE, stream->src);
|
|
if (errorCode != KTX_SUCCESS)
|
|
return errorCode;
|
|
|
|
errorCode = _ktxCheckHeader(&header, &texinfo);
|
|
if (errorCode != KTX_SUCCESS)
|
|
return errorCode;
|
|
|
|
//if (ppKvd) {
|
|
// if (pKvdLen == NULL)
|
|
// return KTX_INVALID_OPERATION;
|
|
// *pKvdLen = header.bytesOfKeyValueData;
|
|
// if (*pKvdLen) {
|
|
// *ppKvd = (unsigned char*)malloc(*pKvdLen);
|
|
// if (*ppKvd == NULL)
|
|
// return KTX_OUT_OF_MEMORY;
|
|
// errorCode = stream->read(*ppKvd, *pKvdLen, stream->src);
|
|
// if (errorCode != KTX_SUCCESS)
|
|
// {
|
|
// free(*ppKvd);
|
|
// *ppKvd = NULL;
|
|
//
|
|
// return errorCode;
|
|
// }
|
|
// }
|
|
//} else {
|
|
/* skip key/value metadata */
|
|
errorCode = stream->skip(header.bytesOfKeyValueData, stream->src);
|
|
if (errorCode != KTX_SUCCESS) {
|
|
return errorCode;
|
|
}
|
|
//}
|
|
|
|
if (contextProfile == 0)
|
|
discoverContextCapabilities();
|
|
|
|
/* KTX files require an unpack alignment of 4 */
|
|
glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment);
|
|
if (previousUnpackAlignment != KTX_GL_UNPACK_ALIGNMENT) {
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, KTX_GL_UNPACK_ALIGNMENT);
|
|
}
|
|
|
|
texnameUser = pTexture && *pTexture;
|
|
if (texnameUser) {
|
|
texname = *pTexture;
|
|
} else {
|
|
glGenTextures(1, &texname);
|
|
}
|
|
glBindTexture(texinfo.glTarget, texname);
|
|
|
|
/* load as 2D texture if 1D textures are not supported */
|
|
if (texinfo.textureDimensions == 1 &&
|
|
((texinfo.compressed && (pfGlCompressedTexImage1D == NULL)) ||
|
|
(!texinfo.compressed && (pfGlTexImage1D == NULL))))
|
|
{
|
|
texinfo.textureDimensions = 2;
|
|
texinfo.glTarget = GL_TEXTURE_2D;
|
|
header.pixelHeight = 1;
|
|
}
|
|
|
|
if (header.numberOfArrayElements > 0)
|
|
{
|
|
if (texinfo.glTarget == GL_TEXTURE_1D)
|
|
{
|
|
texinfo.glTarget = GL_TEXTURE_1D_ARRAY_EXT;
|
|
}
|
|
else if (texinfo.glTarget == GL_TEXTURE_2D)
|
|
{
|
|
texinfo.glTarget = GL_TEXTURE_2D_ARRAY_EXT;
|
|
}
|
|
else if (texinfo.glTarget == GL_TEXTURE_CUBE_MAP)
|
|
{
|
|
texinfo.glTarget = GL_TEXTURE_CUBE_MAP_ARRAY;
|
|
}
|
|
else
|
|
{
|
|
/* No API for 3D arrays yet */
|
|
return KTX_UNSUPPORTED_TEXTURE_TYPE;
|
|
}
|
|
texinfo.textureDimensions++;
|
|
}
|
|
|
|
/* Reject cube map arrays if unsupported. */
|
|
if (texinfo.glTarget == GL_TEXTURE_CUBE_MAP_ARRAY && !supportsCubeMapArrays)
|
|
{
|
|
return KTX_UNSUPPORTED_TEXTURE_TYPE;
|
|
}
|
|
|
|
/* Reject 3D texture if unsupported. */
|
|
if (texinfo.textureDimensions == 3 &&
|
|
((texinfo.compressed && (pfGlCompressedTexImage3D == NULL)) ||
|
|
(!texinfo.compressed && (pfGlTexImage3D == NULL))))
|
|
{
|
|
return KTX_UNSUPPORTED_TEXTURE_TYPE;
|
|
}
|
|
|
|
// Prefer glGenerateMipmaps over GL_GENERATE_MIPMAP
|
|
if (texinfo.generateMipmaps && (pfGlGenerateMipmap == NULL)) {
|
|
glTexParameteri(texinfo.glTarget, GL_GENERATE_MIPMAP, GL_TRUE);
|
|
}
|
|
#ifdef GL_TEXTURE_MAX_LEVEL
|
|
if (!texinfo.generateMipmaps)
|
|
glTexParameteri(texinfo.glTarget, GL_TEXTURE_MAX_LEVEL, header.numberOfMipmapLevels - 1);
|
|
#endif
|
|
|
|
if (texinfo.glTarget == GL_TEXTURE_CUBE_MAP) {
|
|
texinfo.glTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
|
|
}
|
|
|
|
glInternalFormat = header.glInternalFormat;
|
|
glFormat = header.glFormat;
|
|
if (!texinfo.compressed) {
|
|
#if SUPPORT_LEGACY_FORMAT_CONVERSION
|
|
// If sized legacy formats are supported there is no need to convert.
|
|
// If only unsized formats are supported, there is no point in converting
|
|
// as the modern formats aren't supported either.
|
|
if (sizedFormats == _NON_LEGACY_FORMATS && supportsSwizzle) {
|
|
convertFormat(texinfo.glTarget, &glFormat, &glInternalFormat);
|
|
errorTmp = glGetError();
|
|
} else if (sizedFormats == _NO_SIZED_FORMATS)
|
|
glInternalFormat = header.glBaseInternalFormat;
|
|
#else
|
|
// When no sized formats are supported, or legacy sized formats are not
|
|
// supported, must change internal format.
|
|
if (sizedFormats == _NO_SIZED_FORMATS
|
|
|| (!(sizedFormats & _LEGACY_FORMATS) &&
|
|
(header.glBaseInternalFormat == GL_ALPHA
|
|
|| header.glBaseInternalFormat == GL_LUMINANCE
|
|
|| header.glBaseInternalFormat == GL_LUMINANCE_ALPHA
|
|
|| header.glBaseInternalFormat == GL_INTENSITY))) {
|
|
glInternalFormat = header.glBaseInternalFormat;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for (level = 0; level < header.numberOfMipmapLevels; ++level)
|
|
{
|
|
GLsizei pixelWidth = MAX(1, header.pixelWidth >> level);
|
|
GLsizei pixelHeight = MAX(1, header.pixelHeight >> level);
|
|
GLsizei pixelDepth = MAX(1, header.pixelDepth >> level);
|
|
|
|
errorCode = stream->read(&faceLodSize, sizeof(khronos_uint32_t), stream->src);
|
|
if (errorCode != KTX_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
if (header.endianness == KTX_ENDIAN_REF_REV) {
|
|
_ktxSwapEndian32(&faceLodSize, 1);
|
|
}
|
|
faceLodSizeRounded = (faceLodSize + 3) & ~(khronos_uint32_t)3;
|
|
if (!data) {
|
|
/* allocate memory sufficient for the first level */
|
|
data = malloc(faceLodSizeRounded);
|
|
if (!data) {
|
|
errorCode = KTX_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
dataSize = faceLodSizeRounded;
|
|
}
|
|
else if (dataSize < faceLodSizeRounded) {
|
|
/* subsequent levels cannot be larger than the first level */
|
|
errorCode = KTX_INVALID_VALUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (face = 0; face < header.numberOfFaces; ++face)
|
|
{
|
|
errorCode = stream->read(data, faceLodSizeRounded, stream->src);
|
|
if (errorCode != KTX_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Perform endianness conversion on texture data */
|
|
if (header.endianness == KTX_ENDIAN_REF_REV && header.glTypeSize == 2) {
|
|
_ktxSwapEndian16((khronos_uint16_t*)data, faceLodSize / 2);
|
|
}
|
|
else if (header.endianness == KTX_ENDIAN_REF_REV && header.glTypeSize == 4) {
|
|
_ktxSwapEndian32((khronos_uint32_t*)data, faceLodSize / 4);
|
|
}
|
|
|
|
if (texinfo.textureDimensions == 1) {
|
|
assert(pfGlCompressedTexImage1D != NULL && pfGlTexImage1D != NULL);
|
|
if (texinfo.compressed) {
|
|
pfGlCompressedTexImage1D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, 0,
|
|
faceLodSize, data);
|
|
} else {
|
|
pfGlTexImage1D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, 0,
|
|
glFormat, header.glType, data);
|
|
}
|
|
} else if (texinfo.textureDimensions == 2) {
|
|
if (header.numberOfArrayElements) {
|
|
pixelHeight = header.numberOfArrayElements;
|
|
}
|
|
if (texinfo.compressed) {
|
|
// It is simpler to just attempt to load the format, rather than divine which
|
|
// formats are supported by the implementation. In the event of an error,
|
|
// software unpacking can be attempted.
|
|
glCompressedTexImage2D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, pixelHeight, 0,
|
|
faceLodSize, data);
|
|
} else {
|
|
errorTmp = glGetError();
|
|
glTexImage2D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, pixelHeight, 0,
|
|
glFormat, header.glType, data);
|
|
}
|
|
} else if (texinfo.textureDimensions == 3) {
|
|
assert(pfGlCompressedTexImage3D != NULL && pfGlTexImage3D != NULL);
|
|
if (header.numberOfArrayElements) {
|
|
pixelDepth = header.numberOfArrayElements;
|
|
}
|
|
if (texinfo.compressed) {
|
|
pfGlCompressedTexImage3D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, pixelHeight, pixelDepth, 0,
|
|
faceLodSize, data);
|
|
} else {
|
|
pfGlTexImage3D(texinfo.glTarget + face, level,
|
|
glInternalFormat, pixelWidth, pixelHeight, pixelDepth, 0,
|
|
glFormat, header.glType, data);
|
|
}
|
|
}
|
|
|
|
errorTmp = glGetError();
|
|
#if SUPPORT_SOFTWARE_ETC_UNPACK
|
|
// Renderion is returning INVALID_VALUE. Oops!!
|
|
if ((errorTmp == GL_INVALID_ENUM || errorTmp == GL_INVALID_VALUE)
|
|
&& texinfo.compressed
|
|
&& texinfo.textureDimensions == 2
|
|
&& (glInternalFormat == GL_ETC1_RGB8_OES || (glInternalFormat >= GL_COMPRESSED_R11_EAC && glInternalFormat <= GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC)))
|
|
{
|
|
GLubyte* unpacked;
|
|
GLenum format, internalFormat, type;
|
|
|
|
errorCode = _ktxUnpackETC((GLubyte*)data, glInternalFormat, pixelWidth, pixelHeight,
|
|
&unpacked, &format, &internalFormat, &type,
|
|
R16Formats, supportsSRGB);
|
|
if (errorCode != KTX_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
if (!sizedFormats & _NON_LEGACY_FORMATS) {
|
|
if (internalFormat == GL_RGB8)
|
|
internalFormat = GL_RGB;
|
|
else if (internalFormat == GL_RGBA8)
|
|
internalFormat = GL_RGBA;
|
|
}
|
|
glTexImage2D(texinfo.glTarget + face, level,
|
|
internalFormat, pixelWidth, pixelHeight, 0,
|
|
format, type, unpacked);
|
|
|
|
free(unpacked);
|
|
errorTmp = glGetError();
|
|
}
|
|
#endif
|
|
if (errorTmp != GL_NO_ERROR) {
|
|
if (pGlerror)
|
|
*pGlerror = errorTmp;
|
|
errorCode = KTX_GL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
free(data);
|
|
|
|
/* restore previous GL state */
|
|
if (previousUnpackAlignment != KTX_GL_UNPACK_ALIGNMENT) {
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment);
|
|
}
|
|
|
|
if (errorCode == KTX_SUCCESS)
|
|
{
|
|
if (texinfo.generateMipmaps && pfGlGenerateMipmap) {
|
|
pfGlGenerateMipmap(texinfo.glTarget);
|
|
}
|
|
*pTarget = texinfo.glTarget;
|
|
if (pTexture) {
|
|
*pTexture = texname;
|
|
}
|
|
if (pDimensions) {
|
|
pDimensions->width = header.pixelWidth;
|
|
pDimensions->height = header.pixelHeight;
|
|
pDimensions->depth = header.pixelDepth;
|
|
}
|
|
if (pIsMipmapped) {
|
|
if (texinfo.generateMipmaps || header.numberOfMipmapLevels > 1)
|
|
*pIsMipmapped = GL_TRUE;
|
|
else
|
|
*pIsMipmapped = GL_FALSE;
|
|
}
|
|
} else {
|
|
if (ppKvd && *ppKvd)
|
|
{
|
|
free(*ppKvd);
|
|
*ppKvd = NULL;
|
|
}
|
|
|
|
if (!texnameUser) {
|
|
glDeleteTextures(1, &texname);
|
|
}
|
|
}
|
|
return errorCode;
|
|
}
|
|
|
|
/**
|
|
* @~English
|
|
* @brief Load a GL texture object from a stdio FILE stream.
|
|
*
|
|
* The function sets the texture object's GL_TEXTURE_MAX_LEVEL parameter
|
|
* according to the number of levels in the ktxStream, provided the library
|
|
* has been compiled with a version of gl.h where GL_TEXTURE_MAX_LEVEL is
|
|
* defined.
|
|
*
|
|
* It will unpack compressed GL_ETC1_RGB8_OES and GL_ETC2_* format
|
|
* textures in software when the format is not supported by the GL context,
|
|
* provided the library has been compiled with SUPPORT_SOFTWARE_ETC_UNPACK
|
|
* defined as 1.
|
|
*
|
|
* It will also convert texture with legacy formats to their modern equivalents
|
|
* when the format is not supported by the GL context, provided the library
|
|
* has been compiled with SUPPORT_LEGACY_FORMAT_CONVERSION defined as 1.
|
|
*
|
|
* @param [in] file pointer to the stdio FILE stream from which to
|
|
* load.
|
|
* @param [in,out] pTexture name of the GL texture to load. If NULL or if
|
|
* <tt>*pTexture == 0</tt> the function will generate
|
|
* a texture name. The function binds either the
|
|
* generated name or the name given in @p *pTexture
|
|
* to the texture target returned in @p *pTarget,
|
|
* before loading the texture data. If @p pTexture
|
|
* is not NULL and a name was generated, the generated
|
|
* name will be returned in *pTexture.
|
|
* @param [out] pTarget @p *pTarget is set to the texture target used. The
|
|
* target is chosen based on the file contents.
|
|
* @param [out] pDimensions If @p pDimensions is not NULL, the width, height and
|
|
* depth of the texture's base level are returned in the
|
|
* fields of the KTX_dimensions structure to which it points.
|
|
* @param [out] pIsMipmapped
|
|
* If @p pIsMipmapped is not NULL, @p *pIsMipmapped is set
|
|
* to GL_TRUE if the KTX texture is mipmapped, GL_FALSE
|
|
* otherwise.
|
|
* @param [out] pGlerror @p *pGlerror is set to the value returned by
|
|
* glGetError when this function returns the error
|
|
* KTX_GL_ERROR. glerror can be NULL.
|
|
* @param [in,out] pKvdLen If not NULL, @p *pKvdLen is set to the number of bytes
|
|
* of key-value data pointed at by @p *ppKvd. Must not be
|
|
* NULL, if @p ppKvd is not NULL.
|
|
* @param [in,out] ppKvd If not NULL, @p *ppKvd is set to the point to a block of
|
|
* memory containing key-value data read from the file.
|
|
* The application is responsible for freeing the memory.
|
|
*
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p target is @c NULL or the size of a mip
|
|
* level is greater than the size of the
|
|
* preceding level.
|
|
* @exception KTX_INVALID_OPERATION @p ppKvd is not NULL but pKvdLen is NULL.
|
|
* @exception KTX_UNEXPECTED_END_OF_FILE the file does not contain the
|
|
* expected amount of data.
|
|
* @exception KTX_OUT_OF_MEMORY Sufficient memory could not be allocated to store
|
|
* the requested key-value data.
|
|
* @exception KTX_GL_ERROR A GL error was raised by glBindTexture,
|
|
* glGenTextures or gl*TexImage*. The GL error
|
|
* will be returned in @p *glerror, if glerror
|
|
* is not @c NULL.
|
|
*/
|
|
KTX_error_code
|
|
ktxLoadTextureF(FILE* file, GLuint* pTexture, GLenum* pTarget,
|
|
KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
|
|
GLenum* pGlerror,
|
|
unsigned int* pKvdLen, unsigned char** ppKvd)
|
|
{
|
|
struct ktxStream stream;
|
|
KTX_error_code errorCode = KTX_SUCCESS;
|
|
|
|
errorCode = ktxFileInit(&stream, file);
|
|
if (errorCode != KTX_SUCCESS)
|
|
return errorCode;
|
|
|
|
return ktxLoadTextureS(&stream, pTexture, pTarget, pDimensions, pIsMipmapped, pGlerror, pKvdLen, ppKvd);
|
|
}
|
|
|
|
/**
|
|
* @~English
|
|
* @brief Load a GL texture object from a named file on disk.
|
|
*
|
|
* @param [in] filename pointer to a C string that contains the path of
|
|
* the file to load.
|
|
* @param [in,out] pTexture name of the GL texture to load. See
|
|
* ktxLoadTextureF() for details.
|
|
* @param [out] pTarget @p *pTarget is set to the texture target used. See
|
|
* ktxLoadTextureF() for details.
|
|
* @param [out] pDimensions @p the texture's base level width depth and height
|
|
* are returned in structure to which this points.
|
|
* See ktxLoadTextureF() for details.
|
|
* @param [out] pIsMipmapped @p pIsMipMapped is set to indicate if the loaded
|
|
* texture is mipmapped. See ktxLoadTextureF() for
|
|
* details.
|
|
* @param [out] pGlerror @p *pGlerror is set to the value returned by
|
|
* glGetError when this function returns the error
|
|
* KTX_GL_ERROR. glerror can be NULL.
|
|
* @param [in,out] pKvdLen If not NULL, @p *pKvdLen is set to the number of bytes
|
|
* of key-value data pointed at by @p *ppKvd. Must not be
|
|
* NULL, if @p ppKvd is not NULL.
|
|
* @param [in,out] ppKvd If not NULL, @p *ppKvd is set to the point to a block of
|
|
* memory containing key-value data read from the file.
|
|
* The application is responsible for freeing the memory.*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_FILE_OPEN_FAILED The specified file could not be opened.
|
|
* @exception KTX_INVALID_VALUE See ktxLoadTextureF() for causes.
|
|
* @exception KTX_INVALID_OPERATION See ktxLoadTextureF() for causes.
|
|
* @exception KTX_UNEXPECTED_END_OF_FILE See ktxLoadTextureF() for causes.
|
|
*
|
|
* @exception KTX_GL_ERROR See ktxLoadTextureF() for causes.
|
|
*/
|
|
KTX_error_code
|
|
ktxLoadTextureN(const char* const filename, GLuint* pTexture, GLenum* pTarget,
|
|
KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
|
|
GLenum* pGlerror,
|
|
unsigned int* pKvdLen, unsigned char** ppKvd)
|
|
{
|
|
KTX_error_code errorCode;
|
|
FILE* file = fopen(filename, "rb");
|
|
|
|
if (file) {
|
|
errorCode = ktxLoadTextureF(file, pTexture, pTarget, pDimensions,
|
|
pIsMipmapped, pGlerror, pKvdLen, ppKvd);
|
|
fclose(file);
|
|
} else
|
|
errorCode = KTX_FILE_OPEN_FAILED;
|
|
|
|
return errorCode;
|
|
}
|
|
|
|
/**
|
|
* @~English
|
|
* @brief Load a GL texture object from KTX formatted data in memory.
|
|
*
|
|
* @param [in] bytes pointer to the array of bytes containing
|
|
* the KTX format data to load.
|
|
* @param [in] size size of the memory array containing the
|
|
* KTX format data.
|
|
* @param [in,out] pTexture name of the GL texture to load. See
|
|
* ktxLoadTextureF() for details.
|
|
* @param [out] pTarget @p *pTarget is set to the texture target used. See
|
|
* ktxLoadTextureF() for details.
|
|
* @param [out] pDimensions @p the texture's base level width depth and height
|
|
* are returned in structure to which this points.
|
|
* See ktxLoadTextureF() for details.
|
|
* @param [out] pIsMipmapped @p *pIsMipMapped is set to indicate if the loaded
|
|
* texture is mipmapped. See ktxLoadTextureF() for
|
|
* details.
|
|
* @param [out] pGlerror @p *pGlerror is set to the value returned by
|
|
* glGetError when this function returns the error
|
|
* KTX_GL_ERROR. glerror can be NULL.
|
|
* @param [in,out] pKvdLen If not NULL, @p *pKvdLen is set to the number of bytes
|
|
* of key-value data pointed at by @p *ppKvd. Must not be
|
|
* NULL, if @p ppKvd is not NULL.
|
|
* @param [in,out] ppKvd If not NULL, @p *ppKvd is set to the point to a block of
|
|
* memory containing key-value data read from the file.
|
|
* The application is responsible for freeing the memory.*
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_FILE_OPEN_FAILED The specified memory could not be opened as a file.
|
|
* @exception KTX_INVALID_VALUE See ktxLoadTextureF() for causes.
|
|
* @exception KTX_INVALID_OPERATION See ktxLoadTextureF() for causes.
|
|
* @exception KTX_UNEXPECTED_END_OF_FILE See ktxLoadTextureF() for causes.
|
|
*
|
|
* @exception KTX_GL_ERROR See ktxLoadTextureF() for causes.
|
|
*/
|
|
KTX_error_code
|
|
ktxLoadTextureM(const void* bytes, GLsizei size, GLuint* pTexture, GLenum* pTarget,
|
|
KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
|
|
GLenum* pGlerror,
|
|
unsigned int* pKvdLen, unsigned char** ppKvd)
|
|
{
|
|
struct ktxMem mem;
|
|
struct ktxStream stream;
|
|
KTX_error_code errorCode = KTX_SUCCESS;
|
|
|
|
errorCode = ktxMemInit(&stream, &mem, bytes, size);
|
|
if (errorCode != KTX_SUCCESS)
|
|
return errorCode;
|
|
|
|
return ktxLoadTextureS(&stream, pTexture, pTarget, pDimensions, pIsMipmapped, pGlerror, pKvdLen, ppKvd);
|
|
}
|
|
|
|
|