/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/

#ifdef _WIN32
#include <windows.h>
#endif

#include "globalincs/pstypes.h"
#include "globalincs/systemvars.h"
#include "osapi/osregistry.h"
#include "bmpman/bmpman.h"
#include "cmdline/cmdline.h"
#include "graphics/gropengl.h"
#include "graphics/gropengltexture.h"
#include "graphics/gropenglextension.h"
#include "graphics/gropenglstate.h"
#include "graphics/grinternal.h"
#include "ddsutils/ddsutils.h"
#include "math/vecmat.h"


static tcache_slot_opengl *Textures = NULL;
static int *Tex_used_this_frame = NULL;

int GL_texture_ram = 0;
int GL_min_texture_width = 0;
GLint GL_max_texture_width = 0;
int GL_min_texture_height = 0;
GLint GL_max_texture_height = 0;
int GL_textures_in = 0;
int GL_textures_in_frame = 0;
int GL_last_detail = -1;
GLint GL_supported_texture_units = 2;
int GL_should_preload = 0;
ubyte GL_xlat[256];
GLfloat GL_anisotropy = 1.0f;
GLfloat GL_max_anisotropy = -1.0f;
int GL_mipmap_filter = 0;
GLenum GL_texture_target = GL_TEXTURE_2D;
GLenum GL_texture_face = GL_TEXTURE_2D;
GLenum GL_texture_addressing = GL_REPEAT;
bool GL_rendering_to_texture = false;
GLint GL_max_renderbuffer_size = 0;

extern int GLOWMAP;
extern int SPECMAP;
extern int CLOAKMAP;
extern int ENVMAP;
extern int Interp_multitex_cloakmap;


// forward declarations
int opengl_free_texture(tcache_slot_opengl *t);
void opengl_free_texture_with_handle(int handle);
void opengl_tcache_get_adjusted_texture_size(int w_in, int h_in, int *w_out, int *h_out);
int opengl_create_texture_sub(int bitmap_handle, int bitmap_type, int bmap_w, int bmap_h, int tex_w, int tex_h, ubyte *data = NULL, tcache_slot_opengl *t = NULL, int base_level = 0, int resize = 0, int reload = 0);
int opengl_create_texture (int bitmap_handle, int bitmap_type, tcache_slot_opengl *tslot = NULL);

void opengl_set_additive_tex_env()
{
	GL_CHECK_FOR_ERRORS("start of set_additive_tex_env()");

	if ( Is_Extension_Enabled(OGL_ARB_TEXTURE_ENV_COMBINE) ) {
		GL_state.Texture.SetEnvCombineMode(GL_COMBINE_RGB, GL_ADD);
		GL_state.Texture.SetRGBScale(1.0f);
	} else {
		GL_state.Texture.SetEnvMode(GL_ADD);
	}

	GL_CHECK_FOR_ERRORS("end of set_additive_tex_env()");
}

void opengl_set_modulate_tex_env()
{
	GL_CHECK_FOR_ERRORS("start of set_modulate_tex_env()");

	if ( Is_Extension_Enabled(OGL_ARB_TEXTURE_ENV_COMBINE) ) {
		GL_state.Texture.SetEnvCombineMode(GL_COMBINE_RGB, GL_MODULATE);
		GL_state.Texture.SetRGBScale(4.0f);
	} else {
		GL_state.Texture.SetEnvMode(GL_MODULATE);
	}

	GL_CHECK_FOR_ERRORS("end of set_modulate_tex_env()");
}

GLfloat opengl_get_max_anisotropy()
{
	if ( !Is_Extension_Enabled(OGL_EXT_TEXTURE_FILTER_ANISOTROPIC) ) {
		return 0.0f;
	}

	if ( GL_max_anisotropy < 0.0f ) {
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &GL_max_anisotropy);
	}

	// the spec says that it should be a minimum of 2.0
	Assert( GL_max_anisotropy >= 2.0f );

	return GL_max_anisotropy;
}

void opengl_set_texture_target( GLenum target )
{
    GL_texture_target = target;
}

void opengl_tcache_init()
{
	int i;

	GL_should_preload = 1;

	GL_min_texture_width = 16;
	GL_min_texture_height = 16;

	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &GL_max_texture_width);

	GL_max_texture_height = GL_max_texture_width;

	// if we are not using sections then make sure we have the min texture size available to us
	// 1024 is what we need with the standard resolutions - taylor
	if (GL_max_texture_width < 1024) {
		Error(LOCATION, "A minimum texture size of \"1024x1024\" is required for FS2_Open but only \"%ix%i\" was found.  Can not continue.", GL_max_texture_width, GL_max_texture_height);
	}

	if (Textures == NULL) {
		Textures = (tcache_slot_opengl *) vm_malloc_q(MAX_BITMAPS * sizeof(tcache_slot_opengl));
	}

	if (Tex_used_this_frame == NULL) {
		Tex_used_this_frame = (int *) vm_malloc_q(MAX_BITMAPS * sizeof(int));
	}

	if ( !Textures || !Tex_used_this_frame )
		Error(LOCATION, "Unable to allocate memory for OpenGL texture slots!");


	memset( Tex_used_this_frame, 0, MAX_BITMAPS * sizeof(int) );

	// Init the texture structures
	for (i = 0; i < MAX_BITMAPS; i++) {
		Textures[i].reset();
	}

	// check what mipmap filter we should be using
	//   0  ==  Bilinear
	//   1  ==  Trilinear
	GL_mipmap_filter = os_config_read_uint(NULL, "TextureFilter", 1);

	if (GL_mipmap_filter > 1) {
		GL_mipmap_filter = 1;
	}

	// max size (width and/or height) that we can use for framebuffer/renderbuffer
	if ( Is_Extension_Enabled(OGL_EXT_FRAMEBUFFER_OBJECT) ) {
		glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &GL_max_renderbuffer_size);

		// if we can't do at least 128x128 then just disable FBOs
		if (GL_max_renderbuffer_size < 128) {
			mprintf(("WARNING: Max dimensions of FBO, %ix%i, is less the required minimum!!  Extension will be disabled!\n", GL_max_renderbuffer_size, GL_max_renderbuffer_size));
			GL_Extensions[OGL_EXT_FRAMEBUFFER_OBJECT].enabled = 0;
		}
	}

	// anisotropy
	if ( Is_Extension_Enabled(OGL_EXT_TEXTURE_FILTER_ANISOTROPIC) ) {
		// set max value first thing
		opengl_get_max_anisotropy();

		// now for the user setting
		const char *plevel = os_config_read_string( NULL, NOX("OGL_AnisotropicFilter"), NOX("1.0") );
		GL_anisotropy = (GLfloat) strtod(plevel, (char**)NULL);

		CLAMP(GL_anisotropy, 1.0f, GL_max_anisotropy);
	}

	if ( Is_Extension_Enabled(OGL_EXT_TEXTURE_LOD_BIAS) ) {
		if (GL_anisotropy > 1.0f) {
			glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.0f);
		} else {
			glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -0.75f);
		}
	}

	// set the alpha gamma settings (for fonts)
	memset( GL_xlat, 0, sizeof(GL_xlat) );

	for (i = 1; i < 15; i++) {
		GL_xlat[i] = (ubyte)(GL_xlat[i-1] + 17);
	}

	GL_xlat[15] = GL_xlat[1];

	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &GL_supported_texture_units);
	Assert( GL_supported_texture_units >= 2 );

	GL_last_detail = Detail.hardware_textures;

	GL_textures_in = 0;
	GL_textures_in_frame = 0;
}

