/*
								+----------------------------------+
								|                                  |
								| ***   Texture loader class  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2007   |
								|                                  |
								|         TextureUtil.cpp          |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file gles2/TextureUtil.cpp
 *	@author -tHE SWINe-
 *	@date 2007
 *	@brief texture loader class
 *
 *	@date 2007-07-14
 *
 *	removed GL_TEXTURE_2D target from CGLTexture_2D constructor calls
 *
 *	@date 2007-12-24
 *
 *	improved linux compatibility by using posix integer types
 *
 *	@date 2008-03-04
 *
 *	added CTextureLoader::p_LoadTexture which can decide fileformat by itself
 *
 *	@date 2008-05-07
 *
 *	fixed stupid error in CTextureLoader::p_LoadTexture which couldn't decide
 *	fileformat correctly
 *
 *	@date 2008-10-09
 *
 *	renamed CTextureLoader to CGLESTextureLoader (but there's typedef on the end of the file
 *	so the code is backwards-compatible. however, new name should be always used.)
 *
 *	added CGLESTextureLoader::p_LoadTexture_3D()
 *
 *	cleaned-up image format decission and loading a little bit
 *
 *	exposed internal texture format override in all texture loading functions
 *
 *	moved function bodies to TextureUtil.cpp
 *
 *	@date 2009-05-04
 *
 *	fixed mixed windows / linux line endings
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 */

#include "../NewFix.h"
#include "../CallStack.h"
#include <stdio.h>
#include <string.h>
#include <stdexcept>
#include "../Tga.h"
#include "../Jpeg.h"
#include "Texture.h"
#include "../StlUtils.h"
#include "TextureUtil.h"

/*
 *								=== CGLESTextureLoader ===
 */

int CGLESTextureLoader::n_IdentifyFormat(const char *p_s_filename)
{
	const char *p_s_ext = strrchr(p_s_filename, '.');
	if(!p_s_ext)
		return format_Unknown;
	++ p_s_ext;
#if defined(_MSC_VER) && !defined(__MWERKS__) //&& _MSC_VER >= 1400
	if(!_strcmpi(p_s_ext, "tga"))
		return format_Targa;
	if(!_strcmpi(p_s_ext, "jpg") || !_strcmpi(p_s_ext, "jpeg"))
		return format_Jpeg;
	if(!_strcmpi(p_s_ext, "png"))
		return format_PNG;
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	if(!strcasecmp(p_s_ext, "tga"))
		return format_Targa;
	if(!strcasecmp(p_s_ext, "jpg") || !strcasecmp(p_s_ext, "jpeg"))
		return format_Jpeg;
	if(!strcasecmp(p_s_ext, "png"))
		return format_PNG;
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	return format_Unknown;
}

TBmp *CGLESTextureLoader::p_LoadTargaImage(const char *p_s_filename)
{
	return CTgaCodec::p_Load_TGA(p_s_filename);
}

TBmp CGLESTextureLoader::t_TargaImageInfo(const char *p_s_filename)
{
	TBmp t_bitmap = {0};

	CTgaCodec::TTGAInfo t_info;
	if(!CTgaCodec::Get_ImageInfo(t_info, p_s_filename))
		return t_bitmap;
	t_bitmap.n_width = t_info.n_width;
	t_bitmap.n_height = t_info.n_height;
	t_bitmap.b_alpha = t_info.b_alpha;
	t_bitmap.b_grayscale = t_info.b_grayscale;
	t_bitmap.n_former_bpc = t_info.n_bit_depth / (((t_info.b_grayscale)? 1 : 3) + ((t_info.b_alpha)? 1 : 0));

	return t_bitmap;
}

