text: /*
   	Fork by Martin Lucas Golini
   	 
   	Original author
  	Jonathan Dummer
  	2007-07-26-10.36
  
  	Simple OpenGL Image Library 2
  
  	Public Domain
  	using Sean Barret's stb_image as a base
  
  	Thanks to:
  	* Sean Barret - for the awesome stb_image
  	* Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts
  	* everybody at gamedev.net
  */
  
  #define SOIL_CHECK_FOR_GL_ERRORS 0
  
  #if defined( __APPLE_CC__ ) || defined ( __APPLE__ )
  	#include <TargetConditionals.h>
  
  	#if defined( __IPHONE__ ) || ( defined( TARGET_OS_IPHONE ) && TARGET_OS_IPHONE ) || ( defined( TARGET_IPHONE_SIMULATOR ) && TARGET_IPHONE_SIMULATOR )
  		#define SOIL_PLATFORM_IOS
  		#include <dlfcn.h>
  	#else
  		#define SOIL_PLATFORM_OSX
  	#endif
  #elif defined( __ANDROID__ ) || defined( ANDROID )
  	#define SOIL_PLATFORM_ANDROID
  #elif ( defined ( linux ) || defined( __linux__ ) || defined( __FreeBSD__ ) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined( __DragonFly__ ) || defined( __SVR4 ) )
  	#define SOIL_X11_PLATFORM
  #endif
  
  #if ( defined( SOIL_PLATFORM_IOS ) || defined( SOIL_PLATFORM_ANDROID ) ) && ( !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 ) )
  	#define SOIL_GLES2
  #endif
  
  #if ( defined( SOIL_GLES2 ) || defined( SOIL_GLES1 ) ) && !defined( SOIL_NO_EGL ) && !defined( SOIL_PLATFORM_IOS )
  	#include <EGL/egl.h>
  #endif
  
  #if defined( SOIL_GLES2 )
  	#ifdef SOIL_PLATFORM_IOS
  		#include <OpenGLES/ES2/gl.h>
  		#include <OpenGLES/ES2/glext.h>
  	#else
  		#include <GLES2/gl2.h>
  		#include <GLES2/gl2ext.h>
  	#endif
  
  	#define APIENTRY GL_APIENTRY
  #elif defined( SOIL_GLES1 )
  	#ifndef GL_GLEXT_PROTOTYPES
  	#define GL_GLEXT_PROTOTYPES
  	#endif
  	#ifdef SOIL_PLATFORM_IOS
  		#include <OpenGLES/ES1/gl.h>
  		#include <OpenGLES/ES1/glext.h>
  	#else
  		#include <GLES/gl.h>
  		#include <GLES/glext.h>
  	#endif
  
  	#define APIENTRY GL_APIENTRY
  #else
  
  #if defined( __WIN32__ ) || defined( _WIN32 ) || defined( WIN32 )
  	#define SOIL_PLATFORM_WIN32
  	#define WIN32_LEAN_AND_MEAN
  	#include <windows.h>
  	#include <wingdi.h>
  	#include <GL/gl.h>
  	
  	#ifndef GL_UNSIGNED_SHORT_4_4_4_4
  	#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
  	#endif
  	#ifndef GL_UNSIGNED_SHORT_5_5_5_1
  	#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
  	#endif
  	#ifndef GL_UNSIGNED_SHORT_5_6_5
  	#define GL_UNSIGNED_SHORT_5_6_5 0x8363
  	#endif
  #elif defined(__APPLE__) || defined(__APPLE_CC__)
  	/*	I can't test this Apple stuff!	*/
  	#include <OpenGL/gl.h>
  	#include <Carbon/Carbon.h>
  	#define APIENTRY
  #elif defined( SOIL_X11_PLATFORM )
  	#include <GL/gl.h>
  	#include <GL/glx.h>
  #else
  	#include <GL/gl.h>
  #endif
  
  #endif
  
  #ifndef GL_BGRA
  #define GL_BGRA                                             0x80E1
  #endif
  
  #ifndef GL_RG
  #define GL_RG                             0x8227
  #endif
  
  #include "SOIL2.h"
  #define STB_IMAGE_IMPLEMENTATION
  #include "stb_image.h"
  #define STB_IMAGE_WRITE_IMPLEMENTATION
  #include "stb_image_write.h"
  #include "image_helper.h"
  #include "image_DXT.h"
  #include "pvr_helper.h"
  #include "pkm_helper.h"
  #include "jo_jpeg.h"
  
  #include <stdlib.h>
  #include <string.h>
  
  /*	error reporting	*/
  const char *result_string_pointer = "SOIL initialized";
  
  /*	for loading cube maps	*/
  enum{
  	SOIL_CAPABILITY_UNKNOWN = -1,
  	SOIL_CAPABILITY_NONE = 0,
  	SOIL_CAPABILITY_PRESENT = 1
  };
  static int has_cubemap_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_cubemap_capability( void );
  #define SOIL_TEXTURE_WRAP_R					0x8072
  #define SOIL_CLAMP_TO_EDGE					0x812F
  #define SOIL_NORMAL_MAP						0x8511
  #define SOIL_REFLECTION_MAP					0x8512
  #define SOIL_TEXTURE_CUBE_MAP				0x8513
  #define SOIL_TEXTURE_BINDING_CUBE_MAP		0x8514
  #define SOIL_TEXTURE_CUBE_MAP_POSITIVE_X	0x8515
  #define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X	0x8516
  #define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y	0x8517
  #define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y	0x8518
  #define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z	0x8519
  #define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z	0x851A
  #define SOIL_PROXY_TEXTURE_CUBE_MAP			0x851B
  #define SOIL_MAX_CUBE_MAP_TEXTURE_SIZE		0x851C
  /*	for non-power-of-two texture	*/
  #define SOIL_IS_POW2( v ) ( ( v & ( v - 1 ) ) == 0 )
  static int has_NPOT_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_NPOT_capability( void );
  /*	for texture rectangles	*/
  static int has_tex_rectangle_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_tex_rectangle_capability( void );
  #define SOIL_TEXTURE_RECTANGLE_ARB				0x84F5
  #define SOIL_MAX_RECTANGLE_TEXTURE_SIZE_ARB		0x84F8
  /*	for using DXT compression	*/
  static int has_DXT_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_DXT_capability( void );
  #define SOIL_GL_SRGB			0x8C40
  #define SOIL_GL_SRGB_ALPHA		0x8C42
  #define SOIL_RGB_S3TC_DXT1		0x83F0
  #define SOIL_RGBA_S3TC_DXT1		0x83F1
  #define SOIL_RGBA_S3TC_DXT3		0x83F2
  #define SOIL_RGBA_S3TC_DXT5		0x83F3
  #define SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT  0x8C4C
  #define SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
  static int has_sRGB_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_sRGB_capability( void );
  typedef void (APIENTRY * P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid * data);
  static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL;
  
  typedef void (APIENTRY *P_SOIL_GLGENERATEMIPMAPPROC)(GLenum target);
  static P_SOIL_GLGENERATEMIPMAPPROC soilGlGenerateMipmap = NULL;
  
  static int has_gen_mipmap_capability = SOIL_CAPABILITY_UNKNOWN;
  static int query_gen_mipmap_capability( void );
  
  static int has_PVR_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_PVR_capability( void );
  static int has_BGRA8888_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_BGRA8888_capability( void );
  static int has_ETC1_capability = SOIL_CAPABILITY_UNKNOWN;
  int query_ETC1_capability( void );
  
  /* GL_IMG_texture_compression_pvrtc */
  #define SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
  #define SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
  #define SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
  #define SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03
  #define SOIL_GL_ETC1_RGB8_OES                                     0x8D64
  
  #if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX )
  typedef const GLubyte *(APIENTRY * P_SOIL_glGetStringiFunc) (GLenum, GLuint);
  static P_SOIL_glGetStringiFunc soilGlGetStringiFunc = NULL;
  
  static int isAtLeastGL3()
  {
  	static int is_gl3 = SOIL_CAPABILITY_UNKNOWN;
  
  	if ( SOIL_CAPABILITY_UNKNOWN == is_gl3 )
  	{
  		const char * verstr	= (const char *) glGetString( GL_VERSION );
  		is_gl3				= ( verstr && ( atoi(verstr) >= 3 ) );
  	}
  
  	return is_gl3;
  }
  #endif
  
  #ifdef SOIL_PLATFORM_WIN32
  static HMODULE openglModule = NULL;
  static int soilTestWinProcPointer(const PROC pTest)
  {
  	ptrdiff_t iTest;
  	if(!pTest) return 0;
  	iTest = (ptrdiff_t)pTest;
  	if(iTest == 1 || iTest == 2 || iTest == 3 || iTest == -1) return 0;
  	return 1;
  }
  #endif
  
  #if defined(__sgi) || defined (__sun) || defined(__HAIKU__)
  #include <dlfcn.h>
  
  void* dlGetProcAddress (const char* name)
  {
    static void* h = NULL;
    static void* gpa;
  
    if (h == NULL)
    {
  	if ((h = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL)) == NULL) return NULL;
  	gpa = dlsym(h, "glXGetProcAddress");
    }
  
    if (gpa != NULL)
  	return ((void*(*)(const GLubyte*))gpa)((const GLubyte*)name);
    else
  	return dlsym(h, (const char*)name);
  }
  #endif
  
  void * SOIL_GL_GetProcAddress(const char *proc)
  {
  	void *func = NULL;
  
  #if defined( SOIL_PLATFORM_IOS )
  	func = dlsym( RTLD_DEFAULT, proc );
  #elif defined( SOIL_GLES2 ) || defined( SOIL_GLES1 )
  	#ifndef SOIL_NO_EGL
  		func = eglGetProcAddress( proc );
  	#else
  		func = NULL;
  	#endif
  #elif defined( SOIL_PLATFORM_WIN32 )
  	if ( NULL == openglModule )
  		openglModule = LoadLibraryA("opengl32.dll");
  
  	func =  wglGetProcAddress( proc );
  
  	if (!soilTestWinProcPointer((const PROC)func)) {
  		func = (void *)GetProcAddress(openglModule, proc);
  	}
  
  #elif defined( SOIL_PLATFORM_OSX )
  	/*	I can't test this Apple stuff!	*/
  	CFBundleRef bundle;
  	CFURLRef bundleURL =
  	CFURLCreateWithFileSystemPath(
  								  kCFAllocatorDefault,
  								  CFSTR("/System/Library/Frameworks/OpenGL.framework"),
  								  kCFURLPOSIXPathStyle,
  								  true );
  	CFStringRef extensionName =
  	CFStringCreateWithCString(
  							  kCFAllocatorDefault,
  							  proc,
  							  kCFStringEncodingASCII );
  	bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL );
  	assert( bundle != NULL );
  
  	func = CFBundleGetFunctionPointerForName( bundle, extensionName );
  
  	CFRelease( bundleURL );
  	CFRelease( extensionName );
  	CFRelease( bundle );
  #elif defined( SOIL_X11_PLATFORM )
  	func =
  #if !defined(GLX_VERSION_1_4)
  	glXGetProcAddressARB
  #else
  	glXGetProcAddress
  #endif
  	( (const GLubyte *)proc );
  #elif defined(__sgi) || defined (__sun) || defined(__HAIKU__)
  	func = dlGetProcAddress(proc);
  #endif
  
  	return func;
  }
  
  /* Based on the SDL2 implementation */
  int SOIL_GL_ExtensionSupported(const char *extension)
  {
  	const char *extensions;
  	const char *start;
  	const char *where, *terminator;
  
  	/* Extension names should not have spaces. */
  	where = strchr(extension, ' ');
  
  	if (where || *extension == '\0')
  	{
  		return 0;
  	}
  
  	#if defined( SOIL_X11_PLATFORM ) || defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX )
  	/* Lookup the available extensions */
  	if ( isAtLeastGL3() )
  	{
  		GLint num_exts = 0;
  		GLint i;
  
  		if ( NULL == soilGlGetStringiFunc )
  		{
  			soilGlGetStringiFunc = (P_SOIL_glGetStringiFunc)SOIL_GL_GetProcAddress("glGetStringi");
  
  			if ( NULL == soilGlGetStringiFunc )
  			{
  				return 0;
  			}
  		}
  
  		#ifndef GL_NUM_EXTENSIONS
  		#define GL_NUM_EXTENSIONS 0x821D
  		#endif
  		glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts);
  		for (i = 0; i < num_exts; i++)
  		{
  			const char *thisext = (const char *) soilGlGetStringiFunc(GL_EXTENSIONS, i);
  
  			if (strcmp(thisext, extension) == 0)
  			{
  				return 1;
  			}
  		}
  
  		return 0;
  	}
  	#endif
  
  	/* Try the old way with glGetString(GL_EXTENSIONS) ... */
  	extensions = (const char *) glGetString(GL_EXTENSIONS);
  
  	if (!extensions)
  	{
  		return 0;
  	}
  
  	/*
  	 * It takes a bit of care to be fool-proof about parsing the OpenGL
  	 * extensions string. Don't be fooled by sub-strings, etc.
  	 */
  	start = extensions;
  
  	for (;;) {
  		where = strstr(start, extension);
  
  		if (!where)
  			break;
  
  		terminator = where + strlen(extension);
  
  		if (where == start || *(where - 1) == ' ')
  			if (*terminator == ' ' || *terminator == '\0')
  				return 1;
  
  		start = terminator;
  	}
  
  	return 0;
  }
  
  /*	other functions	*/
  unsigned int
  	SOIL_internal_create_OGL_texture
  	(
  		const unsigned char *const data,
  		int *width, int *height, int channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags,
  		unsigned int opengl_texture_type,
  		unsigned int opengl_texture_target,
  		unsigned int texture_check_size_enum
  	);
  
  /*	and the code magic begins here [8^)	*/
  unsigned int
  	SOIL_load_OGL_texture
  	(
  		const char *filename,
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels;
  	unsigned int tex_id;
  	/*	does the user want direct uploading of the image as a DDS file?	*/
  	if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
  	{
  		/*	1st try direct loading of the image as a DDS file
  			note: direct uploading will only load what is in the
  			DDS file, no MIPmaps will be generated, the image will
  			not be flipped, etc.	*/
  		tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 0 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if( flags & SOIL_FLAG_PVR_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 0 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_ETC1( filename, reuse_texture_ID, flags );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	/*	try to load the image	*/
  	img = SOIL_load_image( filename, &width, &height, &channels, force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	OK, make it a texture!	*/
  	tex_id = SOIL_internal_create_OGL_texture(
  			img, &width, &height, channels,
  			reuse_texture_ID, flags,
  			GL_TEXTURE_2D, GL_TEXTURE_2D,
  			GL_MAX_TEXTURE_SIZE );
  	/*	and nuke the image data	*/
  	SOIL_free_image_data( img );
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_HDR_texture
  	(
  		const char *filename,
  		int fake_HDR_format,
  		int rescale_to_max,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img = NULL;
  	int width, height, channels;
  	unsigned int tex_id;
  	/*	no direct uploading of the image as a DDS file	*/
  	/* error check */
  	if( (fake_HDR_format != SOIL_HDR_RGBE) &&
  		(fake_HDR_format != SOIL_HDR_RGBdivA) &&
  		(fake_HDR_format != SOIL_HDR_RGBdivA2) )
  	{
  		result_string_pointer = "Invalid fake HDR format specified";
  		return 0;
  	}
  
  	/* check if the image is HDR */
  	if ( stbi_is_hdr( filename ) )
  	{
  		/*	try to load the image (only the HDR type) */
  		img = stbi_load( filename, &width, &height, &channels, 4 );
  	}
  
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/* the load worked, do I need to convert it? */
  	if( fake_HDR_format == SOIL_HDR_RGBdivA )
  	{
  		RGBE_to_RGBdivA( img, width, height, rescale_to_max );
  	} else if( fake_HDR_format == SOIL_HDR_RGBdivA2 )
  	{
  		RGBE_to_RGBdivA2( img, width, height, rescale_to_max );
  	}
  	/*	OK, make it a texture!	*/
  	tex_id = SOIL_internal_create_OGL_texture(
  			img, &width, &height, channels,
  			reuse_texture_ID, flags,
  			GL_TEXTURE_2D, GL_TEXTURE_2D,
  			GL_MAX_TEXTURE_SIZE );
  	/*	and nuke the image data	*/
  	SOIL_free_image_data( img );
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_texture_from_memory
  	(
  		const unsigned char *const buffer,
  		int buffer_length,
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels;
  	unsigned int tex_id;
  	/*	does the user want direct uploading of the image as a DDS file?	*/
  	if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
  	{
  		/*	1st try direct loading of the image as a DDS file
  			note: direct uploading will only load what is in the
  			DDS file, no MIPmaps will be generated, the image will
  			not be flipped, etc.	*/
  		tex_id = SOIL_direct_load_DDS_from_memory(
  				buffer, buffer_length,
  				reuse_texture_ID, flags, 0 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if( flags & SOIL_FLAG_PVR_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_PVR_from_memory(
  				buffer, buffer_length,
  				reuse_texture_ID, flags, 0 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if( flags & SOIL_FLAG_ETC1_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_ETC1_from_memory(
  				buffer, buffer_length,
  				reuse_texture_ID, flags );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	/*	try to load the image	*/
  	img = SOIL_load_image_from_memory(
  					buffer, buffer_length,
  					&width, &height, &channels,
  					force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	OK, make it a texture!	*/
  	tex_id = SOIL_internal_create_OGL_texture(
  			img, &width, &height, channels,
  			reuse_texture_ID, flags,
  			GL_TEXTURE_2D, GL_TEXTURE_2D,
  			GL_MAX_TEXTURE_SIZE );
  	/*	and nuke the image data	*/
  	SOIL_free_image_data( img );
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_cubemap
  	(
  		const char *x_pos_file,
  		const char *x_neg_file,
  		const char *y_pos_file,
  		const char *y_neg_file,
  		const char *z_pos_file,
  		const char *z_neg_file,
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels;
  	unsigned int tex_id;
  	/*	error checking	*/
  	if( (x_pos_file == NULL) ||
  		(x_neg_file == NULL) ||
  		(y_pos_file == NULL) ||
  		(y_neg_file == NULL) ||
  		(z_pos_file == NULL) ||
  		(z_neg_file == NULL) )
  	{
  		result_string_pointer = "Invalid cube map files list";
  		return 0;
  	}
  	/*	capability checking	*/
  	if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  	{
  		result_string_pointer = "No cube map capability present";
  		return 0;
  	}
  	/*	1st face: try to load the image	*/
  	img = SOIL_load_image( x_pos_file, &width, &height, &channels, force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	upload the texture, and create a texture ID if necessary	*/
  	tex_id = SOIL_internal_create_OGL_texture(
  			img, &width, &height, channels,
  			reuse_texture_ID, flags,
  			SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X,
  			SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  	/*	and nuke the image data	*/
  	SOIL_free_image_data( img );
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image( x_neg_file, &width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image( y_pos_file, &width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image( y_neg_file, &width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image( z_pos_file, &width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image( z_neg_file, &width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_cubemap_from_memory
  	(
  		const unsigned char *const x_pos_buffer,
  		int x_pos_buffer_length,
  		const unsigned char *const x_neg_buffer,
  		int x_neg_buffer_length,
  		const unsigned char *const y_pos_buffer,
  		int y_pos_buffer_length,
  		const unsigned char *const y_neg_buffer,
  		int y_neg_buffer_length,
  		const unsigned char *const z_pos_buffer,
  		int z_pos_buffer_length,
  		const unsigned char *const z_neg_buffer,
  		int z_neg_buffer_length,
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels;
  	unsigned int tex_id;
  	/*	error checking	*/
  	if( (x_pos_buffer == NULL) ||
  		(x_neg_buffer == NULL) ||
  		(y_pos_buffer == NULL) ||
  		(y_neg_buffer == NULL) ||
  		(z_pos_buffer == NULL) ||
  		(z_neg_buffer == NULL) )
  	{
  		result_string_pointer = "Invalid cube map buffers list";
  		return 0;
  	}
  	/*	capability checking	*/
  	if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  	{
  		result_string_pointer = "No cube map capability present";
  		return 0;
  	}
  	/*	1st face: try to load the image	*/
  	img = SOIL_load_image_from_memory(
  			x_pos_buffer, x_pos_buffer_length,
  			&width, &height, &channels, force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	upload the texture, and create a texture ID if necessary	*/
  	tex_id = SOIL_internal_create_OGL_texture(
  			img, &width, &height, channels,
  			reuse_texture_ID, flags,
  			SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X,
  			SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  	/*	and nuke the image data	*/
  	SOIL_free_image_data( img );
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image_from_memory(
  				x_neg_buffer, x_neg_buffer_length,
  				&width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image_from_memory(
  				y_pos_buffer, y_pos_buffer_length,
  				&width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image_from_memory(
  				y_neg_buffer, y_neg_buffer_length,
  				&width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image_from_memory(
  				z_pos_buffer, z_pos_buffer_length,
  				&width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	continue?	*/
  	if( tex_id != 0 )
  	{
  		/*	1st face: try to load the image	*/
  		img = SOIL_load_image_from_memory(
  				z_neg_buffer, z_neg_buffer_length,
  				&width, &height, &channels, force_channels );
  		/*	channels holds the original number of channels, which may have been forced	*/
  		if( (force_channels >= 1) && (force_channels <= 4) )
  		{
  			channels = force_channels;
  		}
  		if( NULL == img )
  		{
  			/*	image loading failed	*/
  			result_string_pointer = stbi_failure_reason();
  			return 0;
  		}
  		/*	upload the texture, but reuse the assigned texture ID	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				img, &width, &height, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  		/*	and nuke the image data	*/
  		SOIL_free_image_data( img );
  	}
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_single_cubemap
  	(
  		const char *filename,
  		const char face_order[6],
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels, i;
  	unsigned int tex_id = 0;
  	/*	error checking	*/
  	if( filename == NULL )
  	{
  		result_string_pointer = "Invalid single cube map file name";
  		return 0;
  	}
  	/*	does the user want direct uploading of the image as a DDS file?	*/
  	if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
  	{
  		/*	1st try direct loading of the image as a DDS file
  			note: direct uploading will only load what is in the
  			DDS file, no MIPmaps will be generated, the image will
  			not be flipped, etc.	*/
  		tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 1 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_PVR( filename, reuse_texture_ID, flags, 1 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT )
  	{
  		return 0;
  	}
  
  	/*	face order checking	*/
  	for( i = 0; i < 6; ++i )
  	{
  		if( (face_order[i] != 'N') &&
  			(face_order[i] != 'S') &&
  			(face_order[i] != 'W') &&
  			(face_order[i] != 'E') &&
  			(face_order[i] != 'U') &&
  			(face_order[i] != 'D') )
  		{
  			result_string_pointer = "Invalid single cube map face order";
  			return 0;
  		};
  	}
  	/*	capability checking	*/
  	if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  	{
  		result_string_pointer = "No cube map capability present";
  		return 0;
  	}
  	/*	1st off, try to load the full image	*/
  	img = SOIL_load_image( filename, &width, &height, &channels, force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	now, does this image have the right dimensions?	*/
  	if( (width != 6*height) &&
  		(6*width != height) )
  	{
  		SOIL_free_image_data( img );
  		result_string_pointer = "Single cubemap image must have a 6:1 ratio";
  		return 0;
  	}
  	/*	try the image split and create	*/
  	tex_id = SOIL_create_OGL_single_cubemap(
  			img, width, height, channels,
  			face_order, reuse_texture_ID, flags
  			);
  	/*	nuke the temporary image data and return the texture handle	*/
  	SOIL_free_image_data( img );
  	return tex_id;
  }
  
  unsigned int
  	SOIL_load_OGL_single_cubemap_from_memory
  	(
  		const unsigned char *const buffer,
  		int buffer_length,
  		const char face_order[6],
  		int force_channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* img;
  	int width, height, channels, i;
  	unsigned int tex_id = 0;
  	/*	error checking	*/
  	if( buffer == NULL )
  	{
  		result_string_pointer = "Invalid single cube map buffer";
  		return 0;
  	}
  	/*	does the user want direct uploading of the image as a DDS file?	*/
  	if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
  	{
  		/*	1st try direct loading of the image as a DDS file
  			note: direct uploading will only load what is in the
  			DDS file, no MIPmaps will be generated, the image will
  			not be flipped, etc.	*/
  		tex_id = SOIL_direct_load_DDS_from_memory(
  				buffer, buffer_length,
  				reuse_texture_ID, flags, 1 );
  		if( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if ( flags & SOIL_FLAG_PVR_LOAD_DIRECT )
  	{
  		tex_id = SOIL_direct_load_PVR_from_memory(
  				buffer, buffer_length,
  				reuse_texture_ID, flags, 1 );
  		if ( tex_id )
  		{
  			/*	hey, it worked!!	*/
  			return tex_id;
  		}
  	}
  
  	if ( flags & SOIL_FLAG_ETC1_LOAD_DIRECT )
  	{
  		return 0;
  	}
  
  	/*	face order checking	*/
  	for( i = 0; i < 6; ++i )
  	{
  		if( (face_order[i] != 'N') &&
  			(face_order[i] != 'S') &&
  			(face_order[i] != 'W') &&
  			(face_order[i] != 'E') &&
  			(face_order[i] != 'U') &&
  			(face_order[i] != 'D') )
  		{
  			result_string_pointer = "Invalid single cube map face order";
  			return 0;
  		};
  	}
  	/*	capability checking	*/
  	if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  	{
  		result_string_pointer = "No cube map capability present";
  		return 0;
  	}
  	/*	1st off, try to load the full image	*/
  	img = SOIL_load_image_from_memory(
  			buffer, buffer_length,
  			&width, &height, &channels,
  			force_channels );
  	/*	channels holds the original number of channels, which may have been forced	*/
  	if( (force_channels >= 1) && (force_channels <= 4) )
  	{
  		channels = force_channels;
  	}
  	if( NULL == img )
  	{
  		/*	image loading failed	*/
  		result_string_pointer = stbi_failure_reason();
  		return 0;
  	}
  	/*	now, does this image have the right dimensions?	*/
  	if( (width != 6*height) &&
  		(6*width != height) )
  	{
  		SOIL_free_image_data( img );
  		result_string_pointer = "Single cubemap image must have a 6:1 ratio";
  		return 0;
  	}
  	/*	try the image split and create	*/
  	tex_id = SOIL_create_OGL_single_cubemap(
  			img, width, height, channels,
  			face_order, reuse_texture_ID, flags
  			);
  	/*	nuke the temporary image data and return the texture handle	*/
  	SOIL_free_image_data( img );
  	return tex_id;
  }
  
  unsigned int
  	SOIL_create_OGL_single_cubemap
  	(
  		const unsigned char *const data,
  		int width, int height, int channels,
  		const char face_order[6],
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	variables	*/
  	unsigned char* sub_img;
  	int dw, dh, sz, i;
  	unsigned int tex_id;
  	/*	error checking	*/
  	if( data == NULL )
  	{
  		result_string_pointer = "Invalid single cube map image data";
  		return 0;
  	}
  	/*	face order checking	*/
  	for( i = 0; i < 6; ++i )
  	{
  		if( (face_order[i] != 'N') &&
  			(face_order[i] != 'S') &&
  			(face_order[i] != 'W') &&
  			(face_order[i] != 'E') &&
  			(face_order[i] != 'U') &&
  			(face_order[i] != 'D') )
  		{
  			result_string_pointer = "Invalid single cube map face order";
  			return 0;
  		};
  	}
  	/*	capability checking	*/
  	if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  	{
  		result_string_pointer = "No cube map capability present";
  		return 0;
  	}
  	/*	now, does this image have the right dimensions?	*/
  	if( (width != 6*height) &&
  		(6*width != height) )
  	{
  		result_string_pointer = "Single cubemap image must have a 6:1 ratio";
  		return 0;
  	}
  	/*	which way am I stepping?	*/
  	if( width > height )
  	{
  		dw = height;
  		dh = 0;
  	} else
  	{
  		dw = 0;
  		dh = width;
  	}
  	sz = dw+dh;
  	sub_img = (unsigned char *)malloc( sz*sz*channels );
  	/*	do the splitting and uploading	*/
  	tex_id = reuse_texture_ID;
  	for( i = 0; i < 6; ++i )
  	{
  		int x, y, idx = 0;
  		unsigned int cubemap_target = 0;
  		/*	copy in the sub-image	*/
  		for( y = i*dh; y < i*dh+sz; ++y )
  		{
  			for( x = i*dw*channels; x < (i*dw+sz)*channels; ++x )
  			{
  				sub_img[idx++] = data[y*width*channels+x];
  			}
  		}
  		/*	what is my texture target?
  			remember, this coordinate system is
  			LHS if viewed from inside the cube!	*/
  		switch( face_order[i] )
  		{
  		case 'N':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z;
  			break;
  		case 'S':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
  			break;
  		case 'W':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X;
  			break;
  		case 'E':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X;
  			break;
  		case 'U':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y;
  			break;
  		case 'D':
  			cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
  			break;
  		}
  		/*	upload it as a texture	*/
  		tex_id = SOIL_internal_create_OGL_texture(
  				sub_img, &sz, &sz, channels,
  				tex_id, flags,
  				SOIL_TEXTURE_CUBE_MAP,
  				cubemap_target,
  				SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
  	}
  	/*	and nuke the image and sub-image data	*/
  	SOIL_free_image_data( sub_img );
  	/*	and return the handle, such as it is	*/
  	return tex_id;
  }
  
  unsigned int
  	SOIL_create_OGL_texture
  	(
  		const unsigned char *const data,
  		int *width, int *height, int channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags
  	)
  {
  	/*	wrapper function for 2D textures	*/
  	return SOIL_internal_create_OGL_texture(
  				data, width, height, channels,
  				reuse_texture_ID, flags,
  				GL_TEXTURE_2D, GL_TEXTURE_2D,
  				GL_MAX_TEXTURE_SIZE );
  }
  
  #if SOIL_CHECK_FOR_GL_ERRORS
  void check_for_GL_errors( const char *calling_location )
  {
  	/*	check for errors	*/
  	GLenum err_code = glGetError();
  	while( GL_NO_ERROR != err_code )
  	{
  		printf( "OpenGL Error @ %s: %i", calling_location, err_code );
  		err_code = glGetError();
  	}
  }
  #else
  void check_for_GL_errors( const char *calling_location )
  {
  	/*	no check for errors	*/
  }
  #endif
  
  static void createMipmaps(const unsigned char *const img,
  		int width, int height, int channels,
  		unsigned int flags,
  		unsigned int opengl_texture_target,
  		unsigned int internal_texture_format,
  		unsigned int original_texture_format,
  		int DXT_mode)
  {
  	if ( ( flags & SOIL_FLAG_GL_MIPMAPS ) && query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT )
  	{
  		soilGlGenerateMipmap(opengl_texture_target);
  	}
  	else
  	{
  		int MIPlevel = 1;
  		int MIPwidth = (width+1) / 2;
  		int MIPheight = (height+1) / 2;
  		unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight );
  
  		while( ((1<<MIPlevel) <= width) || ((1<<MIPlevel) <= height) )
  		{
  			/*	do this MIPmap level	*/
  			mipmap_image(
  					img, width, height, channels,
  					resampled,
  					(1 << MIPlevel), (1 << MIPlevel) );
  
  			/*  upload the MIPmaps	*/
  			if( DXT_mode == SOIL_CAPABILITY_PRESENT )
  			{
  				/*	user wants me to do the DXT conversion!	*/
  				int DDS_size;
  				unsigned char *DDS_data = NULL;
  				if( (channels & 1) == 1 )
  				{
  					/*	RGB, use DXT1	*/
  					DDS_data = convert_image_to_DXT1(
  							resampled, MIPwidth, MIPheight, channels, &DDS_size );
  				} else
  				{
  					/*	RGBA, use DXT5	*/
  					DDS_data = convert_image_to_DXT5(
  							resampled, MIPwidth, MIPheight, channels, &DDS_size );
  				}
  				if( DDS_data )
  				{
  					soilGlCompressedTexImage2D(
  						opengl_texture_target, MIPlevel,
  						internal_texture_format, MIPwidth, MIPheight, 0,
  						DDS_size, DDS_data );
  					check_for_GL_errors( "glCompressedTexImage2D" );
  					SOIL_free_image_data( DDS_data );
  				} else
  				{
  					/*	my compression failed, try the OpenGL driver's version	*/
  					glTexImage2D(
  						opengl_texture_target, MIPlevel,
  						internal_texture_format, MIPwidth, MIPheight, 0,
  						original_texture_format, GL_UNSIGNED_BYTE, resampled );
  					check_for_GL_errors( "glTexImage2D" );
  				}
  			} else
  			{
  				/*	user want OpenGL to do all the work!	*/
  				glTexImage2D(
  					opengl_texture_target, MIPlevel,
  					internal_texture_format, MIPwidth, MIPheight, 0,
  					original_texture_format, GL_UNSIGNED_BYTE, resampled );
  				check_for_GL_errors( "glTexImage2D" );
  			}
  			/*	prep for the next level	*/
  			++MIPlevel;
  			MIPwidth = (MIPwidth + 1) / 2;
  			MIPheight = (MIPheight + 1) / 2;
  		}
  
  		SOIL_free_image_data( resampled );
  	}
  }
  
  unsigned int
  	SOIL_internal_create_OGL_texture
  	(
  		const unsigned char *const data,
  		int *width, int *height, int channels,
  		unsigned int reuse_texture_ID,
  		unsigned int flags,
  		unsigned int opengl_texture_type,
  		unsigned int opengl_texture_target,
  		unsigned int texture_check_size_enum
  	)
  {
  	/*	variables	*/
  	unsigned char* img = NULL;
  	unsigned int tex_id;
  	unsigned int internal_texture_format = 0, original_texture_format = 0;
  	int DXT_mode = SOIL_CAPABILITY_UNKNOWN;
  	int sRGB_texture = query_sRGB_capability() == SOIL_CAPABILITY_PRESENT && ( flags & SOIL_FLAG_SRGB_COLOR_SPACE );;
  	int max_supported_size;
  	int iwidth = *width;
  	int iheight = *height;
  	int needCopy;
  	GLint unpack_aligment;
  
  	/*	how large of a texture can this OpenGL implementation handle?	*/
  	/*	texture_check_size_enum will be GL_MAX_TEXTURE_SIZE or SOIL_MAX_CUBE_MAP_TEXTURE_SIZE	*/
  	glGetIntegerv( texture_check_size_enum, &max_supported_size );
  
  	/*	If the user wants to use the texture rectangle I kill a few flags	*/
  	if( flags & SOIL_FLAG_TEXTURE_RECTANGLE )
  	{
  		/*	well, the user asked for it, can we do that?	*/
  		if( query_tex_rectangle_capability() == SOIL_CAPABILITY_PRESENT )
  		{
  			/*	only allow this if the user in _NOT_ trying to do a cubemap!	*/
  			if( opengl_texture_type == GL_TEXTURE_2D )
  			{
  				/*	clean out the flags that cannot be used with texture rectangles	*/
  				flags &= ~(
  						SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS |
  						SOIL_FLAG_TEXTURE_REPEATS
  					);
  				/*	and change my target	*/
  				opengl_texture_target = SOIL_TEXTURE_RECTANGLE_ARB;
  				opengl_texture_type = SOIL_TEXTURE_RECTANGLE_ARB;
  			} else
  			{
  				/*	not allowed for any other uses (yes, I'm looking at you, cubemaps!)	*/
  				flags &= ~SOIL_FLAG_TEXTURE_RECTANGLE;
  			}
  
  		} else
  		{
  			/*	can't do it, and that is a breakable offense (uv coords use pixels instead of [0,1]!)	*/
  			result_string_pointer = "Texture Rectangle extension unsupported";
  			return 0;
  		}
  	}
  
  	/*	if the user can't support NPOT textures, make sure we force the POT option	*/
  	if( (query_NPOT_capability() == SOIL_CAPABILITY_NONE) &&
  		!(flags & SOIL_FLAG_TEXTURE_RECTANGLE) )
  	{
  		/*	add in the POT flag */
  		flags |= SOIL_FLAG_POWER_OF_TWO;
  	}
  
  	needCopy = ( ( flags & SOIL_FLAG_INVERT_Y ) ||
  				 ( flags & SOIL_FLAG_NTSC_SAFE_RGB ) ||
  				 ( flags & SOIL_FLAG_MULTIPLY_ALPHA ) ||
  				 ( flags & SOIL_FLAG_CoCg_Y )
  				);
  
  	/*	create a copy the image data only if needed */
  	if ( needCopy ) {
  		img = (unsigned char*)malloc( iwidth*iheight*channels );
  		memcpy( img, data, iwidth*iheight*channels );
  	}
  
  	/*	does the user want me to invert the image?	*/
  	if( flags & SOIL_FLAG_INVERT_Y )
  	{
  		int i, j;
  		for( j = 0; j*2 < iheight; ++j )
  		{
  			int index1 = j * iwidth * channels;
  			int index2 = (iheight - 1 - j) * iwidth * channels;
  			for( i = iwidth * channels; i > 0; --i )
  			{
  				unsigned char temp = img[index1];
  				img[index1] = img[index2];
  				img[index2] = temp;
  				++index1;
  				++index2;
  			}
  		}
  	}
  	/*	does the user want me to scale the colors into the NTSC safe RGB range?	*/
  	if( flags & SOIL_FLAG_NTSC_SAFE_RGB )
  	{
  		scale_image_RGB_to_NTSC_safe( img, iwidth, iheight, channels );
  	}
  	/*	does the user want me to convert from straight to pre-multiplied alpha?
  		(and do we even _have_ alpha?)	*/
  	if( flags & SOIL_FLAG_MULTIPLY_ALPHA )
  	{
  		int i;
  		switch( channels )
  		{
  		case 2:
  			for( i = 0; i < 2*iwidth*iheight; i += 2 )
  			{
  				img[i] = (img[i] * img[i+1] + 128) >> 8;
  			}
  			break;
  		case 4:
  			for( i = 0; i < 4*iwidth*iheight; i += 4 )
  			{
  				img[i+0] = (img[i+0] * img[i+3] + 128) >> 8;
  				img[i+1] = (img[i+1] * img[i+3] + 128) >> 8;
  				img[i+2] = (img[i+2] * img[i+3] + 128) >> 8;
  			}
  			break;
  		default:
  			/*	no other number of channels contains alpha data	*/
  			break;
  		}
  	}
  
  	/*	do I need to make it a power of 2?	*/
  	if(
  		( ( flags & SOIL_FLAG_POWER_OF_TWO) && ( !SOIL_IS_POW2(iwidth) || !SOIL_IS_POW2(iheight) ) ) ||	/*	user asked for it and the texture is not power of 2	*/
  		( (flags & SOIL_FLAG_MIPMAPS)&& !( ( flags & SOIL_FLAG_GL_MIPMAPS ) &&
  										   query_gen_mipmap_capability() == SOIL_CAPABILITY_PRESENT &&
  										   query_NPOT_capability() == SOIL_CAPABILITY_PRESENT ) ) ||	/*	need it for the MIP-maps when mipmaps required
  																											and not GL mipmaps required and supported	*/
  		(iwidth > max_supported_size) ||		/*	it's too big, (make sure it's	*/
  		(iheight > max_supported_size) )		/*	2^n for later down-sampling)	*/
  	{
  		int new_width = 1;
  		int new_height = 1;
  		while( new_width < iwidth )
  		{
  			new_width *= 2;
  		}
  		while( new_height < iheight )
  		{
  			new_height *= 2;
  		}
  		/*	still?	*/
  		if( (new_width != iwidth) || (new_height != iheight) )
  		{
  			/*	yep, resize	*/
  			unsigned char *resampled = (unsigned char*)malloc( channels*new_width*new_height );
  			up_scale_image(
  					NULL != img ? img : data, iwidth, iheight, channels,
  					resampled, new_width, new_height );
  
  			/*	nuke the old guy ( if a copy exists ), then point it at the new guy	*/
  			SOIL_free_image_data( img );
  			img = resampled;
  			*width = new_width;
  			*height = new_height;
  			iwidth = new_width;
  			iheight = new_height;
  		}
  	}
  	/*	now, if it is too large...	*/
  	if( (iwidth > max_supported_size) || (iheight > max_supported_size) )
  	{
  		/*	I've already made it a power of two, so simply use the MIPmapping
  			code to reduce its size to the allowable maximum.	*/
  		unsigned char *resampled;
  		int reduce_block_x = 1, reduce_block_y = 1;
  		int new_width, new_height;
  		if( iwidth > max_supported_size )
  		{
  			reduce_block_x = iwidth / max_supported_size;
  		}
  		if( iheight > max_supported_size )
  		{
  			reduce_block_y = iheight / max_supported_size;
  		}
  		new_width = iwidth / reduce_block_x;
  		new_height = iheight / reduce_block_y;
  		resampled = (unsigned char*)malloc( channels*new_width*new_height );
  		/*	perform the actual reduction	*/
  		mipmap_image( NULL != img ? img : data, iwidth, iheight, channels,
  						resampled, reduce_block_x, reduce_block_y );
  		/*	nuke the old guy, then point it at the new guy	*/
  		SOIL_free_image_data( img );
  		img = resampled;
  		*width = new_width;
  		*height = new_height;
  		iwidth = new_width;
  		iheight = new_height;
  	}
  	/*	does the user want us to use YCoCg color space?	*/
  	if( flags & SOIL_FLAG_CoCg_Y )
  	{
  		/*	this will only work with RGB and RGBA images */
  		convert_RGB_to_YCoCg( img, iwidth, iheight, channels );
  	}
  	/*	create the OpenGL texture ID handle
  		(note: allowing a forced texture ID lets me reload a texture)	*/
  	tex_id = reuse_texture_ID;
  	if( tex_id == 0 )
  	{
  		glGenTextures( 1, &tex_id );
  	}
  	check_for_GL_errors( "glGenTextures" );
  	/* Note: sometimes glGenTextures fails (usually no OpenGL context)	*/
  	if( tex_id )
  	{
  		/*	and what type am I using as the internal texture format?	*/
  		switch( channels )
  		{
  		case 1:
  			original_texture_format = GL_LUMINANCE;
  			break;
  		case 2:
  			original_texture_format = GL_LUMINANCE_ALPHA;
  			break;
  		case 3:
  			original_texture_format = GL_RGB;
  			break;
  		case 4:
  			original_texture_format = GL_RGBA;
  			break;
  		}
  		internal_texture_format = original_texture_format;
  		/*	does the user want me to, and can I, save as DXT?	*/
  		if( flags & SOIL_FLAG_COMPRESS_TO_DXT )
  		{
  			DXT_mode = query_DXT_capability();
  			if( DXT_mode == SOIL_CAPABILITY_PRESENT )
  			{
  				/*	I can use DXT, whether I compress it or OpenGL does	*/
  				if( (channels & 1) == 1 )
  				{
  					/*	1 or 3 channels = DXT1	*/
  					internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : SOIL_RGB_S3TC_DXT1;
  				} else
  				{
  					/*	2 or 4 channels = DXT5	*/
  					internal_texture_format = sRGB_texture ? SOIL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : SOIL_RGBA_S3TC_DXT5;
  				}
  			}
  		}
  		else if ( sRGB_texture )
  		{
  			switch( channels )
  			{
  			case 3:
  				internal_texture_format = SOIL_GL_SRGB;
  				break;
  			case 4:
  				internal_texture_format = SOIL_GL_SRGB_ALPHA;
  				break;
  			}
  		}
  
  		/*  bind an OpenGL texture ID	*/
  		glBindTexture( opengl_texture_type, tex_id );
  		check_for_GL_errors( "glBindTexture" );
  
  		/* set the unpack aligment */
  		glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment);
  		if ( 1 != unpack_aligment )
  		{
  			glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  		}
  
  		/*  upload the main image	*/
  		if( DXT_mode == SOIL_CAPABILITY_PRESENT )
  		{
  			/*	user wants me to do the DXT conversion!	*/
  			int DDS_size;
  			unsigned char *DDS_data = NULL;
  			if( (channels & 1) == 1 )
  			{
  				/*	RGB, use DXT1	*/
  				DDS_data = convert_image_to_DXT1( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size );
  			} else
  			{
  				/*	RGBA, use DXT5	*/
  				DDS_data = convert_image_to_DXT5( NULL != img ? img : data, iwidth, iheight, channels, &DDS_size );
  			}
  			if( DDS_data )
  			{
  				soilGlCompressedTexImage2D(
  					opengl_texture_target, 0,
  					internal_texture_format, iwidth, iheight, 0,
  					DDS_size, DDS_data );
  				check_for_GL_errors( "glCompressedTexImage2D" );
  				SOIL_free_image_data( DDS_data );
  				/*	printf( "Internal DXT compressor\n" );	*/
  			} else
  			{
  				/*	my compression failed, try the OpenGL driver's version	*/
  				glTexImage2D(
  					opengl_texture_target, 0,
  					internal_texture_format, iwidth, iheight, 0,
  					original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data );
  				check_for_GL_errors( "glTexImage2D" );
  				/*	printf( "OpenGL DXT compressor\n" );	*/
  			}
  		} else
  		{
  			/*	user want OpenGL to do all the work!	*/
  			glTexImage2D(
  				opengl_texture_target, 0,
  				internal_texture_format, iwidth, iheight, 0,
  				original_texture_format, GL_UNSIGNED_BYTE, NULL != img ? img : data );
  
  			check_for_GL_errors( "glTexImage2D" );
  			/*printf( "OpenGL DXT compressor\n" );	*/
  		}
  
  		/*	are any MIPmaps desired?	*/
  		if( flags & SOIL_FLAG_MIPMAPS || flags & SOIL_FLAG_GL_MIPMAPS )
  		{
  			createMipmaps( NULL != img ? img : data, iwidth, iheight, channels, flags, opengl_texture_target, internal_texture_format, original_texture_format, DXT_mode );
  
  			/*	instruct OpenGL to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
  			check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" );
  		} else
  		{
  			/*	instruct OpenGL _NOT_ to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  			check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" );
  		}
  
  		/* recover the unpack aligment */
  		if ( 1 != unpack_aligment )
  		{
  			glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment);
  		}
  
  		/*	does the user want clamping, or wrapping?	*/
  		if( flags & SOIL_FLAG_TEXTURE_REPEATS )
  		{
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
  			if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP )
  			{
  				/*	SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported	*/
  				glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
  			}
  			check_for_GL_errors( "GL_TEXTURE_WRAP_*" );
  		} else
  		{
  			unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;
  			/* unsigned int clamp_mode = GL_CLAMP; */
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
  			if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP )
  			{
  				/*	SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported	*/
  				glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
  			}
  			check_for_GL_errors( "GL_TEXTURE_WRAP_*" );
  		}
  		/*	done	*/
  		result_string_pointer = "Image loaded as an OpenGL texture";
  	} else
  	{
  		/*	failed	*/
  		result_string_pointer = "Failed to generate an OpenGL texture name; missing OpenGL context?";
  	}
  
  	SOIL_free_image_data( img );
  
  	return tex_id;
  }
  
  int
  	SOIL_save_screenshot
  	(
  		const char *filename,
  		int image_type,
  		int x, int y,
  		int width, int height
  	)
  {
  	unsigned char *pixel_data;
  	int i, j;
  	int save_result;
  	GLint pack_aligment;
  
  	/*	error checks	*/
  	if( (width < 1) || (height < 1) )
  	{
  		result_string_pointer = "Invalid screenshot dimensions";
  		return 0;
  	}
  	if( (x < 0) || (y < 0) )
  	{
  		result_string_pointer = "Invalid screenshot location";
  		return 0;
  	}
  	if( filename == NULL )
  	{
  		result_string_pointer = "Invalid screenshot filename";
  		return 0;
  	}
  
  	glGetIntegerv(GL_PACK_ALIGNMENT, &pack_aligment);
  	if ( 1 != pack_aligment )
  	{
  		glPixelStorei(GL_PACK_ALIGNMENT,1);
  	}
  
  	/*  Get the data from OpenGL	*/
  	pixel_data = (unsigned char*)malloc( 3*width*height );
  	glReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixel_data);
  
  	if ( 1 != pack_aligment )
  	{
  		glPixelStorei(GL_PACK_ALIGNMENT, pack_aligment);
  	}
  
  	/*	invert the image	*/
  	for( j = 0; j*2 < height; ++j )
  	{
  		int index1 = j * width * 3;
  		int index2 = (height - 1 - j) * width * 3;
  		for( i = width * 3; i > 0; --i )
  		{
  			unsigned char temp = pixel_data[index1];
  			pixel_data[index1] = pixel_data[index2];
  			pixel_data[index2] = temp;
  			++index1;
  			++index2;
  		}
  	}
  
  	/*	save the image	*/
  	save_result = SOIL_save_image( filename, image_type, width, height, 3, pixel_data);
  
  	/*	And free the memory	*/
  	SOIL_free_image_data( pixel_data );
  	return save_result;
  }
  
  unsigned char*
  	SOIL_load_image
  	(
  		const char *filename,
  		int *width, int *height, int *channels,
  		int force_channels
  	)
  {
  	unsigned char *result = stbi_load( filename,
  			width, height, channels, force_channels );
  	if( result == NULL )
  	{
  		result_string_pointer = stbi_failure_reason();
  	} else
  	{
  		result_string_pointer = "Image loaded";
  	}
  	return result;
  }
  
  unsigned char*
  	SOIL_load_image_from_memory
  	(
  		const unsigned char *const buffer,
  		int buffer_length,
  		int *width, int *height, int *channels,
  		int force_channels
  	)
  {
  	unsigned char *result = stbi_load_from_memory(
  				buffer, buffer_length,
  				width, height, channels,
  				force_channels );
  	if( result == NULL )
  	{
  		result_string_pointer = stbi_failure_reason();
  	} else
  	{
  		result_string_pointer = "Image loaded from memory";
  	}
  	return result;
  }
  
  
  int
  	SOIL_save_image
  	(
  		const char *filename,
  		int image_type,
  		int width, int height, int channels,
  		const unsigned char *const data
  	)
  {
  	return SOIL_save_image_quality( filename, image_type, width, height, channels, data, 80 );
  }
  
  int
  	SOIL_save_image_quality
  	(
  		const char *filename,
  		int image_type,
  		int width, int height, int channels,
  		const unsigned char *const data,
  		int quality
  	)
  {
  	int save_result;
  
  	/*	error check	*/
  	if( (width < 1) || (height < 1) ||
  		(channels < 1) || (channels > 4) ||
  		(data == NULL) ||
  		(filename == NULL) )
  	{
  		return 0;
  	}
  	if( image_type == SOIL_SAVE_TYPE_BMP )
  	{
  		save_result = stbi_write_bmp( filename,
  				width, height, channels, (void*)data );
  	} else
  	if( image_type == SOIL_SAVE_TYPE_TGA )
  	{
  		save_result = stbi_write_tga( filename,
  				width, height, channels, (void*)data );
  	} else
  	if( image_type == SOIL_SAVE_TYPE_DDS )
  	{
  		save_result = save_image_as_DDS( filename,
  				width, height, channels, (const unsigned char *const)data );
  	} else
  	if( image_type == SOIL_SAVE_TYPE_PNG )
  	{
  		save_result = stbi_write_png( filename,
  				width, height, channels, (const unsigned char *const)data, 0 );
  	} else
  	if ( image_type == SOIL_SAVE_TYPE_JPG )
  	{
  		save_result = jo_write_jpg( filename, (const void*)data, width, height, channels, quality );
  	}
  	else
  	{
  		save_result = 0;
  	}
  
  	if( save_result == 0 )
  	{
  		result_string_pointer = "Saving the image failed";
  	} else
  	{
  		result_string_pointer = "Image saved";
  	}
  	return save_result;
  }
  
  void
  	SOIL_free_image_data
  	(
  		unsigned char *img_data
  	)
  {
  	if ( img_data )
  		free( (void*)img_data );
  }
  
  const char*
  	SOIL_last_result
  	(
  		void
  	)
  {
  	return result_string_pointer;
  }
  
  unsigned int SOIL_direct_load_DDS_from_memory(
  		const unsigned char *const buffer,
  		int buffer_length,
  		unsigned int reuse_texture_ID,
  		int flags,
  		int loading_as_cubemap )
  {
  	/*	variables	*/
  	DDS_header header;
  	unsigned int buffer_index = 0;
  	unsigned int tex_ID = 0;
  	/*	file reading variables	*/
  	unsigned int S3TC_type = 0;
  	unsigned char *DDS_data;
  	unsigned int DDS_main_size;
  	unsigned int DDS_full_size;
  	unsigned int width, height;
  	int mipmaps, cubemap, uncompressed, block_size = 16;
  	unsigned int flag;
  	unsigned int cf_target, ogl_target_start, ogl_target_end;
  	unsigned int opengl_texture_type;
  	int i;
  	/*	1st off, does the filename even exist?	*/
  	if( NULL == buffer )
  	{
  		/*	we can't do it!	*/
  		result_string_pointer = "NULL buffer";
  		return 0;
  	}
  	if( buffer_length < sizeof( DDS_header ) )
  	{
  		/*	we can't do it!	*/
  		result_string_pointer = "DDS file was too small to contain the DDS header";
  		return 0;
  	}
  	/*	try reading in the header	*/
  	memcpy ( (void*)(&header), (const void *)buffer, sizeof( DDS_header ) );
  	buffer_index = sizeof( DDS_header );
  	/*	guilty until proven innocent	*/
  	result_string_pointer = "Failed to read a known DDS header";
  	/*	validate the header (warning, "goto"'s ahead, shield your eyes!!)	*/
  	flag = ('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24);
  	if( header.dwMagic != flag ) {goto quick_exit;}
  	if( header.dwSize != 124 ) {goto quick_exit;}
  	/*	I need all of these	*/
  	flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
  	if( (header.dwFlags & flag) != flag ) {goto quick_exit;}
  	/*	According to the MSDN spec, the dwFlags should contain
  		DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if
  		uncompressed.  Some DDS writers do not conform to the
  		spec, so I need to make my reader more tolerant	*/
  	/*	I need one of these	*/
  	flag = DDPF_FOURCC | DDPF_RGB;
  	if( (header.sPixelFormat.dwFlags & flag) == 0 ) {goto quick_exit;}
  	if( header.sPixelFormat.dwSize != 32 ) {goto quick_exit;}
  	if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) {goto quick_exit;}
  	/*	make sure it is a type we can upload	*/
  	if( (header.sPixelFormat.dwFlags & DDPF_FOURCC) &&
  		!(
  		(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) ||
  		(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) ||
  		(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24)))
  		) )
  	{
  		goto quick_exit;
  	}
  	/*	OK, validated the header, let's load the image data	*/
  	result_string_pointer = "DDS header loaded and validated";
  	width = header.dwWidth;
  	height = header.dwHeight;
  	uncompressed = 1 - (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC;
  	cubemap = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP;
  	if( uncompressed )
  	{
  		S3TC_type = GL_RGB;
  		block_size = 3;
  		if( header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS )
  		{
  			S3TC_type = GL_RGBA;
  			block_size = 4;
  		}
  		DDS_main_size = width * height * block_size;
  	} else
  	{
  		/*	can we even handle direct uploading to OpenGL DXT compressed images?	*/
  		if( query_DXT_capability() != SOIL_CAPABILITY_PRESENT )
  		{
  			/*	we can't do it!	*/
  			result_string_pointer = "Direct upload of S3TC images not supported by the OpenGL driver";
  			return 0;
  		}
  		/*	well, we know it is DXT1/3/5, because we checked above	*/
  		switch( (header.sPixelFormat.dwFourCC >> 24) - '0' )
  		{
  		case 1:
  			S3TC_type = SOIL_RGBA_S3TC_DXT1;
  			block_size = 8;
  			break;
  		case 3:
  			S3TC_type = SOIL_RGBA_S3TC_DXT3;
  			block_size = 16;
  			break;
  		case 5:
  			S3TC_type = SOIL_RGBA_S3TC_DXT5;
  			block_size = 16;
  			break;
  		}
  		DDS_main_size = ((width+3)>>2)*((height+3)>>2)*block_size;
  	}
  	if( cubemap )
  	{
  		/* does the user want a cubemap?	*/
  		if( !loading_as_cubemap )
  		{
  			/*	we can't do it!	*/
  			result_string_pointer = "DDS image was a cubemap";
  			return 0;
  		}
  		/*	can we even handle cubemaps with the OpenGL driver?	*/
  		if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
  		{
  			/*	we can't do it!	*/
  			result_string_pointer = "Direct upload of cubemap images not supported by the OpenGL driver";
  			return 0;
  		}
  		ogl_target_start = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X;
  		ogl_target_end =   SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
  		opengl_texture_type = SOIL_TEXTURE_CUBE_MAP;
  	} else
  	{
  		/* does the user want a non-cubemap?	*/
  		if( loading_as_cubemap )
  		{
  			/*	we can't do it!	*/
  			result_string_pointer = "DDS image was not a cubemap";
  			return 0;
  		}
  		ogl_target_start = GL_TEXTURE_2D;
  		ogl_target_end =   GL_TEXTURE_2D;
  		opengl_texture_type = GL_TEXTURE_2D;
  	}
  	if( (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1) )
  	{
  		mipmaps = header.dwMipMapCount - 1;
  		DDS_full_size = DDS_main_size;
  		for( i = 1; i <= mipmaps; ++ i )
  		{
  			int w, h;
  			w = width >> i;
  			h = height >> i;
  			if( w < 1 )
  			{
  				w = 1;
  			}
  			if( h < 1 )
  			{
  				h = 1;
  			}
  			if ( uncompressed )
  			{
  				/*	uncompressed DDS, simple MIPmap size calculation	*/
  				DDS_full_size += w*h*block_size;
  			} else
  			{
  				/*	compressed DDS, MIPmap size calculation is block based	*/
  				DDS_full_size += ((w+3)/4)*((h+3)/4)*block_size;
  			}
  		}
  	} else
  	{
  		mipmaps = 0;
  		DDS_full_size = DDS_main_size;
  	}
  	DDS_data = (unsigned char*)malloc( DDS_full_size );
  	/*	got the image data RAM, create or use an existing OpenGL texture handle	*/
  	tex_ID = reuse_texture_ID;
  	if( tex_ID == 0 )
  	{
  		glGenTextures( 1, &tex_ID );
  	}
  	/*  bind an OpenGL texture ID	*/
  	glBindTexture( opengl_texture_type, tex_ID );
  	/*	do this for each face of the cubemap!	*/
  	for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target )
  	{
  		if( buffer_index + DDS_full_size <= (unsigned int)buffer_length )
  		{
  			unsigned int byte_offset = DDS_main_size;
  			memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size );
  			buffer_index += DDS_full_size;
  			/*	upload the main chunk	*/
  			if( uncompressed )
  			{
  				/*	and remember, DXT uncompressed uses BGR(A),
  					so swap to RGB(A) for ALL MIPmap levels	*/
  				for( i = 0; i < (int)DDS_full_size; i += block_size )
  				{
  					unsigned char temp = DDS_data[i];
  					DDS_data[i] = DDS_data[i+2];
  					DDS_data[i+2] = temp;
  				}
  				glTexImage2D(
  					cf_target, 0,
  					S3TC_type, width, height, 0,
  					S3TC_type, GL_UNSIGNED_BYTE, DDS_data );
  			} else
  			{
  				soilGlCompressedTexImage2D(
  					cf_target, 0,
  					S3TC_type, width, height, 0,
  					DDS_main_size, DDS_data );
  			}
  			/*	upload the mipmaps, if we have them	*/
  			for( i = 1; i <= mipmaps; ++i )
  			{
  				int w, h, mip_size;
  				w = width >> i;
  				h = height >> i;
  				if( w < 1 )
  				{
  					w = 1;
  				}
  				if( h < 1 )
  				{
  					h = 1;
  				}
  				/*	upload this mipmap	*/
  				if( uncompressed )
  				{
  					mip_size = w*h*block_size;
  					glTexImage2D(
  						cf_target, i,
  						S3TC_type, w, h, 0,
  						S3TC_type, GL_UNSIGNED_BYTE, &DDS_data[byte_offset] );
  				} else
  				{
  					mip_size = ((w+3)/4)*((h+3)/4)*block_size;
  					soilGlCompressedTexImage2D(
  						cf_target, i,
  						S3TC_type, w, h, 0,
  						mip_size, &DDS_data[byte_offset] );
  				}
  				/*	and move to the next mipmap	*/
  				byte_offset += mip_size;
  			}
  			/*	it worked!	*/
  			result_string_pointer = "DDS file loaded";
  		} else
  		{
  			glDeleteTextures( 1, & tex_ID );
  			tex_ID = 0;
  			cf_target = ogl_target_end + 1;
  			result_string_pointer = "DDS file was too small for expected image data";
  		}
  	}/* end reading each face */
  	SOIL_free_image_data( DDS_data );
  	if( tex_ID )
  	{
  		/*	did I have MIPmaps?	*/
  		if( mipmaps > 0 )
  		{
  			/*	instruct OpenGL to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
  		} else
  		{
  			/*	instruct OpenGL _NOT_ to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  		}
  		/*	does the user want clamping, or wrapping?	*/
  		if( flags & SOIL_FLAG_TEXTURE_REPEATS )
  		{
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
  		} else
  		{
  			unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;
  			/* unsigned int clamp_mode = GL_CLAMP; */
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
  		}
  	}
  
  quick_exit:
  	/*	report success or failure	*/
  	return tex_ID;
  }
  
  unsigned int SOIL_direct_load_DDS(
  		const char *filename,
  		unsigned int reuse_texture_ID,
  		int flags,
  		int loading_as_cubemap )
  {
  	FILE *f;
  	unsigned char *buffer;
  	size_t buffer_length, bytes_read;
  	unsigned int tex_ID = 0;
  	/*	error checks	*/
  	if( NULL == filename )
  	{
  		result_string_pointer = "NULL filename";
  		return 0;
  	}
  	f = fopen( filename, "rb" );
  	if( NULL == f )
  	{
  		/*	the file doesn't seem to exist (or be open-able)	*/
  		result_string_pointer = "Can not find DDS file";
  		return 0;
  	}
  	fseek( f, 0, SEEK_END );
  	buffer_length = ftell( f );
  	fseek( f, 0, SEEK_SET );
  	buffer = (unsigned char *) malloc( buffer_length );
  	if( NULL == buffer )
  	{
  		result_string_pointer = "malloc failed";
  		fclose( f );
  		return 0;
  	}
  	bytes_read = fread( (void*)buffer, 1, buffer_length, f );
  	fclose( f );
  	if( bytes_read < buffer_length )
  	{
  		/*	huh?	*/
  		buffer_length = bytes_read;
  	}
  	/*	now try to do the loading	*/
  	tex_ID = SOIL_direct_load_DDS_from_memory(
  		(const unsigned char *const)buffer, (int)buffer_length,
  		reuse_texture_ID, flags, loading_as_cubemap );
  	SOIL_free_image_data( buffer );
  	return tex_ID;
  }
  
  unsigned int SOIL_direct_load_PVR_from_memory(
  		const unsigned char *const buffer,
  		int buffer_length,
  		unsigned int reuse_texture_ID,
  		int flags,
  		int loading_as_cubemap )
  {
  	PVR_Texture_Header* header = (PVR_Texture_Header*)buffer;
  	int num_surfs = 1;
  	GLuint tex_ID = 0;
  	GLenum PVR_format = 0;
  	GLenum PVR_type = GL_RGB;
  	unsigned int opengl_texture_type = loading_as_cubemap ? SOIL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
  	int is_PVRTC_supported = query_PVR_capability() == SOIL_CAPABILITY_PRESENT;
  	int is_BGRA8888_supported  = query_BGRA8888_capability() == SOIL_CAPABILITY_PRESENT;
  	int is_compressed_format_supported = 0;
  	int is_compressed_format = 0;
  	int mipmaps = 0;
  	int i;
  	GLint unpack_aligment;
  
  	// Check the header size
  	if ( header->dwHeaderSize != sizeof(PVR_Texture_Header) ) {
  		if ( header->dwHeaderSize == PVRTEX_V1_HEADER_SIZE ) {
  			result_string_pointer = "this is an old pvr ( update the PVR file )";
  
  			if ( loading_as_cubemap ) {
  				if( header->dwpfFlags & PVRTEX_CUBEMAP ) {
  					num_surfs = 6;
  				} else {
  					result_string_pointer = "tried to load a non-cubemap PVR as cubemap";
  					return 0;
  				}
  			}
  		} else {
  			result_string_pointer = "invalid PVR header";
  
  			return 0;
  		}
  	} else {
  		if ( loading_as_cubemap ) {
  			// Header V2
  			if( header->dwNumSurfs < 1 ) {
  				if( header->dwpfFlags & PVRTEX_CUBEMAP ) {
  					num_surfs = 6;
  				} else {
  					result_string_pointer = "tried to load a non-cubemap PVR as cubemap";
  					return 0;
  				}
  			} else {
  				num_surfs = header->dwNumSurfs;
  			}
  		}
  	}
  
  	// Check the magic identifier
  	if ( header->dwPVR != PVRTEX_IDENTIFIER ) {
  		result_string_pointer = "invalid PVR header";
  		return 0;
  	}
  
  	/* Only accept untwiddled data UNLESS texture format is PVRTC */
  	if ( ((header->dwpfFlags & PVRTEX_TWIDDLE) == PVRTEX_TWIDDLE)
  		&& ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC2)
  		&& ((header->dwpfFlags & PVRTEX_PIXELTYPE)!=OGL_PVRTC4) )
  	{
  		// We need to load untwiddled textures -- hw will twiddle for us.
  		result_string_pointer = "pvr is not compressed ( untwiddled texture )";
  		return 0;
  	}
  
  	switch( header->dwpfFlags & PVRTEX_PIXELTYPE )
  	{
  		case OGL_RGBA_4444:
  			PVR_format = GL_UNSIGNED_SHORT_4_4_4_4;
  			PVR_type = GL_RGBA;
  			break;
  		case OGL_RGBA_5551:
  			PVR_format = GL_UNSIGNED_SHORT_5_5_5_1;
  			PVR_type = GL_RGBA;
  			break;
  		case OGL_RGBA_8888:
  			PVR_format = GL_UNSIGNED_BYTE;
  			PVR_type = GL_RGBA;
  			break;
  		case OGL_RGB_565:
  			PVR_format = GL_UNSIGNED_SHORT_5_6_5;
  			PVR_type = GL_RGB;
  			break;
  		case OGL_RGB_555:
  			result_string_pointer = "failed: pixel type OGL_RGB_555 not supported.";
  			return 0;
  		case OGL_RGB_888:
  			PVR_format = GL_UNSIGNED_BYTE;
  			PVR_type = GL_RGB;
  			break;
  		case OGL_I_8:
  			PVR_format = GL_UNSIGNED_BYTE;
  			PVR_type = GL_LUMINANCE;
  			break;
  		case OGL_AI_88:
  			PVR_format = GL_UNSIGNED_BYTE;
  			PVR_type = GL_LUMINANCE_ALPHA;
  			break;
  		case MGLPT_PVRTC2:
  		case OGL_PVRTC2:
  			if(is_PVRTC_supported) {
  				is_compressed_format_supported = is_compressed_format = 1;
  				PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG ;	// PVRTC2
  			} else {
  				result_string_pointer = "error: PVRTC2 not supported.Decompress the texture first.";
  				return 0;
  			}
  			break;
  		case MGLPT_PVRTC4:
  		case OGL_PVRTC4:
  			if(is_PVRTC_supported) {
  				is_compressed_format_supported = is_compressed_format = 1;
  				PVR_format = header->dwAlphaBitMask==0 ? SOIL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG : SOIL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ;	// PVRTC4
  			} else {
  				result_string_pointer = "error: PVRTC4 not supported. Decompress the texture first.";
  				return 0;
  			}
  			break;
  		case OGL_BGRA_8888:
  			if(is_BGRA8888_supported) {
  				PVR_format = GL_UNSIGNED_BYTE;
  				PVR_type   = GL_BGRA;
  				break;
  			} else {
  				result_string_pointer = "Unable to load GL_BGRA texture as extension GL_IMG_texture_format_BGRA8888 is unsupported.";
  				return 0;
  			}
  		default:											// NOT SUPPORTED
  			result_string_pointer = "failed: pixel type not supported.";
  			return 0;
  	}
  
  	#ifdef SOIL_GLES1
  	//  check that this data is cube map data or not.
  	if( loading_as_cubemap ) {
  		result_string_pointer = "cube map textures are not available in GLES1.x.";
  		return 0;
  	}
  	#endif
  
  	// load the texture up
  	tex_ID = reuse_texture_ID;
  	if( tex_ID == 0 )
  	{
  		glGenTextures( 1, &tex_ID );
  	}
  
  	glBindTexture( opengl_texture_type, tex_ID );
  
  	if( glGetError() ) {
  		result_string_pointer = "failed: glBindTexture() failed.";
  		return 0;
  	}
  
  	glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment);
  	if ( 1 != unpack_aligment )
  	{
  		glPixelStorei(GL_UNPACK_ALIGNMENT,1);				// Never have row-aligned in headers
  	}
  
  	#define _MAX( a, b ) (( a <= b )? b : a)
  	for(i=0; i<num_surfs; i++) {
  		char *texture_ptr = (char*)buffer + header->dwHeaderSize + header->dwTextureDataSize * i;
  		char *cur_texture_ptr = 0;
  		int	mipmap_level;
  		unsigned int width= header->dwWidth, height = header->dwHeight;
  		unsigned int compressed_image_size = 0;
  
  		mipmaps = ( ( flags & SOIL_FLAG_MIPMAPS ) && (header->dwpfFlags & PVRTEX_MIPMAP) ) ? header->dwMipMapCount : 0;
  
  		for(mipmap_level = 0; mipmap_level <= mipmaps; width = _MAX(width/2, (unsigned int)1), height = _MAX(height/2, (unsigned int)1), mipmap_level++ ) {
  			// Do Alpha-swap if needed
  			cur_texture_ptr = texture_ptr;
  
  			// Load the Texture
  			/* If the texture is PVRTC then use GLCompressedTexImage2D */
  			if( is_compressed_format ) {
  				/* Calculate how many bytes this MIP level occupies */
  				if ((header->dwpfFlags & PVRTEX_PIXELTYPE)==OGL_PVRTC2) {
  					compressed_image_size = ( _MAX(width, PVRTC2_MIN_TEXWIDTH) * _MAX(height, PVRTC2_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8;
  				} else {// PVRTC4 case
  					compressed_image_size = ( _MAX(width, PVRTC4_MIN_TEXWIDTH) * _MAX(height, PVRTC4_MIN_TEXHEIGHT) * header->dwBitCount + 7 ) / 8;
  				}
  
  				if ( is_compressed_format_supported ) {
  					/* Load compressed texture data at selected MIP level */
  					if ( loading_as_cubemap ) {
  						soilGlCompressedTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr );
  					} else {
  						soilGlCompressedTexImage2D( opengl_texture_type, mipmap_level, PVR_format, width, height, 0, compressed_image_size, cur_texture_ptr );
  					}
  				} else {
  					result_string_pointer = "failed: GPU doesnt support compressed textures";
  				}
  			} else {
  				/* Load uncompressed texture data at selected MIP level */
  				if ( loading_as_cubemap ) {
  					glTexImage2D( SOIL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr );
  				} else {
  					glTexImage2D( opengl_texture_type, mipmap_level, PVR_type, width, height, 0, PVR_type, PVR_format, cur_texture_ptr );
  				}
  			}
  
  			if( glGetError() ) {
  				result_string_pointer = "failed: glCompressedTexImage2D() failed.";
  				if ( 1 != unpack_aligment )
  				{
  					glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment);
  				}
  				return 0;
  			}
  
  			// offset the texture pointer by one mip-map level
  			/* PVRTC case */
  			if ( is_compressed_format ) {
  				texture_ptr += compressed_image_size;
  			} else {
  				/* New formula that takes into account bit counts inferior to 8 (e.g. 1 bpp) */
  				texture_ptr += (width * height * header->dwBitCount + 7) / 8;
  			}
  		}
  	}
  	#undef _MAX
  
  	if ( 1 != unpack_aligment )
  	{
  		glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment);
  	}
  
  	if( tex_ID )
  	{
  		/*	did I have MIPmaps?	*/
  		if( mipmaps )
  		{
  			/*	instruct OpenGL to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
  		} else
  		{
  			/*	instruct OpenGL _NOT_ to use the MIPmaps	*/
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  		}
  
  		/*	does the user want clamping, or wrapping?	*/
  		if( flags & SOIL_FLAG_TEXTURE_REPEATS )
  		{
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
  		} else
  		{
  			unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;
  			/* unsigned int clamp_mode = GL_CLAMP; */
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
  		}
  	}
  
  	return tex_ID;
  }
  
  unsigned int SOIL_direct_load_PVR(
  		const char *filename,
  		unsigned int reuse_texture_ID,
  		int flags,
  		int loading_as_cubemap )
  {
  	FILE *f;
  	unsigned char *buffer;
  	size_t buffer_length, bytes_read;
  	unsigned int tex_ID = 0;
  	/*	error checks	*/
  	if( NULL == filename )
  	{
  		result_string_pointer = "NULL filename";
  		return 0;
  	}
  	f = fopen( filename, "rb" );
  	if( NULL == f )
  	{
  		/*	the file doesn't seem to exist (or be open-able)	*/
  		result_string_pointer = "Can not find PVR file";
  		return 0;
  	}
  	fseek( f, 0, SEEK_END );
  	buffer_length = ftell( f );
  	fseek( f, 0, SEEK_SET );
  	buffer = (unsigned char *) malloc( buffer_length );
  	if( NULL == buffer )
  	{
  		result_string_pointer = "malloc failed";
  		fclose( f );
  		return 0;
  	}
  	bytes_read = fread( (void*)buffer, 1, buffer_length, f );
  	fclose( f );
  	if( bytes_read < buffer_length )
  	{
  		/*	huh?	*/
  		buffer_length = bytes_read;
  	}
  	/*	now try to do the loading	*/
  	tex_ID = SOIL_direct_load_PVR_from_memory(
  		(const unsigned char *const)buffer, (int)buffer_length,
  		reuse_texture_ID, flags, loading_as_cubemap );
  	SOIL_free_image_data( buffer );
  	return tex_ID;
  }
  
  unsigned int SOIL_direct_load_ETC1_from_memory(
  		const unsigned char *const buffer,
  		int buffer_length,
  		unsigned int reuse_texture_ID,
  		int flags )
  {
  	GLuint tex_ID = 0;
  	PKMHeader* header = (PKMHeader*)buffer;
  	unsigned int opengl_texture_type = GL_TEXTURE_2D;
  	unsigned int width;
  	unsigned int height;
  	unsigned long compressed_image_size = buffer_length - PKM_HEADER_SIZE;
  	char *texture_ptr = (char*)buffer + PKM_HEADER_SIZE;
  	GLint unpack_aligment;
  
  	if ( query_ETC1_capability() != SOIL_CAPABILITY_PRESENT ) {
  		result_string_pointer = "error: ETC1 not supported. Decompress the texture first.";
  		return 0;
  	}
  
  	if ( 0 != strcmp( header->aName, "PKM 10" ) ) {
  		result_string_pointer = "error: PKM 10 header not found.";
  		return 0;
  	}
  
  	width = (header->iWidthMSB << 8) | header->iWidthLSB;
  	height = (header->iHeightMSB << 8) | header->iHeightLSB;
  	compressed_image_size = (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1;
  
  	// load the texture up
  	tex_ID = reuse_texture_ID;
  	if( tex_ID == 0 )
  	{
  		glGenTextures( 1, &tex_ID );
  	}
  
  	glBindTexture( opengl_texture_type, tex_ID );
  
  	if( glGetError() ) {
  		result_string_pointer = "failed: glBindTexture() failed.";
  		return 0;
  	}
  
  	glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_aligment);
  	if ( 1 != unpack_aligment )
  	{
  		glPixelStorei(GL_UNPACK_ALIGNMENT,1);				// Never have row-aligned in headers
  	}
  
  	soilGlCompressedTexImage2D( opengl_texture_type, 0, SOIL_GL_ETC1_RGB8_OES, width, height, 0, compressed_image_size, texture_ptr );
  
  	if( glGetError() ) {
  		result_string_pointer = "failed: glCompressedTexImage2D() failed.";
  
  		if ( 1 != unpack_aligment )
  		{
  			glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment);
  		}
  		return 0;
  	}
  
  	if ( 1 != unpack_aligment )
  	{
  		glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_aligment);
  	}
  
  	if( tex_ID )
  	{
  		/* No MIPmaps for ETC1 */
  		glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  		glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  
  		/*	does the user want clamping, or wrapping?	*/
  		if( flags & SOIL_FLAG_TEXTURE_REPEATS )
  		{
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
  		} else
  		{
  			unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;
  			/* unsigned int clamp_mode = GL_CLAMP; */
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
  			glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
  			glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
  		}
  	}
  
  	return tex_ID;
  }
  
  unsigned int SOIL_direct_load_ETC1(const char *filename,
  		unsigned int reuse_texture_ID,
  		int flags )
  {
  	FILE *f;
  	unsigned char *buffer;
  	size_t buffer_length, bytes_read;
  	unsigned int tex_ID = 0;
  	/*	error checks	*/
  	if( NULL == filename )
  	{
  		result_string_pointer = "NULL filename";
  		return 0;
  	}
  	f = fopen( filename, "rb" );
  	if( NULL == f )
  	{
  		/*	the file doesn't seem to exist (or be open-able)	*/
  		result_string_pointer = "Can not find PVR file";
  		return 0;
  	}
  	fseek( f, 0, SEEK_END );
  	buffer_length = ftell( f );
  	fseek( f, 0, SEEK_SET );
  	buffer = (unsigned char *) malloc( buffer_length );
  	if( NULL == buffer )
  	{
  		result_string_pointer = "malloc failed";
  		fclose( f );
  		return 0;
  	}
  	bytes_read = fread( (void*)buffer, 1, buffer_length, f );
  	fclose( f );
  	if( bytes_read < buffer_length )
  	{
  		/*	huh?	*/
  		buffer_length = bytes_read;
  	}
  	/*	now try to do the loading	*/
  	tex_ID = SOIL_direct_load_ETC1_from_memory(
  		(const unsigned char *const)buffer, (int)buffer_length,
  		reuse_texture_ID, flags );
  	SOIL_free_image_data( buffer );
  	return tex_ID;
  }
  
  int query_NPOT_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_NPOT_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if(
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_ARB_texture_non_power_of_two" ) )
  		&&
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_OES_texture_npot" ) )
  			)
  		{
  			/*	not there, flag the failure	*/
  			has_NPOT_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			/*	it's there!	*/
  			has_NPOT_capability = SOIL_CAPABILITY_PRESENT;
  		}
  
  		#if defined( __emscripten__ ) || defined( EMSCRIPTEN )
  		has_NPOT_capability = SOIL_CAPABILITY_PRESENT;
  		#endif
  	}
  	/*	let the user know if we can do non-power-of-two textures or not	*/
  	return has_NPOT_capability;
  }
  
  int query_tex_rectangle_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_tex_rectangle_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if(
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_ARB_texture_rectangle" ) )
  		&&
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_EXT_texture_rectangle" ) )
  		&&
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_NV_texture_rectangle" ) )
  			)
  		{
  			/*	not there, flag the failure	*/
  			has_tex_rectangle_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			/*	it's there!	*/
  			has_tex_rectangle_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  	/*	let the user know if we can do texture rectangles or not	*/
  	return has_tex_rectangle_capability;
  }
  
  int query_cubemap_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_cubemap_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if(
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_ARB_texture_cube_map" ) )
  		&&
  			(0 == SOIL_GL_ExtensionSupported(
  				"GL_EXT_texture_cube_map" ) )
  			)
  		{
  			/*	not there, flag the failure	*/
  			has_cubemap_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			/*	it's there!	*/
  			has_cubemap_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  	/*	let the user know if we can do cubemaps or not	*/
  	return has_cubemap_capability;
  }
  
  static P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC get_glCompressedTexImage2D_addr()
  {
  	/*	and find the address of the extension function	*/
  	P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL;
  	
  #if defined( SOIL_PLATFORM_WIN32 ) || defined( SOIL_PLATFORM_OSX ) || defined( SOIL_X11_PLATFORM )
  	ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)SOIL_GL_GetProcAddress( "glCompressedTexImage2D" );
  #else
  	ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)&glCompressedTexImage2D;
  #endif
  	
  	return ext_addr;
  }
  
  int query_DXT_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_DXT_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if (	0 == SOIL_GL_ExtensionSupported(
  					"GL_EXT_texture_compression_s3tc" )  &&
  				0 == SOIL_GL_ExtensionSupported(
  					"WEBGL_compressed_texture_s3tc ") &&
  				0 == SOIL_GL_ExtensionSupported(
  					"WEBKIT_WEBGL_compressed_texture_s3tc") &&
  				0 == SOIL_GL_ExtensionSupported(
  					"MOZ_WEBGL_compressed_texture_s3tc"
  				)
  			)
  		{
  			/*	not there, flag the failure	*/
  			has_DXT_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = get_glCompressedTexImage2D_addr();
  
  			/*	Flag it so no checks needed later	*/
  			if( NULL == ext_addr )
  			{
  				/*	hmm, not good!!  This should not happen, but does on my
  					laptop's VIA chipset.  The GL_EXT_texture_compression_s3tc
  					spec requires that ARB_texture_compression be present too.
  					this means I can upload and have the OpenGL drive do the
  					conversion, but I can't use my own routines or load DDS files
  					from disk and upload them directly [8^(	*/
  				has_DXT_capability = SOIL_CAPABILITY_NONE;
  			} else
  			{
  				/*	all's well!	*/
  				soilGlCompressedTexImage2D = ext_addr;
  				has_DXT_capability = SOIL_CAPABILITY_PRESENT;
  			}
  		}
  	}
  	/*	let the user know if we can do DXT or not	*/
  	return has_DXT_capability;
  }
  
  int query_PVR_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_PVR_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if (0 == SOIL_GL_ExtensionSupported(
  				"GL_IMG_texture_compression_pvrtc" ) )
  		{
  			/*	not there, flag the failure	*/
  			has_PVR_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			if ( NULL == soilGlCompressedTexImage2D ) {
  				soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr();
  			}
  			
  			/*	it's there!	*/
  			has_PVR_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  	/*	let the user know if we can do cubemaps or not	*/
  	return has_PVR_capability;
  }
  
  int query_BGRA8888_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_BGRA8888_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if (0 == SOIL_GL_ExtensionSupported(
  				"GL_IMG_texture_format_BGRA8888" ) )
  		{
  			/*	not there, flag the failure	*/
  			has_BGRA8888_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			/*	it's there!	*/
  			has_BGRA8888_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  	/*	let the user know if we can do cubemaps or not	*/
  	return has_BGRA8888_capability;
  }
  
  int query_sRGB_capability( void )
  {
  	if ( has_sRGB_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		if (0 == SOIL_GL_ExtensionSupported(
  				"GL_EXT_texture_sRGB" )
  				&&
  			0 == SOIL_GL_ExtensionSupported(
  				"GL_EXT_sRGB" )
  				&&
  			0 == SOIL_GL_ExtensionSupported(
  				"EXT_sRGB" ) )
  		{
  			has_sRGB_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			has_sRGB_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  
  	return has_sRGB_capability;
  }
  
  int query_ETC1_capability( void )
  {
  	/*	check for the capability	*/
  	if( has_ETC1_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		/*	we haven't yet checked for the capability, do so	*/
  		if (0 == SOIL_GL_ExtensionSupported(
  				"GL_OES_compressed_ETC1_RGB8_texture" ) )
  		{
  			/*	not there, flag the failure	*/
  			has_ETC1_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			if ( NULL == soilGlCompressedTexImage2D ) {
  				soilGlCompressedTexImage2D = get_glCompressedTexImage2D_addr();
  			}
  
  			/*	it's there!	*/
  			has_ETC1_capability = SOIL_CAPABILITY_PRESENT;
  		}
  	}
  	/*	let the user know if we can do cubemaps or not	*/
  	return has_ETC1_capability;
  }
  
  int query_gen_mipmap_capability( void )
  {
  	/* check for the capability   */
  	P_SOIL_GLGENERATEMIPMAPPROC ext_addr = NULL;
  
  	if( has_gen_mipmap_capability == SOIL_CAPABILITY_UNKNOWN )
  	{
  		if (	0 == SOIL_GL_ExtensionSupported(
  					"GL_ARB_framebuffer_object" )
  			&&
  				0 == SOIL_GL_ExtensionSupported(
  					"GL_EXT_framebuffer_object" )
  			&&  0 == SOIL_GL_ExtensionSupported(
  					"GL_OES_framebuffer_object" )
  			)
  		{
  			/* not there, flag the failure */
  			has_gen_mipmap_capability = SOIL_CAPABILITY_NONE;
  		}
  		else
  		{
  			#if !defined( SOIL_GLES1 ) && !defined( SOIL_GLES2 )
  
  			ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap");
  
  			if(ext_addr == NULL)
  			{
  				ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapEXT");
  			}
  
  			#elif !defined( SOIL_NO_EGL )
  
  			ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmapOES");
  
  			if(ext_addr == NULL)
  			{
  				ext_addr = (P_SOIL_GLGENERATEMIPMAPPROC)SOIL_GL_GetProcAddress("glGenerateMipmap");
  			}
  
  			#elif defined( SOIL_GLES2 )
  				ext_addr = 	&glGenerateMipmap;
  			#else /** SOIL_GLES1 */
  				ext_addr = &glGenerateMipmapOES;
  			#endif
  		}
  
  		if(ext_addr == NULL)
  		{
  			/* this should never happen */
  			has_gen_mipmap_capability = SOIL_CAPABILITY_NONE;
  		} else
  		{
  			/* it's there! */
  			has_gen_mipmap_capability = SOIL_CAPABILITY_PRESENT;
  			soilGlGenerateMipmap = ext_addr;
  		}
  	}
  
  	return has_gen_mipmap_capability;
  }
  