void opengl_free_texture_with_handle(int handle)
{
	int n = bm_get_cache_slot(handle, 1);

	Tex_used_this_frame[n] = 0;
	opengl_free_texture( &Textures[n] );
}

void opengl_tcache_flush()
{
	int i;

	if ( Textures == NULL )
		return;

	for (i = 0; i < MAX_BITMAPS; i++)
		opengl_free_texture( &Textures[i] );

	if (GL_textures_in != 0) {
		mprintf(( "WARNING: VRAM is at %d instead of zero after flushing!\n", GL_textures_in ));
		GL_textures_in = 0;
	}
}

extern void opengl_kill_all_render_targets();

void opengl_tcache_shutdown()
{
	opengl_kill_all_render_targets();

	opengl_tcache_flush();

	GL_textures_in = 0;
	GL_textures_in_frame = 0;

	if (Textures != NULL) {
		vm_free(Textures);
		Textures = NULL;
	}

	if (Tex_used_this_frame != NULL) {
		vm_free(Tex_used_this_frame);
		Tex_used_this_frame = NULL;
	}
}

void opengl_tcache_frame()
{
	GL_textures_in_frame = 0;

	// make all textures as not used
	memset( Tex_used_this_frame, 0, MAX_BITMAPS * sizeof(int) );
}

extern bool GL_initted;

void opengl_free_texture_slot( int n )
{
	if ( !GL_initted ) {
		return;
	}

	Tex_used_this_frame[n] = 0;
	opengl_free_texture( &Textures[n] );
}

/**
 * Determine if a bitmap is in API memory, so that we can just reuse it rather
 * that having to load it from disk again
 */
bool opengl_texture_slot_valid(int n, int handle)
{
	tcache_slot_opengl *t = &Textures[n];

	if (t->bitmap_handle < 0) {
		return false;
	}

	if (t->bitmap_handle != handle) {
		return false;
	}

	if ( !glIsTexture(t->texture_id) ) {
		return false;
	}

	return true;
}

int opengl_free_texture(tcache_slot_opengl *t)
{
	// Bitmap changed!!     
	if (t->texture_id) {
		// if I, or any of my children have been used this frame, bail  
		// can't use bm_get_cache_slot() here since bitmap_id probably isn't valid
		if ( (t->bitmap_handle >= 0) && Tex_used_this_frame[t->bitmap_handle % MAX_BITMAPS] ) {
			return 0;
		}

		// ok, now we know its legal to free everything safely
		GL_state.Texture.Delete(t->texture_id);
		glDeleteTextures (1, &t->texture_id);

		GL_textures_in -= t->size;

		t->reset();
	}

	return 1;
}

void opengl_tcache_get_adjusted_texture_size(int w_in, int h_in, int *w_out, int *h_out)
{
	int i, tex_w, tex_h;

	// bogus
	if ( (w_out == NULL) ||  (h_out == NULL) ) {
		return;
	}

	// if we can support non-power-of-2 textures then just return current sizes - taylor
	if ( Is_Extension_Enabled(OGL_ARB_TEXTURE_NON_POWER_OF_TWO) ) {
		*w_out = w_in;
		*h_out = h_in;

		return;
	}

	// starting size
	tex_w = w_in;
	tex_h = h_in;

	for (i = 0; i < 16; i++) {
		if ( (tex_w > (1<<i)) && (tex_w <= (1<<(i+1))) ) {
			tex_w = 1 << (i+1);
			break;
		}
	}

	for (i = 0; i < 16; i++) {
		if ( (tex_h > (1<<i)) && (tex_h <= (1<<(i+1))) ) {
			tex_h = 1 << (i+1);
			break;
		}
	}

	CLAMP(tex_w, GL_min_texture_width, GL_max_texture_width);
	CLAMP(tex_h, GL_min_texture_height, GL_max_texture_height);

	// store the outgoing size
	*w_out = tex_w;
	*h_out = tex_h;
}

