diff --git a/KTX/LICENSING.md b/KTX/LICENSING.md
new file mode 100644
--- /dev/null
+++ b/KTX/LICENSING.md
@@ -0,0 +1,7 @@
+Licensing
+---
+The library and tools are open source software. Most of the code is licensed
+under a modified BSD license. The code for unpacking ETC1, ETC2 and EAC
+compressed textures has a separate license that restricts it to uses
+associated with Khronos Group APIs. See [Licensing](http://www.khronos.org/opengles/sdk/tools/KTX/doc/libktx/licensing.html)
+for more details.
diff --git a/KTX/include/ktx.h b/KTX/include/ktx.h
new file mode 100644
--- /dev/null
+++ b/KTX/include/ktx.h
@@ -0,0 +1,486 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+#ifndef _KTX_H_
+#define _KTX_H_
+
+/**
+ * @file
+ * @~English
+ *
+ * @brief Declares the public functions and structures of the
+ *        KTX API.
+ *
+ * @author Georg Kolling, Imagination Technology
+ * @author with modifications by Mark Callow, HI Corporation
+ *
+ * $Id: 026beff16eeb0b507d918c88ca9fcfcdecc9167d $
+ * $Date$
+ *
+ * @todo Find a way so that applications do not have to define KTX_OPENGL{,_ES*}
+ *       when using the library.
+ */
+
+/*
+ * This file copyright (c) 2010 The Khronos Group, Inc.
+ */
+
+/**
+@~English
+@page licensing Licensing
+
+LibKTX contains code
+
+@li (c) 2010 & (c) 2013 The Khronos Group Inc.
+@li (c) 2008 and (c) 2010 HI Corporation
+@li (c) 2005 Ericsson AB
+@li (c) 2003-2010, Troy D. Hanson
+
+A specific copyright is given in each source file.
+
+@section default Default License
+
+With the exception of the files listed explicitly below, the source
+files are made available under the following BSD-like license.
+
+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.
+
+@section hi_mark hi_mark{,_sq}.ktx
+
+The HI logo textures are &copy; & &trade; HI Corporation and are
+provided for use only in testing the KTX loader. Any other use requires
+specific prior written permission from HI. Furthermore the name HI may
+not be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+@section uthash uthash.h
+
+uthash.h is made available under the following revised BSD license.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * @~English
+ * @mainpage The KTX Library
+ *
+ * @section intro_sec Introduction
+ *
+ * libktx is a small library of functions for creating KTX (Khronos
+ * TeXture) files and instantiating OpenGL&reg; and OpenGL&reg; ES
+ * textures from them.
+ *
+ * For information about the KTX format see the
+ * <a href="http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/">
+ * formal specification.</a>
+ *
+ * The library is open source software. Most of the code is licensed under a
+ * modified BSD license. The code for unpacking ETC1, ETC2 and EAC compressed
+ * textures has a separate license that restricts it to uses associated with
+ * Khronos Group APIs. See @ref licensing for more details.
+ *
+ * See @ref history for the list of changes.
+ *
+ * @author Mark Callow, <a href="http://www.hicorp.co.jp">HI Corporation</a>
+ * @author Georg Kolling, <a href="http://www.imgtec.com">Imagination Technology</a>
+ * @author Jacob Str&ouml;m, <a href="http://www.ericsson.com">Ericsson AB</a>
+ *
+ * @version 2.0
+ *
+ * $Date$
+ */
+
+#include <stdio.h>
+
+#include "KHR/khrplatform.h"
+
+#if KTX_OPENGL
+
+	#ifdef _WIN32
+	  #include <windows.h>
+	  #include <GL/glew.h>
+    #elif KTX_USE_GETPROC
+      #include <GL/glew.h>
+    #else
+      #define GLCOREARB_PROTOTYPES
+      #include <GL/glcorearb.h>
+	#endif
+
+	#define GL_APIENTRY APIENTRY
+    #define KTX_GLFUNCPTRS "gl_funcptrs.h"
+
+#elif KTX_OPENGL_ES1
+
+	#include <GLES/gl.h>
+	#include <GLES/glext.h>
+
+    #define KTX_GLFUNCPTRS "gles1_funcptrs.h"
+
+#elif KTX_OPENGL_ES2
+
+	#include <GLES2/gl2.h>
+	#include <GLES2/gl2ext.h>
+
+    #define KTX_GLFUNCPTRS "gles2_funcptrs.h"
+
+#elif KTX_OPENGL_ES3
+
+	#include <GLES3/gl3.h>
+	#include <GLES3/gl3ext.h>
+
+    #define KTX_GLFUNCPTRS "gles3_funcptrs.h"
+
+#else
+#error Please #define one of KTX_OPENGL, KTX_OPENGL_ES1, KTX_OPENGL_ES2 or KTX_OPENGL_ES3 as 1
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Key String for standard orientation value.
+ */
+#define KTX_ORIENTATION_KEY	"KTXorientation"
+/**
+ * @brief Standard format for 2D orientation value.
+ */
+#define KTX_ORIENTATION2_FMT "S=%c,T=%c"
+/**
+ * @brief Standard format for 3D orientation value.
+ */
+#define KTX_ORIENTATION3_FMT "S=%c,T=%c,R=%c"
+/**
+ * @brief Required unpack alignment
+ */
+#define KTX_GL_UNPACK_ALIGNMENT 4
+
+/**
+ * @brief Error codes returned by library functions.
+ */
+typedef enum KTX_error_code_t {
+	KTX_SUCCESS = 0,		 /*!< Operation was successful. */
+	KTX_FILE_OPEN_FAILED,	 /*!< The target file could not be opened. */
+	KTX_FILE_WRITE_ERROR,    /*!< An error occurred while writing to the file. */
+	KTX_GL_ERROR,            /*!< GL operations resulted in an error. */
+	KTX_INVALID_OPERATION,   /*!< The operation is not allowed in the current state. */
+	KTX_INVALID_VALUE,	     /*!< A parameter value was not valid */
+	KTX_NOT_FOUND,			 /*!< Requested key was not found */
+	KTX_OUT_OF_MEMORY,       /*!< Not enough memory to complete the operation. */
+	KTX_UNEXPECTED_END_OF_FILE, /*!< The file did not contain enough data */
+	KTX_UNKNOWN_FILE_FORMAT, /*!< The file not a KTX file */
+	KTX_UNSUPPORTED_TEXTURE_TYPE, /*!< The KTX file specifies an unsupported texture type. */
+} KTX_error_code;
+
+/**
+ * @brief structure used to pass information about the texture to ktxWriteKTX
+ */
+typedef struct KTX_texture_info_t
+{
+	/**
+	 * @brief The type of the image data.
+	 *
+	 * Values are the same as in the @p type parameter of
+     * glTexImage*D. Must be 0 for compressed images.
+	 */
+	khronos_uint32_t glType;
+	/**
+	 * @brief The data type size to be used in case of endianness
+	 *        conversion.
+	 *
+	 * This value is used in the event conversion is required when the
+	 * KTX file is loaded. It should be the size in bytes corresponding
+	 * to glType. Must be 1 for compressed images.
+	 */
+	khronos_uint32_t glTypeSize;
+	/**
+	 * @brief The format of the image(s).
+	 *
+	 * Values are the same as in the format parameter
+	 * of glTexImage*D. Must be 0 for compressed images.
+     */
+	khronos_uint32_t glFormat;
+	/** @brief The internalformat of the image(s).
+	 *
+	 * Values are the same as for the internalformat parameter of
+	 * glTexImage*2D. Note: it will not be used when a KTX file
+	 * containing an uncompressed texture is loaded into OpenGL ES.
+	 */
+	khronos_uint32_t glInternalFormat;
+    /** @brief The base internalformat of the image(s)
+	 *
+	 * For non-compressed textures, should be the same as glFormat.
+	 * For compressed textures specifies the base internal, e.g.
+	 * GL_RGB, GL_RGBA.
+     */
+	khronos_uint32_t glBaseInternalFormat;
+	/** @brief Width of the image for texture level 0, in pixels. */
+	khronos_uint32_t pixelWidth;
+	/** @brief Height of the texture image for level 0, in pixels.
+	 *
+	 * Must be 0 for 1D textures.
+	 */
+ 	khronos_uint32_t pixelHeight;
+	/** @brief Depth of the texture image for level 0, in pixels.
+	 *
+	 * Must be 0 for 1D, 2D and cube textures.
+	 */
+	khronos_uint32_t pixelDepth;
+	/** @brief The number of array elements.
+	 *
+	 * Must be 0 if not an array texture.
+	 */
+	khronos_uint32_t numberOfArrayElements;
+	/** @brief The number of cubemap faces.
+	 *
+	 * Must be 6 for cubemaps and cubemap arrays, 1 otherwise. Cubemap
+     * faces must be provided in the order: +X, -X, +Y, -Y, +Z, -Z.
+	 */
+	khronos_uint32_t numberOfFaces;
+    /** @brief The number of mipmap levels.
+	 *
+     * 1 for non-mipmapped texture. 0 indicates that a full mipmap pyramid should
+     * be generated from level 0 at load time (this is usually not allowed for
+	 * compressed formats). Mipmaps must be provided in order from largest size to
+	 * smallest size. The first mipmap level is always level 0.
+	 */
+	khronos_uint32_t numberOfMipmapLevels;
+} KTX_texture_info;
+
+
+/**
+ * @brief Structure used to pass image data to ktxWriteKTX.
+ */
+typedef struct KTX_image_info {
+	GLsizei size;	/*!< Size of the image data in bytes. */
+	GLubyte* data;  /*!< Pointer to the image data. */
+} KTX_image_info;
+
+
+/**
+ * @brief Structure used to return texture dimensions
+ */
+typedef struct KTX_dimensions {
+	GLsizei width;  /*!< */
+	GLsizei height; /*!< */
+	GLsizei depth;  /*!< */
+} KTX_dimensions;
+
+/**
+ * @brief Opaque handle to a KTX_hash_table.
+ */
+typedef void* KTX_hash_table;
+
+/* ktxLoadTextureF
+ *
+ * Loads a texture from a stdio FILE.
+ */
+KTX_error_code
+ktxLoadTextureF(FILE*, GLuint* pTexture, GLenum* pTarget,
+				KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
+				GLenum* pGlerror,
+				unsigned int* pKvdLen, unsigned char** ppKvd);
+
+/* ktxLoadTextureN
+ *
+ * Loads a texture from a KTX file on disk.
+ */
+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);
+
+/* ktxLoadTextureM
+ *
+ * Loads a texture from a KTX file in memory.
+ */
+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);
+
+/* ktxWriteKTXF
+ *
+ * Writes a KTX file using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXF(FILE*, const KTX_texture_info* imageInfo,
+			 GLsizei bytesOfKeyValueData, const void* keyValueData,
+			 GLuint numImages, KTX_image_info images[]);
+
+/* ktxWriteKTXN
+ *
+ * Writes a KTX file using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXN(const char* dstname, const KTX_texture_info* imageInfo,
+			 GLsizei bytesOfKeyValueData, const void* keyValueData,
+			 GLuint numImages, KTX_image_info images[]);
+
+/* ktxWriteKTXM
+ *
+ * Writes a KTX file into memory using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXM(unsigned char** bytes, GLsizei* size, const KTX_texture_info* textureInfo,
+			 GLsizei bytesOfKeyValueData, const void* keyValueData,
+			 GLuint numImages, KTX_image_info images[]);
+
+/* ktxErrorString()
+ *
+ * Returns a string corresponding to a KTX error code.
+ */
+const char* const ktxErrorString(KTX_error_code error);
+
+/* ktxHashTable_Create()
+ *
+ * Creates a key-value hash table
+ */
+KTX_hash_table ktxHashTable_Create();
+
+/* ktxHashTable_Destroy()
+ *
+ * Destroys a key-value hash table
+ */
+void ktxHashTable_Destroy(KTX_hash_table This);
+
+/* ktxHashTable_AddKVPair()
+ *
+ * Adds a key-value pair to a hash table.
+ */
+KTX_error_code
+ktxHashTable_AddKVPair(KTX_hash_table This, const char* key,
+					   unsigned int valueLen, const void* value);
+
+
+/* ktxHashTable_FindValue()
+ *
+ * Looks up a key and returns the value.
+ */
+KTX_error_code
+ktxHashTable_FindValue(KTX_hash_table This, const char* key,
+					   unsigned int* pValueLen, void** pValue);
+
+
+/* ktxHashTable_Serialize()
+ *
+ * Serializes the hash table to a block of memory suitable for
+ * writing to a KTX file.
+ */
+KTX_error_code
+ktxHashTable_Serialize(KTX_hash_table This, unsigned int* kvdLen, unsigned char** kvd);
+
+
+/* ktxHashTable_Deserialize()
+ *
+ * Creates a hash table from the serialized data read from a
+ * a KTX file.
+ */
+KTX_error_code
+ktxHashTable_Deserialize(unsigned int kvdLen, void* kvd, KTX_hash_table* pKvt);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+@page history Revision History
+
+@section v4 Version XXX
+Added:
+@li CMake build files. Thanks to Pavel Rotjberg for the initial version.
+
+Changed:
+@li ktxWriteKTXF to check the validity of the type & format combinations
+    passed to it.
+
+Fixed:
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=999">999</a>: 16-bit luminance texture cannot be written.
+@li compile warnings from compilers stricter than MS Visual C++. Thanks to
+    Pavel Rotjberg.
+
+@section v3 Version 2.0
+Added:
+@li support for decoding ETC2 and EAC formats in the absence of a hardware
+    decoder.
+@li support for converting textures with legacy LUMINANCE, LUMINANCE_ALPHA,
+    etc. formats to the equivalent R, RG, etc. format with an
+	appropriate swizzle, when loading in OpenGL Core Profile contexts.
+@li ktxErrorString function to return a string corresponding to an error code.
+@li	tests for ktxLoadTexture[FN] that run under OpenGL ES 3.0 and OpenGL 3.3.
+    The latter includes an EGL on WGL wrapper that makes porting apps between
+	OpenGL ES and OpenGL easier on Windows.
+@li more texture formats to ktxLoadTexture[FN] and toktx tests.
+
+Changed:
+@li ktxLoadTexture[FMN] to discover the capabilities of the GL context at
+    run time and load textures, or not, according to those capabilities.
+
+Fixed:
+@li failure of ktxWriteKTXF to pad image rows to 4 bytes as required by the KTX
+    format.
+@li ktxWriteKTXF exiting with KTX_FILE_WRITE_ERROR when attempting to write
+    more than 1 byte of face-LOD padding.
+
+Although there is only a very minor API change, the addition of ktxErrorString, the functional changes
+are large enough to justify bumping the major revision number.
+
+@section v2 Version 1.0.1
+Implemented ktxLoadTextureM.
+Fixed the following:
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=571">571</a>: crash when null passed for pIsMipmapped.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=572">572</a>: memory leak when unpacking ETC textures.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=573">573</a>: potential crash when unpacking ETC textures with unused padding pixels.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=576">576</a>: various small fixes.
+
+Thanks to Krystian Bigaj for the ktxLoadTextureM implementation and these fixes.
+
+@section v1 Version 1.0
+Initial release.
+
+*/
+
+#endif /* _KTX_H_ */
diff --git a/KTX/lib/checkheader.c b/KTX/lib/checkheader.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/checkheader.c
@@ -0,0 +1,209 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 5c28a496fa7703a75b91eb8dd7b1722c2e299741 $ */
+
+/**
+ * @internal
+ * @file checkheader.c
+ * @~English
+ *
+ * @brief Function to verify a KTX file header
+ *
+ * @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.
+*/
+
+/*
+ * Author: Georg Kolling, Imagination Technology with modifications
+ * by Mark Callow, HI Corporation.
+ */
+#include <string.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+
+#include KTX_GLFUNCPTRS
+
+DECLARE_GL_FUNCPTRS
+DECLARE_GL_EXTGLOBALS
+
+/**
+ * @internal
+ * @~English
+ * @brief Check a KTX file header.
+ *
+ * As well as checking that the header identifies a KTX file, the function
+ * sanity checks the values and returns information about the texture in a
+ * KTX_texinfo structure.
+ *
+ * @param header	pointer to the KTX header to check
+ * @param texinfo	pointer to a KTX_texinfo structure in which to return
+ *                  information about the texture.
+ * 
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+KTX_error_code _ktxCheckHeader(KTX_header* header, KTX_texinfo* texinfo)
+{
+	khronos_uint8_t identifier_reference[12] = KTX_IDENTIFIER_REF;
+	khronos_uint32_t max_dim;
+
+	/* Compare identifier, is this a KTX file? */
+	if (memcmp(header->identifier, identifier_reference, 12) != 0)
+	{
+		return KTX_UNKNOWN_FILE_FORMAT;
+	}
+
+	if (header->endianness == KTX_ENDIAN_REF_REV)
+	{
+		/* Convert endianness of header fields if necessary */
+		_ktxSwapEndian32(&header->glType, 12);
+
+		if (header->glTypeSize != 1 ||
+			header->glTypeSize != 2 ||
+			header->glTypeSize != 4)
+		{
+			/* Only 8, 16, and 32-bit types supported so far */
+			return KTX_INVALID_VALUE;
+		}
+	}
+	else if (header->endianness != KTX_ENDIAN_REF)
+	{
+		return KTX_INVALID_VALUE;
+	}
+
+	/* Check glType and glFormat */
+	texinfo->compressed = 0;
+	if (header->glType == 0 || header->glFormat == 0)
+	{
+		if (header->glType + header->glFormat != 0)
+		{
+			/* either both or none of glType, glFormat must be zero */
+			return KTX_INVALID_VALUE;
+		}
+		texinfo->compressed = 1;
+	}
+
+	/* 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 or cube array textures. */
+	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; 
+	}
+
+	texinfo->textureDimensions = 1;
+	texinfo->glTarget = GL_TEXTURE_1D;
+	texinfo->generateMipmaps = 0;
+	if (header->pixelHeight > 0)
+	{
+		texinfo->textureDimensions = 2;
+		texinfo->glTarget = GL_TEXTURE_2D;
+	}
+	if (header->pixelDepth > 0)
+	{
+		texinfo->textureDimensions = 3;
+		texinfo->glTarget = GL_TEXTURE_3D;
+	}
+
+	if (header->numberOfFaces == 6)
+	{
+		if (texinfo->textureDimensions == 2)
+		{
+			texinfo->glTarget = GL_TEXTURE_CUBE_MAP;
+		}
+		else
+		{
+			/* cube map needs 2D faces */
+			return KTX_INVALID_VALUE;
+		}
+	}
+	else if (header->numberOfFaces != 1)
+	{
+		/* numberOfFaces must be either 1 or 6 */
+		return KTX_INVALID_VALUE;
+	}
+
+	/* load as 2D texture if 1D textures are not supported */
+	if (texinfo->textureDimensions == 1 &&
+		((texinfo->compressed && (glCompressedTexImage1D == NULL)) ||
+		 (!texinfo->compressed && (glTexImage1D == 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
+		{
+			/* No API for 3D and cube arrays yet */
+			return KTX_UNSUPPORTED_TEXTURE_TYPE;
+		}
+		texinfo->textureDimensions++;
+	}
+
+	/* reject 3D texture if unsupported */
+	if (texinfo->textureDimensions == 3 &&
+		((texinfo->compressed && (glCompressedTexImage3D == NULL)) ||
+		 (!texinfo->compressed && (glTexImage3D == NULL))))
+	{
+		return KTX_UNSUPPORTED_TEXTURE_TYPE;
+	}
+
+	/* Check number of mipmap levels */
+	if (header->numberOfMipmapLevels == 0)
+	{
+		texinfo->generateMipmaps = 1;
+		header->numberOfMipmapLevels = 1;
+	}
+	max_dim = MAX(MAX(header->pixelWidth, header->pixelHeight), header->pixelDepth);
+	if (max_dim < ((khronos_uint32_t)1 << (header->numberOfMipmapLevels - 1)))
+	{
+		/* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
+		return KTX_INVALID_VALUE;
+	}
+
+	return KTX_SUCCESS;
+}
diff --git a/KTX/lib/errstr.c b/KTX/lib/errstr.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/errstr.c
@@ -0,0 +1,79 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 66fc783ff8cf9a7836f10cccf03607bad0860246 $ */
+
+/**
+ * @file errstr.c
+ * @~English
+ *
+ * @brief Function to return a string corresponding to a KTX error code.
+ *
+ * @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.
+*/
+
+#include "ktx.h"
+
+static const char* const errorStrings[] = {
+	"Operation succeeded",							  /* KTX_SUCCESS */
+	"File open failed",								  /* KTX_FILE_OPEN_FAILED */
+	"File write failed",							  /* KTX_FILE_WRITE_ERROR */
+	"GL error occurred",							  /* KTX_GL_ERROR */
+	"Operation not allowed in the current state",     /* KTX_INVALID_OPERATION */
+	"Invalid parameter value",						  /* KTX_INVALID_VALUE */
+	"Key not found",								  /* KTX_NOT_FOUND */
+	"Out of memory",								  /* KTX_OUT_OF_MEMORY */
+	"Unexpected end of file",						  /* KTX_UNEXPECTED_END_OF_FILE */
+	"Not a KTX file",								  /* KTX_UNKNOWN_FILE_FORMAT */
+	"Texture type not supported by GL context"		  /* KTX_UNSUPPORTED_TEXTURE_TYPE */
+};
+static const int lastErrorCode = (sizeof(errorStrings) / sizeof(char*)) - 1;
+
+
+/**
+ * @~English
+ * @brief Return a string corresponding to a KTX error code.
+ *
+ * @param error		the error code for which to return a string
+ *
+ * @return pointer to the message string.
+ *
+ * @internal Use UTF-8 for translated message strings.
+ * 
+ * @author Mark Callow, HI Corporation
+ */
+const char* const ktxErrorString(KTX_error_code error)
+{
+	if (error > lastErrorCode)
+		return "Unrecognized error code";
+	return errorStrings[error];
+}
diff --git a/KTX/lib/etcdec.cxx b/KTX/lib/etcdec.cxx
new file mode 100644
--- /dev/null
+++ b/KTX/lib/etcdec.cxx
@@ -0,0 +1,1842 @@
+/**
+ 
+@~English
+@page licensing Licensing
+ 
+@section etcdec etcdec.cxx License
+ 
+etcdec.cxx is made available under the terms and conditions of the following
+License Agreement.
+
+Software License Agreement
+
+PLEASE REVIEW THE FOLLOWING TERMS AND CONDITIONS PRIOR TO USING THE
+ERICSSON TEXTURE COMPRESSION CODEC SOFTWARE (THE "SOFTWARE"). THE USE
+OF THE SOFTWARE IS SUBJECT TO THE TERMS AND CONDITIONS OF THE
+FOLLOWING SOFTWARE LICENSE AGREEMENT (THE "SLA"). IF YOU DO NOT ACCEPT
+SUCH TERMS AND CONDITIONS YOU MAY NOT USE THE SOFTWARE.
+
+Subject to the terms and conditions of the SLA, the licensee of the
+Software (the "Licensee") hereby, receives a non-exclusive,
+non-transferable, limited, free-of-charge, perpetual and worldwide
+license, to copy, use, distribute and modify the Software, but only
+for the purpose of developing, manufacturing, selling, using and
+distributing products including the Software in binary form, which
+products are used for compression and/or decompression according to
+the Khronos standard specifications OpenGL, OpenGL ES and
+WebGL. Notwithstanding anything of the above, Licensee may distribute
+[etcdec.cxx] in source code form provided (i) it is in unmodified
+form; and (ii) it is included in software owned by Licensee.
+
+If Licensee institutes, or threatens to institute, patent litigation
+against Ericsson or Ericsson's affiliates for using the Software for
+developing, having developed, manufacturing, having manufactured,
+selling, offer for sale, importing, using, leasing, operating,
+repairing and/or distributing products (i) within the scope of the
+Khronos framework; or (ii) using software or other intellectual
+property rights owned by Ericsson or its affiliates and provided under
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. Moreover, if Licensee institutes, or
+threatens to institute, patent litigation against any other licensee
+of the Software for using the Software in products within the scope of
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. However, should Licensee institute, or
+threaten to institute, patent litigation against any other licensee of
+the Software based on such other licensee's use of any other software
+together with the Software, then Ericsson shall have no right to
+terminate this SLA.
+
+This SLA does not transfer to Licensee any ownership to any Ericsson
+or third party intellectual property rights. All rights not expressly
+granted by Ericsson under this SLA are hereby expressly
+reserved. Furthermore, nothing in this SLA shall be construed as a
+right to use or sell products in a manner which conveys or purports to
+convey whether explicitly, by principles of implied license, or
+otherwise, any rights to any third party, under any patent of Ericsson
+or of Ericsson's affiliates covering or relating to any combination of
+the Software with any other software or product (not licensed
+hereunder) where the right applies specifically to the combination and
+not to the software or product itself.
+
+THE SOFTWARE IS PROVIDED "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF
+ANY KIND, EXTENDS NO WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+EXPRESS, IMPLIED OR STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS,
+IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS OF TITLE,
+MERCHANTABILITY, SATISFACTORY QUALITY, SUITABILITY, AND FITNESS FOR A
+PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE SOFTWARE IS WITH THE LICENSEE. SHOULD THE SOFTWARE PROVE
+DEFECTIVE, THE LICENSEE ASSUMES THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION. ERICSSON MAKES NO WARRANTY THAT THE MANUFACTURE,
+SALE, OFFERING FOR SALE, DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER
+THE SLA WILL BE FREE FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER
+INTELLECTUAL PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE
+LICENSE AND THE SLA ARE SUBJECT TO LICENSEE'S SOLE RESPONSIBILITY TO
+MAKE SUCH DETERMINATION AND ACQUIRE SUCH LICENSES AS MAY BE NECESSARY
+WITH RESPECT TO PATENTS, COPYRIGHT AND OTHER INTELLECTUAL PROPERTY OF
+THIRD PARTIES.
+
+THE LICENSEE ACKNOWLEDGES AND ACCEPTS THAT THE SOFTWARE (I) IS NOT
+LICENSED FOR; (II) IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY
+NOT BE USED FOR; ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT
+LIMITED TO OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR
+NETWORKS, AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR
+ANY OTHER COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR
+COMMUNICATION SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE SOFTWARE
+COULD LEAD TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR
+ENVIRONMENTAL DAMAGE. LICENSEE'S RIGHTS UNDER THIS LICENSE WILL
+TERMINATE AUTOMATICALLY AND IMMEDIATELY WITHOUT NOTICE IF LICENSEE
+FAILS TO COMPLY WITH THIS PARAGRAPH.
+
+IN NO EVENT SHALL ERICSSON BE LIABLE FOR ANY DAMAGES WHATSOEVER,
+INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL,
+INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING
+BUT NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY
+OTHER COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY THE LICENSEE OR THIRD
+PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER
+SOFTWARE) REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT, OR
+OTHERWISE), EVEN IF THE LICENSEE OR ANY OTHER PARTY HAS BEEN ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGES.
+
+Licensee acknowledges that "ERICSSON ///" is the corporate trademark
+of Telefonaktiebolaget LM Ericsson and that both "Ericsson" and the
+figure "///" are important features of the trade names of
+Telefonaktiebolaget LM Ericsson. Nothing contained in these terms and
+conditions shall be deemed to grant Licensee any right, title or
+interest in the word "Ericsson" or the figure "///". No delay or
+omission by Ericsson to exercise any right or power shall impair any
+such right or power to be construed to be a waiver thereof. Consent by
+Ericsson to, or waiver of, a breach by the Licensee shall not
+constitute consent to, waiver of, or excuse for any other different or
+subsequent breach.
+
+This SLA shall be governed by the substantive law of Sweden. Any
+dispute, controversy or claim arising out of or in connection with
+this SLA, or the breach, termination or invalidity thereof, shall be
+submitted to the exclusive jurisdiction of the Swedish Courts.
+
+*/
+
+//// etcpack v2.74
+//// 
+//// NO WARRANTY 
+//// 
+//// BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE THE PROGRAM IS PROVIDED
+//// "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF ANY KIND, EXTENDS NO
+//// WARRANTIES OR CONDITIONS OF ANY KIND; EITHER EXPRESS, IMPLIED OR
+//// STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, IMPLIED OR
+//// STATUTORY WARRANTIES OR CONDITIONS OF TITLE, MERCHANTABILITY,
+//// SATISFACTORY QUALITY, SUITABILITY AND FITNESS FOR A PARTICULAR
+//// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+//// PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+//// THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ERICSSON
+//// MAKES NO WARRANTY THAT THE MANUFACTURE, SALE, OFFERING FOR SALE,
+//// DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER THE LICENSE WILL BE FREE
+//// FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER INTELLECTUAL
+//// PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE LICENSE IS SUBJECT
+//// TO YOUR SOLE RESPONSIBILITY TO MAKE SUCH DETERMINATION AND ACQUIRE
+//// SUCH LICENSES AS MAY BE NECESSARY WITH RESPECT TO PATENTS, COPYRIGHT
+//// AND OTHER INTELLECTUAL PROPERTY OF THIRD PARTIES.
+//// 
+//// FOR THE AVOIDANCE OF DOUBT THE PROGRAM (I) IS NOT LICENSED FOR; (II)
+//// IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY NOT BE USED FOR;
+//// ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT LIMITED TO
+//// OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR NETWORKS,
+//// AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR ANY OTHER
+//// COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR COMMUNICATION
+//// SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE PROGRAM COULD LEAD TO
+//// DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR ENVIRONMENTAL
+//// DAMAGE. YOUR RIGHTS UNDER THIS LICENSE WILL TERMINATE AUTOMATICALLY
+//// AND IMMEDIATELY WITHOUT NOTICE IF YOU FAIL TO COMPLY WITH THIS
+//// PARAGRAPH.
+//// 
+//// IN NO EVENT WILL ERICSSON, BE LIABLE FOR ANY DAMAGES WHATSOEVER,
+//// INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL,
+//// INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN
+//// CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
+//// NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY OTHER
+//// COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING RENDERED
+//// INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+//// THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) REGARDLESS OF THE
+//// THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE), EVEN IF SUCH HOLDER
+//// OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+//// 
+//// (C) Ericsson AB 2013. All Rights Reserved.
+//// 
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// Typedefs
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef short int16;
+
+// Macros to help with bit extraction/insertion
+#define SHIFT(size,startpos) ((startpos)-(size)+1)
+#define MASK(size, startpos) (((2<<(size-1))-1) << SHIFT(size,startpos))
+#define PUTBITS( dest, data, size, startpos) dest = ((dest & ~MASK(size, startpos)) | ((data << SHIFT(size, startpos)) & MASK(size,startpos)))
+#define SHIFTHIGH(size, startpos) (((startpos)-32)-(size)+1)
+#define MASKHIGH(size, startpos) (((1<<(size))-1) << SHIFTHIGH(size,startpos))
+#define PUTBITSHIGH(dest, data, size, startpos) dest = ((dest & ~MASKHIGH(size, startpos)) | ((data << SHIFTHIGH(size, startpos)) & MASKHIGH(size,startpos)))
+#define GETBITS(source, size, startpos)  (( (source) >> ((startpos)-(size)+1) ) & ((1<<(size)) -1))
+#define GETBITSHIGH(source, size, startpos)  (( (source) >> (((startpos)-32)-(size)+1) ) & ((1<<(size)) -1))
+#ifndef PGMOUT
+#define PGMOUT 0
+#endif
+// Thumb macros and definitions
+#define	R_BITS59T 4
+#define G_BITS59T 4
+#define	B_BITS59T 4
+#define	R_BITS58H 4
+#define G_BITS58H 4
+#define	B_BITS58H 4
+#define	MAXIMUM_ERROR (255*255*16*1000)
+#define R 0
+#define G 1
+#define B 2
+#define BLOCKHEIGHT 4
+#define BLOCKWIDTH 4
+#define BINPOW(power) (1<<(power))
+#define	TABLE_BITS_59T 3
+#define	TABLE_BITS_58H 3
+
+// Helper Macros
+#define CLAMP(ll,x,ul) (((x)<(ll)) ? (ll) : (((x)>(ul)) ? (ul) : (x)))
+#define JAS_ROUND(x) (((x) < 0.0 ) ? ((int)((x)-0.5)) : ((int)((x)+0.5)))
+
+#define RED_CHANNEL(img,width,x,y,channels)   img[channels*(y*width+x)+0]
+#define GREEN_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+1]
+#define BLUE_CHANNEL(img,width,x,y,channels)  img[channels*(y*width+x)+2]
+#define ALPHA_CHANNEL(img,width,x,y,channels)  img[channels*(y*width+x)+3]
+
+
+// Global tables
+static uint8 table59T[8] = {3,6,11,16,23,32,41,64};  // 3-bit table for the 59 bit T-mode
+static uint8 table58H[8] = {3,6,11,16,23,32,41,64};  // 3-bit table for the 58 bit H-mode
+static int compressParams[16][4] = {{-8, -2,  2, 8}, {-8, -2,  2, 8}, {-17, -5, 5, 17}, {-17, -5, 5, 17}, {-29, -9, 9, 29}, {-29, -9, 9, 29}, {-42, -13, 13, 42}, {-42, -13, 13, 42}, {-60, -18, 18, 60}, {-60, -18, 18, 60}, {-80, -24, 24, 80}, {-80, -24, 24, 80}, {-106, -33, 33, 106}, {-106, -33, 33, 106}, {-183, -47, 47, 183}, {-183, -47, 47, 183}};
+static int unscramble[4] = {2, 3, 1, 0};
+int alphaTableInitialized = 0;
+int alphaTable[256][8];
+int alphaBase[16][4] = {	
+              {-15,-9,-6,-3},
+							{-13,-10,-7,-3},
+							{-13,-8,-5,-2},
+							{-13,-6,-4,-2},
+							{-12,-8,-6,-3},
+							{-11,-9,-7,-3},
+							{-11,-8,-7,-4},
+							{-11,-8,-5,-3},
+							{ -10,-8,-6,-2},
+							{ -10,-8,-5,-2},
+							{ -10,-8,-4,-2},
+							{ -10,-7,-5,-2},
+							{ -10,-7,-4,-3},
+							{ -10,-3,-2, -1},
+							{ -9,-8,-6,-4},
+							{ -9,-7,-5,-3}
+											};
+
+// Global variables
+int formatSigned = 0;
+
+// Enums
+ enum{PATTERN_H = 0, 
+      PATTERN_T = 1};
+
+
+// Code used to create the valtab
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void setupAlphaTable() 
+{
+  if(alphaTableInitialized)
+    return;
+  alphaTableInitialized = 1;
+
+	//read table used for alpha compression
+	int buf;
+	for(int i = 16; i<32; i++) 
+	{
+		for(int j=0; j<8; j++) 
+		{
+			buf=alphaBase[i-16][3-j%4];
+			if(j<4)
+				alphaTable[i][j]=buf;
+			else
+				alphaTable[i][j]=(-buf-1);
+		}
+	}
+	
+	//beyond the first 16 values, the rest of the table is implicit.. so calculate that!
+	for(int i=0; i<256; i++) 
+	{
+		//fill remaining slots in table with multiples of the first ones.
+		int mul = i/16;
+		int old = 16+i%16;
+		for(int j = 0; j<8; j++) 
+		{
+			alphaTable[i][j]=alphaTable[old][j]*mul;
+			//note: we don't do clamping here, though we could, because we'll be clamped afterwards anyway.
+		}
+	}
+}
+
+// Read a word in big endian style
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void read_big_endian_2byte_word(unsigned short *blockadr, FILE *f)
+{
+	uint8 bytes[2];
+	unsigned short block;
+
+	(void) fread(&bytes[0], 1, 1, f);
+	(void) fread(&bytes[1], 1, 1, f);
+
+	block = 0;
+	block |= bytes[0];
+	block = block << 8;
+	block |= bytes[1];
+
+	blockadr[0] = block;
+}
+
+// Read a word in big endian style
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void read_big_endian_4byte_word(unsigned int *blockadr, FILE *f)
+{
+	uint8 bytes[4];
+	unsigned int block;
+
+	(void) fread(&bytes[0], 1, 1, f);
+	(void) fread(&bytes[1], 1, 1, f);
+	(void) fread(&bytes[2], 1, 1, f);
+	(void) fread(&bytes[3], 1, 1, f);
+
+	block = 0;
+	block |= bytes[0];
+	block = block << 8;
+	block |= bytes[1];
+	block = block << 8;
+	block |= bytes[2];
+	block = block << 8;
+	block |= bytes[3];
+
+	blockadr[0] = block;
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff57bits(unsigned int planar_word1, unsigned int planar_word2, unsigned int &planar57_word1, unsigned int &planar57_word2)
+{
+	// Get bits from twotimer configuration for 57 bits
+	// 
+	// Go to this bit layout:
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     |R0               |G01G02              |B01B02  ;B03     |RH1           |RH2|GH                 |
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
+	//      -----------------------------------------------------------------------------------------------
+	//     |BH               |RV               |GV                  |BV                | not used          |   
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//  From this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      ------------------------------------------------------------------------------------------------
+	//     |//|R0               |G01|/|G02              |B01|/ // //|B02  |//|B03     |RH1           |df|RH2|
+	//      ------------------------------------------------------------------------------------------------
+	//
+	//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
+	//      -----------------------------------------------------------------------------------------------
+	//     |GH                  |BH               |RV               |GV                   |BV              |
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32 
+	//      ---------------------------------------------------------------------------------------------------
+	//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |diff|flip|
+	//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
+	//      ---------------------------------------------------------------------------------------------------
+
+	uint8 RO, GO1, GO2, BO1, BO2, BO3, RH1, RH2, GH, BH, RV, GV, BV;
+
+	RO  = GETBITSHIGH( planar_word1, 6, 62);
+	GO1 = GETBITSHIGH( planar_word1, 1, 56);
+	GO2 = GETBITSHIGH( planar_word1, 6, 54);
+	BO1 = GETBITSHIGH( planar_word1, 1, 48);
+	BO2 = GETBITSHIGH( planar_word1, 2, 44);
+	BO3 = GETBITSHIGH( planar_word1, 3, 41);
+	RH1 = GETBITSHIGH( planar_word1, 5, 38);
+	RH2 = GETBITSHIGH( planar_word1, 1, 32);
+	GH  = GETBITS(     planar_word2, 7, 31);
+	BH  = GETBITS(     planar_word2, 6, 24);
+	RV  = GETBITS(     planar_word2, 6, 18);
+	GV  = GETBITS(     planar_word2, 7, 12);
+	BV  = GETBITS(     planar_word2, 6,  5);
+
+	planar57_word1 = 0; planar57_word2 = 0;
+	PUTBITSHIGH( planar57_word1, RO,  6, 63);
+	PUTBITSHIGH( planar57_word1, GO1, 1, 57);
+	PUTBITSHIGH( planar57_word1, GO2, 6, 56);
+	PUTBITSHIGH( planar57_word1, BO1, 1, 50);
+	PUTBITSHIGH( planar57_word1, BO2, 2, 49);
+	PUTBITSHIGH( planar57_word1, BO3, 3, 47);
+	PUTBITSHIGH( planar57_word1, RH1, 5, 44);
+	PUTBITSHIGH( planar57_word1, RH2, 1, 39);
+	PUTBITSHIGH( planar57_word1, GH, 7, 38);
+	PUTBITS(     planar57_word2, BH, 6, 31);
+	PUTBITS(     planar57_word2, RV, 6, 25);
+	PUTBITS(     planar57_word2, GV, 7, 19);
+	PUTBITS(     planar57_word2, BV, 6, 12);
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff58bits(unsigned int thumbH_word1, unsigned int thumbH_word2, unsigned int &thumbH58_word1, unsigned int &thumbH58_word2)
+{
+	// Go to this layout:
+	//
+	//     |63 62 61 60 59 58|57 56 55 54 53 52 51|50 49|48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33|32   |
+	//     |-------empty-----|part0---------------|part1|part2------------------------------------------|part3|
+	//
+	//  from this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      --------------------------------------------------------------------------------------------------|
+	//     |//|part0               |// // //|part1|//|part2                                          |df|part3|
+	//      --------------------------------------------------------------------------------------------------|
+
+	unsigned int part0, part1, part2, part3;
+
+	// move parts
+	part0 = GETBITSHIGH( thumbH_word1, 7, 62);
+	part1 = GETBITSHIGH( thumbH_word1, 2, 52);
+	part2 = GETBITSHIGH( thumbH_word1,16, 49);
+	part3 = GETBITSHIGH( thumbH_word1, 1, 32);
+	thumbH58_word1 = 0;
+	PUTBITSHIGH( thumbH58_word1, part0,  7, 57);
+	PUTBITSHIGH( thumbH58_word1, part1,  2, 50);
+	PUTBITSHIGH( thumbH58_word1, part2, 16, 48);
+	PUTBITSHIGH( thumbH58_word1, part3,  1, 32);
+
+	thumbH58_word2 = thumbH_word2;
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff59bits(unsigned int thumbT_word1, unsigned int thumbT_word2, unsigned int &thumbT59_word1, unsigned int &thumbT59_word2)
+{
+	// Get bits from twotimer configuration 59 bits. 
+	// 
+	// Go to this bit layout:
+	//
+	//     |63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32|
+	//     |----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--|
+	//
+	//     |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+	//     |----------------------------------------index bits---------------------------------------------|
+	//
+	//
+	//  From this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     |// // //|R0a  |//|R0b  |G0         |B0         |R1         |G1         |B1          |da  |df|db|
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//     |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+	//     |----------------------------------------index bits---------------------------------------------|
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |df|fp|
+	//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bt|bt|
+	//      ------------------------------------------------------------------------------------------------
+
+	uint8 R0a;
+
+	// Fix middle part
+	thumbT59_word1 = thumbT_word1 >> 1;
+	// Fix db (lowest bit of d)
+	PUTBITSHIGH( thumbT59_word1, thumbT_word1,  1, 32);
+	// Fix R0a (top two bits of R0)
+	R0a = GETBITSHIGH( thumbT_word1, 2, 60);
+	PUTBITSHIGH( thumbT59_word1, R0a,  2, 58);
+
+	// Zero top part (not needed)
+	PUTBITSHIGH( thumbT59_word1, 0,  5, 63);
+
+	thumbT59_word2 = thumbT_word2;
+}
+
+// The color bits are expanded to the full color
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressColor(int R_B, int G_B, int B_B, uint8 (colors_RGB444)[2][3], uint8 (colors)[2][3]) 
+{
+	// The color should be retrieved as:
+	//
+	// c = round(255/(r_bits^2-1))*comp_color
+	//
+	// This is similar to bit replication
+	// 
+	// Note -- this code only work for bit replication from 4 bits and up --- 3 bits needs
+	// two copy operations.
+
+ 	colors[0][R] = (colors_RGB444[0][R] << (8 - R_B)) | (colors_RGB444[0][R] >> (R_B - (8-R_B)) );
+ 	colors[0][G] = (colors_RGB444[0][G] << (8 - G_B)) | (colors_RGB444[0][G] >> (G_B - (8-G_B)) );
+ 	colors[0][B] = (colors_RGB444[0][B] << (8 - B_B)) | (colors_RGB444[0][B] >> (B_B - (8-B_B)) );
+ 	colors[1][R] = (colors_RGB444[1][R] << (8 - R_B)) | (colors_RGB444[1][R] >> (R_B - (8-R_B)) );
+ 	colors[1][G] = (colors_RGB444[1][G] << (8 - G_B)) | (colors_RGB444[1][G] >> (G_B - (8-G_B)) );
+ 	colors[1][B] = (colors_RGB444[1][B] << (8 - B_B)) | (colors_RGB444[1][B] >> (B_B - (8-B_B)) );
+}
+
+void calculatePaintColors59T(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) 
+{
+	//////////////////////////////////////////////
+	//
+	//		C3      C1		C4----C1---C2
+	//		|		|			  |
+	//		|		|			  |
+	//		|-------|			  |
+	//		|		|			  |
+	//		|		|			  |
+	//		C4      C2			  C3
+	//
+	//////////////////////////////////////////////
+
+	// C4
+	possible_colors[3][R] = CLAMP(0,colors[1][R] - table59T[d],255);
+	possible_colors[3][G] = CLAMP(0,colors[1][G] - table59T[d],255);
+	possible_colors[3][B] = CLAMP(0,colors[1][B] - table59T[d],255);
+	
+	if (p == PATTERN_T) 
+	{
+		// C3
+		possible_colors[0][R] = colors[0][R];
+		possible_colors[0][G] = colors[0][G];
+		possible_colors[0][B] = colors[0][B];
+		// C2
+		possible_colors[1][R] = CLAMP(0,colors[1][R] + table59T[d],255);
+		possible_colors[1][G] = CLAMP(0,colors[1][G] + table59T[d],255);
+		possible_colors[1][B] = CLAMP(0,colors[1][B] + table59T[d],255);
+		// C1
+		possible_colors[2][R] = colors[1][R];
+		possible_colors[2][G] = colors[1][G];
+		possible_colors[2][B] = colors[1][B];
+
+	} 
+	else 
+	{
+		printf("Invalid pattern. Terminating");
+		exit(1);
+	}
+}
+// Decompress a T-mode block (simple packing)
+// Simple 59T packing:
+//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32|
+//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--|
+//
+//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+//|----------------------------------------index bits---------------------------------------------|
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB59Tc(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty, int channels)
+{
+	uint8 colorsRGB444[2][3];
+	uint8 colors[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38);
+
+	distance   = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34);
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors);	
+	calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channels*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channels*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channels*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+		}
+	}
+}
+
+void decompressBlockTHUMB59T(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB59Tc(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Calculate the paint colors from the block colors 
+// using a distance d and one of the H- or T-patterns.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void calculatePaintColors58H(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) 
+{
+	
+	//////////////////////////////////////////////
+	//
+	//		C3      C1		C4----C1---C2
+	//		|		|			  |
+	//		|		|			  |
+	//		|-------|			  |
+	//		|		|			  |
+	//		|		|			  |
+	//		C4      C2			  C3
+	//
+	//////////////////////////////////////////////
+
+	// C4
+	possible_colors[3][R] = CLAMP(0,colors[1][R] - table58H[d],255);
+	possible_colors[3][G] = CLAMP(0,colors[1][G] - table58H[d],255);
+	possible_colors[3][B] = CLAMP(0,colors[1][B] - table58H[d],255);
+	
+	if (p == PATTERN_H) 
+	{ 
+		// C1
+		possible_colors[0][R] = CLAMP(0,colors[0][R] + table58H[d],255);
+		possible_colors[0][G] = CLAMP(0,colors[0][G] + table58H[d],255);
+		possible_colors[0][B] = CLAMP(0,colors[0][B] + table58H[d],255);
+		// C2
+		possible_colors[1][R] = CLAMP(0,colors[0][R] - table58H[d],255);
+		possible_colors[1][G] = CLAMP(0,colors[0][G] - table58H[d],255);
+		possible_colors[1][B] = CLAMP(0,colors[0][B] - table58H[d],255);
+		// C3
+		possible_colors[2][R] = CLAMP(0,colors[1][R] + table58H[d],255);
+		possible_colors[2][G] = CLAMP(0,colors[1][G] + table58H[d],255);
+		possible_colors[2][B] = CLAMP(0,colors[1][B] + table58H[d],255);
+	} 
+	else 
+	{
+		printf("Invalid pattern. Terminating");
+		exit(1);
+	}
+}
+
+// Decompress an H-mode block 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB58Hc(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	unsigned int col0, col1;
+	uint8 colors[2][3];
+	uint8 colorsRGB444[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+	
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37);
+
+	distance = 0;
+	distance = (GETBITSHIGH(block_part1, 2, 33)) << 1;
+
+	col0 = GETBITSHIGH(block_part1, 12, 57);
+	col1 = GETBITSHIGH(block_part1, 12, 45);
+
+	if(col0 >= col1)
+	{
+		distance |= 1;
+	}
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors);	
+	
+	calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channels*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channels*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channels*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+		}
+	}
+}
+void decompressBlockTHUMB58H(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB58Hc(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Decompress the planar mode.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockPlanar57c(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	uint8 colorO[3], colorH[3], colorV[3];
+
+	colorO[0] = GETBITSHIGH( compressed57_1, 6, 63);
+	colorO[1] = GETBITSHIGH( compressed57_1, 7, 57);
+	colorO[2] = GETBITSHIGH( compressed57_1, 6, 50);
+	colorH[0] = GETBITSHIGH( compressed57_1, 6, 44);
+	colorH[1] = GETBITSHIGH( compressed57_1, 7, 38);
+	colorH[2] = GETBITS(     compressed57_2, 6, 31);
+	colorV[0] = GETBITS(     compressed57_2, 6, 25);
+	colorV[1] = GETBITS(     compressed57_2, 7, 19);
+	colorV[2] = GETBITS(     compressed57_2, 6, 12);
+
+	colorO[0] = (colorO[0] << 2) | (colorO[0] >> 4);
+	colorO[1] = (colorO[1] << 1) | (colorO[1] >> 6);
+	colorO[2] = (colorO[2] << 2) | (colorO[2] >> 4);
+
+	colorH[0] = (colorH[0] << 2) | (colorH[0] >> 4);
+	colorH[1] = (colorH[1] << 1) | (colorH[1] >> 6);
+	colorH[2] = (colorH[2] << 2) | (colorH[2] >> 4);
+
+	colorV[0] = (colorV[0] << 2) | (colorV[0] >> 4);
+	colorV[1] = (colorV[1] << 1) | (colorV[1] >> 6);
+	colorV[2] = (colorV[2] << 2) | (colorV[2] >> 4);
+
+	int xx, yy;
+
+	for( xx=0; xx<4; xx++)
+	{
+		for( yy=0; yy<4; yy++)
+		{
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = CLAMP(0, ((xx*(colorH[0]-colorO[0]) + yy*(colorV[0]-colorO[0]) + 4*colorO[0] + 2) >> 2),255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = CLAMP(0, ((xx*(colorH[1]-colorO[1]) + yy*(colorV[1]-colorO[1]) + 4*colorO[1] + 2) >> 2),255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = CLAMP(0, ((xx*(colorH[2]-colorO[2]) + yy*(colorV[2]-colorO[2]) + 4*colorO[2] + 2) >> 2),255);
+
+			//Equivalent method
+			/*img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[0]-colorO[0])/4.0 + yy*(colorV[0]-colorO[0])/4.0 + colorO[0])), 255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[1]-colorO[1])/4.0 + yy*(colorV[1]-colorO[1])/4.0 + colorO[1])), 255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[2]-colorO[2])/4.0 + yy*(colorV[2]-colorO[2])/4.0 + colorO[2])), 255);*/
+			
+		}
+	}
+}
+void decompressBlockPlanar57(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockPlanar57c(compressed57_1, compressed57_2, img, width, height, startx, starty, 3);
+}
+// Decompress an ETC1 block (or ETC2 using individual or differential mode).
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockDiffFlipC(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	uint8 avg_color[3], enc_color1[3], enc_color2[3];
+	signed char diff[3];
+	int table;
+	int index,shift;
+	int r,g,b;
+	int diffbit;
+	int flipbit;
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+	flipbit = (GETBITSHIGH(block_part1, 1, 32));
+
+	if( !diffbit )
+	{
+		// We have diffbit = 0.
+
+		// First decode left part of block.
+		avg_color[0]= GETBITSHIGH(block_part1, 4, 63);
+		avg_color[1]= GETBITSHIGH(block_part1, 4, 55);
+		avg_color[2]= GETBITSHIGH(block_part1, 4, 47);
+
+		// Here, we should really multiply by 17 instead of 16. This can
+		// be done by just copying the four lower bits to the upper ones
+		// while keeping the lower bits.
+		avg_color[0] |= (avg_color[0] <<4);
+		avg_color[1] |= (avg_color[1] <<4);
+		avg_color[2] |= (avg_color[2] <<4);
+
+		table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+		unsigned int pixel_indices_MSB, pixel_indices_LSB;
+			
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift = 0;
+			for(int x=startx; x<startx+2; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift = 0;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+2; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift+=2;
+			}
+		}
+
+		// Now decode other part of block. 
+		avg_color[0]= GETBITSHIGH(block_part1, 4, 59);
+		avg_color[1]= GETBITSHIGH(block_part1, 4, 51);
+		avg_color[2]= GETBITSHIGH(block_part1, 4, 43);
+
+		// Here, we should really multiply by 17 instead of 16. This can
+		// be done by just copying the four lower bits to the upper ones
+		// while keeping the lower bits.
+		avg_color[0] |= (avg_color[0] <<4);
+		avg_color[1] |= (avg_color[1] <<4);
+		avg_color[2] |= (avg_color[2] <<4);
+
+		table = GETBITSHIGH(block_part1, 3, 36) << 1;
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift=8;
+			for(int x=startx+2; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift=2;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty+2; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift += 2;
+			}
+		}
+	}
+	else
+	{
+		// We have diffbit = 1. 
+
+//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32 
+//      ---------------------------------------------------------------------------------------------------
+//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |diff|flip|
+//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
+//      ---------------------------------------------------------------------------------------------------
+// 
+// 
+//     c) bit layout in bits 31 through 0 (in both cases)
+// 
+//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3   2   1  0
+//      --------------------------------------------------------------------------------------------------
+//     |       most significant pixel index bits       |         least significant pixel index bits       |  
+//     | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a |
+//      --------------------------------------------------------------------------------------------------      
+
+		// First decode left part of block.
+		enc_color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		enc_color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		enc_color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Expand from 5 to 8 bits
+		avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2);
+		avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2);
+		avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2);
+
+		table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+		unsigned int pixel_indices_MSB, pixel_indices_LSB;
+			
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift = 0;
+			for(int x=startx; x<startx+2; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift = 0;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+2; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift+=2;
+			}
+		}
+
+		// Now decode right part of block. 
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		//  Calculale second color
+		enc_color2[0]= enc_color1[0] + diff[0];
+		enc_color2[1]= enc_color1[1] + diff[1];
+		enc_color2[2]= enc_color1[2] + diff[2];
+
+		// Expand from 5 to 8 bits
+		avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2);
+		avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2);
+		avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2);
+
+		table = GETBITSHIGH(block_part1, 3, 36) << 1;
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift=8;
+			for(int x=startx+2; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift=2;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty+2; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift += 2;
+			}
+		}
+	}
+}
+void decompressBlockDiffFlip(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Decompress an ETC2 RGB block
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockETC2c(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	int diffbit;
+	signed char color1[3];
+	signed char diff[3];
+	signed char red, green, blue;
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+
+	if( diffbit )
+	{
+		// We have diffbit = 1;
+
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channels);
+		}
+		else if (green < 0 || green > 31)
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channels);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channels);
+		}
+		else
+		{
+ 			decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels);
+		}
+	}
+	else
+	{
+		// We have diffbit = 0;
+		decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels);
+	}
+}
+void decompressBlockETC2(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockETC2c(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+// Decompress an ETC2 block with punchthrough alpha
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockDifferentialWithAlphaC(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+	
+	uint8 avg_color[3], enc_color1[3], enc_color2[3];
+	signed char diff[3];
+	int table;
+	int index,shift;
+	int r,g,b;
+	int diffbit;
+	int flipbit;
+  int channelsA;
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	//the diffbit now encodes whether or not the entire alpha channel is 255.
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+ 	flipbit = (GETBITSHIGH(block_part1, 1, 32));
+
+	// First decode left part of block.
+	enc_color1[0]= GETBITSHIGH(block_part1, 5, 63);
+	enc_color1[1]= GETBITSHIGH(block_part1, 5, 55);
+	enc_color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+	// Expand from 5 to 8 bits
+	avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2);
+	avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2);
+	avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2);
+
+	table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+	unsigned int pixel_indices_MSB, pixel_indices_LSB;
+		
+	pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+	pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+	if( (flipbit) == 0 )
+	{
+		// We should not flip
+		shift = 0;
+		for(int x=startx; x<startx+2; x++)
+		{
+			for(int y=starty; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+
+			}
+		}
+	}
+	else
+	{
+		// We should flip
+		shift = 0;
+		for(int x=startx; x<startx+4; x++)
+		{
+			for(int y=starty; y<starty+2; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+			shift+=2;
+		}
+	}
+	// Now decode right part of block. 
+	diff[0]= GETBITSHIGH(block_part1, 3, 58);
+	diff[1]= GETBITSHIGH(block_part1, 3, 50);
+	diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+	// Extend sign bit to entire byte. 
+	diff[0] = (diff[0] << 5);
+	diff[1] = (diff[1] << 5);
+	diff[2] = (diff[2] << 5);
+	diff[0] = diff[0] >> 5;
+	diff[1] = diff[1] >> 5;
+	diff[2] = diff[2] >> 5;
+
+	//  Calculate second color
+	enc_color2[0]= enc_color1[0] + diff[0];
+	enc_color2[1]= enc_color1[1] + diff[1];
+	enc_color2[2]= enc_color1[2] + diff[2];
+
+	// Expand from 5 to 8 bits
+	avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2);
+	avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2);
+	avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2);
+
+	table = GETBITSHIGH(block_part1, 3, 36) << 1;
+	pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+	pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+	if( (flipbit) == 0 )
+	{
+		// We should not flip
+		shift=8;
+		for(int x=startx+2; x<startx+4; x++)
+		{
+			for(int y=starty; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+		}
+	}
+	else
+	{
+		// We should flip
+		shift=2;
+		for(int x=startx; x<startx+4; x++)
+		{
+			for(int y=starty+2; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+			shift += 2;
+		}
+	}
+}
+void decompressBlockDifferentialWithAlpha(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+
+
+// similar to regular decompression, but alpha channel is set to 0 if pixel index is 2, otherwise 255.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB59TAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+
+	uint8 colorsRGB444[2][3];
+	uint8 colors[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+  int channelsA;
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38);
+
+	distance   = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34);
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors);	
+	calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channelsRGB*((starty+y)*width+startx+x)+R] = 
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channelsRGB*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channelsRGB*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+			if(block_mask[x][y]==2)  
+			{
+				alpha[channelsA*(x+startx+(y+starty)*width)]=0;
+				img[channelsRGB*((starty+y)*width+startx+x)+R] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+G] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+B] =0;
+			}
+			else
+				alpha[channelsA*(x+startx+(y+starty)*width)]=255;
+		}
+	}
+}
+void decompressBlockTHUMB59TAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB59TAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+
+
+// Decompress an H-mode block with alpha
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB58HAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+	unsigned int col0, col1;
+	uint8 colors[2][3];
+	uint8 colorsRGB444[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+  int channelsA;	
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37);
+
+  distance = 0;
+	distance = (GETBITSHIGH(block_part1, 2, 33)) << 1;
+
+	col0 = GETBITSHIGH(block_part1, 12, 57);
+	col1 = GETBITSHIGH(block_part1, 12, 45);
+
+	if(col0 >= col1)
+	{
+		distance |= 1;
+	}
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors);	
+	
+	calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channelsRGB*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channelsRGB*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channelsRGB*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+			
+			if(block_mask[x][y]==2)  
+			{
+				alpha[channelsA*(x+startx+(y+starty)*width)]=0;
+				img[channelsRGB*((starty+y)*width+startx+x)+R] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+G] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+B] =0;
+			}
+			else
+				alpha[channelsA*(x+startx+(y+starty)*width)]=255;
+		}
+	}
+}
+void decompressBlockTHUMB58HAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB58HAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+// Decompression function for ETC2_RGBA1 format.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockETC21BitAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width, int height, int startx, int starty, int channelsRGB)
+{
+	int diffbit;
+	signed char color1[3];
+	signed char diff[3];
+	signed char red, green, blue;
+  int channelsA;	
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alphaimg = &img[0+3];
+  }
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+
+	if( diffbit )
+	{
+		// We have diffbit = 1, meaning no transparent pixels. regular decompression.
+
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else if (green < 0 || green > 31)
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else
+		{
+ 			decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		for(int x=startx; x<startx+4; x++) 
+		{
+			for(int y=starty; y<starty+4; y++) 
+			{
+				alphaimg[channelsA*(x+y*width)]=255;
+			}
+		}
+	}
+	else
+	{
+		// We have diffbit = 0, transparent pixels. Only T-, H- or regular diff-mode possible.
+		
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59TAlphaC(block59_part1, block59_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		else if(green < 0 || green > 31) 
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58HAlphaC(block58_part1, block58_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB);
+			for(int x=startx; x<startx+4; x++) 
+			{
+				for(int y=starty; y<starty+4; y++) 
+				{
+					alphaimg[channelsA*(x+y*width)]=255;
+				}
+			}
+		}
+		else
+			decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img,alphaimg, width, height, startx, starty, channelsRGB);
+	}
+}
+void decompressBlockETC21BitAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width, int height, int startx, int starty)
+{
+  decompressBlockETC21BitAlphaC(block_part1, block_part2, img, alphaimg, width, height, startx, starty, 3);
+}
+//
+//	Utility functions used for alpha compression
+//
+
+// bit number frompos is extracted from input, and moved to bit number topos in the return value.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+uint8 getbit(uint8 input, int frompos, int topos) 
+{
+	uint8 output=0;
+	if(frompos>topos)
+		return ((1<<frompos)&input)>>(frompos-topos);
+	return ((1<<frompos)&input)<<(topos-frompos);
+}
+
+// takes as input a value, returns the value clamped to the interval [0,255].
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+int clamp(int val) 
+{
+	if(val<0)
+		val=0;
+	if(val>255)
+		val=255;
+	return val;
+}
+
+// Decodes tha alpha component in a block coded with GL_COMPRESSED_RGBA8_ETC2_EAC.
+// Note that this decoding is slightly different from that of GL_COMPRESSED_R11_EAC.
+// However, a hardware decoder can share gates between the two formats as explained
+// in the specification under GL_COMPRESSED_R11_EAC.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockAlphaC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) 
+{
+	int alpha = data[0];
+	int table = data[1];
+	
+	int bit=0;
+	int byte=2;
+	//extract an alpha value for each pixel.
+	for(int x=0; x<4; x++) 
+	{
+		for(int y=0; y<4; y++) 
+		{
+			//Extract table index
+			int index=0;
+			for(int bitpos=0; bitpos<3; bitpos++) 
+			{
+				index|=getbit(data[byte],7-bit,2-bitpos);
+				bit++;
+				if(bit>7) 
+				{
+					bit=0;
+					byte++;
+				}
+			}
+			img[(ix+x+(iy+y)*width)*channels]=clamp(alpha +alphaTable[table][index]);
+		}
+	}
+}
+void decompressBlockAlpha(uint8* data, uint8* img, int width, int height, int ix, int iy) 
+{
+  decompressBlockAlphaC(data, img, width, height, ix, iy, 1);
+}
+
+// Does decompression and then immediately converts from 11 bit signed to a 16-bit format.
+// 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+int16 get16bits11signed(int base, int table, int mul, int index) 
+{
+	int elevenbase = base-128;
+	if(elevenbase==-128)
+		elevenbase=-127;
+	elevenbase*=8;
+	//i want the positive value here
+	int tabVal = -alphaBase[table][3-index%4]-1;
+	//and the sign, please
+	int sign = 1-(index/4);
+	
+	if(sign)
+		tabVal=tabVal+1;
+	int elevenTabVal = tabVal*8;
+
+	if(mul!=0)
+		elevenTabVal*=mul;
+	else
+		elevenTabVal/=8;
+
+	if(sign)
+		elevenTabVal=-elevenTabVal;
+
+	//calculate sum
+	int elevenbits = elevenbase+elevenTabVal;
+
+	//clamp..
+	if(elevenbits>=1024)
+		elevenbits=1023;
+	else if(elevenbits<-1023)
+		elevenbits=-1023;
+	//this is the value we would actually output.. 
+	//but there aren't any good 11-bit file or uncompressed GL formats
+	//so we extend to 15 bits signed.
+	sign = elevenbits<0;
+	elevenbits=abs(elevenbits);
+	int16 fifteenbits = (elevenbits<<5)+(elevenbits>>5);
+	int16 sixteenbits=fifteenbits;
+
+	if(sign)
+		sixteenbits=-sixteenbits;
+	
+	return sixteenbits;
+}
+
+// Does decompression and then immediately converts from 11 bit signed to a 16-bit format 
+// Calculates the 11 bit value represented by base, table, mul and index, and extends it to 16 bits.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+uint16 get16bits11bits(int base, int table, int mul, int index) 
+{
+	int elevenbase = base*8+4;
+
+	//i want the positive value here
+	int tabVal = -alphaBase[table][3-index%4]-1;
+	//and the sign, please
+	int sign = 1-(index/4);
+	
+	if(sign)
+		tabVal=tabVal+1;
+	int elevenTabVal = tabVal*8;
+
+	if(mul!=0)
+		elevenTabVal*=mul;
+	else
+		elevenTabVal/=8;
+
+	if(sign)
+		elevenTabVal=-elevenTabVal;
+
+	//calculate sum
+	int elevenbits = elevenbase+elevenTabVal;
+
+	//clamp..
+	if(elevenbits>=256*8)
+		elevenbits=256*8-1;
+	else if(elevenbits<0)
+		elevenbits=0;
+	//elevenbits now contains the 11 bit alpha value as defined in the spec.
+
+	//extend to 16 bits before returning, since we don't have any good 11-bit file formats.
+	uint16 sixteenbits = (elevenbits<<5)+(elevenbits>>6);
+
+	return sixteenbits;
+}
+
+// Decompresses a block using one of the GL_COMPRESSED_R11_EAC or GL_COMPRESSED_SIGNED_R11_EAC-formats
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockAlpha16bitC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) 
+{
+	int alpha = data[0];
+	int table = data[1];
+
+	if(formatSigned) 
+	{
+		//if we have a signed format, the base value is given as a signed byte. We convert it to (0-255) here,
+		//so more code can be shared with the unsigned mode.
+		alpha = *((signed char*)(&data[0]));
+		alpha = alpha+128;
+	}
+
+	int bit=0;
+	int byte=2;
+	//extract an alpha value for each pixel.
+	for(int x=0; x<4; x++) 
+	{
+		for(int y=0; y<4; y++) 
+		{
+			//Extract table index
+			int index=0;
+			for(int bitpos=0; bitpos<3; bitpos++) 
+			{
+				index|=getbit(data[byte],7-bit,2-bitpos);
+				bit++;
+				if(bit>7) 
+				{
+					bit=0;
+					byte++;
+				}
+			}
+			int windex = channels*(2*(ix+x+(iy+y)*width));
+#if !PGMOUT
+			if(formatSigned)
+			{
+				*(int16 *)&img[windex] = get16bits11signed(alpha,(table%16),(table/16),index);
+			}
+			else
+			{
+				*(uint16 *)&img[windex] = get16bits11bits(alpha,(table%16),(table/16),index);
+			}
+#else
+			//make data compatible with the .pgm format. See the comment in compressBlockAlpha16() for details.
+			uint16 uSixteen;
+			if (formatSigned)
+			{
+				//the pgm-format only allows unsigned images,
+				//so we add 2^15 to get a 16-bit value.
+				uSixteen = get16bits11signed(alpha,(table%16),(table/16),index) + 256*128;
+			}
+			else
+			{
+				uSixteen = get16bits11bits(alpha,(table%16),(table/16),index);
+			}
+			//byte swap for pgm
+			img[windex] = uSixteen/256;
+			img[windex+1] = uSixteen%256;
+#endif
+
+		}
+	}			
+}
+
+void decompressBlockAlpha16bit(uint8* data, uint8* img, int width, int height, int ix, int iy)
+{
+  decompressBlockAlpha16bitC(data, img, width, height, ix, iy, 1);
+}
diff --git a/KTX/lib/etcunpack.cxx b/KTX/lib/etcunpack.cxx
new file mode 100644
--- /dev/null
+++ b/KTX/lib/etcunpack.cxx
@@ -0,0 +1,296 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: d555f6fdc46c28e4db334b44fdb8ac5b4bcef91a $ */
+
+/* @internal
+ * @~English
+ * @file
+ *
+ * Unpack a texture compressed with ETC1
+ *
+ * @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.
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include "ktx.h"
+#include "ktxint.h"
+
+#if SUPPORT_SOFTWARE_ETC_UNPACK
+typedef unsigned int uint;
+typedef unsigned char uint8;
+
+extern void decompressBlockETC2c(uint block_part1, uint block_part2, uint8* img,
+								 int width, int height, int startx, int starty, int channels);
+extern void decompressBlockETC21BitAlphaC(uint block_part1, uint block_part2, uint8* img, uint8* alphaimg,
+										  int width, int height, int startx, int starty, int channels);
+extern void decompressBlockAlphaC(uint8* data, uint8* img,
+								  int width, int height, int startx, int starty, int channels);
+extern void decompressBlockAlpha16bitC(uint8* data, uint8* img,
+									   int width, int height, int startx, int starty, int channels);
+
+extern void setupAlphaTable();
+
+// This global variable affects the behaviour of decompressBlockAlpha16bitC.
+extern int formatSigned;
+
+static void
+readBigEndian4byteWord(khronos_uint32_t* pBlock, const GLubyte *s)
+{
+	*pBlock = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+}
+
+
+/* Unpack an ETC1_RGB8_OES format compressed texture */
+extern "C" KTX_error_code
+_ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat,
+			  khronos_uint32_t activeWidth, khronos_uint32_t activeHeight,
+			  GLubyte** dstImage,
+			  GLenum* format, GLenum* internalFormat, GLenum* type,
+			  GLint R16Formats, GLboolean supportsSRGB)
+{
+	unsigned int width, height;
+	unsigned int block_part1, block_part2;
+	unsigned int x, y;
+	/*const*/ GLubyte* src = (GLubyte*)srcETC;
+	// AF_11BIT is used to compress R11 & RG11 though its not alpha data.
+	enum {AF_NONE, AF_1BIT, AF_8BIT, AF_11BIT} alphaFormat = AF_NONE;
+	int dstChannels, dstChannelBytes;
+
+	switch (srcFormat) {
+	  case GL_COMPRESSED_SIGNED_R11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_SNORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 1;
+			formatSigned = GL_TRUE;
+			*internalFormat = GL_R16_SNORM;
+			*format = GL_RED;
+			*type = GL_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+		break;
+
+	  case GL_COMPRESSED_R11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_NORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 1;
+			formatSigned = GL_FALSE;
+			*internalFormat = GL_R16;
+			*format = GL_RED;
+			*type = GL_UNSIGNED_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_SIGNED_RG11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_SNORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 2;
+			formatSigned = GL_TRUE;
+			*internalFormat = GL_RG16_SNORM;
+			*format = GL_RG;
+			*type = GL_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_RG11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_NORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 2;
+			formatSigned = GL_FALSE;
+			*internalFormat = GL_RG16;
+			*format = GL_RG;
+			*type = GL_UNSIGNED_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_ETC1_RGB8_OES:
+	  case GL_COMPRESSED_RGB8_ETC2:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 3;
+		*internalFormat = GL_RGB8;
+		*format = GL_RGB;
+		*type = GL_UNSIGNED_BYTE;
+        break;
+
+	  case GL_COMPRESSED_RGBA8_ETC2_EAC:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 4;
+		*internalFormat = GL_RGBA8;
+		*format = GL_RGBA;
+		*type = GL_UNSIGNED_BYTE;
+	    alphaFormat = AF_8BIT;
+		break;
+
+	  case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 4;
+		*internalFormat = GL_RGBA8;
+		*format = GL_RGBA;
+		*type = GL_UNSIGNED_BYTE;
+		alphaFormat = AF_1BIT;
+        break;
+
+	  case GL_COMPRESSED_SRGB8_ETC2:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 3;
+			*internalFormat = GL_SRGB8;
+			*format = GL_RGB;
+			*type = GL_UNSIGNED_BYTE;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 4;
+			*internalFormat = GL_SRGB8_ALPHA8;
+ 			*format = GL_RGBA;
+			*type = GL_UNSIGNED_BYTE;
+			alphaFormat = AF_8BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+		break;
+
+	  case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 4;
+			*internalFormat = GL_SRGB8_ALPHA8;
+ 			*format = GL_RGBA;
+			*type = GL_UNSIGNED_BYTE;
+			alphaFormat = AF_1BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  default:
+	    assert(0); // Upper levels should be passing only one of the above srcFormats.
+	}
+
+    /* active_{width,height} show how many pixels contain active data,
+	 * (the rest are just for making sure we have a 2*a x 4*b size).
+	 */
+
+	/* Compute the full width & height. */
+	width = ((activeWidth+3)/4)*4;
+	height = ((activeHeight+3)/4)*4;
+
+	/* printf("Width = %d, Height = %d\n", width, height); */
+	/* printf("active pixel area: top left %d x %d area.\n", activeWidth, activeHeight); */
+
+	*dstImage = (GLubyte*)malloc(dstChannels*dstChannelBytes*width*height);
+	if (!*dstImage) {
+		return KTX_OUT_OF_MEMORY;
+	}
+	
+	if (alphaFormat != AF_NONE)
+		setupAlphaTable();
+
+	// NOTE: none of the decompress functions actually use the <height> parameter
+	if (alphaFormat == AF_11BIT) {
+		// One or two 11-bit alpha channels for R or RG.
+		for (y=0; y < height/4; y++) {
+			for (x=0; x < width/4; x++) {
+				decompressBlockAlpha16bitC(src, *dstImage, width, height, 4*x, 4*y, dstChannels);
+				src += 8;
+				if (srcFormat == GL_COMPRESSED_RG11_EAC || srcFormat == GL_COMPRESSED_SIGNED_RG11_EAC) {
+					decompressBlockAlpha16bitC(src, *dstImage + dstChannelBytes, width, height, 4*x, 4*y, dstChannels);
+					src += 8;
+				}
+			}
+		}
+	} else {
+		for (y=0; y < height/4; y++) {
+			for (x=0; x < width/4; x++) {
+				// Decode alpha channel for RGBA
+				if (alphaFormat == AF_8BIT) {
+					decompressBlockAlphaC(src, *dstImage + 3, width, height, 4*x, 4*y, dstChannels);
+					src += 8;
+				}
+				// Decode color dstChannels
+				readBigEndian4byteWord(&block_part1, src);
+				src += 4;
+				readBigEndian4byteWord(&block_part2, src);
+				src += 4;
+				if (alphaFormat == AF_1BIT)
+				    decompressBlockETC21BitAlphaC(block_part1, block_part2, *dstImage, 0, width, height, 4*x, 4*y, dstChannels);
+				else
+				    decompressBlockETC2c(block_part1, block_part2, *dstImage, width, height, 4*x, 4*y, dstChannels);
+			}
+		}
+	}
+
+	/* Ok, now write out the active pixels to the destination image.
+	 * (But only if the active pixels differ from the total pixels)
+	 */
+
+	if( !(height == activeHeight && width == activeWidth) ) {
+		int dstPixelBytes = dstChannels * dstChannelBytes;
+		int dstRowBytes = dstPixelBytes * width;
+		int activeRowBytes = activeWidth * dstPixelBytes;
+		GLubyte *newimg = (GLubyte*)malloc(dstPixelBytes * activeWidth * activeHeight);
+		unsigned int xx, yy;
+		int zz;
+
+		if (!newimg) {
+			free(*dstImage);
+			return KTX_OUT_OF_MEMORY;
+		}
+		
+		/* Convert from total area to active area: */
+
+		for (yy = 0; yy < activeHeight; yy++) {
+			for (xx = 0; xx < activeWidth; xx++) {
+				for (zz = 0; zz < dstPixelBytes; zz++) {
+					newimg[ yy*activeRowBytes + xx*dstPixelBytes + zz ] = (*dstImage)[ yy*dstRowBytes + xx*dstPixelBytes + zz];
+				}
+			}
+		}
+
+		free(*dstImage);
+		*dstImage = newimg;
+	}
+
+	return KTX_SUCCESS;
+}
+
+#endif /* SUPPORT_SOFTWARE_ETC_UNPACK */
diff --git a/KTX/lib/gl_funcptrs.h b/KTX/lib/gl_funcptrs.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/gl_funcptrs.h
@@ -0,0 +1,59 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 75ba91467bd9953cfa4bd4000f40e7ea6bf90d85 $ */
+
+/*
+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.
+*/
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef _GL_FUNCPTRS_H_
+#define _GL_FUNCPTRS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DECLARE_GL_FUNCPTRS
+
+/* remove this if you use GLEW and already have this */
+extern int GLEW_OES_compressed_ETC1_RGB8_texture;
+
+/* and make this macro empty */
+#define DECLARE_GL_EXTGLOBALS \
+	int GLEW_OES_compressed_ETC1_RGB8_texture = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GL_FUNCPTRS */
diff --git a/KTX/lib/gles1_funcptrs.h b/KTX/lib/gles1_funcptrs.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/gles1_funcptrs.h
@@ -0,0 +1,79 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 813736feacba1142549e915a1a8cfe27d2deafc4 $ */
+
+/*
+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.
+*/
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef _GLES1_FUNCPTRS_H_
+#define _GLES1_FUNCPTRS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs (GCC 4 complains of duplicate definitions) */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLGENERATEMIPMAPPROC) (GLenum target);
+
+/* remove these where already defined as functions */
+extern PFNGLTEXIMAGE1DPROC glTexImage1D;
+extern PFNGLTEXIMAGE3DPROC glTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D;
+extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
+
+/* and these */
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC glTexImage1D = 0; \
+    PFNGLTEXIMAGE3DPROC glTexImage3D = 0; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D = 0; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D = 0; \
+    PFNGLGENERATEMIPMAPPROC glGenerateMipmap = 0;
+
+/* remove this if you use GLEW and already have this */
+extern int GLEW_OES_compressed_ETC1_RGB8_texture;
+
+/* and make this macro empty */
+#define DECLARE_GL_EXTGLOBALS \
+	int GLEW_OES_compressed_ETC1_RGB8_texture = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES1_FUNCPTRS */
diff --git a/KTX/lib/gles2_funcptrs.h b/KTX/lib/gles2_funcptrs.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/gles2_funcptrs.h
@@ -0,0 +1,76 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 533eabaad64677e05ae09588f4279d46a32d776c $ */
+
+/*
+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.
+*/
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef _GLES2_FUNCPTRS_H_
+#define _GLES2_FUNCPTRS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs (GCC 4 complains of duplicate definitions) */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+
+/* remove these where already defined as functions */
+extern PFNGLTEXIMAGE1DPROC glTexImage1D;
+extern PFNGLTEXIMAGE3DPROC glTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D;
+
+/* and these */
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC glTexImage1D = 0; \
+    PFNGLTEXIMAGE3DPROC glTexImage3D = 0; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D = 0; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D = 0;
+
+/* remove this if you use GLEW and already have this */
+extern int GLEW_OES_compressed_ETC1_RGB8_texture;
+
+/* and make this macro empty */
+#define DECLARE_GL_EXTGLOBALS \
+	int GLEW_OES_compressed_ETC1_RGB8_texture = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES2_FUNCPTRS */
diff --git a/KTX/lib/gles3_funcptrs.h b/KTX/lib/gles3_funcptrs.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/gles3_funcptrs.h
@@ -0,0 +1,70 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 5e1de2ac1d62a6fa0112f5dca750ae1bd2aab126 $ */
+
+/*
+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.
+*/
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef _GLES3_FUNCPTRS_H_
+#define _GLES3_FUNCPTRS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs (GCC 4 complains of duplicate definitions) */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data);
+
+/* remove these where already defined as functions */
+extern PFNGLTEXIMAGE1DPROC glTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D;
+
+/* and these */
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC glTexImage1D = 0; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC glCompressedTexImage1D = 0;
+
+/* remove this if you use GLEW and already have this */
+extern int GLEW_OES_compressed_ETC1_RGB8_texture;
+
+/* and make this macro empty */
+#define DECLARE_GL_EXTGLOBALS \
+	int GLEW_OES_compressed_ETC1_RGB8_texture = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES3_FUNCPTRS */
diff --git a/KTX/lib/hashtable.c b/KTX/lib/hashtable.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/hashtable.c
@@ -0,0 +1,324 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 38d57d885272a48bc32e906349cc6b610c1453c0 $ */
+
+/**
+ * @file hashtable.c
+ * @~English
+ *
+ * @brief Functions for creating and using a hash table of key-value
+ *        pairs.
+ *
+ * @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.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "uthash.h"
+
+#include "ktx.h"
+#include "ktxint.h"
+
+
+/**
+ * @internal
+ * @struct _keyAndValue
+ * @brief Hash table entry structure
+ */
+typedef struct _keyAndValue {
+	unsigned int keyLen;	/*!< Length of the key */
+	char* key;				/*!< Pointer to key string */
+	unsigned int valueLen;	/*!< Length of the value */
+	void* value;			/*!< Pointer to the value */
+	UT_hash_handle hh;		/*!< handle used by UT hash */
+} key_and_value_t;
+/**
+ * @internal
+ * @typedef key_and_value_t
+ * @brief type of a hash table entry.
+ */
+
+/**
+ * @~English
+ * @brief Create an empty hash table for storying key-value pairs.
+ *
+ * @return pointer to the newly created hash table or NULL if there
+ *         is not enough memory.
+ *
+ *
+ */
+KTX_hash_table
+ktxHashTable_Create()
+{
+	key_and_value_t** kvt = (key_and_value_t**)malloc(sizeof (key_and_value_t**));
+	*kvt = NULL;
+	return (KTX_hash_table)kvt;
+}
+
+
+/**
+ * @~English
+ * @brief Destroy a hash table.
+ *
+ * All memory associated with the hash table and its keys and values
+ * is freed.
+ *
+ * @param [in] This pointer to the hash table to be destroyed.
+ */
+ /*
+ * @memberof ktxHashTable @public
+ */
+void
+ktxHashTable_Destroy(KTX_hash_table This)
+{
+	key_and_value_t* kv;
+
+	for(kv = *(key_and_value_t**)This; kv != NULL;) {
+		key_and_value_t* tmp = (key_and_value_t*)kv->hh.next;
+		HASH_DELETE(hh, /*head*/*(key_and_value_t**)This, kv);
+		free(kv);
+		kv = tmp;
+	}
+	free(This);
+}
+
+
+/**
+ * @~English
+ * @brief Adds a key value pair to a hash table
+ *
+ * @param [in] This		pointer to the target hash table.
+ * @param [in] key		pointer to the UTF8 NUL-terminated string to be used as the key.
+ * @param [in] valueLen	the number of bytes of data in @p value.
+ * @param [in] value	pointer to the bytes of data constituting the value.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p This, @p key or @p value are NULL, @p key is an
+ *            empty string or @p valueLen == 0.
+ */
+/*
+ * @memberof ktxHashTable @public
+ */
+KTX_error_code
+ktxHashTable_AddKVPair(KTX_hash_table This, const char* key, unsigned int valueLen, const void* value)
+{
+	if (This && key && value && valueLen != 0) {
+		int keyLen = strlen(key) + 1;
+		/* key_and_value_t* head = *(key_and_value_t**)This; */
+		key_and_value_t* kv;
+
+		if (keyLen == 1)
+			return KTX_INVALID_VALUE;	/* Empty string */
+
+		/* Allocate all the memory as a block */
+		kv = (key_and_value_t*)malloc(sizeof(key_and_value_t) + keyLen + valueLen);
+		/* Put key first */
+		kv->key = (char *)kv + sizeof(key_and_value_t);
+		kv->keyLen = keyLen;
+		/* then value */
+		kv->value = kv->key + keyLen;
+		kv->valueLen = valueLen;
+		memcpy(kv->key, key, keyLen);
+		memcpy(kv->value, value, valueLen);
+
+		HASH_ADD_KEYPTR( hh, /*head*/*(key_and_value_t**)This, kv->key, kv->keyLen-1, kv);
+		return KTX_SUCCESS;
+	} else
+		return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @~English
+ * @brief Looks up a key a hash table and returns the value.
+ *
+ * @param [in]     This			pointer to the target hash table.
+ * @param [in]     key			pointer to a UTF8 NUL-terminated string to find.
+ * @param [in,out] pValueLen	@p *pValueLen is set to the number of bytes of
+ *                              data in the returned value.
+ * @param [in,out] ppValue		@p *ppValue is set to the point to the value for
+ *							    @p key.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p This, @p key or @p pValueLen or @p ppValue
+ *                              is NULL.
+ * @exception KTX_NOT_FOUND     an entry matching @p key was not found.
+ */
+/*
+ * @memberof ktxHashTable @public
+ */
+KTX_error_code
+ktxHashTable_FindValue(KTX_hash_table This, const char* key, unsigned int* pValueLen, void** ppValue)
+{
+	if (This && key && pValueLen && ppValue) {
+		key_and_value_t* kv;
+		/* key_and_value_t* head = *(key_and_value_t**)This; */
+
+		HASH_FIND_STR( /*head*/*(key_and_value_t**)This, key, kv );  /* kv: output pointer */
+
+		if (kv) {
+			*pValueLen = kv->valueLen;
+			*ppValue = kv->value;
+			return KTX_SUCCESS;
+		} else
+			return KTX_NOT_FOUND;
+	} else
+		return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @~English
+ * @brief Serialize a hash table to a block of data suitable for writing
+ *        to a file.
+ *
+ * The caller is responsible for freeing the data block returned by this
+ * function.
+ *
+ * @param [in]     This			pointer to the target hash table.
+ * @param [in,out] pKvdLen		@p *pKvdLen is set to the number of bytes of
+ *                              data in the returned data block.
+ * @param [in,out] ppKvd		@p *ppKvd is set to the point to the block of
+ *                              memory containing the serialized data.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p This, @p pKvdLen or @p ppKvd is NULL.
+ * @exception KTX_OUT_OF_MEMORY there was not enough memory to serialize the data.
+ */
+ /* @memberof ktxHashTable @public
+ */
+KTX_error_code
+ktxHashTable_Serialize(KTX_hash_table This, unsigned int* pKvdLen, unsigned char** ppKvd)
+{
+
+	if (This && pKvdLen && ppKvd) {
+		key_and_value_t* kv;
+		unsigned int bytesOfKeyValueData = 0;
+		unsigned int keyValueLen;
+		unsigned char* sd;
+		char padding[4] = {0, 0, 0, 0};
+
+		for (kv = *(key_and_value_t**)This; kv != NULL; kv = kv->hh.next) {
+			/* sizeof(*sd) is to make space to write keyAndValueByteSize */
+			keyValueLen = kv->keyLen + kv->valueLen + sizeof(khronos_uint32_t);
+			/* Add valuePadding */
+			keyValueLen += 3 - ((keyValueLen + 3) % 4);
+			bytesOfKeyValueData += keyValueLen;
+		}
+		sd = malloc(bytesOfKeyValueData);
+		if (!sd)
+			return KTX_OUT_OF_MEMORY;
+
+		*pKvdLen = bytesOfKeyValueData;
+		*ppKvd = sd;
+
+		for (kv = *(key_and_value_t **)This; kv != NULL; kv = kv->hh.next) {
+			int padLen;
+
+			keyValueLen = kv->keyLen + kv->valueLen;
+			*(khronos_uint32_t*)sd = keyValueLen;
+			sd += sizeof(khronos_uint32_t);
+			memcpy(sd, kv->key, kv->keyLen);
+			sd += kv->keyLen;
+			memcpy(sd, kv->value, kv->valueLen);
+			sd += kv->valueLen;
+			padLen = 3 - ((keyValueLen + 3) % 4);
+			memcpy(sd, padding, padLen);
+			sd += padLen;
+		}
+		return KTX_SUCCESS;
+	} else
+		return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @~English
+ * @brief Create a hash table from a block of serialized key-value
+ *        data read from a file.
+ *
+ * The caller is responsible for freeing the returned hash table.
+ *
+ * @param [in]		kvdLen		the length of the serialized key-value data.
+ * @param [in]		pKvd		pointer to the serialized key-value data.
+ * @param [in,out]	pHt			@p *pHt is set to point to the created hash
+ *                              table.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p pKvd or @p pHt is NULL or kvdLen == 0.
+ * @exception KTX_OUT_OF_MEMORY there was not enough memory to create the hash
+ *                              table.
+ */
+ /* @memberof ktxHashTable @public
+ */
+KTX_error_code
+ktxHashTable_Deserialize(unsigned int kvdLen, void* pKvd, KTX_hash_table* pHt)
+{
+	KTX_hash_table kvt;
+	char* src = pKvd;
+
+	if (kvdLen == 0 || pKvd == NULL || pHt == NULL)
+		return KTX_INVALID_VALUE;
+
+	kvt = ktxHashTable_Create();
+	if (kvt == NULL)
+		return KTX_OUT_OF_MEMORY;
+
+	while (src < (char *)pKvd + kvdLen) {
+		char* key;
+		int keyLen;
+		void* value;
+		khronos_uint32_t keyAndValueByteSize = *((khronos_uint32_t*)src);
+
+		src += sizeof(keyAndValueByteSize);
+		key = src;
+		keyLen = strlen(key) + 1;
+		value = key + keyLen;
+
+		ktxHashTable_AddKVPair(kvt, key, keyAndValueByteSize - keyLen, value);
+		/* Round keyAndValueByteSize */
+		keyAndValueByteSize = (keyAndValueByteSize + 3) & ~(khronos_uint32_t)3;
+		src += keyAndValueByteSize;
+	}
+
+	*pHt = kvt;
+	return KTX_SUCCESS;
+}
+
+
diff --git a/KTX/lib/ktxfilestream.c b/KTX/lib/ktxfilestream.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxfilestream.c
@@ -0,0 +1,154 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/**
+ * @file
+ * @~English
+ *
+ * @brief FImplementation of ktxStream for FILE.
+ *
+ * @author Maksim Kolesin, Under Development
+ * @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.
+*/
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "KHR/khrplatform.h"
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxfilestream.h"
+
+/**
+ * @internal
+ * @~English
+ * @brief Read bytes from a ktxFileStream.
+ *
+ * @param [out] dst           pointer to a block of memory with a size
+			   of at least @p size bytes, converted to a void*.
+ * @param [in] size          total size of bytes to be read.
+ * @param [in] src           pointer to a FILE object, converted to a void*, that specifies an input stream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p src is @c NULL.
+ * @exception KTX_UNEXPECTED_END_OF_FILE the file does not contain the expected amount of data.
+ */
+static
+KTX_error_code ktxFileStream_read(void* dst, const GLsizei size, void* src)
+{
+	if (!dst || !src)
+		return KTX_INVALID_VALUE;
+
+	if (fread(dst, size, 1, (FILE*)src) != 1)
+		return KTX_UNEXPECTED_END_OF_FILE;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Skip bytes in a ktxFileStream.
+ *
+ * @param [in] count         number of bytes to be skipped.
+ * @param [in] src           pointer to a FILE object, converted to a void*, that specifies an input stream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p count is less than zero.
+ * @exception KTX_UNEXPECTED_END_OF_FILE the file does not contain the expected amount of data.
+ */
+static
+KTX_error_code ktxFileStream_skip(const GLsizei count, void* src)
+{
+	if (!src || (count < 0))
+		return KTX_INVALID_VALUE;
+
+	if (fseek((FILE*)src, count, SEEK_CUR) != 0)
+		return KTX_UNEXPECTED_END_OF_FILE;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Write bytes to a ktxFileStream.
+ *
+ * @param [in] src           pointer to the array of elements to be written, converted to a const void*.
+ * @param [in] size          size in bytes of each element to be written.
+ * @param [in] count         number of elements, each one with a @p size of size bytes.
+ * @param [out] dst          pointer to a FILE object, converted to a void*, that specifies an output stream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p src is @c NULL.
+ * @exception KTX_FILE_WRITE_ERROR a system error occurred while writing the file.
+ */
+static
+KTX_error_code ktxFileStream_write(const void *src, const GLsizei size, const GLsizei count, void* dst)
+{
+	if (!dst || !src)
+		return KTX_INVALID_VALUE;
+
+	if (fwrite(src, size, count, (FILE*)dst) != count)
+		return KTX_FILE_WRITE_ERROR;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Initializes a ktxFileStream.
+ *
+ * @param [in] stream
+ * @param [in] file
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE @p stream is @c NULL or @p file is @c NULL.
+ */
+KTX_error_code ktxFileInit(struct ktxStream* stream, FILE* file)
+{
+	if (!stream || !file)
+		return KTX_INVALID_VALUE;
+
+	stream->src = (void*)file;
+	stream->read = ktxFileStream_read;
+	stream->skip = ktxFileStream_skip;
+	stream->write = ktxFileStream_write;
+
+	return KTX_SUCCESS;
+}
diff --git a/KTX/lib/ktxfilestream.h b/KTX/lib/ktxfilestream.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxfilestream.h
@@ -0,0 +1,51 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/*
+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.
+*/
+
+/*
+ * Author: Maksim Kolesin from original code
+ * by Mark Callow and Georg Kolling
+ */
+
+#ifndef _KTXFILESTREAM_H_
+#define _KTXFILESTREAM_H_
+
+#include "KHR/khrplatform.h"
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxstream.h"
+
+/*
+ * ktxFileInit: Initialize a ktxStream to a ktxFileStream with a FILE object
+ */
+KTX_error_code ktxFileInit(struct ktxStream* stream, FILE* file);
+
+#endif /* _KTXFILESTREAM_H_ */
diff --git a/KTX/lib/ktxint.h b/KTX/lib/ktxint.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxint.h
@@ -0,0 +1,327 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: a694f52e03818dfba0de386459aa63031b23d3ea $ */
+
+/*
+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.
+*/
+
+/* 
+ * Author: Mark Callow from original code by Georg Kolling
+ */
+
+#ifndef _KTXINT_H_
+#define _KTXINT_H_
+
+/* Define this to include the ETC unpack software in the library. */
+#ifndef SUPPORT_SOFTWARE_ETC_UNPACK
+  /* Include for all GL versions because have seen OpenGL ES 3
+   * implementaions that do not support ETC1 (ARM Mali emulator v1.0)!
+   */
+  #define SUPPORT_SOFTWARE_ETC_UNPACK 1
+#endif
+
+#ifndef SUPPORT_LEGACY_FORMAT_CONVERSION
+  #if KTX_OPENGL
+    #define SUPPORT_LEGACY_FORMAT_CONVERSION 1
+  #elif KTX_OPENGL_ES1
+    /* ES1, ES2 & ES3 support the legacy formats */
+    #define SUPPORT_LEGACY_FORMAT_CONVERSION 0
+  #endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define KTX_IDENTIFIER_REF  { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }
+#define KTX_ENDIAN_REF      (0x04030201)
+#define KTX_ENDIAN_REF_REV  (0x01020304)
+#define KTX_HEADER_SIZE		(64)
+
+/**
+ * @internal
+ * @brief KTX file header
+ *
+ * See the KTX specification for descriptions
+ * 
+ */
+typedef struct KTX_header_t {
+	khronos_uint8_t  identifier[12];
+	khronos_uint32_t endianness;
+	khronos_uint32_t glType;
+	khronos_uint32_t glTypeSize;
+	khronos_uint32_t glFormat;
+	khronos_uint32_t glInternalFormat;
+	khronos_uint32_t glBaseInternalFormat;
+	khronos_uint32_t pixelWidth;
+	khronos_uint32_t pixelHeight;
+	khronos_uint32_t pixelDepth;
+	khronos_uint32_t numberOfArrayElements;
+	khronos_uint32_t numberOfFaces;
+	khronos_uint32_t numberOfMipmapLevels;
+	khronos_uint32_t bytesOfKeyValueData;
+} KTX_header;
+
+/* This will cause compilation to fail if the struct size doesn't match */
+typedef int KTX_header_SIZE_ASSERT [sizeof(KTX_header) == KTX_HEADER_SIZE];
+
+/**
+ * @internal
+ * @brief _ktxCheckHeader returns texture information in this structure
+ *
+ * TO DO: document properly
+ */
+typedef struct KTX_texinfo_t {
+	/* Data filled in by _ktxCheckHeader() */
+	khronos_uint32_t textureDimensions;
+	khronos_uint32_t glTarget;
+	khronos_uint32_t compressed;
+	khronos_uint32_t generateMipmaps;
+} KTX_texinfo;
+
+/**
+ * @internal
+ * @brief used to pass GL context capabilites to subroutines.
+ */
+#define _KTX_NO_R16_FORMATS     0x0
+#define _KTX_R16_FORMATS_NORM	0x1
+#define _KTX_R16_FORMATS_SNORM	0x2
+#define _KTX_ALL_R16_FORMATS (_KTX_R16_FORMATS_NORM | _KTX_R16_FORMATS_SNORM)
+extern GLint _ktxR16Formats;
+extern GLboolean _ktxSupportsSRGB;
+
+
+/*
+ * These defines are needed to compile the KTX library. When
+ * these things are not available in the GL header in use at
+ * compile time, the library provides its own support, handles
+ * the expected run-time errors or just needs the token value.
+ */
+#ifndef GL_LUMINANCE
+#define GL_ALPHA						0x1906
+#define GL_LUMINANCE					0x1909
+#define GL_LUMINANCE_ALPHA				0x190A
+#endif
+#ifndef GL_INTENSITY
+#define GL_INTENSITY					0x8049
+#endif
+#if SUPPORT_LEGACY_FORMAT_CONVERSION
+/* For loading legacy KTX files. */
+#ifndef GL_LUMINANCE4
+#define GL_ALPHA4						0x803B
+#define GL_ALPHA8						0x803C
+#define GL_ALPHA12						0x803D
+#define GL_ALPHA16						0x803E
+#define GL_LUMINANCE4					0x803F
+#define GL_LUMINANCE8					0x8040
+#define GL_LUMINANCE12					0x8041
+#define GL_LUMINANCE16					0x8042
+#define GL_LUMINANCE4_ALPHA4			0x8043
+#define GL_LUMINANCE6_ALPHA2			0x8044
+#define GL_LUMINANCE8_ALPHA8			0x8045
+#define GL_LUMINANCE12_ALPHA4			0x8046
+#define GL_LUMINANCE12_ALPHA12			0x8047
+#define GL_LUMINANCE16_ALPHA16			0x8048
+#endif
+#ifndef GL_INTENSITY4
+#define GL_INTENSITY4					0x804A
+#define GL_INTENSITY8					0x804B
+#define GL_INTENSITY12					0x804C
+#define GL_INTENSITY16					0x804D
+#endif
+#ifndef GL_SLUMINANCE
+#define GL_SLUMINANCE_ALPHA				0x8C44
+#define GL_SLUMINANCE8_ALPHA8			0x8C45
+#define GL_SLUMINANCE					0x8C46
+#define GL_SLUMINANCE8					0x8C47
+#endif
+#endif /* SUPPORT_LEGACY_FORMAT_CONVERSION */
+#ifndef GL_TEXTURE_1D
+#define GL_TEXTURE_1D                   0x0DE0
+#endif
+#ifndef GL_TEXTURE_3D
+#define GL_TEXTURE_3D                   0x806F
+#endif
+#ifndef GL_TEXTURE_CUBE_MAP
+#define GL_TEXTURE_CUBE_MAP             0x8513
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_X  0x8515
+#endif
+/* from GL_EXT_texture_array */
+#ifndef GL_TEXTURE_1D_ARRAY_EXT
+#define GL_TEXTURE_1D_ARRAY_EXT         0x8C18
+#define GL_TEXTURE_2D_ARRAY_EXT         0x8C1A
+#endif
+#ifndef GL_GENERATE_MIPMAP
+#define GL_GENERATE_MIPMAP              0x8191
+#endif
+
+/* For writer.c */
+#if !defined(GL_BGR)
+#define GL_BGR							0x80E0
+#define GL_BGRA							0x80E1
+#endif
+#if !defined(GL_RED_INTEGER)
+#define GL_RED_INTEGER					0x8D94
+#define GL_RGB_INTEGER					0x8D98
+#define GL_RGBA_INTEGER					0x8D99
+#endif
+#if !defined(GL_GREEN_INTEGER)
+#define GL_GREEN_INTEGER				0x8D95
+#define GL_BLUE_INTEGER					0x8D96
+#define GL_ALPHA_INTEGER				0x8D97
+#endif
+#if !defined (GL_BGR_INTEGER)
+#define GL_BGR_INTEGER					0x8D9A
+#define GL_BGRA_INTEGER					0x8D9B
+#endif
+#if !defined(GL_INT)
+#define GL_INT 0x1404
+#define GL_UNSIGNED_INT 0x1405
+#endif
+#if !defined(GL_HALF_FLOAT)
+typedef unsigned short GLhalf;
+#define GL_HALF_FLOAT					0x140B
+#endif
+#if !defined(GL_UNSIGNED_BYTE_3_3_2)
+#define GL_UNSIGNED_BYTE_3_3_2			0x8032
+#define GL_UNSIGNED_INT_8_8_8_8			0x8035
+#define GL_UNSIGNED_INT_10_10_10_2		0x8036
+#endif
+#if !defined(GL_UNSIGNED_BYTE_2_3_3_REV)
+#define GL_UNSIGNED_BYTE_2_3_3_REV		0x8362
+#define GL_UNSIGNED_SHORT_5_6_5			0x8363
+#define GL_UNSIGNED_SHORT_5_6_5_REV		0x8364
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV	0x8365
+#define GL_UNSIGNED_SHORT_1_5_5_5_REV	0x8366
+#define GL_UNSIGNED_INT_8_8_8_8_REV		0x8367
+#define GL_UNSIGNED_INT_2_10_10_10_REV	0x8368
+#endif
+#if !defined(GL_UNSIGNED_INT_24_8)
+#define GL_DEPTH_STENCIL				0x84F9
+#define GL_UNSIGNED_INT_24_8			0x84FA
+#endif
+#if !defined(GL_UNSIGNED_INT_5_9_9_9_REV)
+#define GL_UNSIGNED_INT_5_9_9_9_REV		0x8C3E
+#endif
+#if !defined(GL_UNSIGNED_INT_10F_11F_11F_REV)
+#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
+#endif
+#if !defined (GL_FLOAT_32_UNSIGNED_INT_24_8_REV)
+#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV	0x8DAD
+#endif
+
+#ifndef GL_ETC1_RGB8_OES
+#define GL_ETC1_RGB8_OES				0x8D64
+#endif
+
+#if SUPPORT_SOFTWARE_ETC_UNPACK
+#ifndef GL_COMPRESSED_R11_EAC
+#define GL_COMPRESSED_R11_EAC                            0x9270
+#define GL_COMPRESSED_SIGNED_R11_EAC                     0x9271
+#define GL_COMPRESSED_RG11_EAC                           0x9272
+#define GL_COMPRESSED_SIGNED_RG11_EAC                    0x9273
+#define GL_COMPRESSED_RGB8_ETC2                          0x9274
+#define GL_COMPRESSED_SRGB8_ETC2                         0x9275
+#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2      0x9276
+#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2     0x9277
+#define GL_COMPRESSED_RGBA8_ETC2_EAC                     0x9278
+#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC				 0x9279
+#endif
+#ifndef GL_R16_SNORM
+#define GL_R16_SNORM					0x8F98
+#define GL_RG16_SNORM					0x8F99
+#endif
+#ifndef GL_RED
+#define GL_RED							0x1903
+#define GL_GREEN						0x1904
+#define GL_BLUE							0x1905
+#define GL_RG							0x8227
+#define GL_RG_INTEGER					0x8228
+#endif
+#ifndef GL_R16
+#define GL_R16							0x822A
+#define GL_RG16							0x822C
+#endif
+#ifndef GL_RGB8
+#define GL_RGB8                         0x8051
+#define GL_RGBA8                        0x8058
+#endif
+#ifndef GL_SRGB8
+#define GL_SRGB8						0x8C41
+#define GL_SRGB8_ALPHA8					0x8C43
+#endif
+#endif
+
+#ifndef GL_MAJOR_VERSION
+#define GL_MAJOR_VERSION                0x821B
+#define GL_MINOR_VERSION                0x821C
+#endif
+
+#ifndef GL_CONTEXT_PROFILE_MASK
+#define GL_CONTEXT_PROFILE_MASK				 0x9126
+#define GL_CONTEXT_CORE_PROFILE_BIT			 0x00000001
+#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
+#endif
+
+#ifndef MAX
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#endif
+
+/* CheckHeader
+ * 
+ * Reads the KTX file header and performs some sanity checking on the values
+ */
+KTX_error_code _ktxCheckHeader(KTX_header* header, KTX_texinfo* texinfo);
+
+/*
+ * SwapEndian16: Swaps endianness in an array of 16-bit values
+ */
+void _ktxSwapEndian16(khronos_uint16_t* pData16, int count);
+
+/*
+ * SwapEndian32: Swaps endianness in an array of 32-bit values
+ */
+void _ktxSwapEndian32(khronos_uint32_t* pData32, int count);
+
+/*
+ * UncompressETC: uncompresses an ETC compressed texture image
+ */
+KTX_error_code _ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat,
+							 khronos_uint32_t active_width, khronos_uint32_t active_height,
+							 GLubyte** dstImage,
+							 GLenum* format, GLenum* internalFormat, GLenum* type,
+							 GLint R16Formats, GLboolean supportsSRGB);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _KTXINT_H_ */
diff --git a/KTX/lib/ktxmemstream.c b/KTX/lib/ktxmemstream.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxmemstream.c
@@ -0,0 +1,225 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/**
+ * @file
+ * @~English
+ *
+ * @brief Implementation of ktxStream for memory.
+ *
+ * @author Maksim Kolesin, Under Development
+ * @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.
+*/
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "KHR/khrplatform.h"
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxmemstream.h"
+
+/**
+ * @internal
+ * @~English
+ * @brief Expand a ktxMem to fit to a newsize.
+ *
+ * @param [in] mem           pointer to ktxMem struct to expand.
+ * @param [in] newsize       minimum new size required.
+ *
+ * @return      KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
+ *
+ * @exception KTX_OUT_OF_MEMORY        System failed to allocate sufficient memory.
+ */
+static
+KTX_error_code ktxMem_expand(struct ktxMem *mem, const GLsizei newsize)
+{
+	GLsizei new_alloc_size = mem->alloc_size;
+	while (new_alloc_size < newsize)
+		new_alloc_size <<= 1;
+
+	if (new_alloc_size == mem->alloc_size)
+		return KTX_SUCCESS;
+
+	mem->bytes = (unsigned char*)realloc(mem->bytes, new_alloc_size);
+	if(!mem->bytes)
+	{
+		mem->alloc_size = 0;
+		mem->used_size = 0;
+		return KTX_OUT_OF_MEMORY;
+	}
+
+	mem->alloc_size = new_alloc_size;
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Read bytes from a ktxMemStream.
+ *
+ * @param [out] dst          pointer to memory where to copy read bytes.
+ * @param [in] count         number of bytes to read.
+ * @param [in] src           pointer to ktxMem struct, converted to a void*, that specifies an input stream.
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE        @p dst is @c NULL or @p mem is @c NULL or not sufficient data is available in ktxMem.
+ */
+static
+KTX_error_code ktxMemStream_read(void* dst, const GLsizei count, void* src)
+{
+	struct ktxMem* mem = (struct ktxMem*)src;
+
+	if(!dst || !mem || (mem->pos + count > mem->used_size) || (mem->pos + count < mem->pos))
+		return KTX_INVALID_VALUE;
+
+	memcpy(dst, mem->bytes + mem->pos, count);
+	mem->pos += count;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Skip bytes in a ktxFileStream.
+ *
+ * @param [in] count         number of bytes to skip.
+ * @param [in] src           pointer to a ktxMem struct, converted to a void*, that specifies an input stream.
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE        @p mem is @c NULL or not sufficient data is available in ktxMem.
+ */
+static
+KTX_error_code ktxMemStream_skip(const GLsizei count, void* src)
+{
+	struct ktxMem* mem = (struct ktxMem*)src;
+
+	if(!mem || (mem->pos + count > mem->used_size) || (mem->pos + count < mem->pos))
+		return KTX_INVALID_VALUE;
+
+	mem->pos += count;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Write bytes to a ktxFileStream.
+ *
+ * @param [in] src           pointer to the array of elements to be written, converted to a const void*.
+ * @param [in] size          size in bytes of each element to be written.
+ * @param [in] count         number of elements, each one with a @p size of size bytes.
+ * @param [out] dst          pointer to a ktxMem struct, converted to a void*, that specifies an output stream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE        @p dst is @c NULL or @p mem is @c NULL.
+ * @exception KTX_OUT_OF_MEMORY        See ktxMem_expand() for causes.
+ */
+static
+KTX_error_code ktxMemStream_write(const void* src, const GLsizei size, const GLsizei count, void* dst)
+{
+	struct ktxMem* mem = (struct ktxMem*)dst;
+	KTX_error_code rc = KTX_SUCCESS;
+
+	if(!dst || !mem)
+		return KTX_INVALID_VALUE;
+
+	if(mem->alloc_size < mem->used_size + size*count)
+	{
+		rc = ktxMem_expand(mem, mem->used_size + size*count);
+		if(rc != KTX_SUCCESS)
+			return rc;
+	}
+
+	memcpy(mem->bytes + mem->used_size, src, size*count);
+	mem->used_size += size*count;
+
+	return KTX_SUCCESS;
+}
+
+/**
+ * @brief Default allocation size for a ktxMemStream.
+ */
+#define KTX_MEM_DEFAULT_ALLOCATED_SIZE 256
+
+/**
+ * @internal
+ * @~English
+ * @brief Initialize a ktxMemStream.
+ *
+ * @param [in] stream        pointer to a ktxStream struct to initialize.
+ * @param [in] mem           pointer to a ktxMem struct to use in ktxMemStream.
+ * @param [in] bytes         pointer to an array of bytes to use as initial data.
+ * @param [in] size          size of array of initial data for ktxMemStream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE        @p stream is @c NULL or @p mem is @c NULL or @p size is less than 0.
+ * @exception KTX_OUT_OF_MEMORY        system failed to allocate sufficient memory.
+ */
+KTX_error_code ktxMemInit(struct ktxStream* stream, struct ktxMem* mem, const void* bytes, GLsizei size)
+{
+	if (!stream || !mem || size < 0)
+		return KTX_INVALID_VALUE;
+
+	if(!bytes)
+	{
+		if (size == 0)
+			size = KTX_MEM_DEFAULT_ALLOCATED_SIZE;
+		mem->bytes = (unsigned char*)malloc(size);
+		if (!mem->bytes)
+			return KTX_OUT_OF_MEMORY;
+		mem->alloc_size = size;
+		mem->used_size = 0;
+		mem->pos = 0;
+	}
+	else
+	{
+		mem->bytes = (unsigned char*)bytes;
+		mem->used_size = size;
+		mem->alloc_size = size;
+		mem->pos = 0;
+	}
+
+	stream->src = mem;
+	stream->read = ktxMemStream_read;
+	stream->skip = ktxMemStream_skip;
+	stream->write = ktxMemStream_write;
+
+	return KTX_SUCCESS;
+}
diff --git a/KTX/lib/ktxmemstream.h b/KTX/lib/ktxmemstream.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxmemstream.h
@@ -0,0 +1,64 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/*
+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.
+*/
+
+/*
+ * Author: Maksim Kolesin from original code
+ * by Mark Callow and Georg Kolling
+ */
+
+#ifndef _KTXMEMSTREAM_H_
+#define _KTXMEMSTREAM_H_
+
+#include "KHR/khrplatform.h"
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxstream.h"
+
+/**
+ * @internal
+ * @brief Structure to store information
+ *    about data allocated for ktxMemStream
+ */
+struct ktxMem
+{
+	unsigned char* bytes;
+	GLsizei alloc_size;
+	GLsizei used_size;
+	GLsizei pos;
+};
+
+/*
+ * ktxMemInit: Initialize a ktxStream to a ktxMemStream with ktxMem struct and/or array of bytes
+ */
+KTX_error_code ktxMemInit(struct ktxStream* stream, struct ktxMem* mem, const void* bytes, GLsizei size);
+
+#endif /* _KTXMEMSTREAM_H_ */
diff --git a/KTX/lib/ktxstream.h b/KTX/lib/ktxstream.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/ktxstream.h
@@ -0,0 +1,84 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/*
+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.
+*/
+
+/*
+ * Author: Maksim Kolesin from original code
+ * by Mark Callow and Georg Kolling
+ */
+
+#ifndef _KTXSTREAM_H_
+#define _KTXSTREAM_H_
+
+#include "ktx.h"
+
+/* @private is not preventing the typedefs, structs and defines from
+ * appearing in the Doxygen output even though EXTRACT_PRIVATE is NO
+ * in the config file. To prevent these items appearing I have changed
+ * the special comments to ordinary comments, and have set
+ * HIDE_UNDOC_MEMBERS = YES in the Doxygen config file.
+ *
+ * Items declared "static" are omitted, as expected, due to EXTRACT_STATIC
+ * being NO, so there is no need to convert those to ordinary comments.
+ */
+/*
+ * @private
+ * @~English
+ * @brief type for a pointer to a stream reading function
+ */
+typedef KTX_error_code(*ktxStream_read)(void* dst, const GLsizei count, void* src);
+/*
+ * @private
+ * @~English
+ * @brief type for a pointer to a stream skipping function
+ */
+typedef KTX_error_code(*ktxStream_skip)(const GLsizei count, void* src);
+/*
+ * @private
+ * @~English
+ * @brief type for a pointer to a stream reading function
+ */
+typedef KTX_error_code(*ktxStream_write)(const void *src, const GLsizei size, const GLsizei count, void* dst);
+
+/*
+ * @private
+ * @~English
+ * @brief KTX stream interface
+ */
+struct ktxStream
+{
+	void* src;                              /**< pointer to the stream source */
+	ktxStream_read read;    /**< pointer to function for reading bytes */
+	ktxStream_skip skip;    /**< pointer to function for skipping bytes */
+	ktxStream_write write;  /**< pointer to function for writing bytes */
+};
+
+#endif /* _KTXSTREAM_H_ */
diff --git a/KTX/lib/loader.c b/KTX/lib/loader.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/loader.c
@@ -0,0 +1,855 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 357385446ea454dcee3c9ce6d663723a3bcdc7b3 $ */
+
+/**
+ * @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.
+*/
+
+#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
+
+/* @private is not preventing the typedefs, structs and defines from
+ * appearing in the Doxygen output even though EXTRACT_PRIVATE is NO
+ * in the config file. To prevent these items appearing I have changed
+ * the special comments to ordinary comments, and have set
+ * HIDE_UNDOC_MEMBERS = YES in the Doxygen config file.
+ *
+ * Items declared "static" are omitted, as expected, due to EXTRACT_STATIC
+ * being NO, so there is no need to convert those to ordinary comments.
+ */
+/*
+ * @private
+ * @~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
+
+/*
+ * @private
+ * @~English
+ * @name Supported Sized Format Macros
+ *
+ * These macros describe values that may be used with the sizedFormats
+ * variable.
+ */
+/**@{*/
+#define _NON_LEGACY_FORMATS 0x1 /*< @private @internal non-legacy sized formats are supported. */
+#define _LEGACY_FORMATS 0x2  /*< @private @internal legacy sized formats are supported. */
+/*
+ * @private
+ * @~English
+ * @brief all sized formats are supported
+ */
+#define _ALL_SIZED_FORMATS (_NON_LEGACY_FORMATS | _LEGACY_FORMATS)
+#define _NO_SIZED_FORMATS 0 /*< @private @internal no sized formats are supported. */
+/**@}*/
+
+/**
+ * @private
+ * @~English
+ * @brief indicates the profile of the current context.
+ */
+static GLint contextProfile = 0;
+/**
+ * @private
+ * @~English
+ * @brief indicates what sized texture formats are supported
+ *        by the current context.
+ */
+static GLint sizedFormats = _ALL_SIZED_FORMATS;
+static GLboolean supportsSwizzle = GL_TRUE;
+/**
+ * @private
+ * @~English
+ * @brief indicates which R16 & RG16 formats are supported by the current context.
+ */
+static GLint R16Formats = _KTX_ALL_R16_FORMATS;
+/**
+ * @private
+ * @~English
+ * @brief indicates if the current context supports sRGB textures.
+ */
+static GLboolean supportsSRGB = GL_TRUE;
+
+/**
+ * @private
+ * ~English
+ * @brief workaround mismatch of glGetString declaration and standard string
+ *        function parameters.
+ */
+#define glGetString(x) (const char*)glGetString(x)
+
+/**
+ * @private
+ * @~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;
+
+	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 (strstr(glGetString(GL_EXTENSIONS), "GL_OES_required_internalformat") != NULL) {
+			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;
+		} else {
+			// < 3.2
+			contextProfile = GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
+			supportsSwizzle = GL_FALSE;
+			// sRGB textures introduced in 2.0
+			if (majorVersion < 2 && strstr(glGetString(GL_EXTENSIONS), "GL_EXT_texture_sRGB") == NULL) {
+				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 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.
+ *
+ * This function 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 that 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((long)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);
+
+	// Prefer glGenerateMipmaps over GL_GENERATE_MIPMAP
+	if (texinfo.generateMipmaps && (glGenerateMipmap == NULL)) {
+		glTexParameteri(texinfo.glTarget, GL_GENERATE_MIPMAP, GL_TRUE);
+	}
+
+	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) {
+				if (texinfo.compressed) {
+					glCompressedTexImage1D(texinfo.glTarget + face, level,
+						glInternalFormat, pixelWidth, 0,
+						faceLodSize, data);
+				} else {
+					glTexImage1D(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 {
+					glTexImage2D(texinfo.glTarget + face, level,
+						glInternalFormat, pixelWidth, pixelHeight, 0,
+						glFormat, header.glType, data);
+				}
+			} else if (texinfo.textureDimensions == 3) {
+				if (header.numberOfArrayElements) {
+					pixelDepth = header.numberOfArrayElements;
+				}
+				if (texinfo.compressed) {
+					glCompressedTexImage3D(texinfo.glTarget + face, level,
+						glInternalFormat, pixelWidth, pixelHeight, pixelDepth, 0,
+						faceLodSize, data);
+				} else {
+					glTexImage3D(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 && glGenerateMipmap) {
+			glGenerateMipmap(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;
+		}
+        if (!texinfo.generateMipmaps) {
+            glTexParameteri(texinfo.glTarget, GL_TEXTURE_MAX_LEVEL, header.numberOfMipmapLevels - 1);
+        }
+	} 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.
+ *
+ * This function 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 that 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);
+}
+
+
diff --git a/KTX/lib/swap.c b/KTX/lib/swap.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/swap.c
@@ -0,0 +1,65 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 2319952fa702d4c9a8e3c02868541b1d7cff9aa0 $ */
+
+/*
+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.
+*/
+
+#include "KHR/khrplatform.h"
+
+/*
+ * SwapEndian16: Swaps endianness in an array of 16-bit values
+ */
+void
+_ktxSwapEndian16(khronos_uint16_t* pData16, int count)
+{
+	int i;
+	for (i = 0; i < count; ++i)
+	{
+		khronos_uint16_t x = *pData16;
+		*pData16++ = (x << 8) | (x >> 8);
+	}
+}
+
+/*
+ * SwapEndian32: Swaps endianness in an array of 32-bit values
+ */
+void 
+_ktxSwapEndian32(khronos_uint32_t* pData32, int count)
+{
+	int i;
+	for (i = 0; i < count; ++i)
+	{
+		khronos_uint32_t x = *pData32;
+		*pData32++ = (x << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | (x >> 24);
+	}
+}
+
+
diff --git a/KTX/lib/uthash.h b/KTX/lib/uthash.h
new file mode 100644
--- /dev/null
+++ b/KTX/lib/uthash.h
@@ -0,0 +1,960 @@
+/*
+Copyright (c) 2003-2010, Troy D. Hanson     http://uthash.sourceforge.net
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H 
+
+#include <string.h>   /* memcmp,strlen */
+#include <stddef.h>   /* ptrdiff_t */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#ifdef _MSC_VER         /* MS compiler */
+#if _MSC_VER >= 1600 && __cplusplus  /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#endif
+#else                   /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  char **_da_dst = (char**)(&(dst));                                             \
+  *_da_dst = (char*)(src);                                                       \
+} while(0)
+#else 
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  (dst) = DECLTYPE(dst)(src);                                                    \
+} while(0)
+#endif
+
+/* a number of the hash function use uint32_t which isn't defined on win32 */
+#ifdef _MSC_VER
+typedef unsigned int uint32_t;
+#else
+#include <inttypes.h>   /* uint32_t */
+#endif
+
+#define UTHASH_VERSION 1.9.1
+
+#define uthash_fatal(msg) exit(-1)        /* fatal error (out of memory,etc) */
+#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */
+#define uthash_free(ptr) free(ptr)        /* free fcn                        */
+
+#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */
+#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 32      /* initial number of buckets        */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5  /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 10      /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhe */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+
+#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \
+do {                                                                             \
+  unsigned _hf_bkt,_hf_hashv;                                                    \
+  out=NULL;                                                                      \
+  if (head) {                                                                    \
+     HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt);   \
+     if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) {                           \
+       HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ],  \
+                        keyptr,keylen,out);                                      \
+     }                                                                           \
+  }                                                                              \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0)
+#define HASH_BLOOM_MAKE(tbl)                                                     \
+do {                                                                             \
+  (tbl)->bloom_nbits = HASH_BLOOM;                                               \
+  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \
+  if (!((tbl)->bloom_bv))  { uthash_fatal( "out of memory"); }                   \
+  memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN);                                \
+  (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                       \
+} while (0);
+
+#define HASH_BLOOM_FREE(tbl)                                                     \
+do {                                                                             \
+  uthash_free((tbl)->bloom_bv);                                                  \
+} while (0);
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8)))
+
+#define HASH_BLOOM_ADD(tbl,hashv)                                                \
+  HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#define HASH_BLOOM_TEST(tbl,hashv)                                               \
+  HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl) 
+#define HASH_BLOOM_FREE(tbl) 
+#define HASH_BLOOM_ADD(tbl,hashv) 
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#endif
+
+#define HASH_MAKE_TABLE(hh,head)                                                 \
+do {                                                                             \
+  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(                                \
+                  sizeof(UT_hash_table));                                        \
+  if (!((head)->hh.tbl))  { uthash_fatal( "out of memory"); }                    \
+  memset((head)->hh.tbl, 0, sizeof(UT_hash_table));                              \
+  (head)->hh.tbl->tail = &((head)->hh);                                          \
+  (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                        \
+  (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;              \
+  (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                    \
+  (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                      \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); }             \
+  memset((head)->hh.tbl->buckets, 0,                                             \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  HASH_BLOOM_MAKE((head)->hh.tbl);                                               \
+  (head)->hh.tbl->signature = HASH_SIGNATURE;                                    \
+} while(0)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \
+        HASH_ADD_KEYPTR(hh,head,&add->fieldname,keylen_in,add)
+ 
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \
+do {                                                                             \
+ unsigned _ha_bkt;                                                               \
+ (add)->hh.next = NULL;                                                          \
+ (add)->hh.key = (char*)keyptr;                                                  \
+ (add)->hh.keylen = keylen_in;                                                   \
+ if (!(head)) {                                                                  \
+    head = (add);                                                                \
+    (head)->hh.prev = NULL;                                                      \
+    HASH_MAKE_TABLE(hh,head);                                                    \
+ } else {                                                                        \
+    (head)->hh.tbl->tail->next = (add);                                          \
+    (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);         \
+    (head)->hh.tbl->tail = &((add)->hh);                                         \
+ }                                                                               \
+ (head)->hh.tbl->num_items++;                                                    \
+ (add)->hh.tbl = (head)->hh.tbl;                                                 \
+ HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets,                         \
+         (add)->hh.hashv, _ha_bkt);                                              \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh);                   \
+ HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv);                                 \
+ HASH_EMIT_KEY(hh,head,keyptr,keylen_in);                                        \
+ HASH_FSCK(hh,head);                                                             \
+} while(0)
+
+#define HASH_TO_BKT( hashv, num_bkts, bkt )                                      \
+do {                                                                             \
+  bkt = ((hashv) & ((num_bkts) - 1));                                            \
+} while(0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ *  HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr)                                              \
+do {                                                                             \
+    unsigned _hd_bkt;                                                            \
+    struct UT_hash_handle *_hd_hh_del;                                           \
+    if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) )  {         \
+        uthash_free((head)->hh.tbl->buckets );                                   \
+        HASH_BLOOM_FREE((head)->hh.tbl);                                         \
+        uthash_free((head)->hh.tbl);                                             \
+        head = NULL;                                                             \
+    } else {                                                                     \
+        _hd_hh_del = &((delptr)->hh);                                            \
+        if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) {     \
+            (head)->hh.tbl->tail =                                               \
+                (UT_hash_handle*)((char*)((delptr)->hh.prev) +                   \
+                (head)->hh.tbl->hho);                                            \
+        }                                                                        \
+        if ((delptr)->hh.prev) {                                                 \
+            ((UT_hash_handle*)((char*)((delptr)->hh.prev) +                      \
+                    (head)->hh.tbl->hho))->next = (delptr)->hh.next;             \
+        } else {                                                                 \
+            DECLTYPE_ASSIGN(head,(delptr)->hh.next);                             \
+        }                                                                        \
+        if (_hd_hh_del->next) {                                                  \
+            ((UT_hash_handle*)((char*)_hd_hh_del->next +                         \
+                    (head)->hh.tbl->hho))->prev =                                \
+                    _hd_hh_del->prev;                                            \
+        }                                                                        \
+        HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);   \
+        HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);        \
+        (head)->hh.tbl->num_items--;                                             \
+    }                                                                            \
+    HASH_FSCK(hh,head);                                                          \
+} while (0)
+
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out)                                          \
+    HASH_FIND(hh,head,findstr,strlen(findstr),out)
+#define HASH_ADD_STR(head,strfield,add)                                          \
+    HASH_ADD(hh,head,strfield,strlen(add->strfield),add)
+#define HASH_FIND_INT(head,findint,out)                                          \
+    HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add)                                          \
+    HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_FIND_PTR(head,findptr,out)                                          \
+    HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add)                                          \
+    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_DEL(head,delptr)                                                    \
+    HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head)                                                       \
+do {                                                                             \
+    unsigned _bkt_i;                                                             \
+    unsigned _count, _bkt_count;                                                 \
+    char *_prev;                                                                 \
+    struct UT_hash_handle *_thh;                                                 \
+    if (head) {                                                                  \
+        _count = 0;                                                              \
+        for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) {       \
+            _bkt_count = 0;                                                      \
+            _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                      \
+            _prev = NULL;                                                        \
+            while (_thh) {                                                       \
+               if (_prev != (char*)(_thh->hh_prev)) {                            \
+                   HASH_OOPS("invalid hh_prev %p, actual %p\n",                  \
+                    _thh->hh_prev, _prev );                                      \
+               }                                                                 \
+               _bkt_count++;                                                     \
+               _prev = (char*)(_thh);                                            \
+               _thh = _thh->hh_next;                                             \
+            }                                                                    \
+            _count += _bkt_count;                                                \
+            if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {          \
+               HASH_OOPS("invalid bucket count %d, actual %d\n",                 \
+                (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);              \
+            }                                                                    \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid hh item count %d, actual %d\n",                   \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+        /* traverse hh in app order; check next/prev integrity, count */         \
+        _count = 0;                                                              \
+        _prev = NULL;                                                            \
+        _thh =  &(head)->hh;                                                     \
+        while (_thh) {                                                           \
+           _count++;                                                             \
+           if (_prev !=(char*)(_thh->prev)) {                                    \
+              HASH_OOPS("invalid prev %p, actual %p\n",                          \
+                    _thh->prev, _prev );                                         \
+           }                                                                     \
+           _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                    \
+           _thh = ( _thh->next ?  (UT_hash_handle*)((char*)(_thh->next) +        \
+                                  (head)->hh.tbl->hho) : NULL );                 \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid app item count %d, actual %d\n",                  \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+    }                                                                            \
+} while (0)
+#else
+#define HASH_FSCK(hh,head) 
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to 
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \
+do {                                                                             \
+    unsigned _klen = fieldlen;                                                   \
+    write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                \
+    write(HASH_EMIT_KEYS, keyptr, fieldlen);                                     \
+} while (0)
+#else 
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                    
+#endif
+
+/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
+#ifdef HASH_FUNCTION 
+#define HASH_FCN HASH_FUNCTION
+#else
+#define HASH_FCN HASH_JEN
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6 */
+#define HASH_BER(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _hb_keylen=keylen;                                                    \
+  char *_hb_key=(char*)key;                                                      \
+  (hashv) = 0;                                                                   \
+  while (_hb_keylen--)  { (hashv) = ((hashv) * 33) + *_hb_key++; }               \
+  bkt = (hashv) & (num_bkts-1);                                                  \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at 
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define HASH_SAX(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _sx_i;                                                                \
+  char *_hs_key=(char*)key;                                                      \
+  hashv = 0;                                                                     \
+  for(_sx_i=0; _sx_i < keylen; _sx_i++)                                          \
+      hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                     \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while (0)
+
+#define HASH_FNV(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _fn_i;                                                                \
+  char *_hf_key=(char*)key;                                                      \
+  hashv = 2166136261UL;                                                          \
+  for(_fn_i=0; _fn_i < keylen; _fn_i++)                                          \
+      hashv = (hashv * 16777619) ^ _hf_key[_fn_i];                               \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0);
+ 
+#define HASH_OAT(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _ho_i;                                                                \
+  char *_ho_key=(char*)key;                                                      \
+  hashv = 0;                                                                     \
+  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \
+      hashv += _ho_key[_ho_i];                                                   \
+      hashv += (hashv << 10);                                                    \
+      hashv ^= (hashv >> 6);                                                     \
+  }                                                                              \
+  hashv += (hashv << 3);                                                         \
+  hashv ^= (hashv >> 11);                                                        \
+  hashv += (hashv << 15);                                                        \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+#define HASH_JEN_MIX(a,b,c)                                                      \
+do {                                                                             \
+  a -= b; a -= c; a ^= ( c >> 13 );                                              \
+  b -= c; b -= a; b ^= ( a << 8 );                                               \
+  c -= a; c -= b; c ^= ( b >> 13 );                                              \
+  a -= b; a -= c; a ^= ( c >> 12 );                                              \
+  b -= c; b -= a; b ^= ( a << 16 );                                              \
+  c -= a; c -= b; c ^= ( b >> 5 );                                               \
+  a -= b; a -= c; a ^= ( c >> 3 );                                               \
+  b -= c; b -= a; b ^= ( a << 10 );                                              \
+  c -= a; c -= b; c ^= ( b >> 15 );                                              \
+} while (0)
+
+#define HASH_JEN(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _hj_i,_hj_j,_hj_k;                                                    \
+  char *_hj_key=(char*)key;														 \
+  hashv = 0xfeedbeef;                                                            \
+  _hj_i = _hj_j = 0x9e3779b9;                                                    \
+  _hj_k = keylen;                                                                \
+  while (_hj_k >= 12) {                                                          \
+    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \
+        + ( (unsigned)_hj_key[2] << 16 )                                         \
+        + ( (unsigned)_hj_key[3] << 24 ) );                                      \
+    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \
+        + ( (unsigned)_hj_key[6] << 16 )                                         \
+        + ( (unsigned)_hj_key[7] << 24 ) );                                      \
+    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \
+        + ( (unsigned)_hj_key[10] << 16 )                                        \
+        + ( (unsigned)_hj_key[11] << 24 ) );                                     \
+                                                                                 \
+     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \
+                                                                                 \
+     _hj_key += 12;                                                              \
+     _hj_k -= 12;                                                                \
+  }                                                                              \
+  hashv += keylen;                                                               \
+  switch ( _hj_k ) {                                                             \
+     case 11: hashv += ( (unsigned)_hj_key[10] << 24 );                          \
+     case 10: hashv += ( (unsigned)_hj_key[9] << 16 );                           \
+     case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );                            \
+     case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );                           \
+     case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );                           \
+     case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );                            \
+     case 5:  _hj_j += _hj_key[4];                                               \
+     case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );                           \
+     case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );                           \
+     case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );                            \
+     case 1:  _hj_i += _hj_key[0];                                               \
+  }                                                                              \
+  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  char *_sfh_key=(char*)key;                                                     \
+  uint32_t _sfh_tmp, _sfh_len = keylen;                                          \
+                                                                                 \
+  int _sfh_rem = _sfh_len & 3;                                                   \
+  _sfh_len >>= 2;                                                                \
+  hashv = 0xcafebabe;                                                            \
+                                                                                 \
+  /* Main loop */                                                                \
+  for (;_sfh_len > 0; _sfh_len--) {                                              \
+    hashv    += get16bits (_sfh_key);                                            \
+    _sfh_tmp       = (get16bits (_sfh_key+2) << 11) ^ hashv;                     \
+    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \
+    _sfh_key += 2*sizeof (uint16_t);                                             \
+    hashv    += hashv >> 11;                                                     \
+  }                                                                              \
+                                                                                 \
+  /* Handle end cases */                                                         \
+  switch (_sfh_rem) {                                                            \
+    case 3: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 16;                                                \
+            hashv ^= _sfh_key[sizeof (uint16_t)] << 18;                          \
+            hashv += hashv >> 11;                                                \
+            break;                                                               \
+    case 2: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 11;                                                \
+            hashv += hashv >> 17;                                                \
+            break;                                                               \
+    case 1: hashv += *_sfh_key;                                                  \
+            hashv ^= hashv << 10;                                                \
+            hashv += hashv >> 1;                                                 \
+  }                                                                              \
+                                                                                 \
+    /* Force "avalanching" of final 127 bits */                                  \
+    hashv ^= hashv << 3;                                                         \
+    hashv += hashv >> 5;                                                         \
+    hashv ^= hashv << 4;                                                         \
+    hashv += hashv >> 17;                                                        \
+    hashv ^= hashv << 25;                                                        \
+    hashv += hashv >> 6;                                                         \
+    bkt = hashv & (num_bkts-1);                                                  \
+} while(0);
+
+#ifdef HASH_USING_NO_STRICT_ALIASING
+/* The MurmurHash exploits some CPU's (e.g. x86) tolerance for unaligned reads.
+ * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.
+ * So MurmurHash comes in two versions, the faster unaligned one and the slower
+ * aligned one. We only use the faster one on CPU's where we know it's safe. 
+ *
+ * Note the preprocessor built-in defines can be emitted using:
+ *
+ *   gcc -m64 -dM -E - < /dev/null                  (on gcc)
+ *   cc -## a.c (where a.c is a simple test file)   (Sun Studio)
+ */
+#if (defined(__i386__) || defined(__x86_64__)) 
+#define HASH_MUR HASH_MUR_UNALIGNED
+#else
+#define HASH_MUR HASH_MUR_ALIGNED
+#endif
+
+/* Appleby's MurmurHash fast version for unaligned-tolerant archs like i386 */
+#define HASH_MUR_UNALIGNED(key,keylen,num_bkts,hashv,bkt)                        \
+do {                                                                             \
+  const unsigned int _mur_m = 0x5bd1e995;                                        \
+  const int _mur_r = 24;                                                         \
+  hashv = 0xcafebabe ^ keylen;                                                   \
+  char *_mur_key = (char *)key;                                                  \
+  uint32_t _mur_tmp, _mur_len = keylen;                                          \
+                                                                                 \
+  for (;_mur_len >= 4; _mur_len-=4) {                                            \
+    _mur_tmp = *(uint32_t *)_mur_key;                                            \
+    _mur_tmp *= _mur_m;                                                          \
+    _mur_tmp ^= _mur_tmp >> _mur_r;                                              \
+    _mur_tmp *= _mur_m;                                                          \
+    hashv *= _mur_m;                                                             \
+    hashv ^= _mur_tmp;                                                           \
+    _mur_key += 4;                                                               \
+  }                                                                              \
+                                                                                 \
+  switch(_mur_len)                                                               \
+  {                                                                              \
+    case 3: hashv ^= _mur_key[2] << 16;                                          \
+    case 2: hashv ^= _mur_key[1] << 8;                                           \
+    case 1: hashv ^= _mur_key[0];                                                \
+            hashv *= _mur_m;                                                     \
+  };                                                                             \
+                                                                                 \
+  hashv ^= hashv >> 13;                                                          \
+  hashv *= _mur_m;                                                               \
+  hashv ^= hashv >> 15;                                                          \
+                                                                                 \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+/* Appleby's MurmurHash version for alignment-sensitive archs like Sparc */
+#define HASH_MUR_ALIGNED(key,keylen,num_bkts,hashv,bkt)                          \
+do {                                                                             \
+  const unsigned int _mur_m = 0x5bd1e995;                                        \
+  const int _mur_r = 24;                                                         \
+  hashv = 0xcafebabe ^ keylen;                                                   \
+  char *_mur_key = (char *)key;                                                  \
+  uint32_t _mur_len = keylen;                                                    \
+  int _mur_align = (int)_mur_key & 3;                                            \
+                                                                                 \
+  if (_mur_align && (_mur_len >= 4)) {                                           \
+    unsigned _mur_t = 0, _mur_d = 0;                                             \
+    switch(_mur_align) {                                                         \
+      case 1: _mur_t |= _mur_key[2] << 16;                                       \
+      case 2: _mur_t |= _mur_key[1] << 8;                                        \
+      case 3: _mur_t |= _mur_key[0];                                             \
+    }                                                                            \
+    _mur_t <<= (8 * _mur_align);                                                 \
+    _mur_key += 4-_mur_align;                                                    \
+    _mur_len -= 4-_mur_align;                                                    \
+    int _mur_sl = 8 * (4-_mur_align);                                            \
+    int _mur_sr = 8 * _mur_align;                                                \
+                                                                                 \
+    for (;_mur_len >= 4; _mur_len-=4) {                                          \
+      _mur_d = *(unsigned *)_mur_key;                                            \
+      _mur_t = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);                        \
+      unsigned _mur_k = _mur_t;                                                  \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_t = _mur_d;                                                           \
+      _mur_key += 4;                                                             \
+    }                                                                            \
+    _mur_d = 0;                                                                  \
+    if(_mur_len >= _mur_align) {                                                 \
+      switch(_mur_align) {                                                       \
+        case 3: _mur_d |= _mur_key[2] << 16;                                     \
+        case 2: _mur_d |= _mur_key[1] << 8;                                      \
+        case 1: _mur_d |= _mur_key[0];                                           \
+      }                                                                          \
+      unsigned _mur_k = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);               \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_k += _mur_align;                                                      \
+      _mur_len -= _mur_align;                                                    \
+                                                                                 \
+      switch(_mur_len)                                                           \
+      {                                                                          \
+        case 3: hashv ^= _mur_key[2] << 16;                                      \
+        case 2: hashv ^= _mur_key[1] << 8;                                       \
+        case 1: hashv ^= _mur_key[0];                                            \
+                hashv *= _mur_m;                                                 \
+      }                                                                          \
+    } else {                                                                     \
+      switch(_mur_len)                                                           \
+      {                                                                          \
+        case 3: _mur_d ^= _mur_key[2] << 16;                                     \
+        case 2: _mur_d ^= _mur_key[1] << 8;                                      \
+        case 1: _mur_d ^= _mur_key[0];                                           \
+        case 0: hashv ^= (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);              \
+        hashv *= _mur_m;                                                         \
+      }                                                                          \
+    }                                                                            \
+                                                                                 \
+    hashv ^= hashv >> 13;                                                        \
+    hashv *= _mur_m;                                                             \
+    hashv ^= hashv >> 15;                                                        \
+  } else {                                                                       \
+    for (;_mur_len >= 4; _mur_len-=4) {                                          \
+      unsigned _mur_k = *(unsigned*)_mur_key;                                    \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_key += 4;                                                             \
+    }                                                                            \
+    switch(_mur_len)                                                             \
+    {                                                                            \
+      case 3: hashv ^= _mur_key[2] << 16;                                        \
+      case 2: hashv ^= _mur_key[1] << 8;                                         \
+      case 1: hashv ^= _mur_key[0];                                              \
+      hashv *= _mur_m;                                                           \
+    }                                                                            \
+                                                                                 \
+    hashv ^= hashv >> 13;                                                        \
+    hashv *= _mur_m;                                                             \
+    hashv ^= hashv >> 15;                                                        \
+  }                                                                              \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+#endif  /* HASH_USING_NO_STRICT_ALIASING */
+
+/* key comparison function; return 0 if keys equal */
+#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) 
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out)                       \
+do {                                                                             \
+ if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head));          \
+ else out=NULL;                                                                  \
+ while (out) {                                                                   \
+    if (out->hh.keylen == keylen_in) {                                           \
+        if ((HASH_KEYCMP(out->hh.key,keyptr,keylen_in)) == 0) break;             \
+    }                                                                            \
+    if (out->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,out->hh.hh_next)); \
+    else out = NULL;                                                             \
+ }                                                                               \
+} while(0)
+
+/* add an item to a bucket  */
+#define HASH_ADD_TO_BKT(head,addhh)                                              \
+do {                                                                             \
+ head.count++;                                                                   \
+ (addhh)->hh_next = head.hh_head;                                                \
+ (addhh)->hh_prev = NULL;                                                        \
+ if (head.hh_head) { (head).hh_head->hh_prev = (addhh); }                        \
+ (head).hh_head=addhh;                                                           \
+ if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH)             \
+     && (addhh)->tbl->noexpand != 1) {                                           \
+       HASH_EXPAND_BUCKETS((addhh)->tbl);                                        \
+ }                                                                               \
+} while(0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(hh,head,hh_del)                                          \
+    (head).count--;                                                              \
+    if ((head).hh_head == hh_del) {                                              \
+      (head).hh_head = hh_del->hh_next;                                          \
+    }                                                                            \
+    if (hh_del->hh_prev) {                                                       \
+        hh_del->hh_prev->hh_next = hh_del->hh_next;                              \
+    }                                                                            \
+    if (hh_del->hh_next) {                                                       \
+        hh_del->hh_next->hh_prev = hh_del->hh_prev;                              \
+    }                                                                
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain). 
+ * 
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain 
+ * length is the essence of how a hash provides constant time lookup.
+ * 
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate 
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ * 
+ *      ceil(n/b) = (n/b) + ((n%b)?1:0)
+ * 
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ * 
+ *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ * 
+ */
+#define HASH_EXPAND_BUCKETS(tbl)                                                 \
+do {                                                                             \
+    unsigned _he_bkt;                                                            \
+    unsigned _he_bkt_i;                                                          \
+    struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                 \
+    UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                \
+    _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                            \
+             2 * tbl->num_buckets * sizeof(struct UT_hash_bucket));              \
+    if (!_he_new_buckets) { uthash_fatal( "out of memory"); }                    \
+    memset(_he_new_buckets, 0,                                                   \
+            2 * tbl->num_buckets * sizeof(struct UT_hash_bucket));               \
+    tbl->ideal_chain_maxlen =                                                    \
+       (tbl->num_items >> (tbl->log2_num_buckets+1)) +                           \
+       ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0);                    \
+    tbl->nonideal_items = 0;                                                     \
+    for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++)                \
+    {                                                                            \
+        _he_thh = tbl->buckets[ _he_bkt_i ].hh_head;                             \
+        while (_he_thh) {                                                        \
+           _he_hh_nxt = _he_thh->hh_next;                                        \
+           HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt);            \
+           _he_newbkt = &(_he_new_buckets[ _he_bkt ]);                           \
+           if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) {                \
+             tbl->nonideal_items++;                                              \
+             _he_newbkt->expand_mult = _he_newbkt->count /                       \
+                                        tbl->ideal_chain_maxlen;                 \
+           }                                                                     \
+           _he_thh->hh_prev = NULL;                                              \
+           _he_thh->hh_next = _he_newbkt->hh_head;                               \
+           if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev =               \
+                _he_thh;                                                         \
+           _he_newbkt->hh_head = _he_thh;                                        \
+           _he_thh = _he_hh_nxt;                                                 \
+        }                                                                        \
+    }                                                                            \
+    tbl->num_buckets *= 2;                                                       \
+    tbl->log2_num_buckets++;                                                     \
+    uthash_free( tbl->buckets );                                                 \
+    tbl->buckets = _he_new_buckets;                                              \
+    tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ?         \
+        (tbl->ineff_expands+1) : 0;                                              \
+    if (tbl->ineff_expands > 1) {                                                \
+        tbl->noexpand=1;                                                         \
+        uthash_noexpand_fyi(tbl);                                                \
+    }                                                                            \
+    uthash_expand_fyi(tbl);                                                      \
+} while(0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh. 
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn)                                                 \
+do {                                                                             \
+  unsigned _hs_i;                                                                \
+  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \
+  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \
+  if (head) {                                                                    \
+      _hs_insize = 1;                                                            \
+      _hs_looping = 1;                                                           \
+      _hs_list = &((head)->hh);                                                  \
+      while (_hs_looping) {                                                      \
+          _hs_p = _hs_list;                                                      \
+          _hs_list = NULL;                                                       \
+          _hs_tail = NULL;                                                       \
+          _hs_nmerges = 0;                                                       \
+          while (_hs_p) {                                                        \
+              _hs_nmerges++;                                                     \
+              _hs_q = _hs_p;                                                     \
+              _hs_psize = 0;                                                     \
+              for ( _hs_i = 0; _hs_i  < _hs_insize; _hs_i++ ) {                  \
+                  _hs_psize++;                                                   \
+                  _hs_q = (UT_hash_handle*)((_hs_q->next) ?                      \
+                          ((void*)((char*)(_hs_q->next) +                        \
+                          (head)->hh.tbl->hho)) : NULL);                         \
+                  if (! (_hs_q) ) break;                                         \
+              }                                                                  \
+              _hs_qsize = _hs_insize;                                            \
+              while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) {           \
+                  if (_hs_psize == 0) {                                          \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next) ?                  \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  } else if ( (_hs_qsize == 0) || !(_hs_q) ) {                   \
+                      _hs_e = _hs_p;                                             \
+                      _hs_p = (UT_hash_handle*)((_hs_p->next) ?                  \
+                              ((void*)((char*)(_hs_p->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_psize--;                                               \
+                  } else if ((                                                   \
+                      cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
+                             DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
+                             ) <= 0) {                                           \
+                      _hs_e = _hs_p;                                             \
+                      _hs_p = (UT_hash_handle*)((_hs_p->next) ?                  \
+                              ((void*)((char*)(_hs_p->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_psize--;                                               \
+                  } else {                                                       \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next) ?                  \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  }                                                              \
+                  if ( _hs_tail ) {                                              \
+                      _hs_tail->next = ((_hs_e) ?                                \
+                            ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL);          \
+                  } else {                                                       \
+                      _hs_list = _hs_e;                                          \
+                  }                                                              \
+                  _hs_e->prev = ((_hs_tail) ?                                    \
+                     ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL);              \
+                  _hs_tail = _hs_e;                                              \
+              }                                                                  \
+              _hs_p = _hs_q;                                                     \
+          }                                                                      \
+          _hs_tail->next = NULL;                                                 \
+          if ( _hs_nmerges <= 1 ) {                                              \
+              _hs_looping=0;                                                     \
+              (head)->hh.tbl->tail = _hs_tail;                                   \
+              DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list));      \
+          }                                                                      \
+          _hs_insize *= 2;                                                       \
+      }                                                                          \
+      HASH_FSCK(hh,head);                                                        \
+ }                                                                               \
+} while (0)
+
+/* This function selects items from one hash into another hash. 
+ * The end result is that the selected items have dual presence 
+ * in both hashes. There is no copy of the items made; rather 
+ * they are added into the new hash through a secondary hash 
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \
+do {                                                                             \
+  unsigned _src_bkt, _dst_bkt;                                                   \
+  void *_last_elt=NULL, *_elt;                                                   \
+  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \
+  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \
+  if (src) {                                                                     \
+    for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {     \
+      for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;                \
+          _src_hh;                                                               \
+          _src_hh = _src_hh->hh_next) {                                          \
+          _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                       \
+          if (cond(_elt)) {                                                      \
+            _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho);               \
+            _dst_hh->key = _src_hh->key;                                         \
+            _dst_hh->keylen = _src_hh->keylen;                                   \
+            _dst_hh->hashv = _src_hh->hashv;                                     \
+            _dst_hh->prev = _last_elt;                                           \
+            _dst_hh->next = NULL;                                                \
+            if (_last_elt_hh) { _last_elt_hh->next = _elt; }                     \
+            if (!dst) {                                                          \
+              DECLTYPE_ASSIGN(dst,_elt);                                         \
+              HASH_MAKE_TABLE(hh_dst,dst);                                       \
+            } else {                                                             \
+              _dst_hh->tbl = (dst)->hh_dst.tbl;                                  \
+            }                                                                    \
+            HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);    \
+            HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh);            \
+            (dst)->hh_dst.tbl->num_items++;                                      \
+            _last_elt = _elt;                                                    \
+            _last_elt_hh = _dst_hh;                                              \
+          }                                                                      \
+      }                                                                          \
+    }                                                                            \
+  }                                                                              \
+  HASH_FSCK(hh_dst,dst);                                                         \
+} while (0)
+
+#define HASH_CLEAR(hh,head)                                                      \
+do {                                                                             \
+  if (head) {                                                                    \
+    uthash_free((head)->hh.tbl->buckets );                                       \
+    uthash_free((head)->hh.tbl);                                                 \
+    (head)=NULL;                                                                 \
+  }                                                                              \
+} while(0)
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head) 
+#define HASH_CNT(hh,head) (head?(head->hh.tbl->num_items):0)
+
+typedef struct UT_hash_bucket {
+   struct UT_hash_handle *hh_head;
+   unsigned count;
+
+   /* expand_mult is normally set to 0. In this situation, the max chain length
+    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+    * the bucket's chain exceeds this length, bucket expansion is triggered). 
+    * However, setting expand_mult to a non-zero value delays bucket expansion
+    * (that would be triggered by additions to this particular bucket)
+    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+    * (The multiplier is simply expand_mult+1). The whole idea of this
+    * multiplier is to reduce bucket expansions, since they are expensive, in
+    * situations where we know that a particular bucket tends to be overused.
+    * It is better to let its chain length grow to a longer yet-still-bounded
+    * value, than to do an O(n) bucket expansion too often. 
+    */
+   unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1
+#define HASH_BLOOM_SIGNATURE 0xb12220f2
+
+typedef struct UT_hash_table {
+   UT_hash_bucket *buckets;
+   unsigned num_buckets, log2_num_buckets;
+   unsigned num_items;
+   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */
+   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+   /* in an ideal situation (all buckets used equally), no bucket would have
+    * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+   unsigned ideal_chain_maxlen;
+
+   /* nonideal_items is the number of items in the hash whose chain position
+    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+    * hash distribution; reaching them in a chain traversal takes >ideal steps */
+   unsigned nonideal_items;
+
+   /* ineffective expands occur when a bucket doubling was performed, but 
+    * afterward, more than half the items in the hash had nonideal chain
+    * positions. If this happens on two consecutive expansions we inhibit any
+    * further expansion, as it's not helping; this happens when the hash
+    * function isn't a good fit for the key domain. When expansion is inhibited
+    * the hash will still work, albeit no longer in constant time. */
+   unsigned ineff_expands, noexpand;
+
+   uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+   uint8_t *bloom_bv;
+   char bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+   struct UT_hash_table *tbl;
+   void *prev;                       /* prev element in app order      */
+   void *next;                       /* next element in app order      */
+   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */
+   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */
+   void *key;                        /* ptr to enclosing struct's key  */
+   unsigned keylen;                  /* enclosing struct's key len     */
+   unsigned hashv;                   /* result of hash-fcn(key)        */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/KTX/lib/writer.c b/KTX/lib/writer.c
new file mode 100644
--- /dev/null
+++ b/KTX/lib/writer.c
@@ -0,0 +1,735 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: b2ab7bcfcab83b13e5c65b45c006154fe0c68062 $ */
+
+/**
+ * @file writer.c
+ * @~English
+ *
+ * @brief Functions for creating KTX-format files from a set of images.
+ *
+ * @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.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxstream.h"
+#include "ktxfilestream.h"
+#include "ktxmemstream.h"
+
+static KTX_error_code validateTypeAndFormat(GLenum format, GLenum type);
+static KTX_error_code sizeofGroupAndElement(GLenum format, GLenum type,
+											GLuint* groupBytes, GLuint* elementBytes,
+											GLboolean* packed);
+static KTX_error_code sizeofGLtype(GLenum type, GLuint* size, GLboolean* packed);
+
+
+/**
+ * @~English
+ * @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_image_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 target are @c 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 @c numberOfFaces != 1 || 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
+ *                              numberOfFaces == 6 and images are either not 2D or
+ *                              are not square.
+ * @exception KTX_INVALID_OPERATION
+ *							    number of images 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 errorCode = KTX_SUCCESS;
+	GLboolean compressed = GL_FALSE;
+	GLuint groupBytes, elementBytes;
+
+	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
+	{
+		GLboolean packed;
+
+		/* Get size of group and element */
+		if ((errorCode = sizeofGroupAndElement(header.glFormat, header.glType,
+											   &groupBytes, &elementBytes, &packed)) != KTX_SUCCESS)
+		{
+			return errorCode;
+		}
+
+		/* Check validity of type/format combination for packed types */
+		if (packed && (errorCode = validateTypeAndFormat(header.glFormat, header.glType)) != KTX_SUCCESS)
+		{
+			return errorCode;
+		}
+
+		if (header.glTypeSize != elementBytes)
+		{
+#if defined(GL_FLOAT_32_UNSIGNED_INT_24_8_REV)
+			if (header.glType != GL_FLOAT_32_UNSIGNED_INT_24_8_REV || header.glTypeSize != 1)
+#endif
+			return KTX_INVALID_VALUE;
+		}
+	}
+
+
+	/* 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 or cube 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 == 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;
+		if (header.numberOfFaces == 6)
+			cubemap = 1;
+	}
+	else
+		numArrayElements = header.numberOfArrayElements;
+
+	/* 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
+	errorCode = stream->write(&header, sizeof(KTX_header), 1, stream->src);
+	if (errorCode != KTX_SUCCESS)
+		return errorCode;
+
+	//write keyValueData
+	if (bytesOfKeyValueData != 0) {
+		if (keyValueData == NULL)
+			return KTX_INVALID_OPERATION;
+
+		errorCode = stream->write(keyValueData, 1, bytesOfKeyValueData, stream->src);
+		if (errorCode != KTX_SUCCESS)
+			return errorCode;
+	}
+
+	/* Write the image data */
+	for (level = 0, i = 0; level < numMipmapLevels; ++level)
+	{
+		GLsizei expectedFaceSize;
+		GLuint face, faceLodSize, faceLodRounding;
+		GLuint pixelWidth, pixelHeight, pixelDepth;
+		GLuint packedRowBytes, rowBytes, rowRounding;
+
+		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 */
+		expectedFaceSize = groupBytes
+						   * pixelWidth
+						   * pixelHeight
+						   * pixelDepth
+						   * numArrayElements;
+
+		rowRounding = 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 && elementBytes < KTX_GL_UNPACK_ALIGNMENT) {
+			// Equivalent to UNPACK_ALIGNMENT * ceil((groupSize * pixelWidth) / UNPACK_ALIGNMENT)
+			rowRounding = 3 - ((packedRowBytes + KTX_GL_UNPACK_ALIGNMENT-1) % KTX_GL_UNPACK_ALIGNMENT);
+			rowBytes = packedRowBytes + rowRounding;
+		}
+
+		if (rowRounding == 0) {
+			faceLodSize = images[i].size;
+		} else {
+			/* Need to pad the rows to meet the required UNPACK_ALIGNMENT */
+			faceLodSize = rowBytes * pixelHeight * pixelDepth * numArrayElements;
+		}
+		faceLodRounding = 3 - ((faceLodSize + 3) % 4);
+
+		errorCode = stream->write(&faceLodSize, sizeof(faceLodSize), 1, stream->src);
+		if (errorCode != KTX_SUCCESS)
+			goto cleanup;
+
+		for (face = 0; face < header.numberOfFaces; ++face, ++i) {
+			if (!compressed) {
+				/* Sanity check. */
+				if (images[i].size != expectedFaceSize) {
+					errorCode = KTX_INVALID_OPERATION;
+					goto cleanup;
+				}
+			}
+			if (rowRounding == 0) {
+				/* Can write whole face at once */
+				errorCode = stream->write(images[i].data, faceLodSize, 1, stream->src);
+				if (errorCode != KTX_SUCCESS)
+					goto cleanup;
+			} else {
+				/* Write the rows individually, padding each one */
+				GLuint row;
+				GLuint numRows = pixelHeight
+								* pixelDepth
+								* numArrayElements;
+				for (row = 0; row < numRows; row++) {
+					errorCode = stream->write(&images[i].data[row*packedRowBytes], packedRowBytes, 1, stream->src);
+					if (errorCode != KTX_SUCCESS)
+						goto cleanup;
+
+					errorCode = stream->write(pad, sizeof(GLbyte), rowRounding, stream->src);
+					if (errorCode != KTX_SUCCESS)
+						goto cleanup;
+				}
+			}
+			if (faceLodRounding) {
+				errorCode = stream->write(pad, sizeof(GLbyte), faceLodRounding, stream->src);
+				if (errorCode != KTX_SUCCESS)
+					goto cleanup;
+			}
+		}
+	}
+
+cleanup:
+	return errorCode;
+}
+
+/**
+ * @~English
+ * @brief Write image(s) in a KTX-formatted stdio FILE stream.
+ *
+ * @param [in] file         pointer to the FILE stream to write to.
+ * @param [in] textureInfo  pointer to a KTX_image_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 target are @c 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 @c numberOfFaces != 1 || 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
+ *                              numberOfFaces == 6 and images are either not 2D or
+ *                              are not square.
+ * @exception KTX_INVALID_OPERATION
+ *                                                          number of images 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.
+ */
+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 errorCode = KTX_SUCCESS;
+
+		errorCode = ktxFileInit(&stream, file);
+		if (errorCode != KTX_SUCCESS)
+				return errorCode;
+
+		return ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData, numImages, images);
+}
+
+/**
+ * @~English
+ * @brief Write image(s) to a KTX file on disk.
+ *
+ * @param [in] dstname		pointer to a C string that contains the path of
+ * 							the file to load.
+ * @param [in] textureInfo  pointer to a KTX_image_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[])
+{
+	KTX_error_code errorCode;
+	FILE* dst = fopen(dstname, "wb");
+
+	if (dst) {
+		errorCode = ktxWriteKTXF(dst, textureInfo, bytesOfKeyValueData, keyValueData,
+								 numImages, images);
+		fclose(dst);
+	} else
+		errorCode = KTX_FILE_OPEN_FAILED;
+
+	return errorCode;
+}
+
+/**
+ * @~English
+ * @brief Write image(s) in KTX format to memory.
+ *
+ * @param [out] bytes        pointer to the output with KTX data. Application
+							is responsible for freeing that memory.
+ * @param [out] size         pointer to store size of the memory written.
+ * @param [in] textureInfo  pointer to a KTX_image_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.
+ *
+ */
+KTX_error_code
+ktxWriteKTXM(unsigned char** bytes, GLsizei* size, const KTX_texture_info* textureInfo,
+			GLsizei bytesOfKeyValueData, const void* keyValueData,
+			GLuint numImages, KTX_image_info images[])
+{
+	struct ktxMem mem;
+	struct ktxStream stream;
+	KTX_error_code rc;
+
+	*bytes = NULL;
+
+	rc = ktxMemInit(&stream, &mem, NULL, 0);
+	if (rc != KTX_SUCCESS)
+		return rc;
+
+	rc = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData, numImages, images);
+	if(rc != KTX_SUCCESS)
+	{
+		if(mem.bytes)
+		{
+			free(mem.bytes);
+		}
+		return rc;
+	}
+
+	*bytes = mem.bytes;
+	*size = mem.used_size;
+	return KTX_SUCCESS;
+}
+
+/*
+ * @brief Check format and type matching as required by OpenGL.
+ *
+ * @param [in] format	the format of the image data
+ * @param [in] type		the type of the image data
+ *
+ * @return	KTX_SUCCESS if matched, KTX_INVALID_OPERATION, if mismatched
+ *			or KTX_INVALID_VALUE if @p type is invalid.
+ */
+static KTX_error_code
+validateTypeAndFormat(GLenum format, GLenum type)
+{
+	KTX_error_code retVal = KTX_SUCCESS;
+
+	if ((format >= GL_RED_INTEGER && format <= GL_BGRA_INTEGER) && (type == GL_FLOAT || type == GL_HALF_FLOAT))
+	{
+		retVal = KTX_INVALID_OPERATION; // Note: OpenGL 4.4 says GL_INVALID_VALUE but we'll mirror the others.
+	}
+
+	switch (type)
+	{
+		case GL_UNSIGNED_BYTE_3_3_2:
+		case GL_UNSIGNED_BYTE_2_3_3_REV:
+			if (format != GL_RGB && format != GL_RGB_INTEGER)
+				retVal = KTX_INVALID_OPERATION; // Matches OpenGL 4.4
+			break;
+
+		case GL_UNSIGNED_SHORT_5_6_5:
+		case GL_UNSIGNED_SHORT_5_6_5_REV:
+			if (format != GL_RGB && format != GL_RGB_INTEGER)
+				retVal = KTX_INVALID_OPERATION;
+			break;
+
+		case GL_UNSIGNED_SHORT_4_4_4_4:
+		case GL_UNSIGNED_SHORT_5_5_5_1:
+		case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+		case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+			if (format != GL_RGBA && format != GL_BGRA
+				&& format != GL_RGBA_INTEGER && format != GL_BGRA_INTEGER)
+			{
+				retVal = KTX_INVALID_OPERATION;
+			}
+			break;
+
+		case GL_UNSIGNED_INT_8_8_8_8:
+		case GL_UNSIGNED_INT_8_8_8_8_REV:
+		case GL_UNSIGNED_INT_10_10_10_2:
+		case GL_UNSIGNED_INT_2_10_10_10_REV:
+			if (format != GL_RGBA && format != GL_BGRA
+				&& format != GL_RGBA_INTEGER && format != GL_BGRA_INTEGER)
+			{
+				retVal = KTX_INVALID_OPERATION;
+			}
+			break;
+
+		case GL_UNSIGNED_INT_24_8:
+			if (format != GL_DEPTH_STENCIL)
+				retVal = KTX_INVALID_OPERATION;
+			break;
+
+		case GL_UNSIGNED_INT_10F_11F_11F_REV:
+		case GL_UNSIGNED_INT_5_9_9_9_REV:
+			if (format != GL_RGB && format != GL_BGR)
+				retVal = KTX_INVALID_OPERATION;
+			break;
+
+		case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+			// Note: OpenGL 4.4 says GL_INVALID_VALUE in one place,
+			// GL_INVALID_OPERATION in another. The latter is more logical.
+			retVal = KTX_INVALID_OPERATION;
+
+		default:
+			retVal = KTX_INVALID_VALUE;
+	}
+
+	return retVal;
+}
+
+
+/*
+ * @brief Get the size of the group of elements constituting a pixel in
+ *        the given @p type and @p format and the size of an element.
+ *
+ * Sizes are returned in basic machine units (bytes). The function also
+ * indicates if @type is a packed pixel format.
+ *
+ * @param [in]  format		the format of the image data
+ * @param [in]  type		the type of the image data
+ * @param [out] groupBytes	pointer to location where to write the size of a group
+ * @param [out] size		pointer to location where to write the size of an element
+ * @param [out] packed		pointer to location where to write flag indicating
+ *							if the type is a packed type.
+ *
+ * @return	KTX_INVALID_VALUE if the @p type or @p format is invalid.
+ */
+static KTX_error_code
+sizeofGroupAndElement(GLenum format, GLenum type, GLuint* groupBytes,
+					  GLuint* elementBytes, GLboolean* packed)
+{
+	KTX_error_code retVal;
+
+	if ((retVal = sizeofGLtype(type, elementBytes, packed)) != KTX_SUCCESS)
+	{
+		return retVal;
+	}
+
+	if (*packed)
+	{
+		*groupBytes = *elementBytes;
+		return retVal;
+	}
+
+	switch (format) {
+	case GL_ALPHA:
+	case GL_RED:
+	case GL_GREEN:
+	case GL_BLUE:
+	case GL_LUMINANCE: /* deprecated but needed for ES 1 & 2 */
+	case GL_ALPHA_INTEGER:
+	case GL_RED_INTEGER:
+	case GL_GREEN_INTEGER:
+	case GL_BLUE_INTEGER:
+	/* case GL_LUMINANCE_INTEGER: deprecated */
+		*groupBytes = *elementBytes;
+		break;
+	case GL_LUMINANCE_ALPHA:
+	case GL_RG:
+	/* case GL_LUMINANCE_ALPHA_INTEGER: deprecated */
+	case GL_RG_INTEGER:
+		*groupBytes = *elementBytes * 2;
+		break;
+	case GL_RGB:
+	case GL_BGR:
+	case GL_RGB_INTEGER:
+	case GL_BGR_INTEGER:
+		*groupBytes = *elementBytes * 3;
+		break;
+	case GL_RGBA:
+	case GL_BGRA:
+	case GL_RGBA_INTEGER:
+	case GL_BGRA_INTEGER:
+		*groupBytes = *elementBytes * 4;
+		break;
+	default:
+		retVal = KTX_INVALID_VALUE;
+	}
+
+	return retVal;
+}
+
+/*
+ * @brief Get the size of a GL type in basic machine units
+ *        and indicate whether or not it is a packed type.
+ *
+ * @param [in]  type	the type whose size is to be returned.
+ * @param [out] size	pointer to location where to write the size
+ * @param [out] packed	pointer to location where to write flag indicating
+ *                      if the type is a packed type.
+ *
+ * @return KTX_INVALID_VALUE if the @p type is unrecognized.
+ */
+static KTX_error_code
+sizeofGLtype(GLenum type, GLuint* size, GLboolean* packed)
+{
+	assert(packed && size);
+	*packed = GL_FALSE;
+
+	switch (type) {
+		case GL_BYTE:
+			*size = sizeof(GLbyte);
+			break;
+
+		case GL_UNSIGNED_BYTE:
+			*size = sizeof(GLubyte);
+			break;
+
+		case GL_UNSIGNED_BYTE_3_3_2:
+		case GL_UNSIGNED_BYTE_2_3_3_REV:
+			*packed = GL_TRUE;
+			*size = sizeof(GLubyte);
+			break;
+
+		case GL_SHORT:
+			*size = sizeof(GLshort);
+			break;
+
+		case GL_UNSIGNED_SHORT:
+			*size = sizeof(GLushort);
+			break;
+
+		case GL_UNSIGNED_SHORT_5_6_5:
+		case GL_UNSIGNED_SHORT_4_4_4_4:
+		case GL_UNSIGNED_SHORT_5_5_5_1:
+		case GL_UNSIGNED_SHORT_5_6_5_REV:
+		case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+		case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+			*packed = GL_TRUE;
+			*size = sizeof(GLushort);
+			break;
+
+		case GL_INT:
+			*size = sizeof(GLint);
+			break;
+
+		case GL_UNSIGNED_INT:
+			*size = sizeof(GLuint);
+			break;
+
+		case GL_UNSIGNED_INT_8_8_8_8:
+		case GL_UNSIGNED_INT_8_8_8_8_REV:
+		case GL_UNSIGNED_INT_10_10_10_2:
+		case GL_UNSIGNED_INT_2_10_10_10_REV:
+		case GL_UNSIGNED_INT_24_8:
+		case GL_UNSIGNED_INT_10F_11F_11F_REV:
+		case GL_UNSIGNED_INT_5_9_9_9_REV:
+			*packed = GL_TRUE;
+			*size = sizeof(GLuint);
+			break;
+
+		case GL_HALF_FLOAT:
+			*size = sizeof(GLhalf);
+			break;
+
+		case GL_FLOAT:
+			*size = sizeof(GLfloat);
+			break;
+
+		case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+			*packed = GL_TRUE;
+			*size = sizeof(GLfloat) + sizeof(GLint);
+			break;
+
+		default:
+			return KTX_INVALID_VALUE;
+	}
+	return KTX_SUCCESS;
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) [year] [fullname]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/ktx.cabal b/ktx.cabal
new file mode 100644
--- /dev/null
+++ b/ktx.cabal
@@ -0,0 +1,68 @@
+name: ktx
+version: 0.1
+cabal-version: >= 1.10
+build-type: Simple
+license: MIT
+license-file: LICENSE
+copyright: 2015 David McFarland
+author: David McFarland <corngood@gmail.com>
+maintainer: David McFarland <corngood@gmail.com>
+stability: alpha
+homepage: https://github.com/corngood/ktx
+bug-reports: https://github.com/corngood/ktx/issues
+synopsis: A binding for libktx from Khronos
+description:
+    Allows ktx format textures to be loaded into an OpenGL context.
+
+    libktx is included as a submodule and compiled with 'c-sources'.
+category: Graphics
+extra-source-files:
+    KTX/LICENSING.md
+    KTX/include/*.h
+    KTX/lib/*.h
+
+source-repository head
+    type: git
+    location: https://github.com/corngood/ktx.git
+
+flag dev
+    manual: True
+    default: False
+
+library
+    exposed-modules:
+        Graphics.Rendering.KTX
+    hs-source-dirs:
+        src
+    default-language:
+        Haskell2010
+    build-depends:
+        base >= 3 && < 5,
+        OpenGL >= 2.9 && < 3
+    ghc-options:
+        -Wall
+        -fno-warn-unused-do-bind
+        -fno-warn-name-shadowing
+    if flag(dev)
+        ghc-options: -Werror
+    c-sources:
+        KTX/lib/checkheader.c
+        KTX/lib/errstr.c
+        KTX/lib/hashtable.c
+        KTX/lib/ktxfilestream.c
+        KTX/lib/ktxmemstream.c
+        KTX/lib/loader.c
+        KTX/lib/swap.c
+        KTX/lib/writer.c
+        KTX/lib/etcdec.cxx
+        KTX/lib/etcunpack.cxx
+    pkgconfig-depends:
+        glew,
+        egl
+    include-dirs:
+        KTX/include
+    cc-options:
+        -fPIC
+        -DKTX_OPENGL=1
+        -DKTX_USE_GETPROC=1
+        -Wno-unused-result
diff --git a/src/Graphics/Rendering/KTX.hsc b/src/Graphics/Rendering/KTX.hsc
new file mode 100644
--- /dev/null
+++ b/src/Graphics/Rendering/KTX.hsc
@@ -0,0 +1,64 @@
+{-# Language DeriveDataTypeable #-}
+
+module Graphics.Rendering.KTX
+    ( ErrorCode
+    , Dimensions(..)
+    , initContext
+    , loadTextureN
+    ) where
+
+import Control.Exception (Exception, throwIO)
+import Data.Typeable (Typeable)
+import Data.Word (Word32)
+-- constructors were not exported until here
+#if MIN_VERSION_base(4,5,0)
+import Foreign.C.Types (CUInt(CUInt))
+#else
+import Foreign.C.Types (CUInt)
+#endif
+import Foreign.C.Types (CUChar)
+import Foreign.C.String (CString, withCString)
+import Foreign.Storable (peek)
+import Foreign.Marshal.Utils (with)
+import Foreign.Ptr (Ptr, nullPtr)
+import Graphics.Rendering.OpenGL (GLsizei, GLenum, GLuint, GLboolean, TextureObject(TextureObject))
+
+#include <ktx.h>
+
+type ErrorCode = (#type KTX_error_code)
+data Dimensions = Dimensions GLsizei GLsizei GLsizei
+
+foreign import ccall "GL/glew.h glewInit" glewInit :: IO GLenum
+
+foreign import ccall "ktx.h ktxLoadTextureN" ktxLoadTextureN
+    :: CString
+    -> Ptr GLuint
+    -> Ptr GLenum
+    -> Ptr Dimensions
+    -> Ptr GLboolean
+    -> Ptr GLenum
+    -> Ptr CUInt
+    -> Ptr (Ptr CUChar)
+    -> IO ErrorCode
+
+data GLEWException = GLEWException GLenum deriving (Eq, Ord, Typeable, Show)
+instance Exception GLEWException
+
+data KTXException = KTXException ErrorCode deriving (Eq, Ord, Typeable, Show)
+instance Exception KTXException
+
+initContext :: IO ()
+initContext = do
+    e <- glewInit
+    case e of
+        0 -> return ()
+        _ -> throwIO (GLEWException e)
+
+loadTextureN :: FilePath -> IO TextureObject
+loadTextureN path = withCString path $ \pathStr -> do
+    with 0 $ \texPtr -> with 0 $ \bindingPtr -> do
+        e <- ktxLoadTextureN pathStr texPtr bindingPtr nullPtr nullPtr nullPtr nullPtr nullPtr
+        t <- peek texPtr
+        case e of
+            0 -> return $ TextureObject t
+            _ -> throwIO $ KTXException e