TBmp *CGLESTextureLoader::p_LoadJpegImage(const char *p_s_filename)
{
	TBmp *p_bitmap;
	FILE *p_fr;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
	if(fopen_s(&p_fr, p_s_filename, "rb"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	if(!(p_fr = fopen(p_s_filename, "rb")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		return 0;
	CTinyJpegDecoder decoder;
	if(!(p_bitmap = decoder.p_Decode_JPEG(p_fr))) {
		fclose(p_fr);
		return 0;
	}
	fclose(p_fr);
	// load jpeg

	return p_bitmap;
}

TBmp CGLESTextureLoader::t_JpegImageInfo(const char *p_s_filename)
{
	TBmp t_bitmap = {0};

	CTinyJpegDecoder::TImageInfo t_info = CTinyJpegDecoder::t_Jpeg_Info(p_s_filename, false);
	if(!t_info.n_width && !t_info.n_height)
		return t_bitmap;
	t_bitmap.n_width = t_info.n_width;
	t_bitmap.n_height = t_info.n_height;
	t_bitmap.b_alpha = false;
	t_bitmap.b_grayscale = (t_info.n_component_num == 1);
	t_bitmap.n_former_bpc = 8;

	return t_bitmap;
}

#ifdef __GLES_TEXTURE_LOADER_USE_PNG

//#include "PNGLoad.h"

/**
 *	@brief forward declaration of a class for PNG loading
 */
class CPngCodec {
public:
	/**
	 *	@brief information about a PNG image
	 */
	struct TImageInfo {
		bool b_alpha; /**< @brief alpha channel flag */
		bool b_grayscale; /**< @brief grayscale flag */
		int n_bit_depth; /**< @brief number of bits per pixel (or per palette color) */
		int n_palette_entry_num; /**< @brief number of palette entries or 0 for true color */
		int n_width; /**< @brief image width */
		int n_height; /**< @brief image height */
	};

protected:
	/*static TBmp *p_Load_PNG_int(png_structp p_png_ptr);
	static bool GetInfo_int(TImageInfo &r_t_info, png_structp p_png_ptr);*/
	class CInternal;

public:
	/**
	 *	@brief reads image header from a file and returns image properties
	 *
	 *	@param[out] r_t_info is structure to be filled with image information
	 *	@param[in] p_s_filename is input file name
	 *
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_ImageInfo(TImageInfo &r_t_info, const char *p_s_filename);

	/**
	 *	@brief reads image header from memory and returns image properties
	 *
	 *	@param[out] r_t_info is structure to be filled with image information
	 *	@param[in] p_data is image data
	 *	@param[in] n_size is image data size, in bytes
	 *
	 *	@return Returns true on success, false on failure.
	 */
	static bool Get_ImageInfo(TImageInfo &r_t_info, const void *p_data, size_t n_size);

	/**
	 *	@brief loads jpeg image from file
	 *
	 *	@param[in] p_s_filename is input file name
	 *
	 *	@return Returns pointer to loaded bitmap on success, or 0 on failure.
	 */
	static TBmp *p_Load_PNG(const char *p_s_filename);

	/**
	 *	@brief loads jpeg image from memory
	 *
	 *	@param[in] p_data is image data
	 *	@param[in] n_size is image data size, in bytes
	 *
	 *	@return Returns pointer to loaded bitmap on success, or 0 on failure.
	 */
	static TBmp *p_Load_PNG(const void *p_data, size_t n_size);
};

#endif // __GLES_TEXTURE_LOADER_USE_PNG

TBmp *CGLESTextureLoader::p_LoadPNGImage(const char *p_s_filename)
{
	TBmp *p_bitmap = 0;

#ifdef __GLES_TEXTURE_LOADER_USE_PNG
	p_bitmap = CPngCodec::p_Load_PNG(p_s_filename);
#endif // __GLES_TEXTURE_LOADER_USE_PNG

	return p_bitmap;
}

TBmp CGLESTextureLoader::t_PNGImageInfo(const char *p_s_filename)
{
	TBmp t_bitmap = {0};

#ifdef __GLES_TEXTURE_LOADER_USE_PNG
	CPngCodec::TImageInfo t_info;
	if(!CPngCodec::Get_ImageInfo(t_info, p_s_filename))
		return t_bitmap;
	t_bitmap.n_width = t_info.n_width;
	t_bitmap.n_height = t_info.n_height;
	t_bitmap.b_alpha = t_info.b_alpha;
	t_bitmap.b_grayscale = t_info.b_grayscale;
	t_bitmap.n_former_bpc = t_info.n_bit_depth / (((t_info.b_grayscale)? 1 : 3) + ((t_info.b_alpha)? 1 : 0));
#endif // __GLES_TEXTURE_LOADER_USE_PNG

	return t_bitmap;
}

TBmp *CGLESTextureLoader::p_LoadImage(const char *p_s_filename, int n_force_format)
{
	int n_fmt = (n_force_format == format_Unknown)?
		n_IdentifyFormat(p_s_filename) : n_force_format;
	switch(n_fmt) {
	case format_Targa:
		return p_LoadTargaImage(p_s_filename);
	case format_Jpeg:
		return p_LoadJpegImage(p_s_filename);
	case format_PNG:
		return p_LoadPNGImage(p_s_filename);
	default:
		TBmp *p_bitmap;
		if((p_bitmap = p_LoadPNGImage(p_s_filename)))
			return p_bitmap;
		if((p_bitmap = p_LoadTargaImage(p_s_filename)))
			return p_bitmap;
#ifndef __JPEG_DEC_STRIP_ERROR_CHECKS
		return p_LoadJpegImage(p_s_filename);
#else
		return 0;
		// won't risk loading non-jpeg when error checks are stripped
#endif
	}
}

TBmp CGLESTextureLoader::t_GetImageInfo(const char *p_s_filename, int n_force_format)
{
	int n_fmt = (n_force_format == format_Unknown)?
		n_IdentifyFormat(p_s_filename) : n_force_format;
	switch(n_fmt) {
	case format_Targa:
		return t_TargaImageInfo(p_s_filename);
	case format_Jpeg:
		return t_JpegImageInfo(p_s_filename);
	case format_PNG:
		return t_PNGImageInfo(p_s_filename);
	default:
		TBmp t_null = {0};
		TBmp t_bitmap = t_PNGImageInfo(p_s_filename);
		if(memcmp(&t_bitmap, &t_null, sizeof(TBmp)))
			return t_bitmap;
		t_bitmap = t_TargaImageInfo(p_s_filename);
		if(memcmp(&t_bitmap, &t_null, sizeof(TBmp)))
			return t_bitmap;
#ifndef __JPEG_DEC_STRIP_ERROR_CHECKS
		return t_JpegImageInfo(p_s_filename);
#else
		return 0;
		// won't risk loading non-jpeg when error checks are stripped
#endif
	}
}

GLenum CGLESTextureLoader::n_Optimal_InternalFormat(const TBmp *p_bitmap, bool b_force_alpha)
{
	if(p_bitmap->b_alpha || b_force_alpha) {
		if(p_bitmap->b_grayscale)
			return GL_LUMINANCE_ALPHA;
		else
			return GL_RGBA;
	} else {
		if(p_bitmap->b_grayscale)
			return GL_RGBA;//GL_RGB;//GL_LUMINANCE; // GL_LUMINANCE doesn't quite work (amybe on tegra
		else
			return GL_RGBA;//GL_RGB; // GL_RGB doesn't work since we have data as RGBA and OpenGL ES seems to be unable to convert. or something. (todo?)
	}
}

CGLTexture_2D *CGLESTextureLoader::p_TextureFromBitmap(const TBmp *p_bitmap,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format)
{
	GLenum n_internal_fmt = n_Optimal_InternalFormat(p_bitmap);
	if(n_force_internal_format)
		n_internal_fmt = n_force_internal_format;
	// find appropriate internal format

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_bitmap->n_width, p_bitmap->n_height,
	   n_internal_fmt, b_mipmaps, GL_RGBA, GL_UNSIGNED_BYTE, p_bitmap->p_buffer)))
		return 0;
	// create the texture

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

#ifdef __HAVE_GLES
#define GL_RED                            GL_RGB8_OES
#define GL_RG                             GL_RGB8_OES
#define GL_R8                             GL_RGB8_OES
#define GL_R16                            GL_RGB8_OES
#define GL_RG8                            GL_RGB8_OES
#define GL_RG16                           GL_RGB8_OES
#define GL_R16F                           GL_RGB8_OES
#define GL_R32F                           GL_RGB8_OES
#define GL_RG16F                          GL_RGB8_OES
#define GL_RG32F                          GL_RGB8_OES

#define GL_RGB8                           GL_RGB8_OES
#define GL_RGBA8                          GL_RGBA8_OES
#define GL_RGB16                          GL_RGB8_OES
#define GL_RGBA16                         GL_RGBA8_OES
#define GL_RGBA32F                        GL_RGBA8_OES
#define GL_RGB32F                         GL_RGB8_OES
#define GL_RGBA16F                        GL_RGBA8_OES
#define GL_RGB16F                         GL_RGB8_OES
// todo - find extensions, try to support better textures
#else // __HAVE_GLES
#define GL_RED                            0x1903
#define GL_RG                             0x8227
#define GL_R8                             0x8229
#define GL_R16                            0x822A
#define GL_RG8                            0x822B
#define GL_RG16                           0x822C
#define GL_R16F                           0x822D
#define GL_R32F                           0x822E
#define GL_RG16F                          0x822F
#define GL_RG32F                          0x8230

#define GL_RGB8                           0x8051
#define GL_RGBA8                          0x8058
#define GL_RGB16                          0x8054
#define GL_RGBA16                         0x805B
#define GL_RGBA32F                        0x8814
#define GL_RGB32F                         0x8815
#define GL_RGBA16F                        0x881A
#define GL_RGB16F                         0x881B
// hack - those are *not* OpenGL ES 2.0
#endif

GLenum CGLESTextureLoader::n_Pixel_InternalFormat(int n_component_num, int n_bit_num, bool b_float)
{
	GLenum n_image_fmt = (b_float)? ((n_bit_num == 16)? GL_RGBA16F : (n_bit_num == 32)? GL_RGBA32F : 0) :
		((n_bit_num == 16)? GL_RGBA16 : /*(n_bit_num == 32)? GL_RGBA32 :*/ (n_bit_num == 8)? GL_RGBA8 : 0);
	if(!n_image_fmt)
		return 0;

	switch(n_component_num) {
	case 1:
		n_image_fmt = (b_float)? ((n_bit_num == 16)? GL_RGBA16F : (n_bit_num == 32)? GL_RGBA32F : 0) :
			((n_bit_num == 16)? GL_RGBA16 : /*(n_bit_num == 32)? GL_RGBA32 :*/ (n_bit_num == 8)? GL_R8 : 0);
		// GL_LUMINANCE in OpenGL ES 2.0
		break;
	case 2:
		n_image_fmt = (b_float)? ((n_bit_num == 16)? GL_RG16F : (n_bit_num == 32)? GL_RG32F : 0) :
			((n_bit_num == 16)? GL_RG16 : /*(n_bit_num == 32)? GL_RG32 :*/ (n_bit_num == 8)? GL_RG8 : 0);
		// GL_LUMINANCE_ALPHA in OpenGL ES 2.0
		break;
	case 3:
		n_image_fmt = (b_float)? ((n_bit_num == 16)? GL_RGB16F : (n_bit_num == 32)? GL_RGB32F : 0) :
			((n_bit_num == 16)? GL_RGB16 : /*(n_bit_num == 32)? GL_RGB32 :*/ (n_bit_num == 8)? GL_RGB8 : 0);
		break;
	case 4:
		//n_image_fmt = GL_RGBA; // keep default to save code
		break;
	}
	return n_image_fmt;
}

GLenum CGLESTextureLoader::n_Pixel_ExternalFormat(int n_component_num)
{
	GLenum n_image_fmt = GL_RGBA;
	switch(n_component_num) {
	case 0://TImage::color_Grayscale:
		n_image_fmt = GL_RED; // GL_LUMINANCE in OpenGL ES 2.0
		break;
	case 1://TImage::color_AlphaGrayscale:
		n_image_fmt = GL_RG; // GL_LUMINANCE_ALPHA in OpenGL ES 2.0
		break;
	case 3://TImage::color_RGB:
		n_image_fmt = GL_RGB;
		break;
	case 4://TImage::color_ARGB:
		n_image_fmt = GL_RGBA;
		break;
	}
	return n_image_fmt;
}

#if 0

GLenum CGLESTextureLoader::n_Image_InternalFormat(const TImage *p_image)
{
	GLenum n_format = n_Pixel_InternalFormat(p_image->n_format,
		n_Align_Up_POT(p_image->n_sample_bit_num / p_image->n_format, 8));
	return (n_format)? n_format : n_Image_ExternalFormat(p_image);
}

GLenum CGLESTextureLoader::n_Image_ExternalFormat(const TImage *p_image)
{
	GLenum n_image_fmt = GL_RGBA;
	switch(p_image->n_format) {
	case TImage::color_Grayscale:
		n_image_fmt = GL_RED; // GL_LUMINANCE in OpenGL ES 2.0
		break;
	case TImage::color_AlphaGrayscale:
		n_image_fmt = GL_RG; // GL_LUMINANCE_ALPHA in OpenGL ES 2.0
		break;
	case TImage::color_RGB:
		n_image_fmt = GL_RGB;
		break;
	case TImage::color_ARGB:
		n_image_fmt = GL_RGBA;
		break;
	}
	return n_image_fmt;
}

GLenum CGLESTextureLoader::n_Image_DataType(const TImage *p_image, bool b_force_signed, bool b_force_unsigned)
{
	const GLenum n_byte_fmt = (b_force_signed)? GL_BYTE : (b_force_unsigned)? GL_UNSIGNED_BYTE : GL_UNSIGNED_BYTE;
	const GLenum n_short_fmt = (b_force_signed)? GL_SHORT : (b_force_unsigned)? GL_UNSIGNED_SHORT : GL_SHORT;
	const GLenum n_int_fmt = (b_force_signed)? GL_INT : (b_force_unsigned)? GL_UNSIGNED_INT : GL_INT;
	// eval signed / unsigned / default formats for different data widths

	GLenum n_image_datatype = n_byte_fmt;
	int n_bpc = p_image->n_sample_bit_num / p_image->n_format;
	if(n_bpc <= 8)
		n_image_datatype = n_byte_fmt;
	else if(n_bpc <= 16)
		n_image_datatype = n_short_fmt;
	else //if(n_bpc <= 32)
		n_image_datatype = n_int_fmt;
	return n_image_datatype;
	// guess the format by bits per component (should work for most sane formats)
}

void CGLESTextureLoader::Upload_TextureArray_Layer(CGLTexture_2D_Array *p_texture, const TImage *p_image, int n_layer,
	GLenum n_force_datatype, bool b_force_signed, bool b_force_unsigned)
{
	_ASSERTE(n_layer >= 0 && n_layer < p_texture->n_Layer_Num());

	GLenum n_image_fmt = n_Image_ExternalFormat(p_image);
	GLenum n_image_datatype;
	if(!n_force_datatype) {
		n_image_datatype = n_Image_DataType(p_image, b_force_signed, b_force_unsigned);
	} else
		n_image_datatype = n_force_datatype;
	// find appropriate image format

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, p_image->n_pixel_align);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	p_texture->Bind();
	// bind the texture

	_ASSERTE(p_image->n_sample_bit_num / 8 == p_image->n_pixel_align);

	if(p_image->n_scanline_size == p_image->n_width * p_image->n_pixel_align) {
		glTexSubImage3DOES(GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, n_layer, p_image->n_width, p_image->n_height,
			1, n_image_fmt, n_image_datatype, p_image->p_buffer);
		// load the entire first layer
	} else {
		DEBUG_MESSAGE("warning: slow image transfer");
		// have to load the layer scanline by scanline (slow)

		const uint8_t *p_top_scanline = p_image->p_buffer;
		if(p_image->n_scanline_size < 0)
			p_top_scanline += p_image->n_scanline_size * (p_image->n_height - 1);
		// find the top scanline

		const uint8_t *p_scanline = p_top_scanline;
		for(int i = 0, h = p_image->n_height, ss = p_image->n_scanline_size;
		   i < h; ++ i, p_scanline += ss) {
			glTexSubImage3DOES(GL_TEXTURE_2D_ARRAY_EXT, 0, 0, i, n_layer, p_image->n_width, 1, 1,
				n_image_fmt, n_image_datatype, p_scanline);
		}
		// load the image scanline by scanline
	}
	// create the texture
}

bool CGLESTextureLoader::Finalize_TextureArray(CGLTexture_2D_Array *p_texture, bool b_build_mipmaps)
{
	p_texture->Bind();
	if(b_build_mipmaps)
		glGenerateMipmap(GL_TEXTURE_2D_ARRAY_EXT);
	if(!p_texture->b_Status()) {
		delete p_texture;
		return false;
	}
	return true;
}

int CGLESTextureLoader::n_Max_TextureArray_Layer_Num()
{
	if(!GLEH_EXT_TEXTURE_ARRAY)
		return 0; // not supported
	int n_layer_num;
	glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &n_layer_num);
	return n_layer_num;
}