// data == start of bitmap data
// bmap_w == width of source bitmap
// bmap_h == height of source bitmap
// tex_w == width of final texture
// tex_h == height of final texture
int opengl_create_texture_sub(int bitmap_handle, int bitmap_type, int bmap_w, int bmap_h, int tex_w, int tex_h, ubyte *data, tcache_slot_opengl *t, int base_level, int resize, int reload)
{
	int ret_val = 1;
	int byte_mult = 0;
	GLenum texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
	GLenum glFormat = GL_BGRA;
	GLint intFormat = GL_RGBA;
	ubyte saved_bpp = 0;
	int mipmap_w = 0, mipmap_h = 0;
	int dsize = 0, doffset = 0, block_size = 0;
	int i,j,k;
	ubyte *bmp_data = data;
	ubyte *texmem = NULL, *texmemp = NULL;
	int skip_size = 0, mipmap_levels = 0;

	GL_CHECK_FOR_ERRORS("start of create_texture_sub()");

	// bogus
	if ( (t == NULL) || (bmp_data == NULL) ) {
		return 0;
	}

	int idx = bm_get_cache_slot( bitmap_handle, 1 );

	if ( Tex_used_this_frame[idx] ) {
		mprintf(( "ARGHH!!! Texture already used this frame!  Cannot free it!\n" ));
		return 0;
	}

	if ( !reload )  {
		// save the bpp since it will get reset - fixes anis being 0 bpp
		saved_bpp = t->bpp;

		// gah
		if ( !opengl_free_texture(t) ) {
			return 0;
		}
	}

	// for everything that might use mipmaps
	mipmap_w = tex_w;
	mipmap_h = tex_h;

	if ( (bitmap_type == TCACHE_TYPE_AABITMAP) || (bitmap_type == TCACHE_TYPE_INTERFACE) ) {
		t->u_scale = (float)bmap_w / (float)tex_w;
		t->v_scale = (float)bmap_h / (float)tex_h;
	} else {
		t->u_scale = 1.0f;
		t->v_scale = 1.0f;
	}

	if ( !reload ) {
		glGenTextures (1, &t->texture_id);
	}
	
	if (t->texture_id == 0) {
		mprintf(("!!OpenGL DEBUG!! t->texture_id == 0\n"));
		return 0;
	}

	if (t->bpp == 0) {
		// it got reset, revert to saved setting
		t->bpp = saved_bpp;
	}

	// set the byte per pixel multiplier
	byte_mult = (t->bpp >> 3);

	// GL_BGRA_EXT is *much* faster with some hardware/drivers
	if (byte_mult == 4) {
		texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
		intFormat = (gr_screen.bits_per_pixel == 32) ? GL_RGBA8 : GL_RGB5_A1;
		glFormat = GL_BGRA;
	} else if (byte_mult == 3) {
		texFormat = GL_UNSIGNED_BYTE;
		intFormat = (gr_screen.bits_per_pixel == 32) ? GL_RGB8 : GL_RGB5;
		glFormat = GL_BGR;
	} else if (byte_mult == 2) {
		texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		intFormat = GL_RGB5_A1;
		glFormat = GL_BGRA;
	} else if (byte_mult == 1) {
		Assertion( bitmap_type == TCACHE_TYPE_AABITMAP, "Invalid type for bitmap: %s BMPMAN handle: %d. Type expected was 0, we got %d instead.\nThis can be caused by using texture compression on a non-power-of-2 texture.\n", bm_get_filename(bitmap_handle), bitmap_handle, bitmap_type );
		texFormat = GL_UNSIGNED_BYTE;
		intFormat = GL_ALPHA;
		glFormat = GL_ALPHA;
	} else {
		texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
	}

	// check for compressed image types
	switch ( bm_is_compressed(bitmap_handle) ) {
		case DDS_DXT1:
		case DDS_CUBEMAP_DXT1:
			intFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
			block_size = 8;
			break;

		case DDS_DXT3:
		case DDS_CUBEMAP_DXT3:
			intFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
			block_size = 16;
			break;

		case DDS_DXT5:
		case DDS_CUBEMAP_DXT5:
			intFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
			block_size = 16;
			break;
	}


	if ( bitmap_type == TCACHE_TYPE_CUBEMAP ) {
		t->texture_target = GL_TEXTURE_CUBE_MAP;
	}

	glBindTexture(t->texture_target, t->texture_id);

	GLenum min_filter = GL_LINEAR;

	if (t->mipmap_levels > 1) {
		min_filter = (GL_mipmap_filter) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST;

		if ( Is_Extension_Enabled(OGL_EXT_TEXTURE_FILTER_ANISOTROPIC) ) {
			glTexParameterf(t->texture_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, GL_anisotropy);
		}
	}

	glTexParameteri(t->texture_target, GL_TEXTURE_MAX_LEVEL, t->mipmap_levels-1);
	glTexParameteri(t->texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(t->texture_target, GL_TEXTURE_MIN_FILTER, min_filter);
	glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	t->wrap_mode = GL_CLAMP_TO_EDGE;

	mipmap_levels = bm_get_num_mipmaps(bitmap_handle);


	switch (bitmap_type) {
		case TCACHE_TYPE_COMPRESSED: {
			if (block_size > 0) {
				// size of data block (4x4)
				dsize = ((mipmap_h + 3) / 4) * ((mipmap_w + 3) / 4) * block_size;

				// if we are skipping mipmap levels in order to resize then we have to calc the new offset
				for (i = 0; i < base_level; i++) {
					doffset += dsize;

					mipmap_w >>= 1;
					mipmap_h >>= 1;

					if (mipmap_w <= 0) {
						mipmap_w = 1;
					}

					if (mipmap_h <= 0) {
						mipmap_h = 1;
					}

					dsize = ((mipmap_h + 3) / 4) * ((mipmap_w + 3) / 4) * block_size;
				}

				skip_size = doffset;

				if ( !reload ) {
					vglCompressedTexImage2D(GL_TEXTURE_2D, 0, intFormat, mipmap_w, mipmap_h, 0, dsize, bmp_data + doffset);
				} else {
					vglCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mipmap_w, mipmap_h, intFormat, dsize, bmp_data + doffset);
				}

				// now that the base image is done handle any mipmap levels
				for (i = 1; i < (mipmap_levels - base_level); i++) {
					// adjust the data offset for the next block
					doffset += dsize;

					// reduce size by half for the next pass
					mipmap_w >>= 1;
					mipmap_h >>= 1;

					if (mipmap_w <= 0) {
						mipmap_w = 1;
					}

					if (mipmap_h <= 0) {
						mipmap_h = 1;
					}

					// size of data block (4x4)
					dsize = ((mipmap_h + 3) / 4) * ((mipmap_w + 3) / 4) * block_size;

					if ( !reload ) {
						vglCompressedTexImage2D(GL_TEXTURE_2D, i, intFormat, mipmap_w, mipmap_h, 0, dsize, bmp_data + doffset);
					} else {
						vglCompressedTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, mipmap_w, mipmap_h, intFormat, dsize, bmp_data + doffset);
					}
				}
			} else {
				Int3();
				return 0;
			}

			break;
		}

		case TCACHE_TYPE_AABITMAP: {
			texmem = (ubyte *) vm_malloc (tex_w*tex_h*byte_mult);
			texmemp = texmem;

			Assert( texmem != NULL );

			int luminance = 0;
			for (i = 0; i < tex_h; i++) {
				for (j = 0; j < tex_w; j++) {
					if ( (i < bmap_h) && (j < bmap_w) ) {
						if ( byte_mult > 1 ) {
							luminance = 0;

							if ( byte_mult > 3 ) {
								for (k = 0; k < 3; k++) {
									luminance += bmp_data[(i*bmap_w+j)*byte_mult+k];
								}

								*texmemp++ = (ubyte)((luminance / 3) * (bmp_data[(i*bmap_w+j)*byte_mult+3]/255.0f));
							} else {
								for (k = 0; k < byte_mult; k++) {
									luminance += bmp_data[(i*bmap_w+j)*byte_mult+k]; 
								}

								*texmemp++ = (ubyte)(luminance / byte_mult);
							}
						} else {
							*texmemp++ = GL_xlat[bmp_data[i*bmap_w+j]];
						}
					} else {
						*texmemp++ = 0;
					}
				}
			}

			if ( !reload ) {
				glTexImage2D (t->texture_target, 0, GL_ALPHA, tex_w, tex_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texmem);
			} else { // faster anis
				glTexSubImage2D (t->texture_target, 0, 0, 0, tex_w, tex_h, GL_ALPHA, GL_UNSIGNED_BYTE, texmem);
			}

			if (texmem != NULL) {
				vm_free(texmem);
			}

			break;
		}

		case TCACHE_TYPE_INTERFACE: {
			// if we aren't resizing then we can just use bmp_data directly
			if (resize) {
				texmem = (ubyte *) vm_malloc (tex_w*tex_h*byte_mult);
				texmemp = texmem;

				Assert( texmem != NULL );

				for (i = 0; i < tex_h; i++) {
					for (j = 0;j < tex_w; j++) {
						if ( (i < bmap_h) && (j < bmap_w) ) {
							for (k = 0; k < byte_mult; k++) {
								*texmemp++ = bmp_data[(i*bmap_w+j)*byte_mult+k];
							}
						} else {
							for (k = 0; k < byte_mult; k++) {
								*texmemp++ = 0;
							}
						}
					}
				}
			}

			if ( !reload ) {
				glTexImage2D (t->texture_target, 0, intFormat, mipmap_w, mipmap_h, 0, glFormat, texFormat, (resize) ? texmem : bmp_data);
			} else { // faster anis
				glTexSubImage2D (t->texture_target, 0, 0, 0, mipmap_w, mipmap_h, glFormat, texFormat, (resize) ? texmem : bmp_data);
			}

			if (texmem != NULL) {
				vm_free(texmem);
			}

			break;
		}

		case TCACHE_TYPE_CUBEMAP: {
			Assert( !resize );
			Assert( texmem == NULL );
			Assert( Is_Extension_Enabled(OGL_ARB_TEXTURE_CUBE_MAP) );

			// we have to load in all 6 faces...
			for (i = 0; i < 6; i++) {
				doffset += dsize;

				// check if it's a compressed cubemap first
				if (block_size > 0) {
					// size of data block (4x4)
					dsize = ((mipmap_h + 3) / 4) * ((mipmap_w + 3) / 4) * block_size;

					if ( !reload ) {
						vglCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, intFormat, mipmap_w, mipmap_h, 0, dsize, bmp_data + doffset);
					} else {
						vglCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, 0, 0, mipmap_w, mipmap_h, intFormat, dsize, bmp_data + doffset);
					}

					// now that the base image is done handle any mipmap levels
					for (j = 1; j < mipmap_levels; j++) {
						// adjust the data offset for the next block
						doffset += dsize;

						// reduce size by half for the next pass
						mipmap_w >>= 1;
						mipmap_h >>= 1;

						if (mipmap_w <= 0) {
							mipmap_w = 1;
						}

						if (mipmap_h <= 0) {
							mipmap_h = 1;
						}

						// size of data block (4x4)
						dsize = ((mipmap_h + 3) / 4) * ((mipmap_w + 3) / 4) * block_size;

						if ( !reload ) {
							vglCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, j, intFormat, mipmap_w, mipmap_h, 0, dsize, bmp_data + doffset);
						} else {
							vglCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, j, 0, 0, mipmap_w, mipmap_h, intFormat, dsize, bmp_data + doffset);
						}
					}
				}
				// nope, it's uncompressed...
				else {
					dsize = mipmap_h * mipmap_w * byte_mult;

					if ( !reload ) {
						glTexImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, intFormat, mipmap_w, mipmap_h, 0, glFormat, texFormat, bmp_data + doffset);
					} else { // faster anis
						glTexSubImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, 0, 0, mipmap_w, mipmap_h, glFormat, texFormat, bmp_data + doffset);
					}

					// base image is done so now take care of any mipmap levels
					for (j = 1; j < mipmap_levels; j++) {
						doffset += dsize;
						mipmap_w >>= 1;
						mipmap_h >>= 1;

						if (mipmap_w <= 0) {
							mipmap_w = 1;
						}

						if (mipmap_h <= 0) {
							mipmap_h = 1;
						}

						dsize = mipmap_h * mipmap_w * byte_mult;

						if ( !reload ) {
							glTexImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, j, intFormat, mipmap_w, mipmap_h, 0, glFormat, texFormat, bmp_data + doffset);
						} else {
							glTexSubImage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, j, 0, 0, mipmap_w, mipmap_h, glFormat, texFormat, bmp_data + doffset);
						}
					}
				}

				// reset width and height for next face
				mipmap_w = tex_w;
				mipmap_h = tex_h;
			}

			break;
		}

		default: {
			// if we aren't resizing then we can just use bmp_data directly
			if (resize) {
				texmem = (ubyte *) vm_malloc (tex_w*tex_h*byte_mult);
				texmemp = texmem;

				Assert( texmem != NULL );
	
				fix u, utmp, v, du, dv;

				u = v = 0;

				du = ( (bmap_w-1)*F1_0 ) / tex_w;
				dv = ( (bmap_h-1)*F1_0 ) / tex_h;

				for (j=0;j<tex_h;j++)
				{
					utmp = u;
					for (i=0;i<tex_w;i++)
					{
						for (k = 0; k < byte_mult; k++) {
							*texmemp++ = bmp_data[(f2i(v)*bmap_w+f2i(utmp))*byte_mult+k];
						}
						utmp += du;
					}
					v += dv;
				}
			}

			// should never have mipmap levels if we also have to manually resize
			if ( (mipmap_levels > 1) && resize ) {
				Assert( texmem == NULL );
			}

			dsize = mipmap_h * mipmap_w * byte_mult;

			// if we are skipping mipmap levels in order to resize then we have to calc the new offset
			for (i = 0; i < base_level; i++) {
				doffset += dsize;

				mipmap_w >>= 1;
				mipmap_h >>= 1;

				if (mipmap_w <= 0) {
					mipmap_w = 1;
				}

				if (mipmap_h <= 0) {
					mipmap_h = 1;
				}

				dsize = mipmap_h * mipmap_w * byte_mult;
			}

			skip_size = doffset;

			if ( !reload ) {
				glTexImage2D (GL_TEXTURE_2D, 0, intFormat, mipmap_w, mipmap_h, 0, glFormat, texFormat, (texmem != NULL) ? texmem : bmp_data + doffset);
			} else { // faster anis
				glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, mipmap_w, mipmap_h, glFormat, texFormat, (texmem != NULL) ? texmem : bmp_data + doffset);
			}


			// base image is done so now take care of any mipmap levels
			for (i = 1; i < (mipmap_levels - base_level); i++) {
				doffset += dsize;
				mipmap_w >>= 1;
				mipmap_h >>= 1;

				if (mipmap_w <= 0) {
					mipmap_w = 1;
				}

				if (mipmap_h <= 0) {
					mipmap_h = 1;
				}

				dsize = mipmap_h * mipmap_w * byte_mult;

				if ( !reload ) {
					glTexImage2D (GL_TEXTURE_2D, i, intFormat, mipmap_w, mipmap_h, 0, glFormat, texFormat, bmp_data + doffset);
				} else {
					glTexSubImage2D (GL_TEXTURE_2D, i, 0, 0, mipmap_w, mipmap_h, glFormat, texFormat, bmp_data + doffset);
				}
			}

			if (texmem != NULL) {
				vm_free(texmem);
			}

			break;
		}
	}//end switch


	t->bitmap_handle = bitmap_handle;
	t->size = (dsize) ? ((doffset + dsize) - skip_size) : (tex_w * tex_h * byte_mult);
	t->w = (ushort)tex_w;
	t->h = (ushort)tex_h;

	GL_textures_in_frame += t->size;

	if ( !reload ) {
		GL_textures_in += t->size;
	}

	Tex_used_this_frame[idx] = 0;

	GL_CHECK_FOR_ERRORS("end of create_texture_sub()");

	return ret_val;
}

