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

/**
 *	@file gl2/TextureUtil.cpp
 *	@date 2007
 *	@author -tHE SWINe-
 *	@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 CGLTextureLoader (but there's typedef on the end of the file
 *	so the code is backwards-compatible. however, new name should be always used.)
 *
 *	added CGLTextureLoader::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 "OpenGL20.h"
#include <GL/glut.h>
#include <stdio.h>
#include "../Tga.h"
#include "../Jpeg.h"
#include "OpenGLState.h"
#include "Texture.h"
#include "../StlUtils.h"
#include "TextureUtil.h"

/*
 *								=== CGLTextureLoader ===
 */

/*
 *	static int CGLTextureLoader::n_IdentifyFormat(const char *p_s_filename)
 *		- identifies format of image file p_s_filename
 *		  by it's extension (doesn't look at file data)
 *		- returns one of format_Unknown, format_Targa, format_Jpeg
 */
int CGLTextureLoader::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;
#else // _MSC_VER && !__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;
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	return format_Unknown;
}

/*
 *	static TBmp *CGLTextureLoader::p_LoadTargaImage(const char *p_s_filename)
 *		- returns targa image, loaded from file p_s_filename
 *		  or 0 on failure
 */
TBmp *CGLTextureLoader::p_LoadTargaImage(const char *p_s_filename)
{
	return CTgaCodec::p_Load_TGA(p_s_filename);
}

/*
 *	static TBmp *CGLTextureLoader::p_LoadJpegImage(const char *p_s_filename)
 *		- returns jpeg image, loaded from file p_s_filename
 *		  or 0 on failure
 */
TBmp *CGLTextureLoader::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;
	}
#ifndef __JPEG_DEC_RGB
	p_bitmap->Swap_RGB_to_BGR();
#endif // __JPEG_DEC_RGB
	fclose(p_fr);
	// load jpeg

	return p_bitmap;
}

/*
 *	static TBmp *CGLTextureLoader::p_LoadImage(const char *p_s_filename,
 *		int n_force_format = format_Unknown)
 *		- returns image, loaded from file p_s_filename or 0 on failure
 *		- in case n_force_format is different than format_Unknown,
 *		  it is used as guide to which codec to use. otherwise format
 *		  is determied using n_IdentifyFormat() by file extension
 *		- in case n_force_format is format_Unknown and file has no
 *		  extension, it tries to load image using targa codec or
 *		  using jpeg codec (only if jpeg error handling is compiled)
 */
TBmp *CGLTextureLoader::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);
	default:
		TBmp *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
	}
}

/*
 *	static GLenum CGLTextureLoader::n_Optimal_InternalFormat(const TBmp *p_bitmap,
 *		bool b_force_alpha = false)
 *		- returns optimal texture internal format for image p_bitmap
 *		  (based on image being greyscale and on alpha channel presence)
 */
GLenum CGLTextureLoader::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_RGBA8;
	} else {
		if(p_bitmap->b_grayscale)
			return GL_LUMINANCE8;
		else
			return GL_RGB8;
	}
}

/*
 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture(CGLState *p_state,
 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = false,
 *		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown)
 *		- returns new 2D texture
 *		- p_state is OpenGL state guard, p_s_filename is image file name
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 *		- if n_force_format is not format_Unknown, image is loaded using
 *		  corresponding codec (otherwise format is determined using filename
 *		  extension)
 */
CGLTexture_2D *CGLTextureLoader::p_LoadTexture(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)
{
	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

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_state, p_bitmap->n_width, p_bitmap->n_height,
	   n_internal_fmt, b_mipmaps, 0, 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->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);
	}
	// set texture params

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

	return p_texture;
}

/*
 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture_TGA(CGLState *p_state,
 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = false,
 *		GLenum n_force_internal_format = 0)
 *		- returns new 2D texture
 *		- p_state is OpenGL state guard, p_s_filename is targa image file name
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 */
CGLTexture_2D *CGLTextureLoader::p_LoadTexture_TGA(CGLState *p_state, const char *p_s_filename,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format)
{
	return p_LoadTexture(p_state, p_s_filename, b_mipmaps,
		b_clamp_to_edge, n_force_internal_format, format_Targa);
}

/*
 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture_Jpeg(CGLState *p_state,
 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = false,
 *		GLenum n_force_internal_format = 0)
 *		- returns new 2D texture
 *		- p_state is OpenGL state guard, p_s_filename is jpeg image file name
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 */
CGLTexture_2D *CGLTextureLoader::p_LoadTexture_Jpeg(CGLState *p_state, const char *p_s_filename,
	bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format)
{
	return p_LoadTexture(p_state, p_s_filename, b_mipmaps,
		b_clamp_to_edge, n_force_internal_format, format_Jpeg);
}

/*
 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture_Hybrid(CGLState *p_state,
 *		const char *p_s_color_filename, const char *p_s_alpha_filename,
 *		bool b_mipmaps = true, bool b_clamp_to_edge = false,
 *		GLenum n_force_internal_format = 0)
 *		- returns new 2D texture
 *		- p_state is OpenGL state guard
 *		- p_s_color_filename and p_s_alpha_filename are image filenames. rgb
 *		  components of first image are merged with second image alpha channel
 *		  (in case the second image doesn't have an alpha channel, it's red
 *		  channel is used)
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 */
CGLTexture_2D *CGLTextureLoader::p_LoadTexture_Hybrid(CGLState *p_state,
	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

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_state, p_color_bitmap->n_width,
	   p_color_bitmap->n_height, n_internal_fmt, b_mipmaps, 0, GL_BGRA,
	   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->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);
	}
	// set texture params

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

	return p_texture;
}

/*
 *	static CGLTexture_3D *CGLTextureLoader::p_LoadTexture_3D(CGLState *p_state,
 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = false,
 *		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown)
 *		- returns new 3D texture
 *		- p_state is OpenGL state guard, p_s_filename is image file name
 *		  (file name contains formatting string for snprintf, such as
 *		  "texture_%d.jpg" or "texture_%03d.jpg". first image index is 0,
 *		  number of images is determined by trying to access image files
 *		  in sequence)
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 *		- if n_force_format is not format_Unknown, image is loaded using
 *		  corresponding codec (otherwise format is determined using filename
 *		  extension)
 */
CGLTexture_3D *CGLTextureLoader::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

	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;
}

/*
 *	static CGLTexture_Cube *CGLTextureLoader::p_LoadTexture_Cube(CGLState *p_state,
 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = true,
 *		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown)
 *		- returns new cube-map texture
 *		- p_state is OpenGL state guard, p_s_filename is image file name
 *		  (file name contains formatting string for snprintf, such as
 *		  "texture_%s.jpg", %s is replaced by "pos-x", "neg-x", "pos-y", ...)
 *		- b_mipmaps controls wheter texture is created with mipmaps
 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
 *		- passing nonzero n_force_internal_format sets texture internal format
 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
 *		- if n_force_format is not format_Unknown, image is loaded using
 *		  corresponding codec (otherwise format is determined using filename
 *		  extension)
 */
CGLTexture_Cube *CGLTextureLoader::p_LoadTexture_Cube(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

	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"
	};

	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_state, 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->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;
}

/*
 *								=== ~CGLTextureLoader ===
 */