CGLTexture_2D_Array *CGLESTextureLoader::p_TextureArrayFromImage(const TImage *p_image, int n_layer_num,
	bool b_alloc_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format,
	GLenum n_force_datatype, bool b_force_signed, bool b_force_unsigned)
{
	GLenum n_image_fmt = n_Image_ExternalFormat(p_image);
	GLenum n_image_datatype;
	if(!n_force_datatype) {
		_ASSERTE(p_image->n_sample_bit_num / 8 == p_image->n_pixel_align);
		n_image_datatype = n_Image_DataType(p_image, b_force_signed, b_force_unsigned);
	} else
		n_image_datatype = n_force_datatype;
	GLenum n_internal_fmt = n_Image_InternalFormat(p_image);;
	if(n_force_internal_format)
		n_internal_fmt = n_force_internal_format;
	// find appropriate image format

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, p_image->n_pixel_align);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_2D_Array *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D_Array(p_image->n_width, p_image->n_height, n_layer_num,
	   n_internal_fmt, b_alloc_mipmaps, n_image_fmt, n_image_datatype, 0)))
		return 0;
	p_texture->Bind();
	// create the image with no data (but do specify dimensions and internal format)

	if(p_image->n_scanline_size == p_image->n_width * p_image->n_pixel_align) {
		glTexSubImage3DOES(GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, 0, p_image->n_width, p_image->n_height,
			1, n_image_fmt, n_image_datatype, p_image->p_buffer);
		// load the entire first layer
	} else {
		DEBUG_MESSAGE("warning: slow image transfer");
		// have to load the layer scanline by scanline (slow)

		const uint8_t *p_top_scanline = p_image->p_buffer;
		if(p_image->n_scanline_size < 0)
			p_top_scanline += p_image->n_scanline_size * (p_image->n_height - 1);
		// find the top scanline

		const uint8_t *p_scanline = p_top_scanline;
		for(int i = 0, h = p_image->n_height, ss = p_image->n_scanline_size;
		   i < h; ++ i, p_scanline += ss) {
			glTexSubImage3DOES(GL_TEXTURE_2D_ARRAY_EXT, 0, 0, i, 0, p_image->n_width, 1, 1,
				n_image_fmt, n_image_datatype, p_scanline);
		}
		// load the image scanline by scanline
	}
	// create the texture

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Texture_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Texture_Wrap_T(GL_CLAMP_TO_EDGE);
		p_texture->Set_Texture_Wrap_R(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

CGLTexture_2D *CGLESTextureLoader::p_TextureFromImage(const TImage *p_image,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format,
	GLenum n_force_datatype, bool b_force_signed, bool b_force_unsigned)
{
	GLenum n_image_fmt = n_Image_ExternalFormat(p_image);
	GLenum n_image_datatype;
	if(!n_force_datatype) {
		n_image_datatype = n_Image_DataType(p_image, b_force_signed, b_force_unsigned);
	} else
		n_image_datatype = n_force_datatype;
	GLenum n_internal_fmt = n_Image_InternalFormat(p_image);;
	if(n_force_internal_format)
		n_internal_fmt = n_force_internal_format;
	// find appropriate image format

	_ASSERTE(p_image->n_sample_bit_num / 8 == p_image->n_pixel_align);

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, p_image->n_pixel_align);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_2D *p_texture;
	if(p_image->n_scanline_size == p_image->n_width * p_image->n_pixel_align) {
		if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_image->n_width, p_image->n_height,
		   n_internal_fmt, b_mipmaps, n_image_fmt, n_image_datatype, p_image->p_buffer)))
			return 0;
		// can load the whole image as a single memory block
	} else {
		DEBUG_MESSAGE("warning: slow image transfer");
		// have to load the image scanline by scanline (slow)

		if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_image->n_width, p_image->n_height,
		   n_internal_fmt, b_mipmaps, n_image_fmt, n_image_datatype, 0)))
			return 0;
		p_texture->Bind();
		// create the image with no data (but do specify dimensions and internal format)

		const uint8_t *p_top_scanline = p_image->p_buffer;
		if(p_image->n_scanline_size < 0)
			p_top_scanline += p_image->n_scanline_size * (p_image->n_height - 1);
		// find the top scanline

		const uint8_t *p_scanline = p_top_scanline;
		for(int i = 0, h = p_image->n_height, ss = p_image->n_scanline_size; i < h; ++ i, p_scanline += ss)
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i, p_image->n_width, 1, n_image_fmt, n_image_datatype, p_scanline);
		// load the image scanline by scanline
	}
	// create the texture

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Texture_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Texture_Wrap_T(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

#endif // 0

CGLTexture_2D *CGLESTextureLoader::p_LoadTexture(const char *p_s_filename,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format, int n_force_format)
{
	int n_fmt = n_IdentifyFormat(p_s_filename);
	if(n_force_format != format_Unknown)
		n_fmt = n_force_format;
	// identify format

	GLenum n_image_fmt = GL_RGBA;
	if(n_fmt == format_Jpeg) {
#ifdef __JPEG_DEC_BGR
		n_image_fmt = GL_BGRA;
#endif //__JPEG_DEC_BGR
	}
	// find appropriate image format

	TBmp *p_bitmap;
	if(!(p_bitmap = p_LoadImage(p_s_filename, n_force_format)))
		return 0;
	// load the image

	GLenum n_internal_fmt = n_Optimal_InternalFormat(p_bitmap);
	if(n_force_internal_format)
		n_internal_fmt = n_force_internal_format;
	// find appropriate internal format

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_bitmap->n_width, p_bitmap->n_height,
	   n_internal_fmt, b_mipmaps, n_image_fmt, GL_UNSIGNED_BYTE, p_bitmap->p_buffer))) {
		delete[] p_bitmap->p_buffer;
		delete p_bitmap;
		return 0;
	}
	// create the texture

	delete[] p_bitmap->p_buffer;
	delete p_bitmap;
	// cleanup

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