int opengl_create_texture(int bitmap_handle, int bitmap_type, tcache_slot_opengl *tslot)
{
	ubyte flags;
	bitmap *bmp;
	int final_w, final_h;
	ubyte bpp = 16;
	int reload = 0, resize = 0, base_level = 0;
	int max_levels = 0;

	GL_CHECK_FOR_ERRORS("start of create_texture()");

	if (tslot == NULL) {
		return 0;
	}

	// setup texture/bitmap flags
	flags = 0;
	switch (bitmap_type) {
		case TCACHE_TYPE_AABITMAP:
			flags |= BMP_AABITMAP;
			bpp = 8;
			break;

		case TCACHE_TYPE_CUBEMAP:
		case TCACHE_TYPE_NORMAL:
			flags |= BMP_TEX_OTHER;
			break;

		case TCACHE_TYPE_INTERFACE:
		case TCACHE_TYPE_XPARENT:
			flags |= BMP_TEX_XPARENT;
			break;

		case TCACHE_TYPE_COMPRESSED:
			switch ( bm_is_compressed(bitmap_handle) ) {
				case DDS_DXT1:				//dxt1
					bpp = 24;
					flags |= BMP_TEX_DXT1;
					break;

				case DDS_DXT3:				//dxt3
					bpp = 32;
					flags |= BMP_TEX_DXT3;
					break;

				case DDS_DXT5:				//dxt5
					bpp = 32;
					flags |= BMP_TEX_DXT5;
					break;

				case DDS_CUBEMAP_DXT1:
					bpp = 24;
					flags |= BMP_TEX_CUBEMAP;
					break;

				case DDS_CUBEMAP_DXT3:
				case DDS_CUBEMAP_DXT5:
					bpp = 32;
					flags |= BMP_TEX_CUBEMAP;
					break;

				default:
					Assert( 0 );
					break;
			}

			break;
	}

	// lock the bitmap into the proper format
	bmp = bm_lock(bitmap_handle, bpp, flags);

	if ( bmp == NULL ) {
		mprintf(("Couldn't lock bitmap %d (%s).\n", bitmap_handle, bm_get_filename(bitmap_handle) ));
		return 0;
	}

	int max_w = bmp->w;
	int max_h = bmp->h;

	max_levels = bm_get_num_mipmaps(bitmap_handle);

	// there should only ever be one mipmap level for interface graphics!!!
	if ( (bitmap_type == TCACHE_TYPE_INTERFACE) && (max_levels > 1) ) {
		max_levels = 1;
	}

	// if we ended up locking a texture that wasn't originally compressed then this should catch it
	if ( !(bitmap_type == TCACHE_TYPE_CUBEMAP) && bm_is_compressed(bitmap_handle) ) {
		bitmap_type = TCACHE_TYPE_COMPRESSED;
	}

	if ( (Detail.hardware_textures < 4) && (bitmap_type != TCACHE_TYPE_AABITMAP) && (bitmap_type != TCACHE_TYPE_INTERFACE)
			&& ((bitmap_type != TCACHE_TYPE_COMPRESSED) || ((bitmap_type == TCACHE_TYPE_COMPRESSED) && (max_levels > 1))) )
	{
		if (max_levels == 1) {
			// if we are going to cull the size then we need to force a resize
			// Detail.debris_culling goes from 0 to 4.
			max_w /= (16 >> Detail.hardware_textures);
			max_h /= (16 >> Detail.hardware_textures);

			CLAMP(max_w, GL_min_texture_width, GL_max_texture_width);
			CLAMP(max_h, GL_min_texture_height, GL_max_texture_height);

			resize = 1;
		} else {
			// we have mipmap levels so use those as a resize point (image should already be power-of-2)
			base_level = -(Detail.hardware_textures - 4);
			Assert(base_level >= 0);

			if (base_level >= max_levels) {
				base_level = max_levels - 1;
			}

			Assert( (max_levels - base_level) >= 1 );
		}
	}

	// get final texture size
	opengl_tcache_get_adjusted_texture_size(max_w, max_h, &final_w, &final_h);

	// only resize if we actually need a new size, better data use and speed out of opengl_create_texture_sub()
	if ( (max_w != final_w) || (max_h != final_h) ) {
		resize = 1;
		// little safety check for later, we can't manually resize AND use mipmap resizing
		Assert( !base_level );
	}

	if ( (final_h < 1) || (final_w < 1) )       {
		mprintf(("Bitmap %s is too small at %dx%d.\n", bm_get_filename(bitmap_handle), final_w, final_h ));
		return 0;
	}

	// if this tcache slot has no bitmap
	if (tslot->bitmap_handle < 0) {
		reload = 0;
	}
	// different bitmap altogether - determine if the new one can use the old one's slot
	else if (tslot->bitmap_handle != bitmap_handle) {
		if ( (final_w == tslot->w) && (final_h == tslot->h) ) {
			reload = 1;
		} else {
			reload = 0;
		}
	}

	// set the bits per pixel
	tslot->bpp = bmp->bpp;

	// max number of mipmap levels (NOTE: this is the max number used by the API, not how many true mipmap levels there are in the image!!)
	tslot->mipmap_levels = (ubyte)(max_levels - base_level);

	// call the helper
	int ret_val = opengl_create_texture_sub(bitmap_handle, bitmap_type, bmp->w, bmp->h, final_w, final_h, (ubyte*)bmp->data, tslot, base_level, resize, reload);

	// unlock the bitmap
	bm_unlock(bitmap_handle);

	// if we successfully sent the texture to API memory then go ahead and dump
	// the rest of the bitmap data in system memory.
	// NOTE: this doesn't do anything for user bitmaps (like screen grabs or streamed animations)
	if (ret_val) {
		bm_unload_fast(bitmap_handle);
	}

	GL_CHECK_FOR_ERRORS("end of create_texture()");

	return ret_val;
}

// WARNING:  Needs to match what is in bm_internal.h!!!!!
#define RENDER_TARGET_DYNAMIC	17

int gr_opengl_tcache_set_internal(int bitmap_handle, int bitmap_type, float *u_scale, float *v_scale, int tex_unit = 0)
{
	int ret_val = 1;

	GL_CHECK_FOR_ERRORS("start of tcache_set_internal()");

	if (GL_last_detail != Detail.hardware_textures) {
		GL_last_detail = Detail.hardware_textures;
		opengl_tcache_flush();
	}

	int n = bm_get_cache_slot (bitmap_handle, 1);
	tcache_slot_opengl *t = &Textures[n];

	GL_state.Texture.SetActiveUnit(tex_unit);

	if (!bm_is_render_target(bitmap_handle) &&
		((t->bitmap_handle < 0) || (bitmap_handle != t->bitmap_handle)) )
	{
		ret_val = opengl_create_texture( bitmap_handle, bitmap_type, t );
	}

	// everything went ok
	if (ret_val && t->texture_id) {
		*u_scale = t->u_scale;
		*v_scale = t->v_scale;

		GL_state.Texture.SetTarget(t->texture_target);
		GL_state.Texture.Enable(t->texture_id);

		if ( (t->wrap_mode != GL_texture_addressing) && (bitmap_type != TCACHE_TYPE_AABITMAP)
			&& (bitmap_type != TCACHE_TYPE_INTERFACE) && (bitmap_type != TCACHE_TYPE_CUBEMAP) )
		{
			glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_S, GL_texture_addressing);
			glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_T, GL_texture_addressing);
			glTexParameteri(t->texture_target, GL_TEXTURE_WRAP_R, GL_texture_addressing);

			t->wrap_mode = GL_texture_addressing;
		}

		Tex_used_this_frame[n]++;
	}
	// gah
	else {
		mprintf(("Texturing disabled for bitmap %d (%s) due to internal error.\n", bitmap_handle, bm_get_filename(bitmap_handle)));
		GL_state.Texture.Disable();

		return 0;
	}

	GL_CHECK_FOR_ERRORS("end of tcache_set_internal()");

	return 1;
}

int gr_opengl_tcache_set(int bitmap_handle, int bitmap_type, float *u_scale, float *v_scale, int stage)
{
	int rc = 0;

	if (bitmap_handle <= 0) {
		return 0;
	}

	GL_CHECK_FOR_ERRORS("start of tcache_set()");

	// set special type if it's so, needed to be right later, but cubemaps are special
	if ( !(bitmap_type == TCACHE_TYPE_CUBEMAP) ) {
		int type = bm_get_tcache_type(bitmap_handle);

		if (type != TCACHE_TYPE_NORMAL) {
			bitmap_type = type;
		}
	}

	rc = gr_opengl_tcache_set_internal(bitmap_handle, bitmap_type, u_scale, v_scale, stage);

	GL_CHECK_FOR_ERRORS("end of tcache_set()");

	return rc;
}

void opengl_preload_init()
{
	if (gr_screen.mode != GR_OPENGL)
		return;

//	opengl_tcache_flush ();
}

int gr_opengl_preload(int bitmap_num, int is_aabitmap)
{
	float u_scale, v_scale;
	int retval;

	Assert( gr_screen.mode == GR_OPENGL );

	if ( !GL_should_preload ) {
		return 0;
	}

	retval = gr_opengl_tcache_set(bitmap_num, (is_aabitmap) ? TCACHE_TYPE_AABITMAP : TCACHE_TYPE_NORMAL, &u_scale, &v_scale);

	GL_state.Texture.Disable();

	if ( !retval ) {
		mprintf(("Texture upload failed!\n"));
	}

	return retval;
}

static int GL_texture_panning_enabled = 0;
void gr_opengl_set_texture_panning(float u, float v, bool enable)
{
	if (Cmdline_nohtl)
		return;

	GLint current_matrix;

	if (enable) {
		glGetIntegerv(GL_MATRIX_MODE, &current_matrix);
		glMatrixMode( GL_TEXTURE );
		glPushMatrix();
		glTranslatef( u, v, 0.0f );
		glMatrixMode(current_matrix);

		GL_texture_panning_enabled = 1;
	} else if (GL_texture_panning_enabled) {
		glGetIntegerv(GL_MATRIX_MODE, &current_matrix);
		glMatrixMode( GL_TEXTURE );
		glPopMatrix();
		glMatrixMode(current_matrix);
	
		GL_texture_panning_enabled = 0;
	}
}