CGLTexture_2D *CGLESTextureLoader::p_LoadTexture_Hybrid(
	const char *p_s_color_filename, const char *p_s_alpha_filename,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format)
{
	TBmp *p_color_bitmap;
	if(!(p_color_bitmap = p_LoadImage(p_s_color_filename)))
		return 0;
	// load RGB part

	TBmp *p_alpha_bitmap;
	if(!(p_alpha_bitmap = p_LoadImage(p_s_alpha_filename))) {
		delete[] p_color_bitmap->p_buffer;
		delete p_color_bitmap;
		return 0;
	}
	// load alpha part

	int n_shift = (p_alpha_bitmap->b_alpha)? 0 : 8;
	// take alpha channel if present or red channel

	if(p_alpha_bitmap->n_width != p_color_bitmap->n_width ||
	   p_alpha_bitmap->n_height != p_color_bitmap->n_height) {
		delete[] p_color_bitmap->p_buffer;
		delete p_color_bitmap;
		delete[] p_alpha_bitmap->p_buffer;
		delete p_alpha_bitmap;
		return 0;
	}
	for(uint32_t *p_alpha = p_alpha_bitmap->p_buffer,
	   *p_dest = p_color_bitmap->p_buffer, *p_end = p_color_bitmap->p_buffer +
	   (p_color_bitmap->n_width * p_color_bitmap->n_height); p_dest != p_end;
	   ++ p_dest, ++ p_alpha)
		*p_dest = (*p_dest & 0xffffff) | ((*p_alpha << n_shift) & 0xff000000);
	// merge bitmaps

	delete[] p_alpha_bitmap->p_buffer;
	delete p_alpha_bitmap;

	GLenum n_internal_fmt = n_Optimal_InternalFormat(p_color_bitmap, true);
	if(n_force_internal_format)
		n_internal_fmt = n_force_internal_format;
	// find texture inmternal format

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_color_bitmap->n_width,
	   p_color_bitmap->n_height, n_internal_fmt, b_mipmaps, GL_RGBA,
	   GL_UNSIGNED_BYTE, p_color_bitmap->p_buffer))) {
		delete[] p_color_bitmap->p_buffer;
		delete p_color_bitmap;
		return 0;
	}
	// create texture

	delete[] p_color_bitmap->p_buffer;
	delete p_color_bitmap;
	// cleanup

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