void gr_opengl_set_texture_addressing(int mode)
{
	GL_CHECK_FOR_ERRORS("start of set_texture_addressing()");

	switch (mode) {
		case TMAP_ADDRESS_CLAMP:
			GL_texture_addressing = GL_CLAMP_TO_EDGE;
			break;

		case TMAP_ADDRESS_WRAP:
			GL_texture_addressing = GL_REPEAT;
			break;

		case TMAP_ADDRESS_MIRROR: {
			if ( Is_Extension_Enabled(OGL_ARB_TEXTURE_MIRRORED_REPEAT) ) {
				GL_texture_addressing = GL_MIRRORED_REPEAT_ARB;
			} else {
				GL_texture_addressing = GL_REPEAT;
			}

			break;
		}

		default:
			Int3();
			break;
	}

	GL_CHECK_FOR_ERRORS("end of set_texture_addressing()");
}

int opengl_compress_image( ubyte **compressed_data, ubyte *in_data, int width, int height, int alpha, int num_mipmaps )
{
	Assert( in_data != NULL );

	if ( !Texture_compression_available ) {
		return 0;
	}

	GL_CHECK_FOR_ERRORS("start of compress_image()");

	GLuint tex;
	GLint compressed = GL_FALSE;
	GLint compressed_size = 0;
	ubyte *out_data = NULL;
	GLint testing = 0;
	GLint intFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
	GLenum texFormat = GL_UNSIGNED_BYTE;
	GLenum glFormat = GL_BGR;
	int i;

	if (alpha) {
		intFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
		texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
		glFormat = GL_BGRA;
	}

	glGenTextures(1, &tex);

	GL_state.Texture.SetActiveUnit(0);
	GL_state.Texture.SetTarget(GL_TEXTURE_2D);
	GL_state.Texture.Enable(tex);

	// a quick proxy test.  this will tell us if it's possible without wasting a lot of time and resources in the attempt
	glTexImage2D(GL_PROXY_TEXTURE_2D, 0, intFormat, width, height, 0, glFormat, texFormat, in_data);

	glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed);
	glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &testing);

	if (compressed == GL_TRUE) {
		glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &compressed);
	}

	if ( (compressed == GL_FALSE) || (compressed != intFormat) || (testing == 0) ) {
		GL_state.Texture.Disable();
		GL_state.Texture.Delete(tex);
		glDeleteTextures(1, &tex);
		return 0;
	}

	// if mipmaps are requested then make them with the nicest possible settings
	if (num_mipmaps > 1) {
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
		glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);
	}

	// use best compression quality
	glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);

	// alright, it should work if we are still here, now do it for real
	glTexImage2D(GL_TEXTURE_2D, 0, intFormat, width, height, 0, glFormat, texFormat, in_data);

	// if we got this far then it should have worked, but check anyway
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed);
	Assert( compressed != GL_FALSE );

	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &compressed);
	Assert( compressed == intFormat );

	// for each mipmap level we generate go ahead and figure up the total memory required
	for (i = 0; i < num_mipmaps; i++) {
		glGetTexLevelParameteriv(GL_TEXTURE_2D, i, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &testing);
		compressed_size += testing;
	}

	out_data = (ubyte*)vm_malloc(compressed_size * sizeof(ubyte));

	Assert( out_data != NULL );

	memset(out_data, 0, compressed_size * sizeof(ubyte));

	// reset compressed_size and go back through each mipmap level to get both size and the image data itself
	compressed_size = 0;

	for (i = 0; i < num_mipmaps; i++) {
		glGetTexLevelParameteriv(GL_TEXTURE_2D, i, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &testing);
		vglGetCompressedTexImageARB(GL_TEXTURE_2D, i, out_data + compressed_size);
		compressed_size += testing;
	}

	// done with everything so reset hints to default values
	if (num_mipmaps > 1) {
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_FALSE);
		glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
	}

	glHint(GL_TEXTURE_COMPRESSION_HINT, GL_DONT_CARE);


	GL_state.Texture.Disable();
	GL_state.Texture.Delete(tex);
	glDeleteTextures(1, &tex);

	// send the data back out
	*compressed_data = out_data;

	GL_CHECK_FOR_ERRORS("end of compress_image()");

	return compressed_size;
}

// sends a texture object out to "image_data", which should be memory which is already allocated
// this should only be used for uncompressed 24-bit or 32-bit (distiguished by "alpha" var) images
// returns 0 on failure, size of data on success
int opengl_export_image( int slot, int width, int height, int alpha, int num_mipmaps, ubyte *image_data )
{
	tcache_slot_opengl *ts = &Textures[slot];

	GL_CHECK_FOR_ERRORS("start of export_image()");

	if (!image_data) {
		mprintf(("OpenGL ERROR: Tried to export a texture without a valid export location!\n"));
		return 0;
	}

	if (!ts->texture_target) {
		mprintf(("OpenGL ERROR: Tried to export a texture for which I don't know the texture target!\n"));
		return 0;
	}

	if (ts->mipmap_levels != num_mipmaps) {
		mprintf(("OpenGL ERROR: Number of mipmap levels requested is different from number available!\n"));
		return 0;
	}

	if ( (ts->texture_target != GL_TEXTURE_2D) && (ts->texture_target != GL_TEXTURE_CUBE_MAP) ) {
		mprintf(("OpenGL ERROR: Only 2D textures and cube maps can be exported!\n"));
		return 0;
	}

	if ( (ts->w != width) && (ts->h != height) ) {
		mprintf(("OpenGL ERROR: Passed width and height do not match values for texture!\n"));
		return 0;
	}

	int faces = 1;
	int m_width = width;
	int m_height = height;
	int m_bpp = (alpha) ? 4 : 3;
	int m_offset = 0;
	GLenum target = (ts->texture_target == GL_TEXTURE_CUBE_MAP) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : GL_TEXTURE_2D;

	if (ts->texture_target == GL_TEXTURE_CUBE_MAP)
		faces = 6;

	GL_state.Texture.SetActiveUnit(0);
	GL_state.Texture.SetTarget(ts->texture_target);
	GL_state.Texture.Enable(ts->texture_id);

	for (int i = 0; i < faces; i++) {
		for (int j = 0; j < ts->mipmap_levels; j++) {
			glGetTexImage(target + i, j, (alpha) ? GL_BGRA : GL_BGR, (alpha) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE, image_data + m_offset);

			m_offset += (m_width * m_height * m_bpp);

			// reduce by half for next mipmap level
			m_width >>= 1;
			m_height >>= 1;

			if (m_width < 1)
				m_width = 1;

			if (m_height < 1)
				m_height = 1;
		}

		// restore original width and height for next face
		m_width = width;
		m_height = height;
	}

	GL_state.Texture.Disable();

	GL_CHECK_FOR_ERRORS("end of export_image()");

	return m_offset;
}

void gr_opengl_update_texture(int bitmap_handle, int bpp, ubyte* data, int width, int height)
{
	GLenum texFormat, glFormat;
	int n = bm_get_cache_slot (bitmap_handle, 1);
	tcache_slot_opengl *t = &Textures[n];
	if(!t->texture_id)
		return;
	int byte_mult = (bpp >> 3);
	int true_byte_mult = (t->bpp >> 3);
	ubyte* texmem = NULL;
	// GL_BGRA_EXT is *much* faster with some hardware/drivers
	if (true_byte_mult == 4) {
		texFormat = GL_UNSIGNED_INT_8_8_8_8_REV;
		glFormat = GL_BGRA;
	} else if (true_byte_mult == 3) {
		texFormat = GL_UNSIGNED_BYTE;
		glFormat = GL_BGR;
	} else {
		texFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
		glFormat = GL_BGRA;
	}
	if (byte_mult == 1) {
		texFormat = GL_UNSIGNED_BYTE;
		glFormat = GL_ALPHA;
		texmem = (ubyte *) vm_malloc (width*height*byte_mult);
		ubyte* texmemp = texmem;

		Assert( texmem != NULL );

		int luminance = 0;
		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				if ( (i < height) && (j < width) ) {
					if ( true_byte_mult > 1 ) {
						luminance = 0;

						if ( true_byte_mult > 3 ) {
							for (int k = 0; k < 3; k++) {
								luminance += data[(i*width+j)*true_byte_mult+k];
							}

							*texmemp++ = (ubyte)((luminance / 3) * (data[(i*width+j)*true_byte_mult+3]/255.0f));
						} else {
							for (int k = 0; k < true_byte_mult; k++) {
								luminance += data[(i*width+j)*true_byte_mult+k]; 
							}

							*texmemp++ = (ubyte)(luminance / true_byte_mult);
						}
					} else {
						*texmemp++ = GL_xlat[data[i*width+j]];
					}
				} else {
					*texmemp++ = 0;
				}
			}
		}
	}
	glBindTexture(GL_TEXTURE_2D, t->texture_id);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, glFormat, texFormat, (texmem)?texmem:data);
	if (texmem != NULL)
		vm_free(texmem);
}

// -----------------------------------------------------------------------------
// GL_EXT_framebuffer_object stuff (ie, render-to-texture)
//

struct fbo_t {
	// these first vars should only be modified in opengl_make_render_target()
	GLuint renderbuffer_id;
	GLuint framebuffer_id;
	int width;
	int height;
	int ref_count;
	// these next 2 should only be modifed in opengl_set_render_target()
	int working_slot;
	int is_static;

	fbo_t() :
		renderbuffer_id(0), framebuffer_id(0), width(0), height(0),
		ref_count(0), working_slot(-1), is_static(0)
	{
	}
};

static SCP_vector<fbo_t> RenderTarget;
static fbo_t *render_target = NULL;


int opengl_check_framebuffer()
{
	GLenum status;

	status = vglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

	if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
		char err_txt[100] = { 0 };

		switch (status) {
			case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
				strcpy_s(err_txt, "Incomplete attachments!");
				break;

			case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
				strcpy_s(err_txt, "Missing one or more image attachments!");
				break;

			case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
				strcpy_s(err_txt, "Attached images do not have the same width and height!");
				break;

			case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
				strcpy_s(err_txt, "Attached images do not have the same internal format!");
				break;

			case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
				strcpy_s(err_txt, "Draw buffer attachment point is NONE!");
				break;

			case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
				strcpy_s(err_txt, "Read buffer attachment point is NONE!");
				break;

			case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
				strcpy_s(err_txt, "Attached images violate current FBO restrictions!");
				break;

			default:
				strcpy_s(err_txt, "Unknown error!\n");
				break;
		}

		mprintf(("Framebuffer ERROR: %s\n", err_txt));

		return (int)status;
	}

	return 0;
}

static fbo_t *opengl_get_fbo(int width, int height)
{
	size_t rt_size = RenderTarget.size();

	for (size_t i = 0; i < rt_size; i++) {
		if ( (RenderTarget[i].width == width) && (RenderTarget[i].height == height) ) {
			return &RenderTarget[i];
		}
	}

	return NULL;
}

void opengl_kill_render_target(int slot)
{
	if ( (slot < 0) || (slot >= MAX_BITMAPS) ) {
		Int3();
		return;
	}

	// this will happen when opengl_kill_all_render_targets() gets called first on exit
	if ( RenderTarget.empty() ) {
		return;
	}

	tcache_slot_opengl *ts = &Textures[slot];
	size_t idx = 0;

	for (idx = 0; idx < RenderTarget.size(); idx++) {
		if ( (RenderTarget[idx].width == ts->w) && (RenderTarget[idx].height == ts->h) ) {
			break;
		}
	}

	// this may happen when textures are flushed, the w and h will get reset to 0
	if ( idx >= RenderTarget.size() ) {
		return;
	}

	RenderTarget[idx].ref_count--;

	if (RenderTarget[idx].ref_count <= 0) {
		mprintf(("OpenGL: Killing off unused %ix%i render target...\n", ts->w, ts->h));

		if (RenderTarget[idx].framebuffer_id) {
			vglDeleteFramebuffersEXT(1, &RenderTarget[idx].framebuffer_id);
			RenderTarget[idx].framebuffer_id = 0;
		}

		if (RenderTarget[idx].renderbuffer_id) {
			vglDeleteRenderbuffersEXT(1, &RenderTarget[idx].renderbuffer_id);
			RenderTarget[idx].renderbuffer_id = 0;
		}

		RenderTarget.erase( RenderTarget.begin() + idx );
	} else {
		mprintf(("OpenGL: Keeping %ix%i render target with ref_count of %i.\n", ts->w, ts->h, RenderTarget[idx].ref_count));
	}
}

void opengl_kill_all_render_targets()
{
	for (size_t i = 0; i < RenderTarget.size(); i++) {
		fbo_t *fbo = &RenderTarget[i];

		if (fbo->framebuffer_id) {
			vglDeleteFramebuffersEXT(1, &fbo->framebuffer_id);
			fbo->framebuffer_id = 0;
		}

		if (fbo->renderbuffer_id) {
			vglDeleteRenderbuffersEXT(1, &fbo->renderbuffer_id);
			fbo->renderbuffer_id = 0;
		}
	}

	RenderTarget.clear();
}

int opengl_set_render_target( int slot, int face, int is_static )
{
	tcache_slot_opengl *ts = NULL;
	fbo_t *fbo = NULL;

	GL_CHECK_FOR_ERRORS("start of set_render_target()");

	if (slot < 0) {
		if ( (render_target != NULL) && (render_target->working_slot >= 0) ) {
			if (render_target->is_static) {
				extern void gr_opengl_bm_save_render_target(int slot);
				gr_opengl_bm_save_render_target(render_target->working_slot);
			}
		}

		vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);

		vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		vglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

		if (render_target != NULL) {
			render_target->working_slot = -1;
			render_target->is_static = 0;
		}

		// done with this render target so lets move on
		render_target = NULL;

		GL_rendering_to_texture = false;

		GL_CHECK_FOR_ERRORS("end of set_render_target(0)");

		return 1;
	}

	ts = &Textures[slot];
	Assert( ts != NULL );

	if (!ts->texture_id) {
		Int3();
		return 0;
	}

	fbo = opengl_get_fbo(ts->w, ts->h);

	if (fbo == NULL) {
		mprintf(("Tried to get an OpenGL FBO that didn't exist!\n"));
		return 0;
	}

	if ( !vglIsFramebufferEXT(fbo->framebuffer_id) /*|| !vglIsRenderbufferEXT(fbo->renderbuffer_id)*/ ) {
		Int3();
		return 0;
	}

//	vglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->renderbuffer_id);
	vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->framebuffer_id);

	if (ts->texture_target == GL_TEXTURE_CUBE_MAP) {
		Assert( (face >= 0) && (face < 6) );
		vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, ts->texture_id, 0);
	} else {
		Assert( face <= 0 );
		vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, ts->texture_target, ts->texture_id, 0);
	}

//	vglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuffer_id);

	if ( opengl_check_framebuffer() ) {
		Int3();
		return 0;
	}

	fbo->working_slot = slot;
	fbo->is_static = is_static;

	// save current fbo for later use
	render_target = fbo;

	GL_rendering_to_texture = true;

	GL_CHECK_FOR_ERRORS("end of set_render_target()");

	return 1;
}