#if 0

CGLTexture_3D *CGLESTextureLoader::p_LoadTexture_3D(CGLState *p_state,
	const char *p_s_filename, bool b_mipmaps, bool b_clamp_to_edge,
	GLenum n_force_internal_format, int n_force_format)
{
	std::string s_filename;
	// buffer for filenames

	int n_fmt = n_IdentifyFormat(p_s_filename);
	if(n_force_format != format_Unknown)
		n_fmt = n_force_format;
	// identify format

	GLenum n_image_fmt = GL_RGBA;
	if(n_fmt == format_Jpeg) {
#ifdef __JPEG_DEC_BGR
		n_image_fmt = GL_BGRA;
#endif //__JPEG_DEC_BGR
	}
	// find appropriate image format

	int n_layer_num = 0;
	for(;;) {
		if(!stl_ut::Format(s_filename, p_s_filename, n_layer_num))
			return 0;
		// format filename

		FILE *p_fr;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
		if(!fopen_s(&p_fr, s_filename.c_str(), "rb")) {
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		if(p_fr = fopen(s_filename.c_str(), "rb")) {
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
			fclose(p_fr);
			++ n_layer_num;
		} else
			break;
		// try to open file
	}
	// calculate number of layers (texture depth)

	if(!n_layer_num)
		return 0;
	// no layers, no texture

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_3D *p_texture = 0;
	for(int i = 0; i < n_layer_num; ++ i) {
		if(!stl_ut::Format(s_filename, p_s_filename, i)) {
			if(p_texture)
				delete p_texture;
			return 0;
		}
		// format filename

		TBmp *p_bitmap;
		if(!(p_bitmap = p_LoadImage(s_filename.c_str()))) {
			if(p_texture)
				delete p_texture;
			return 0;
		}
		// load bitmap

		if(!i) {
			GLenum n_internal_fmt = n_Optimal_InternalFormat(p_bitmap);
			if(n_force_internal_format)
				n_internal_fmt = n_force_internal_format;
			// find appropriate internal format

			if(!(p_texture = new(std::nothrow) CGLTexture_3D(p_state, p_bitmap->n_width,
			   p_bitmap->n_height, n_layer_num, n_internal_fmt, b_mipmaps, 0))) {
				delete[] p_bitmap->p_buffer;
				delete p_bitmap;
				return 0;
			}
		} else {
			if(p_texture->n_Width() != p_bitmap->n_width ||
			   p_texture->n_Height() != p_bitmap->n_height) {
				delete[] p_bitmap->p_buffer;
				delete p_bitmap;
				if(p_texture)
					delete p_texture;
				return 0;
			}
		}
		// create texture in the first pass (need to know image dimensions)

		glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, i, p_bitmap->n_width,
			p_bitmap->n_height, 1, n_image_fmt, GL_UNSIGNED_BYTE, p_bitmap->p_buffer);
		// specify texture slice

		delete[] p_bitmap->p_buffer;
		delete p_bitmap;
		// delete bitmap
	}
	// load texture, specify layers one by one (todo - maybe have to update mipmaps afterwards)

	if(b_clamp_to_edge) {
		p_texture->r_Parameters(p_state).Set_Texture_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->r_Parameters(p_state).Set_Texture_Wrap_T(GL_CLAMP_TO_EDGE);
		p_texture->r_Parameters(p_state).Set_Texture_Wrap_R(GL_CLAMP_TO_EDGE);
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

#endif //0

CGLTexture_Cube *CGLESTextureLoader::p_LoadTexture_Cube(
	const char *p_s_filename, bool b_mipmaps, bool b_clamp_to_edge,
	GLenum n_force_internal_format, int n_force_format)
{
	std::string s_filename;
	// buffer for filenames

	int n_fmt = n_IdentifyFormat(p_s_filename);
	if(n_force_format != format_Unknown)
		n_fmt = n_force_format;
	// identify format

	GLenum n_image_fmt = GL_RGBA;
	if(n_fmt == format_Jpeg) {
#ifdef __JPEG_DEC_BGR
		n_image_fmt = GL_BGRA;
#endif //__JPEG_DEC_BGR
	}
	// find appropriate image format

	const GLenum p_cube_face[6] = {
		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Z
	};
	const char *p_cube_face_name[6] = {
		"negx", "posx", "negy", "posy", "negz", "posz"
	};

	//glPixelStorei(GL_PACK_SKIP_ROWS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // not in GLES20
	//glPixelStorei(GL_PACK_SKIP_IMAGES, 0); // not in GLES20
	//glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); // not in GLES20
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, 0); // not in GLES20
	// make sure they are in their default state

	CGLTexture_Cube *p_texture = 0;
	for(int i = 0; i < 6; ++ i) {
		if(!stl_ut::Format(s_filename, p_s_filename, p_cube_face_name[i])) {
			if(p_texture)
				delete p_texture;
			return 0;
		}
		// format filename

		TBmp *p_bitmap;
		if(!(p_bitmap = p_LoadImage(s_filename.c_str()))) {
			if(p_texture)
				delete p_texture;
			return 0;
		}
		// load bitmap

		if(!i) {
			GLenum n_internal_fmt = n_Optimal_InternalFormat(p_bitmap);
			if(n_force_internal_format)
				n_internal_fmt = n_force_internal_format;
			// find appropriate internal format

			if(p_bitmap->n_width != p_bitmap->n_height) {
				delete[] p_bitmap->p_buffer;
				delete p_bitmap;
				return 0;
			}

			if(!(p_texture = new(std::nothrow) CGLTexture_Cube(p_bitmap->n_width,
			   n_internal_fmt, b_mipmaps))) {
				delete[] p_bitmap->p_buffer;
				delete p_bitmap;
				return 0;
			}
		} else {
			if(p_texture->n_Width() != p_bitmap->n_width ||
			   p_texture->n_Width() != p_bitmap->n_height) {
				delete[] p_bitmap->p_buffer;
				delete p_bitmap;
				if(p_texture)
					delete p_texture;
				return 0;
			}
		}
		// create texture in the first pass (need to know image dimensions)

		glTexSubImage2D(p_cube_face[i], 0, 0, 0, p_bitmap->n_width,
			p_bitmap->n_height, n_image_fmt, GL_UNSIGNED_BYTE, p_bitmap->p_buffer);
		// specify texture side

		delete[] p_bitmap->p_buffer;
		delete p_bitmap;
		// delete bitmap
	}
	// load texture, specify sides one by one

	if(b_clamp_to_edge) {
		p_texture->Bind();
		p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
		//p_texture->Set_Texture_Wrap_R(GL_CLAMP_TO_EDGE); // no GL_TEXTURE_WRAP_R in OpenGL ES 2.0
	}
	// set texture params

	if(!p_texture->b_Status() || glGetError() != GL_NO_ERROR) {
		delete p_texture;
		return 0;
	}
	// error check

	return p_texture;
}

/*
 *								=== ~CGLESTextureLoader ===
 */