int opengl_make_render_target( int handle, int slot, int *w, int *h, ubyte *bpp, int *mm_lvl, int flags )
{
	Assert( !GL_rendering_to_texture );

	if (slot < 0) {
		Int3();
		return 0;
	}

	// got to have at least width and height!
	if (!w || !h) {
		Int3();
		return 0;
	}

	// size check
	if (*w > GL_max_renderbuffer_size) {
		*w = GL_max_renderbuffer_size;
	}

	if (*h > GL_max_renderbuffer_size) {
		*h = GL_max_renderbuffer_size;
	}

	tcache_slot_opengl *ts = &Textures[slot];
	fbo_t *fbo = opengl_get_fbo(*w, *h);

	GL_CHECK_FOR_ERRORS("start of make_render_target()");

	// since we only deal with one frame/render buffer, see if we need to modify what we have or use it as is
	if ( (fbo != NULL) && (fbo->width == *w) && (fbo->height == *h) ) {
		// both should be valid, but we check to catch the off-chance that something is fubar
		Assert( vglIsFramebufferEXT(fbo->framebuffer_id) /*&& vglIsRenderbufferEXT(fbo->renderbuffer_id)*/ );

		// we can use the existing FBO without modification so just setup the texture slot and move on
		if (flags & BMP_FLAG_CUBEMAP) {
			opengl_set_texture_target( GL_TEXTURE_CUBE_MAP );
		} else {
			opengl_set_texture_target();
		}

		glGenTextures(1, &ts->texture_id);

		GL_state.Texture.SetActiveUnit(0);
		GL_state.Texture.SetTarget(GL_texture_target);
		GL_state.Texture.Enable(ts->texture_id);

		glTexParameteri(GL_texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		if (flags & BMP_FLAG_CUBEMAP) {
			// if a cubemap then we have to initalize each face
			for (int i = 0; i < 6; i++) {
				glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, *w, *h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
			}

			ts->texture_target = GL_TEXTURE_CUBE_MAP;
		} else {
			// non-cubemap so just do the normal thing
			glTexImage2D(GL_state.Texture.GetTarget(), 0, GL_RGBA, *w, *h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
			ts->texture_target = GL_state.Texture.GetTarget();
		}

	/*	if (Cmdline_mipmap) {
			vglGenerateMipmapEXT(GL_state.Texture.GetTarget());

			extern int get_num_mipmap_levels(int w, int h);
			ts->mipmap_levels = get_num_mipmap_levels(*w, *h);
		} else */
		{
			ts->mipmap_levels = 1;
		}

		GL_state.Texture.Disable();

		ts->w = (ushort)*w;
		ts->h = (ushort)*h;
		ts->bpp = 24;
		ts->bitmap_handle = -1;//handle;
		ts->u_scale = 1.0f;
		ts->v_scale = 1.0f;

		if (bpp) {
			*bpp = ts->bpp;
		}

		if (mm_lvl) {
			*mm_lvl = ts->mipmap_levels;
		}

		opengl_set_texture_target();

		fbo->ref_count++;

		mprintf(("OpenGL: Reusing %ix%i FBO!\n", *w, *h));

		GL_CHECK_FOR_ERRORS("end of make_render_target(0)");

		return 1;
	}

	// now on to the good parts...

	fbo_t new_fbo;

	if (flags & BMP_FLAG_CUBEMAP) {
		opengl_set_texture_target( GL_TEXTURE_CUBE_MAP );
	} else {
		opengl_set_texture_target();
	}

	// initialize color texture
	glGenTextures(1, &ts->texture_id);

	GL_state.Texture.SetActiveUnit(0);
	GL_state.Texture.SetTarget(GL_texture_target);
	GL_state.Texture.Enable(ts->texture_id);

	glTexParameteri(GL_texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	if (flags & BMP_FLAG_CUBEMAP) {
		// if a cubemap then we have to initalize each face
		for (int i = 0; i < 6; i++) {
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, *w, *h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
		}
	} else {
		// non-cubemap so just do the normal thing
		glTexImage2D(GL_state.Texture.GetTarget(), 0, GL_RGBA, *w, *h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
	}

/*	if (Cmdline_mipmap) {
		vglGenerateMipmapEXT(GL_state.Texture.GetTarget());

		extern int get_num_mipmap_levels(int w, int h);
		ts->mipmap_levels = get_num_mipmap_levels(*w, *h);
	} else */
	{
		ts->mipmap_levels = 1;
	}

	GL_state.Texture.Disable();

	// render buffer
//	vglGenRenderbuffersEXT(1, &new_fbo.renderbuffer_id);
//	vglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, new_fbo.renderbuffer_id);
//	vglRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, *w, *h);
	vglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

	// frame buffer
	vglGenFramebuffersEXT(1, &new_fbo.framebuffer_id);
	vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, new_fbo.framebuffer_id);

	if (flags & BMP_FLAG_CUBEMAP) {
		vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, ts->texture_id, 0);
	} else {
		vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_state.Texture.GetTarget(), ts->texture_id, 0);
	}

//	vglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, new_fbo.renderbuffer_id);

	if ( opengl_check_framebuffer() ) {
		// Oops!!  reset everything and then bail
		mprintf(("OpenGL: Unable to create FBO!\n"));

		vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

		GL_state.Texture.Disable();
		GL_state.Texture.Delete(ts->texture_id);
		glDeleteTextures(1, &ts->texture_id);
		ts->texture_id = 0;

		vglDeleteFramebuffersEXT(1, &new_fbo.framebuffer_id);

	//	vglDeleteRenderbuffersEXT(1, &new_fbo.renderbuffer_id);

		opengl_set_texture_target();

		return 0;
	}

	// save anything that needs saving, cleanup, and then exit
	ts->w = (ushort)*w;
	ts->h = (ushort)*h;
	ts->bpp = 24;
	ts->texture_target = GL_state.Texture.GetTarget();
	ts->bitmap_handle = -1;//handle;
	ts->u_scale = 1.0f;
	ts->v_scale = 1.0f;

	new_fbo.width = ts->w;
	new_fbo.height = ts->h;

	if (bpp) {
		*bpp = ts->bpp;
	}

	if (mm_lvl) {
		*mm_lvl = ts->mipmap_levels;
	}

	// Clear the new Texture to black with alpha 1.0
	glClearColor(0.0f,0.0f,0.0f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	new_fbo.ref_count++;

	RenderTarget.push_back(new_fbo);

	opengl_set_texture_target();


	mprintf(("OpenGL: Created %ix%i FBO!\n", ts->w, ts->h));

	GL_CHECK_FOR_ERRORS("end of make_render_target()");

	return 1;
}

/**
 * @fn	GLuint opengl_get_rtt_framebuffer()
 *
 * @brief	Gets the current RTT framebuffer.
 * 
 * Gets the OpenGL framebuffer ID of the currently in use RTT framebuffer.
 * If there is currently none such framebuffer in use then this function returns
 * 0 so it can be used in any place where the framebuffer should be reset to the
 * default drawing surface.
 *
 * @author	m!m
 * @date	14.12.2011
 *
 * @return	The current RTT FBO ID or 0 when not doing RTT.
 */

GLuint opengl_get_rtt_framebuffer()
{
	if (render_target == NULL || render_target->working_slot < 0)
		return 0;
	else
		return render_target->framebuffer_id;
}

//
// End of GL_EXT_framebuffer_object stuff
// -----------------------------------------------------------------------------

