/*
* $Id$
*
* $Date$
* $Revision$
*
* (C) 1999 by Hyperion
* All rights reserved
*
* This file is part of the MiniGL library project
* See the file Licence.txt for more details
*
*/

#include "displaylists.h"
#include "smartlock.h"
#include "sysinc.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "util.h"

static char rcsid[] UNUSED = "$Id$";

#define min(x,y) (x) < (y) ? (x) : (y)
#define max(x,y) (x) > (y) ? (x) : (y)

void tex_FreeTextures(GLcontext context);
void tex_SetEnv(GLcontext context, GLenum env);
static uint32 tex_GLFilter2W3D(GLenum filter, GLboolean useMip, GLboolean useAniso);
void tex_SetFilter(GLcontext context, GLenum min, GLenum mag, GLint maxAniso);
void tex_SetWrap(GLcontext context, GLenum wrap_s, GLenum wrap_t);
void DisableBlendStage(GLcontext context, uint32 stage);
uint32 MGLConvert(GLcontext context, GLenum internalformat, GLuint width,
	GLuint height, GLenum format, GLenum type, GLubyte *source, GLubyte *dest);
BOOL MGLUpdate(GLcontext context, GLenum internalformat, GLint xoffset, GLint yoffset, GLsizei width,
	GLsizei height, const GLubyte *source, GLubyte *dest, GLsizei destWidth,
	 GLenum format, GLenum type);
int32 SelectUnpacker(GLcontext context, GLenum format, GLenum type);
int32 SelectInternalFormat(GLcontext context, GLenum internalformat);
struct W3DtoGL *MGLGetFormatInfo(uint32 w3dFormat);
extern GLsizei next_pwr(GLsizei x);

extern struct InternalToW3D i2w3d[];

extern GLboolean MGLUnpackImage(GLcontext context, GLsizei width, GLsizei height,
		GLimage_info *src, GLimage_info *dst, GLboolean doPixelState);
extern GLboolean MGLPackImage(GLcontext context, GLsizei width, GLsizei height,
		GLimage_info *src, GLimage_info *dst, GLboolean doPixelState);
extern _glPackFn MGLSelectPacker(GLenum format, GLenum type, GLuint *pixelStride);
extern _glUnpackFn MGLSelectUnpacker(GLenum format, GLenum type, GLuint *pixelStride);
extern float CLAMPF(float a);
void m_BuildInvertedFull(GLcontext context);

static uint32 Allocated_Size = 0;
static uint32 Peak_Size	    = 0;


extern int32 mapMGLTexEnvToW3D(int32 mglEnv, int32 defEnv);

void SetBlendStageState(GLcontext context, uint32 stage)
{
	if (context->enable.Texture2D[stage] || context->enable.Texture1D[stage]) {
		int32 w3dMode = mapMGLTexEnvToW3D(context->texture.TextureEnv[stage], W3D_ILLEGALINPUT);
		if (w3dMode != W3D_ILLEGALINPUT) {
			if (context->NumTextureUnits > 1) {
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    w3dMode,
					TAG_DONE
				);
			}
			else {
				// Permedia ?
				W3D_Texture* tex = context->w3dTextures[context->texture.CurrentBinding[0]].texObj;
				if (tex) {
					IWarp3D->W3D_SetTexEnv(context->w3dContext, tex, w3dMode, 0);
				}
			}
		}
/*
		switch (context->texture.TextureEnv[stage]) {
			case GL_REPLACE:
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    W3D_REPLACE,
					TAG_DONE
				);
				break;

			case GL_MODULATE:
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    W3D_MODULATE,
					TAG_DONE
				);
				break;

			case GL_DECAL:
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    W3D_DECAL,
					TAG_DONE
				);
				break;

			case GL_BLEND:
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    W3D_BLEND,
					TAG_DONE
				);
				break;

			case GL_ADD:
				IWarp3D->W3D_SetTextureBlendTags(
					context->w3dContext,
					W3D_BLEND_STAGE, stage,
					W3D_ENV_MODE,    W3D_ADD,
					TAG_DONE
				);
				break;
		}
*/
	}
	else {
		DisableBlendStage(context, stage);
	}
}

void DisableBlendStage(GLcontext context, uint32 stage)
{
	IWarp3D->W3D_SetTextureBlendTags(
		context->w3dContext,
		W3D_BLEND_STAGE, stage,
		W3D_ENV_MODE,    W3D_OFF,
		TAG_DONE
	);
}


void RebindTextures(GLcontext context)
{
	int i;

	for (i = 0; i <= context->texture.MaxTextureUnit; i++) {
		if (context->w3dTextures[context->texture.CurrentBinding[i]].texObj == 0) {
			dprintf("Warning: rebinding texture that does not exist\n");

			IWarp3D->W3D_BindTexture(
				context->w3dContext, i,
				context->w3dTextures[context->texture.CurrentBinding[i]].texObj
			);

			// Incomplete textures should be rendered as if texturing was disabled
			IWarp3D->W3D_SetTextureBlendTags(
				context->w3dContext,
				W3D_BLEND_STAGE,		i,
				W3D_ENV_MODE,			W3D_ADD,
				TAG_DONE
			);
		}
		else {
			IWarp3D->W3D_BindTexture(
				context->w3dContext, i,
				context->w3dTextures[context->texture.CurrentBinding[i]].texObj
			);
			SetBlendStageState(context, i);
		}
	}
}

void *tex_Alloc(uint32 size, BOOL clear)
{
	uint32 *x;
	Allocated_Size += size+4;
	if (clear) {
		x=(uint32 *)AllocVecInternal(size+4, MEMF_ANY | MEMF_CLEAR);
	}
	else {
		x=(uint32 *)AllocVecInternal(size+4, MEMF_ANY);
	}

	if (!x) {
		return 0;
	}

	*x = size;

	if (Allocated_Size > Peak_Size) {
		Peak_Size = Allocated_Size;
	}
	return x+1;
}

void tex_Free(void *chunk)
{
	uint32 *mem = (uint32 *)chunk;
	mem--;
	Allocated_Size -= *mem;
	Allocated_Size -= 4;
	FreeVecInternal(mem);
}

void tex_Statistic(void)
{
	dprintf("Peak Allocation Size: %ld\n", Peak_Size);
}

void MGLTexMemStat(GLcontext context, GLint *Current, GLint *Peak)
{
	if (Current) {
		*Current = (GLint)Allocated_Size;
	}
	if (Peak) {
		*Peak    = (GLint)Peak_Size;
	}
}

void cgl_GLDeleteTextures(struct GLContextIFace *Self, GLsizei n, const GLuint *textures)
{
	GLcontext context = GET_INSTANCE(Self);

	int i;

	if (!context)
		return;

	for (i=0; i<n; i++) {
		int j = *textures++;
		/* Sanity check */
		if (j > 0 && j < context->TexBufferSize) {
			if (context->w3dTextures[j].texObj) {
				// Work around a bug in Warp3D (fixed in the meantime)
				//IWarp3D->W3D_BindTexture(context->w3dContext, j, NULL);

				IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[j].texObj);
				context->w3dTextures[j].texObj = NULL;
			}
			if (context->w3dTextures[j].texData) {
				tex_Free(context->w3dTextures[j].texData);
				context->w3dTextures[j].texData = NULL;
			}
			context->GeneratedTextures[j] = 0;
		}
	}
}

void cgl_GLGenTextures(struct GLContextIFace *Self, GLsizei n, GLuint *textures)
{
	GLcontext context = GET_INSTANCE(Self);

	int i,j;

	j = 1;
	for (i=0; i<n; i++) {
		// Find a free texture
		while ( j < context->TexBufferSize) {
			if (context->GeneratedTextures[j] == 0) {
				break;
			}
			j++;
		}

		// If we get here, we found one, or there's noting available. Flag an error in that case..
		if (j == context->TexBufferSize) {
				GLFlagError(context, j == context->TexBufferSize, GL_INVALID_OPERATION);
				return;
		}

		// Insert the texture in the output array, and flag it as used internally
		*textures = j;
		context->GeneratedTextures[j] = 1;
		textures++;
		j++;
	}
}


void tex_FreeTextures(GLcontext context)
{
	int i;

	for (i=0; i<context->TexBufferSize; i++) {
		if (context->w3dTextures[i].texObj) {
			IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[i].texObj);
			context->w3dTextures[i].texObj = NULL;
		}

		if (context->w3dTextures[i].texData) {
			tex_Free(context->w3dTextures[i].texData);
			context->w3dTextures[i].texData = 0;
		}
	}

	IWarp3D->W3D_FreeAllTexObj(context->w3dContext);
}

void tex_SetEnv(GLcontext context, GLenum env)
{
	SetBlendStageState(context, context->texture.ActiveTexture);
}

static uint32 tex_GLFilter2W3D(GLenum filter, GLboolean useMip, GLboolean useAniso)
{
	switch (filter) {
		case GL_NEAREST:
			return useAniso ? W3D_ANISOTROPIC_NEAREST : W3D_NEAREST;

		case GL_LINEAR:
			return useAniso ? W3D_ANISOTROPIC_LINEAR  : W3D_LINEAR;

		case GL_NEAREST_MIPMAP_NEAREST:
			return useMip ?
				(useAniso ? W3D_ANISOTROPIC_NEAREST_MIP_NEAREST : W3D_NEAREST_MIP_NEAREST) :
				(useAniso ? W3D_ANISOTROPIC_NEAREST : W3D_NEAREST);

		case GL_LINEAR_MIPMAP_NEAREST:
			return useMip ?
				(useAniso ? W3D_ANISOTROPIC_NEAREST_MIP_NEAREST : W3D_LINEAR_MIP_NEAREST) :
				(useAniso ? W3D_ANISOTROPIC_LINEAR : W3D_LINEAR);

		case GL_NEAREST_MIPMAP_LINEAR:
			return useMip ?
				(useAniso ? W3D_ANISOTROPIC_NEAREST_MIP_LINEAR : W3D_NEAREST_MIP_LINEAR) :
				(useAniso ? W3D_ANISOTROPIC_NEAREST : W3D_NEAREST);

		case GL_LINEAR_MIPMAP_LINEAR:
			return useMip ?
				(useAniso ? W3D_ANISOTROPIC_NEAREST_MIP_LINEAR : W3D_LINEAR_MIP_LINEAR) :
				(useAniso ? W3D_ANISOTROPIC_LINEAR : W3D_LINEAR);
	}
	return 0;
}

void tex_SetFilter(GLcontext context, GLenum min, GLenum mag, GLint maxAniso)
{
	uint32 minf, magf;

	W3D_Texture *tex = context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].texObj;

	if (!tex) {
		return;
	}

	if (maxAniso > context->MaxAnisotropy) {
		maxAniso = context->MaxAnisotropy;
	}

	/* Minification can use MIP and/or Anisotropic filtering */
	minf = tex_GLFilter2W3D(min, !context->NoMipMapping, (maxAniso > 1));

	/* Magnification can only use nearest or linear filtering */
	magf = tex_GLFilter2W3D(mag, GL_FALSE, GL_FALSE);

	//kprintf("Setting MinFilter %s, MagFilter %s\n", filters[minf-1], filters[magf-1]);
	IWarp3D->W3D_SetFilter(context->w3dContext, tex, minf, magf);
	if (maxAniso > 1) {
		uint32 w3dAniso = 1 + fast_log2(maxAniso);
		IWarp3D->W3D_SetMaxAnisotropy(context->w3dContext, tex, w3dAniso);
	}
	else if (maxAniso < 1) {
		maxAniso = 1;
	}

	context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].MinFilter     = min;
	context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].MagFilter     = mag;
	context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].MaxAnisotropy = maxAniso;
}

void tex_SetWrap(GLcontext context, GLenum wrap_s, GLenum wrap_t)
{
	uint32 Ws,Wt;
	W3D_Texture *tex = context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].texObj;

	if (!tex) {
		return;
	}
	if (wrap_s == GL_REPEAT) {
		Ws = W3D_REPEAT;
	}
	else {
		Ws = W3D_CLAMP;
	}
	if (wrap_t == GL_REPEAT) {
		Wt = W3D_REPEAT;
	}
	else {
		Wt = W3D_CLAMP;
	}

	IWarp3D->W3D_SetWrapMode(context->w3dContext, tex, Ws, Wt, NULL);
	context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].WrapS = wrap_s;
	context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].WrapT = wrap_t;
}

void cgl_GLTexEnvi(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexEnvi(Self, target, pname, param))

	GLFlagError(context, target != GL_TEXTURE_ENV, GL_INVALID_ENUM);

	switch(pname) {
		case GL_TEXTURE_ENV_MODE:
			GLFlagError(context, param != GL_ADD &&
			param != GL_MODULATE &&
			param != GL_DECAL &&
			param != GL_BLEND &&
			param != GL_REPLACE &&
			param != GL_COMBINE, GL_INVALID_ENUM);
			context->texture.TextureEnv[context->texture.ActiveTexture] = (GLenum)param;
			tex_SetEnv(context, param);
			break;
		case GL_COMBINE_RGB:
			if(param == GL_DOT3_RGB_EXT) { param = GL_DOT3_RGB; }
			else if(param == GL_DOT3_RGBA_EXT) { param = GL_DOT3_RGBA; }
			GLFlagError(context, param != GL_REPLACE &&
			param != GL_MODULATE &&
			param != GL_ADD &&
			param != GL_ADD_SIGNED &&
			param != GL_INTERPOLATE &&
			param != GL_SUBTRACT &&
			param != GL_DOT3_RGB &&
			param != GL_DOT3_RGBA, GL_INVALID_ENUM);
			if (context->texture.colorCombine[context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorCombine[context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_COMBINE_ALPHA:
			GLFlagError(context, param != GL_REPLACE &&
			param != GL_MODULATE &&
			param != GL_ADD &&
			param != GL_ADD_SIGNED &&
			param != GL_INTERPOLATE &&
			param != GL_SUBTRACT, GL_INVALID_ENUM);
			if (context->texture.alphaCombine[context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaCombine[context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE0_RGB:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.colorSource[0][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorSource[0][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE1_RGB:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.colorSource[1][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorSource[1][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE2_RGB:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.colorSource[2][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorSource[2][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE0_ALPHA:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.alphaSource[0][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaSource[0][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE1_ALPHA:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.alphaSource[1][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaSource[1][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_SOURCE2_ALPHA:
			GLFlagError(context,
			param != GL_TEXTURE &&
			param != GL_TEXTURE0 &&
			param != GL_TEXTURE1 &&
			param != GL_TEXTURE2 &&
			param != GL_TEXTURE3 &&
			param != GL_CONSTANT &&
			param != GL_PRIMARY_COLOR &&
			param != GL_PREVIOUS, GL_INVALID_ENUM);
			if (context->texture.alphaSource[2][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaSource[2][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND0_RGB:
			GLFlagError(context,
			param != GL_SRC_COLOR &&
			param != GL_ONE_MINUS_SRC_COLOR &&
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.colorOperand[0][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorOperand[0][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND1_RGB:
			GLFlagError(context,
			param != GL_SRC_COLOR &&
			param != GL_ONE_MINUS_SRC_COLOR &&
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.colorOperand[1][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorOperand[1][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND2_RGB:
			GLFlagError(context,
			param != GL_SRC_COLOR &&
			param != GL_ONE_MINUS_SRC_COLOR &&
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.colorOperand[2][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorOperand[2][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND0_ALPHA:
			GLFlagError(context,
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.alphaOperand[0][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaOperand[0][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND1_ALPHA:
			GLFlagError(context,
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.alphaOperand[1][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaOperand[1][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_OPERAND2_ALPHA:
			GLFlagError(context,
			param != GL_SRC_ALPHA &&
			param != GL_ONE_MINUS_SRC_ALPHA, GL_INVALID_ENUM);
			if (context->texture.alphaOperand[2][context->texture.ActiveTexture] == (GLenum)param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaOperand[2][context->texture.ActiveTexture] = (GLenum)param;
			break;
		case GL_RGB_SCALE:
			GLFlagError(context, param != 1 && param != 2 && param != 4, GL_INVALID_VALUE);
			if (context->texture.colorScale[context->texture.ActiveTexture] == param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.colorScale[context->texture.ActiveTexture] = param;
			break;
		case GL_ALPHA_SCALE:
			GLFlagError(context, param != 1 && param != 2 && param != 4, GL_INVALID_VALUE);
			if (context->texture.alphaScale[context->texture.ActiveTexture] == param) {
				break;
			}
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.alphaScale[context->texture.ActiveTexture] = param;
			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
			break;
	}
}


void cgl_GLTexEnvfv(struct GLContextIFace *Self, GLenum target, GLenum pname, const GLfloat* param)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexEnvfv(Self, target, pname, param))

	GLFlagError(context, target != GL_TEXTURE_ENV, GL_INVALID_ENUM);

	switch (pname) {
		case GL_TEXTURE_ENV_COLOR:
			context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
			context->texture.envColor[context->texture.ActiveTexture].r = CLAMPF(param[0]);
			context->texture.envColor[context->texture.ActiveTexture].g = CLAMPF(param[1]);
			context->texture.envColor[context->texture.ActiveTexture].b = CLAMPF(param[2]);
			context->texture.envColor[context->texture.ActiveTexture].a = CLAMPF(param[3]);
			break;
		default:
			Self->GLTexEnvi(target, pname, (GLint)param[0]);
			break;
	}
}

void cgl_GLGetTexEnviv(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint* params)
{
	GLcontext context = GET_INSTANCE(Self);

	GLFlagError(context, target != GL_TEXTURE_ENV, GL_INVALID_ENUM);

#define FROM_BOOL(x)	(GLint)((x) ? 1 : 0)
#define FROM_COLOR(x)	(GLint)((0xffffffff * (x) - 1)/2)
#define FROM_FLOAT(x)	(GLint)(x)
#define FROM_INT(x)		(x)

	switch (pname) {
		case GL_TEXTURE_ENV_MODE:
			params[0] = FROM_INT(context->texture.TextureEnv[context->texture.ActiveTexture]);
			break;
		case GL_TEXTURE_ENV_COLOR:
			params[0] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].r);
			params[1] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].g);
			params[2] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].b);
			params[3] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].a);
			break;
		case GL_COMBINE_RGB:
			params[0] = FROM_INT(context->texture.colorCombine[context->texture.ActiveTexture]);
			break;
		case GL_COMBINE_ALPHA:
			params[0] = FROM_INT(context->texture.alphaCombine[context->texture.ActiveTexture]);
			break;
		case GL_SOURCE0_RGB:
			params[0] = FROM_INT(context->texture.colorSource[0][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE1_RGB:
			params[0] = FROM_INT(context->texture.colorSource[1][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE2_RGB:
			params[0] = FROM_INT(context->texture.colorSource[2][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE0_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[0][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE1_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[1][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE2_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[2][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND0_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[0][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND1_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[1][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND2_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[2][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND0_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[0][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND1_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[1][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND2_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[2][context->texture.ActiveTexture]);
			break;
		case GL_RGB_SCALE:
			params[0] = FROM_INT(context->texture.colorScale[context->texture.ActiveTexture]);
			break;
		case GL_ALPHA_SCALE:
			params[0] = FROM_INT(context->texture.alphaScale[context->texture.ActiveTexture]);
			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
			break;
	}
#undef FROM_BOOL
#undef FROM_COLOR
#undef FROM_FLOAT
#undef FROM_INT
}

void cgl_GLGetTexEnvfv(struct GLContextIFace* Self, GLenum target, GLenum pname, GLfloat* params)
{
	GLcontext context = GET_INSTANCE(Self);

	GLFlagError(context, target != GL_TEXTURE_ENV, GL_INVALID_ENUM);

#define FROM_BOOL(x)	(GLfloat)((x) ? 1.0f : 0.0f)
#define FROM_COLOR(x)	(GLfloat)(x)
#define FROM_FLOAT(x)	(x)
#define FROM_INT(x)		(GLfloat)(x)

	switch (pname) {
		case GL_TEXTURE_ENV_MODE:
			params[0] = FROM_INT(context->texture.TextureEnv[context->texture.ActiveTexture]);
			break;
		case GL_TEXTURE_ENV_COLOR:
			params[0] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].r);
			params[1] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].g);
			params[2] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].b);
			params[3] = FROM_COLOR(context->texture.envColor[context->texture.ActiveTexture].a);
			break;
		case GL_COMBINE_RGB:
			params[0] = FROM_INT(context->texture.colorCombine[context->texture.ActiveTexture]);
			break;
		case GL_COMBINE_ALPHA:
			params[0] = FROM_INT(context->texture.alphaCombine[context->texture.ActiveTexture]);
			break;
		case GL_SOURCE0_RGB:
			params[0] = FROM_INT(context->texture.colorSource[0][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE1_RGB:
			params[0] = FROM_INT(context->texture.colorSource[1][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE2_RGB:
			params[0] = FROM_INT(context->texture.colorSource[2][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE0_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[0][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE1_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[1][context->texture.ActiveTexture]);
			break;
		case GL_SOURCE2_ALPHA:
			params[0] = FROM_INT(context->texture.alphaSource[2][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND0_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[0][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND1_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[1][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND2_RGB:
			params[0] = FROM_INT(context->texture.colorOperand[2][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND0_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[0][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND1_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[1][context->texture.ActiveTexture]);
			break;
		case GL_OPERAND2_ALPHA:
			params[0] = FROM_INT(context->texture.alphaOperand[2][context->texture.ActiveTexture]);
			break;
		case GL_RGB_SCALE:
			params[0] = FROM_INT(context->texture.colorScale[context->texture.ActiveTexture]);
			break;
		case GL_ALPHA_SCALE:
			params[0] = FROM_INT(context->texture.alphaScale[context->texture.ActiveTexture]);
			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
			break;
	}
#undef FROM_BOOL
#undef FROM_COLOR
#undef FROM_FLOAT
#undef FROM_INT
}


void cgl_GLTexParameteri(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexParameteri(Self, target, pname, param))

	switch (pname) {
		case GL_TEXTURE_MIN_FILTER:
			tex_SetFilter(context, (GLenum)param, context->texture.MagFilter, context->texture.MaxAnisotropy);
			context->texture.MinFilter = (GLenum)param;
			break;

		case GL_TEXTURE_MAG_FILTER:
			tex_SetFilter(context, context->texture.MinFilter, (GLenum)param, context->texture.MaxAnisotropy);
			context->texture.MagFilter = (GLenum)param;
			break;

		case GL_TEXTURE_WRAP_S:
			tex_SetWrap(context, (GLenum)param, context->texture.WrapT);
			context->texture.WrapS = (GLenum)param;
			break;

		case GL_TEXTURE_WRAP_T:
			tex_SetWrap(context, context->texture.WrapS, (GLenum)param);
			context->texture.WrapT = (GLenum)param;
			break;

		case GL_TEXTURE_MAX_ANISOTROPY_EXT:
			context->texture.MaxAnisotropy = param;
			tex_SetFilter(context, context->texture.MinFilter, context->texture.MagFilter, param);
			break;

		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
	}
}


void cgl_GLTexParameterfv(struct GLContextIFace *Self, GLenum target, GLenum pname, GLfloat *param)
{
	Self->GLTexParameteri(target, pname, (GLint)param[0]);
}

void cgl_GLTexParameteriv(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint *param)
{
	Self->GLTexParameteri(target, pname, param[0]);
}

void cgl_GLBindTexture(struct GLContextIFace *Self, GLenum target, GLuint texture)
{
	GLcontext context = GET_INSTANCE(Self);

	if (!context) {
		return;
	}

	DL_CHECK(BindTexture(Self, target, texture))

	GLFlagError(context, target != GL_TEXTURE_2D && target != GL_TEXTURE_1D, GL_INVALID_ENUM);
	GLFlagError(context, texture >= context->TexBufferSize, GL_INVALID_OPERATION);

	context->texture.CurrentBinding[context->texture.ActiveTexture] = texture;

	if (
		context->w3dTextures[
			context->texture.CurrentBinding[context->texture.ActiveTexture]
		].texObj == NULL
	) {
		IWarp3D->W3D_BindTexture(context->w3dContext, context->texture.ActiveTexture,
			context->w3dTextures[context->texture.CurrentBinding[
			context->texture.ActiveTexture]].texObj
		);

	// Incomplete textures should be rendered as if texturing was disabled
		IWarp3D->W3D_SetTextureBlendTags(context->w3dContext,
			W3D_BLEND_STAGE,		context->texture.ActiveTexture,
			W3D_ENV_MODE,			W3D_ADD,
			TAG_DONE
		);
	}
	else {
		IWarp3D->W3D_BindTexture(context->w3dContext, context->texture.ActiveTexture,
			context->w3dTextures[context->texture.CurrentBinding[
			context->texture.ActiveTexture]].texObj
		);
		SetBlendStageState(context, context->texture.ActiveTexture);
	}
}


void GLTexImage2DNoMIP(
	GLcontext context, GLenum gltarget, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
	GLenum format, GLenum type, const GLvoid *pixels
);

static void UpdateTexImage(GLcontext context, int texnum, void *image, int level, uint32 *pal)
{
	IWarp3D->W3D_UpdateTexImage(context->w3dContext,
	context->w3dTextures[texnum].texObj, image, level, pal);

	/* Rebind the currently bound textures, just in case the current one hasn't
	* been bound before
	*/
	RebindTextures(context);
}

static void UpdateTexSubImage(
	GLcontext context, int texnum, void *image, int level, uint32 *pal, int x, int y, int width, int height, int bytesPerRow)
{
	GLboolean relock = GL_FALSE; // for manual locking mode only
	if (context->w3dLocked == GL_TRUE) {
		switch(context->LockMode) {
			case MGL_LOCK_AUTOMATIC:
				break;
			case MGL_LOCK_SMART:
				smartlock_forceUnlock(context->smartLock);
				break;
			case MGL_LOCK_MANUAL:
				IWarp3D->W3D_UnLockHardware(context->w3dContext);
				relock = GL_TRUE;
				context->w3dLocked = GL_FALSE;
				break;
		}
	}

	W3D_Scissor scissor = {x, y, width, height};

	IWarp3D->W3D_UpdateTexSubImage(
		context->w3dContext, context->w3dTextures[texnum].texObj,
		image, level, pal, &scissor, bytesPerRow
	);

	if (relock) {
		IWarp3D->W3D_LockHardware(context->w3dContext);
		context->w3dLocked = GL_TRUE;
	}

	/* Rebind the currently bound textures, just in case the current one hasn't
	* been bound before
	*/
	RebindTextures(context);
}

#ifdef MGL_TEXTURE_COMPRESSION
#define ROUND_UP(num, mult) ((((num) + (mult) - 1) / (mult)) * (mult))
#endif

static uint32 MGLCalculateMipArray(uint32 width, uint32 height, uint32 bpp, GLubyte *mipmaps[])
{
	int level = 0;
#ifdef MGL_TEXTURE_COMPRESSION
	uint32 res = ROUND_UP(width, 16) * ROUND_UP(height, 16) * bpp;
#else
	uint32 res = width * height * bpp;
#endif
	int i;

	for (i = 0; i < 20; i++) {
		mipmaps[i] = (GLubyte *)0xffffffff;
	}
	mipmaps[level++] = (GLubyte *)res;

	do {
		if (width > 1) {
			width /= 2;
		}
		if (height > 1) {
			height /= 2;
		}
#ifdef MGL_TEXTURE_COMPRESSION
		res += ROUND_UP(width, 16) * ROUND_UP(height, 16) * bpp;
#else
		res += width * height * bpp;
#endif
		mipmaps[level++] = (GLubyte *)res;

		if (width == 1 && height == 1) {
			break;
		}
	} while (1);

	return res;
}

void cgl_GLTexImage2D(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLint internalformat, GLsizei width, GLsizei height,
	GLint border, GLenum format, GLenum type, const GLvoid *pixels
)
{
	GLcontext context = GET_INSTANCE(Self);

	// Pretend to support GL_RGBA16
	if (internalformat == GL_RGBA16) {
		internalformat = GL_RGBA8;
	}
/*
	if (gltarget != GL_PROXY_TEXTURE_2D && dl_IsDLActive(Self)){
		dl_save_TexImage2D(Self, gltarget, level, internalformat, width, height, border, format, type, pixels);
		if (!dl_CompileAndExecuteMode(Self)){
			return;
		}
	}
*/
	if (gltarget != GL_PROXY_TEXTURE_2D) {
		DL_CHECK(TexImage2D(Self, gltarget, level, internalformat, width, height, border, format, type, pixels))
	}

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	uint32 w,h;
	uint32 targetsize;
	uint32 error;
	uint8 *target;
	uint32 useFormat;
	int32 internal = SelectInternalFormat(context, internalformat);
	int32 unpacker = SelectUnpacker(context, format, type);

	/* Handle GL_PROXY_TEXTURE_2D */
	if (gltarget == GL_PROXY_TEXTURE_2D) {
		extern struct InternalToW3D i2w3d[];
		context->proxy_width = width << level;
		context->proxy_height = height << level;
		if (internal == -1) {
			context->proxy_internalFormat = -1;
		}
		else {
			context->proxy_internalFormat = i2w3d[internal].internalformat;
		}
		return;
	}

	if (context->NoMipMapping == GL_TRUE) {
		GLTexImage2DNoMIP(context, gltarget, level, internalformat, width,
			height, border, format, type, pixels);
		return;
	}

	GLFlagError(context, internal == -1 || unpacker == -1, GL_INVALID_OPERATION);
	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_PROXY_TEXTURE_2D, GL_INVALID_ENUM);

	/*
	** We will use width and height only when the mipmap level
	** is really 0. Otherwise, we need to upscale the values
	** according to the level.
	**
	** Note this will most likely be difficult with non-square
	** textures, as the first side to reach one will remain
	** there. For example, consider the sequence
	** 8x4, 4x2, 2x1, 1x1
	**  0	  1   2    3
	** If the 1x1 mipmap is given, upscaling will yield 8x8, not 8x4
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		w=(uint32)width<<level;
		h=(uint32)height<<level;
	}
	else {
		w = context->w3dTextures[current].texObj->texwidth;
		h = context->w3dTextures[current].texObj->texheight;

		if (
			(!((width|height)&1) && (w != (width<<level) || h != (height<<level))) ||
			(((width|height)&1) && (w != (width<<level) && h != (height<<level)))
		) {
			IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[current].texObj);
			context->w3dTextures[current].texObj = NULL;

//			dprintf("texture size change detected (%ldx%ld -> %ldx%ld)\n",
//				w, h, width<<level, height<<level);
		}
		w=(uint32)width<<level;
		h=(uint32)height<<level;
	}

	//kprintf("after scale: w=%f,h=%f\n", w, h);

	if (context->w3dTextures[current].texObj == NULL) {
		int i;
		/*
		** Create a new texture object
		** Get the memory
		*/

		targetsize = MGLCalculateMipArray(w, h, i2w3d[internal].w3dBpp,
		context->w3dTextures[current].mipmaps);

		if (context->w3dTextures[current].texData) {
			tex_Free(context->w3dTextures[current].texData);
		}
		context->w3dTextures[current].texData = (GLubyte *)tex_Alloc(targetsize, (pixels ? FALSE : TRUE));
		context->w3dTextures[current].internalFormat = internalformat;

//		dprintf("(re)allocated texData at %p-%p (%ldx%ldx%ld = %ld bytes)\n", context->w3dTextures[current].texData, context->w3dTextures[current].texData + targetsize - 1, w, h, i2w3d[internal].w3dBpp, targetsize);

		/* Fixup mipmaps */
		for (i = 0; i < 20; i++) {
			if (context->w3dTextures[current].mipmaps[i] != (GLubyte *)0xffffffff) {
				context->w3dTextures[current].mipmaps[i] =
					(GLubyte *)((uint32)context->w3dTextures[current].mipmaps[i]
								+ (uint32)context->w3dTextures[current].texData);
			}
			else {
				context->w3dTextures[current].mipmaps[i] = 0;
			}
		}

		if (!context->w3dTextures[current].texData) {
			return;
		}
	}

	/*
	** Find the starting address for the given mipmap level in the
	** texture memory area
	*/

	if (level == 0) {
		target = context->w3dTextures[current].texData;
	}
	else {
		target = context->w3dTextures[current].mipmaps[level-1];
	}
//	dprintf("Mipmap %ld at %p\n", level, target);

	/*
	** Convert the data to the target address
	*/
	if (pixels) {
		useFormat = MGLConvert(context, internalformat, width, height, format, type, (GLubyte *)pixels, (GLubyte *)target);
	}
	else {
		useFormat = i2w3d[internal].w3dFormat;
	}

	/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		context->w3dTextures[current].texObj =
			IWarp3D->W3D_AllocTexObjTags(context->w3dContext, &error,
				W3D_ATO_IMAGE,		context->w3dTextures[current].texData,
				W3D_ATO_FORMAT, 	useFormat,
				W3D_ATO_WIDTH,		w,
				W3D_ATO_HEIGHT, 	h,
				W3D_ATO_MIPMAP, 	0,
				W3D_ATO_MIPMAPPTRS,	context->w3dTextures[current].mipmaps,
				TAG_DONE
			);

		if (context->w3dTextures[current].texObj == NULL || error != W3D_SUCCESS) {
			dprintf("Failed to create texture object, error %d\n", error);
			return;
		}
//		dprintf("Allocated new texture object %p (%ldx%ld)\n", context->w3dTextures[current].texObj, w, h);

		RebindTextures(context);

		/*
		** Set the appropriate wrap modes, texture env, and filters
		*/
		tex_SetWrap(context, context->texture.WrapS, context->texture.WrapT);
		tex_SetFilter(context, context->texture.MinFilter, context->texture.MagFilter, context->texture.MaxAnisotropy);
		tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]);
	}
	else {
		UpdateTexImage(context, current, target, level, NULL);
	}
}

void GLTexImage2DNoMIP(
	GLcontext context, GLenum gltarget, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
	GLenum format, GLenum type, const GLvoid *pixels
)
{
	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	uint32 w,h;
	uint32 targetsize;
	uint32 error;
	uint8 *target;
	uint32 useFormat;
	int32 internal = SelectInternalFormat(context, internalformat);
	int32 unpacker = SelectUnpacker(context, format, type);

	//LOG(3, glTexImage2DNoMIP, "target = %d level=%d, size=%d�%d", gltarget, level, width, height);
	GLFlagError(context, internal == -1 || unpacker == -1, GL_INVALID_OPERATION);
	GLFlagError(context, gltarget != GL_TEXTURE_2D, GL_INVALID_ENUM);

	if (level != 0) {
		return;
	}
	w = (uint32)width;
	h = (uint32)height;

	if (context->w3dTextures[current].texObj && 
		(w != context->w3dTextures[current].texObj->texwidth
		|| h != context->w3dTextures[current].texObj->texheight)
	) {
		dprintf(
			"texture size change detected (%ldx%ld -> %ldx%ld)\n",
			w, h, context->w3dTextures[current].texObj->texwidth,
			context->w3dTextures[current].texObj->texheight
		);

		IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[current].texObj);
		context->w3dTextures[current].texObj = NULL;
	}

	if (context->w3dTextures[current].texObj == NULL) {
		/*
		** Create a new texture object
		** Get the memory
		*/

#ifdef MGL_TEXTURE_COMPRESSION
		targetsize = (ROUND_UP(w, 16) * ROUND_UP(h, 16) * i2w3d[internal].w3dBpp);
#else
		targetsize = (w * h * i2w3d[internal].w3dBpp);
#endif

		if (context->w3dTextures[current].texData) {
			tex_Free(context->w3dTextures[current].texData);
		}
		context->w3dTextures[current].texData = (GLubyte *)tex_Alloc(targetsize, (pixels ? FALSE : TRUE));
		if (!context->w3dTextures[current].texData) {
			return;
		}
		context->w3dTextures[current].internalFormat = internalformat;
	}

	target = context->w3dTextures[current].texData;

	/*
	** Convert the data to the target address
	*/
	if (pixels) {
		useFormat = MGLConvert(context, internalformat, width, height, format, type, (GLubyte *)pixels, (GLubyte *)target);
	}
	else {
		useFormat = i2w3d[internal].w3dFormat;
	}

	/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		int i;
		W3D_Texture *tex;

		tex = IWarp3D->W3D_AllocTexObjTags(context->w3dContext, &error,
			W3D_ATO_IMAGE,  context->w3dTextures[current].texData,
			W3D_ATO_FORMAT, useFormat,
			W3D_ATO_WIDTH,  w,
			W3D_ATO_HEIGHT, h,
			TAG_DONE
		);

		if (tex == NULL || error != W3D_SUCCESS) {
			dprintf("Failed to create texture object, error %d\n", error);
			return;
		}
		context->w3dTextures[current].texObj = tex;
		context->w3dTextures[current].mipmaps[0] = target;
		for (i = 1; i < 20; i++) {
			context->w3dTextures[current].mipmaps[i] = 0;
		}
		/*
		** Set the appropriate wrap modes, texture env, and filters
		*/
		RebindTextures(context);
		tex_SetWrap(context, context->texture.WrapS, context->texture.WrapT);
		tex_SetFilter(context, context->texture.MinFilter, context->texture.MagFilter, context->texture.MaxAnisotropy);
		tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]);
	}
	else {
		UpdateTexImage(context, current, target, level, NULL);
	}
}

void cgl_GLTexImage1D(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLint internalformat, GLsizei width, GLint border,
	GLenum format, GLenum type, const GLvoid *pixels
)
{

	// Faking 1D textures using 2D textures
	// WARNING: This assumes that Warp3D doesn't support texture borders. If this changes, then so must this
	// function

	cgl_GLTexImage2D(Self, GL_TEXTURE_2D, level, internalformat, width, 1, border, format, type, pixels);

	// The vertical direction should always be repeated
	cgl_GLTexParameteri(Self, gltarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
}


void GLTexSubImage2DNoMIP(
	GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
	GLenum format, GLenum type, const GLvoid *pixels
);

#define max(x,y) (x) > (y) ? (x) : (y)

void cgl_GLTexSubImage2D(
	struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
	GLenum format, GLenum type, const GLvoid *pixels
)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexSubImage2D(Self, target, level, xoffset, yoffset, width, height, format, type, pixels))

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;


	if (context->NoMipMapping == GL_TRUE)
	{
		GLTexSubImage2DNoMIP(context, target, level, xoffset, yoffset, width, height, format, type, pixels);
		return;
	}

	GLFlagError(context, target!=GL_TEXTURE_2D, GL_INVALID_ENUM);
	GLFlagError(
		context,
		context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].texObj == NULL,
		GL_INVALID_OPERATION
	);

	if (level == 0) {
		dest = context->w3dTextures[current].texData;
	}
	else {
		dest = context->w3dTextures[current].mipmaps[level-1];
	}
	GLFlagError(context, dest == NULL, GL_INVALID_OPERATION);

	BOOL success = MGLUpdate(
		context, context->w3dTextures[current].internalFormat, xoffset, yoffset, width, height, (GLubyte *)pixels, dest,
		max(1, context->w3dTextures[current].texObj->texwidth >> level), format, type
	);

	GLFlagError(context, !success, GL_INVALID_ENUM);

	UpdateTexImage(context, current, context->w3dTextures[current].texData, 0, NULL);
}

void GLTexSubImage2DNoMIP(
	GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
	GLenum format, GLenum type, const GLvoid *pixels)
{
	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;

	GLFlagError(context, target!=GL_TEXTURE_2D, GL_INVALID_ENUM);
	GLFlagError(
		context,
		context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].texObj == NULL,
		GL_INVALID_OPERATION
	);
	GLFlagError(context, level != 0, GL_INVALID_OPERATION);
	

	dest = context->w3dTextures[current].texData;
	BOOL success = MGLUpdate(
		context, context->w3dTextures[current].internalFormat, xoffset, yoffset, width, height, pixels, dest, context->w3dTextures[current].texObj->texwidth,
		format, type
	);

	GLFlagError(context, !success, GL_INVALID_ENUM);

	UpdateTexImage(context, current, context->w3dTextures[current].texData, 0, NULL);
}

void cgl_GLTexSubImage1D(struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels)
{
	// Faking 1D textures using 2D textures

	cgl_GLTexSubImage2D(Self, GL_TEXTURE_2D, level, xoffset, 0, width,1, format, type, pixels);

	// The vertical direction should always be repeated
	cgl_GLTexParameteri(Self, target, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

static RGBFTYPE getP96Format(GLenum internalFormat);

// returns bytes per row, or 0 if failed
static int p96CopyBMToTex(
	GLcontext context, GLint srcXOffset, GLint srcYOffset, GLint destXOffset, GLint destYOffset, GLsizei width,
	GLsizei height, struct BitMap *source, GLubyte *dest, GLsizei destWidth, RGBFTYPE destFormat
);

void GLCopyTexImage2DNoMIP(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width,
	GLsizei height, GLint border
);

void cgl_GLCopyTexImage2D(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width,
	GLsizei height, GLint border
)
{
	GLcontext context = GET_INSTANCE(Self);

	// Pretend to support GL_RGBA16
	if (internalFormat == GL_RGBA16) {
		internalFormat = GL_RGBA8;
	}

	DL_CHECK(CopyTexImage2D(Self, gltarget, level, internalFormat, x, y, width, height, border))

	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_PROXY_TEXTURE_2D, GL_INVALID_ENUM);

	// Prepare for the copy
	GLFlagError(context, width < 0, GL_INVALID_VALUE);
	GLFlagError(context, height < 0, GL_INVALID_VALUE);

	struct BitMap *inputBitMap = context->readBuffer->bitMap;
	if (!inputBitMap) {
		return;
	}

	if (context->NoMipMapping == GL_TRUE) {
		GLCopyTexImage2DNoMIP(Self, gltarget, level, internalFormat, x, y, width, height, border);
		return;
	}

	GLsizei rw = next_pwr(width);
	GLsizei rh = next_pwr(height);
	uint32 maxTextureSize = IWarp3D->W3D_Query(context->w3dContext, W3D_Q_MAXTEXWIDTH, 0);
	uint32 texWidthReal = min(maxTextureSize, rw);
	uint32 texHeightReal = min(maxTextureSize, rh);

	uint32 ibmWidth = IGraphics->GetBitMapAttr(inputBitMap, BMA_WIDTH);
	uint32 ibmHeight = IGraphics->GetBitMapAttr(inputBitMap, BMA_HEIGHT);
	if (x + width > ibmWidth) {
		width = ibmWidth - x;
	}
	if (y + height > ibmHeight) {
		height = ibmHeight - y;
	}
	if (width > texWidthReal) {
		width = texWidthReal;
	}
	if (height > texHeightReal) {
		height = texHeightReal;
	}

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	uint32 w,h;
	uint32 targetsize;
	uint32 error;
	uint8 *target;
	int32 internal = SelectInternalFormat(context, internalFormat);
	RGBFTYPE p96Format = getP96Format(internalFormat);
	uint32 useFormat = i2w3d[internal].w3dFormat;

	GLFlagError(context, internal == -1, GL_INVALID_OPERATION);

		/*
	** We will use width and height only when the mipmap level
	** is really 0. Otherwise, we need to upscale the values
	** according to the level.
	**
	** Note this will most likely be difficult with non-square
	** textures, as the first side to reach one will remain
	** there. For example, consider the sequence
	** 8x4, 4x2, 2x1, 1x1
	**  0	  1   2    3
	** If the 1x1 mipmap is given, upscaling will yield 8x8, not 8x4
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		w = (uint32)texWidthReal<<level;
		h = (uint32)texHeightReal<<level;
	}
	else {
		w = context->w3dTextures[current].texObj->texwidth;
		h = context->w3dTextures[current].texObj->texheight;

		if (
			(!((texWidthReal|texHeightReal)&1) && (w != (texWidthReal<<level) || h != (texHeightReal<<level))) ||
			(((texWidthReal|texHeightReal)&1) && (w != (texWidthReal<<level) && h != (texHeightReal<<level)))
		) {
			IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[current].texObj);
			context->w3dTextures[current].texObj = NULL;
		}

		w=(uint32)texWidthReal<<level;
		h=(uint32)texHeightReal<<level;
	}

	// Allocate the texture if necessary
	if (context->w3dTextures[current].texObj == NULL) {
		int i;
		/*
		** Create a new texture object
		** Get the memory
		*/

		targetsize = MGLCalculateMipArray(w, h, i2w3d[internal].w3dBpp, context->w3dTextures[current].mipmaps);

		if (context->w3dTextures[current].texData) {
			tex_Free(context->w3dTextures[current].texData);
		}
		context->w3dTextures[current].texData = (GLubyte *)tex_Alloc(targetsize, FALSE);
		context->w3dTextures[current].internalFormat = internalFormat;

		/* Fixup mipmaps */
		for (i = 0; i < 20; i++) {
			if (context->w3dTextures[current].mipmaps[i] != (GLubyte *)0xffffffff) {
				context->w3dTextures[current].mipmaps[i] =
					(GLubyte *)((uint32)context->w3dTextures[current].mipmaps[i]
								+ (uint32)context->w3dTextures[current].texData);
			}
			else {
				context->w3dTextures[current].mipmaps[i] = 0;
			}
		}

		if (!context->w3dTextures[current].texData) {
			return;
		}
	}

		/*
	** Find the starting address for the given mipmap level in the
	** texture memory area
	*/
	if (level == 0) {
		target = context->w3dTextures[current].texData;
	}
	else {
		target = context->w3dTextures[current].mipmaps[level-1];
	}

	// --------------------------------------------------------------------

	// Currently graphics operations and Warp3D cannot be used simultaneously, this speeds it up by
	// forcing an immediate unlock
	smartlock_forceUnlock(context->smartLock);

	if (p96Format != RGBFB_NONE) {
		// The texture data can be copied directly into the texture's buffer using Picasso96
		p96CopyBMToTex(context, x, y, 0, 0, width, height, inputBitMap, target, w, p96Format);
		/* FIXME: Add color scale/bias */ 
	}
	#if 0
	else {
		// Will need to do a pixel format conversion
		// ##### FIXME! #####
	}
	#endif

	// ##### FIXME! ##### what about glPixelZoom?
	// --------------------------------------------------------------------

		/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		context->w3dTextures[current].texObj =
			IWarp3D->W3D_AllocTexObjTags(context->w3dContext, &error,
				W3D_ATO_IMAGE,      context->w3dTextures[current].texData,
				W3D_ATO_FORMAT,     useFormat,
				W3D_ATO_WIDTH,      w,
				W3D_ATO_HEIGHT,     h,
				W3D_ATO_MIPMAP,     0,
				W3D_ATO_MIPMAPPTRS, context->w3dTextures[current].mipmaps,
				TAG_DONE
			);

		if (context->w3dTextures[current].texObj == NULL || error != W3D_SUCCESS) {
			dprintf("Failed to create texture object, error %d\n", error);
			return;
		}
//		dprintf("Allocated new texture object %p (%ldx%ld)\n", context->w3dTextures[current].texObj, w, h);

		RebindTextures(context);

		/*
		** Set the appropriate wrap modes, texture env, and filters
		*/
		tex_SetWrap(context, context->texture.WrapS, context->texture.WrapT);
		tex_SetFilter(context, context->texture.MinFilter, context->texture.MagFilter, context->texture.MaxAnisotropy);
		tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]);
	}
	else {
		UpdateTexImage(context, current, target, level, NULL);
	}
}

void GLCopyTexImage2DNoMIP(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width,
	GLsizei height, GLint border
)
{
	GLcontext context = GET_INSTANCE(Self);

	GLFlagError(context, gltarget != GL_TEXTURE_2D, GL_INVALID_ENUM);

	struct BitMap *inputBitMap = context->readBuffer->bitMap;
	if (!inputBitMap) {
		return;
	}

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	uint32 w,h;
	uint32 targetsize;
	uint32 error;
	uint8 *target;
	int32 internal = SelectInternalFormat(context, internalFormat);
	RGBFTYPE p96Format = getP96Format(internalFormat);
	uint32 useFormat = i2w3d[internal].w3dFormat;

	GLFlagError(context, internal == -1, GL_INVALID_OPERATION);

	GLsizei rw = next_pwr(width);
	GLsizei rh = next_pwr(height);
	uint32 maxTextureSize = IWarp3D->W3D_Query(context->w3dContext, W3D_Q_MAXTEXWIDTH, 0);
	uint32 texWidthReal = min(maxTextureSize, rw);
	uint32 texHeightReal = min(maxTextureSize, rh);

	uint32 ibmWidth = IGraphics->GetBitMapAttr(inputBitMap, BMA_WIDTH);
	uint32 ibmHeight = IGraphics->GetBitMapAttr(inputBitMap, BMA_HEIGHT);
	if (x + width > ibmWidth) {
		width = ibmWidth - x;
	}
	if (y + height > ibmHeight) {
		height = ibmHeight - y;
	}
	if (width > texWidthReal) {
		width = texWidthReal;
	}
	if (height > texHeightReal) {
		height = texHeightReal;
	}

	if (level != 0) {
		return;
	}

	w = (uint32)texWidthReal;
	h = (uint32)texHeightReal;

	if (context->w3dTextures[current].texObj && 
		(w != context->w3dTextures[current].texObj->texwidth
		|| h != context->w3dTextures[current].texObj->texheight)
	) {
		dprintf(
			"texture size change detected (%ldx%ld -> %ldx%ld)\n",
			w, h, context->w3dTextures[current].texObj->texwidth,
			context->w3dTextures[current].texObj->texheight
		);

		IWarp3D->W3D_FreeTexObj(context->w3dContext, context->w3dTextures[current].texObj);
		context->w3dTextures[current].texObj = NULL;
	}

	if (context->w3dTextures[current].texObj == NULL) {
		/*
		** Create a new texture object
		** Get the memory
		*/

#ifdef MGL_TEXTURE_COMPRESSION
		targetsize = (ROUND_UP(w, 16) * ROUND_UP(h, 16) * i2w3d[internal].w3dBpp);
#else
		targetsize = (w * h * i2w3d[internal].w3dBpp);
#endif

		if (context->w3dTextures[current].texData) {
			tex_Free(context->w3dTextures[current].texData);
		}
		context->w3dTextures[current].texData = (GLubyte *)tex_Alloc(targetsize, FALSE);
		if (!context->w3dTextures[current].texData) {
			return;
		}
		context->w3dTextures[current].internalFormat = internalFormat;
	}

	target = context->w3dTextures[current].texData;

	// --------------------------------------------------------------------

	// Currently graphics operations and Warp3D cannot be used simultaneously, this speeds it up by
	// forcing an immediate unlock
	smartlock_forceUnlock(context->smartLock);

	if (p96Format != RGBFB_NONE) {
		// The texture data can be copied directly into the texture's buffer using Picasso96
		p96CopyBMToTex(context, x, y, 0, 0, width, height, inputBitMap, target, w, p96Format);
		/* FIXME: Add color scale/bias */ 
	}
	#if 0
	else {
		// Will need to do a pixel format conversion
		// ##### FIXME! #####
	}
	#endif

	// ##### FIXME! ##### what about glPixelZoom?
	// --------------------------------------------------------------------

	/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTextures[current].texObj == NULL) {
		int i;
		W3D_Texture *tex;

		tex = IWarp3D->W3D_AllocTexObjTags(context->w3dContext, &error,
			W3D_ATO_IMAGE,		context->w3dTextures[current].texData,
			W3D_ATO_FORMAT, 	useFormat,
			W3D_ATO_WIDTH,		w,
			W3D_ATO_HEIGHT, 	h,
			TAG_DONE
		);

		if (tex == NULL || error != W3D_SUCCESS) {
			dprintf("Failed to create texture object, error %d\n", error);
			return;
		}

		context->w3dTextures[current].texObj = tex;
		context->w3dTextures[current].mipmaps[0] = target;
		for (i = 1; i < 20; i++) {
			context->w3dTextures[current].mipmaps[i] = 0;
		}
		/*
		** Set the appropriate wrap modes, texture env, and filters
		*/
		RebindTextures(context);
			tex_SetWrap(context, context->texture.WrapS, context->texture.WrapT);
			tex_SetFilter(context, context->texture.MinFilter, context->texture.MagFilter, context->texture.MaxAnisotropy);
			tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]
		);
	}
	else {
		UpdateTexImage(context, current, target, level, NULL);
	}
}

void cgl_GLCopyTexImage1D(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width,
	GLint border
)
{
	// Faking 1D textures using 2D textures
	// WARNING: This assumes that Warp3D doesn't support texture borders. If this changes, then so must this
	// function

	cgl_GLCopyTexImage2D(Self, GL_TEXTURE_2D, level, internalFormat, x, y, width, 1, border);

	// The vertical direction should always be repeated
	cgl_GLTexParameteri(Self, gltarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

void GLCopyTexSubImage2DNoMIP(
	struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
	GLsizei width, GLsizei height
);

void cgl_GLCopyTexSubImage2D(
	struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
	GLsizei width, GLsizei height
)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(CopyTexSubImage2D(Self, target, level, xoffset, yoffset, x, y, width, height))

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;
	GLenum internalFormat = context->w3dTextures[current].internalFormat;
	RGBFTYPE p96Format = getP96Format(internalFormat);

	if (context->NoMipMapping == GL_TRUE)
	{
		GLCopyTexSubImage2DNoMIP(Self, target, level, xoffset, yoffset, x, y, width, height);
		return;
	}

	struct BitMap *inputBitMap = context->readBuffer->bitMap;
	if (!inputBitMap) {
		return;
	}

	GLFlagError(context, target!=GL_TEXTURE_2D, GL_INVALID_ENUM);
	GLFlagError(
		context,
		context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].texObj == NULL,
		GL_INVALID_OPERATION
	);

	uint32 ibmWidth = IGraphics->GetBitMapAttr(inputBitMap, BMA_WIDTH);
	uint32 ibmHeight = IGraphics->GetBitMapAttr(inputBitMap, BMA_HEIGHT);
	if (x + width > ibmWidth) {
		width = ibmWidth - x;
	}
	if (y + height > ibmHeight) {
		height = ibmHeight - y;
	}
	GLint texWidth = max(1,context->w3dTextures[current].texObj->texwidth >> level);
	GLint texHeight = max(1,context->w3dTextures[current].texObj->texheight >> level);
	if (xoffset + width > texWidth) {
		width = texWidth - xoffset;
	}
	if (yoffset + height > texHeight) {
		height = texHeight - yoffset;
	}

	if (level == 0) {
		dest = context->w3dTextures[current].texData;
	}
	else {
		dest = context->w3dTextures[current].mipmaps[level-1];
	}
	GLFlagError(context, dest == NULL, GL_INVALID_OPERATION);

	// ----------------------------------------------------------------------------------
	int bytesPerRow;
	if (p96Format != RGBFB_NONE) {
		// The texture data can be copied directly into the texture's buffer using Picasso96
		bytesPerRow = p96CopyBMToTex(context, x, y, xoffset, yoffset, width, height, inputBitMap, dest, texWidth, p96Format);
	}
	else {
		bytesPerRow = 0;
		// Will need to do a pixel format conversion
		// ##### FIXME! #####
	}
	//MGLUpdate(context, xoffset, yoffset, width, height, (GLubyte *)pixels, dest,
	//	  max(1,context->w3dTextures[current].texObj->texwidth >> level),
	//	  internal, unpacker);

		// Now perform color scale and bias
	// ##### FIXME! #####

		// ##### FIXME! ##### what about glPixelZoom?
	// ----------------------------------------------------------------------------------

	if (bytesPerRow) {
		//UpdateTexImage(context, current, context->w3dTextures[current].texData, 0, NULL);
		UpdateTexSubImage(
			context, current, context->w3dTextures[current].texData, 0, NULL, xoffset, yoffset, width, height, bytesPerRow
		);
	}
}

void GLCopyTexSubImage2DNoMIP(
	struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
	GLsizei width, GLsizei height
)
{
	GLcontext context = GET_INSTANCE(Self);

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;
	GLenum internalFormat = context->w3dTextures[current].internalFormat;
	RGBFTYPE p96Format = getP96Format(internalFormat);

	struct BitMap *inputBitMap = context->readBuffer->bitMap;
	if (!inputBitMap) {
		return;
	}

	GLFlagError(context, target!=GL_TEXTURE_2D, GL_INVALID_ENUM);
	GLFlagError(context, context->w3dTextures[current].texObj == NULL, GL_INVALID_OPERATION);

	uint32 ibmWidth = IGraphics->GetBitMapAttr(inputBitMap, BMA_WIDTH);
	uint32 ibmHeight = IGraphics->GetBitMapAttr(inputBitMap, BMA_HEIGHT);
	if (x + width > ibmWidth) {
		width = ibmWidth - x;
	}
	if (y + height > ibmHeight) {
		height = ibmHeight - y;
	}

	GLint texWidth = max(1,context->w3dTextures[current].texObj->texwidth >> level);
	GLint texHeight = max(1,context->w3dTextures[current].texObj->texheight >> level);
	if (xoffset + width > texWidth) {
		width = texWidth - xoffset;
	}
	if (yoffset + height > texHeight) {
		height = texHeight - yoffset;
	}

	dest = context->w3dTextures[current].texData;
	GLFlagError(context, level != 0, GL_INVALID_OPERATION);

	// --------------------------------------------------------------------
	int bytesPerRow;
	if (p96Format != RGBFB_NONE) {
		// The texture data can be copied directly into the texture's buffer using Picasso96
		bytesPerRow = p96CopyBMToTex(context, x, y, xoffset, yoffset, width, height, inputBitMap, dest, texWidth, p96Format);
	}
	else {
		bytesPerRow = 0;
		// Will need to do a pixel format conversion
		// ##### FIXME! #####
	}
		// ##### FIXME! #####
	//MGLUpdate(context, xoffset, yoffset, width, height, pixels, dest,
	//	  context->w3dTextures[current].texObj->texwidth,
	//	  internal, unpacker);

		// Now perform color scale and bias
	// ##### FIXME! #####

	// ##### FIXME! ##### what about glPixelZoom?
	// --------------------------------------------------------------------

	if (bytesPerRow) {
		//UpdateTexImage(context, current, context->w3dTextures[current].texData, 0, NULL);
		UpdateTexSubImage(
			context, current, context->w3dTextures[current].texData, 0, NULL, xoffset, yoffset, width, height, bytesPerRow
		);
	}
}

void cgl_GLCopyTexSubImage1D(
	struct GLContextIFace *Self, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width
)
{
	// Faking 1D textures using 2D textures

	cgl_GLCopyTexSubImage2D(Self, GL_TEXTURE_2D, level, xoffset, 0, x, y, width, 1);

	// The vertical direction should always be repeated
	cgl_GLTexParameteri(Self, target, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

static RGBFTYPE getP96Format(GLenum internalFormat)
{
	switch(internalFormat) {
		case GL_RGB:
		case GL_RGB8:
		case 3:
			return RGBFB_R8G8B8;

		case GL_BGR:
	//	  case GL_BGR8:
			return RGBFB_R8G8B8;
		case GL_RGBA:
		case 4:
		case GL_RGBA8:
			return RGBFB_A8R8G8B8;

		case GL_RGB5:
			return RGBFB_R5G6B5PC;

		case GL_RGB5_A1:
			return RGBFB_R5G5B5PC;

		// None of the following have Picasso96 equivalents
		case GL_RGB4:
		case GL_RGBA4:
		case GL_ALPHA:
		case GL_LUMINANCE:
		case 1:
		case GL_LUMINANCE_ALPHA:
		case 2:
		case GL_INTENSITY:
		default:
			return RGBFB_NONE;
	}
}

static int p96CopyBMToTex(
	GLcontext context, GLint srcXOffset, GLint srcYOffset, GLint destXOffset, GLint destYOffset, GLsizei width,
	GLsizei height, struct BitMap *source, GLubyte *dest, GLsizei destWidth, RGBFTYPE destFormat
)
{
	struct RenderInfo rInfo;

	int bytesPerPixel;

	switch (destFormat) {
		case RGBFB_R8G8B8:
		case RGBFB_B8G8R8:
			bytesPerPixel = 3;
			break;

		case RGBFB_A8R8G8B8:
		case RGBFB_A8B8G8R8:
		case RGBFB_R8G8B8A8:
		case RGBFB_B8G8R8A8:
			bytesPerPixel = 4;
			break;

		case RGBFB_R5G6B5:
		case RGBFB_R5G5B5:
		case RGBFB_B5G6R5PC:
		case RGBFB_B5G5R5PC:
		case RGBFB_R5G6B5PC:
		case RGBFB_R5G5B5PC:
			bytesPerPixel = 2;
			break;

			case RGBFB_CLUT:
			bytesPerPixel = 1;
			break;

		default:
			return 0;
	}

	// Need to invert the y-axis coordinates
	uint32 ibmHeight = IGraphics->GetBitMapAttr(source, BMA_HEIGHT);
	srcYOffset = ibmHeight - srcYOffset - height - 1;

	rInfo.Memory = dest + (height + destYOffset - 1) * destWidth * bytesPerPixel;
	rInfo.BytesPerRow = -destWidth * bytesPerPixel; // OpenGL starts at the bottom left, not the top left
	rInfo.RGBFormat = destFormat;

	struct RastPort rPort;
	IGraphics->InitRastPort(&rPort);
	rPort.BitMap = source;

	#if defined (__amigaos4__)
		PIX_FMT pix_fmt=(PIX_FMT)((destFormat-RGBFB_CLUT)+PIXF_CLUT);
		IGraphics->ReadPixelArray(&rPort,srcXOffset,srcYOffset,rInfo.Memory,destXOffset,0,rInfo.BytesPerRow,pix_fmt,width,height);
	#else
		IP96->p96ReadPixelArray(&rInfo, destXOffset, 0, &rPort, srcXOffset, srcYOffset, width, height);
	#endif

	return -rInfo.BytesPerRow;
}

void cgl_GLColorTable(
	struct GLContextIFace *Self, GLenum target, GLenum internalformat, GLint width, GLenum format, GLenum type,
	const GLvoid* data
)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(ColorTable(Self, target, internalformat, width, format, type, data))

	int i;
	GLubyte *palette;
	GLubyte *where;
	GLubyte a,r,g,b;

	GLFlagError(context, width>256, GL_INVALID_VALUE);
	GLFlagError(context, target!=GL_COLOR_TABLE, GL_INVALID_OPERATION);

	palette = (GLubyte *)data;
	where	= (GLubyte *)context->PaletteData;

	GLFlagError(context, where == NULL, GL_INVALID_OPERATION);

	switch (internalformat) {
		case 4:
		case GL_RGBA: // convert to argb from...
			switch(format) {
				case GL_RGB: // ...RGB, ignoring alpha
					for (i=0; i<width; i++) {
						r=*palette++;
						g=*palette++;
						b=*palette++;
						palette++;
						*where++ = r;
						*where++ = g;
						*where++ = b;
					}
					break;
				case GL_RGBA: // ...ARGB with alpha
					for (i=0; i<width; i++) {
						a=*palette++;
						r=*palette++;
						g=*palette++;
						b=*palette++;
						*where++ = a;
						*where++ = r;
						*where++ = g;
						*where++ = b;
					}
					break;
			}
			break;
		case 3:
		case GL_RGB: // convert to RGB from...
			switch(format) {
				case GL_RGB: // ...RGB without alpha
					for (i=0; i<width; i++) {
						r=*palette++;
						g=*palette++;
						b=*palette++;
						*where++ = r;
						*where++ = g;
						*where++ = b;
					}
					break;
				case GL_RGBA: // ...ARGB, assuming alpha == 1
					for (i=0; i<width; i++) {
						r=*palette++;
						g=*palette++;
						b=*palette++;
						*where++ = 255;
						*where++ = r;
						*where++ = g;
						*where++ = b;
					}
					break;
			}
			break;
	}
	context->PaletteFormat = format;
	context->PaletteSize   = width;
}


void cgl_GLActiveTexture(struct GLContextIFace *Self, GLenum unit)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(ActiveTexture(Self, unit))

	unit -= GL_TEXTURE0;

	GLFlagError(context, unit >= context->NumTextureUnits, GL_INVALID_VALUE);

	context->texture.ActiveTexture = unit;

}

GLboolean cgl_GLAreTexturesResident(struct GLContextIFace* Self, GLsizei n, GLuint* textures, GLboolean* residences)
{
	int t = 0;
	GLboolean all_resident = GL_TRUE;
	GLcontext context = GET_INSTANCE(Self);

	// Cannot be called between glBegin and glEnd
	GLFlagError2(context, context->CurrentPrimitive != GL_BASE, GL_INVALID_OPERATION, GL_FALSE);

	for (t = 0; t < n; ++t) {
		GLuint texture = textures[t];
		// Texture object 0 cannot be used
		GLFlagError2(context, texture == 0, GL_INVALID_VALUE, GL_FALSE);

		// Cant have more textures than TexBuffserSize
		GLFlagError2(context, texture >= context->TexBufferSize, GL_INVALID_VALUE, GL_FALSE);

		// Has the texture been generated?
		GLFlagError2(context, context->GeneratedTextures[texture] == 0, GL_INVALID_VALUE, GL_FALSE);

		// Is the texture used?
		GLFlagError2(context, context->w3dTextures[texture].texObj == NULL, GL_INVALID_VALUE, GL_FALSE);
	}

	for (t = 0; t < n; ++t) {
		all_resident &= (GLboolean)context->w3dTextures[textures[t]].texObj->resident;
	}

	if (all_resident == GL_FALSE) {
		for (t = 0; t < n; ++t) {
			residences[t] = (GLboolean)context->w3dTextures[textures[t]].texObj->resident;
		}
		return GL_FALSE;
	}
	return GL_TRUE;
}

void tex_EstablishEnvCombine(GLcontext context)
{
	int i,j;

	uint32 colorarg[3];
	uint32 alphaarg[3];
	uint32
	colorcombine = W3D_COMBINE_DISABLED,
	alphacombine = W3D_COMBINE_DISABLED;

	for (i=0; i<context->NumTextureUnits; i++) {
		if (context->enable.Texture2D[i] == GL_FALSE && context->enable.Texture1D[i] == GL_FALSE) {
			IWarp3D->W3D_SetTextureBlendTags(context->w3dContext,
				W3D_BLEND_STAGE,			i,
				W3D_COLOR_COMBINE,		W3D_COMBINE_DISABLED,
				W3D_ALPHA_COMBINE,		W3D_COMBINE_DISABLED,
			TAG_DONE);
			IWarp3D->W3D_SetTextureBlend(context->w3dContext, NULL);

			continue;
		}
		if (context->texture.TextureEnv[i] != GL_COMBINE) {
			continue;
		}
		if (context->combineDirty[i] == GL_FALSE) {
			continue;
		}
		switch(context->texture.colorCombine[i]) {
			case GL_REPLACE:
				colorcombine = W3D_COMBINE_SELECT_A; break;
			case GL_MODULATE:
				colorcombine = W3D_COMBINE_MODULATE; break;
			case GL_ADD:
				colorcombine = W3D_COMBINE_ADD; break;
			case GL_ADD_SIGNED:
				colorcombine = W3D_COMBINE_ADDSIGNED; break;
			case GL_SUBTRACT:
				colorcombine = W3D_COMBINE_SUBTRACT; break;
			case GL_INTERPOLATE:
				colorcombine = W3D_COMBINE_INTERPOLATE; break;
			case GL_DOT3_RGB:
				colorcombine = W3D_COMBINE_DOT3RGB; break;
			case GL_DOT3_RGBA:
				colorcombine = W3D_COMBINE_DOT3RGB; break;
		}

		switch(context->texture.alphaCombine[i]) {
			case GL_REPLACE:
				alphacombine = W3D_COMBINE_SELECT_A; break;
			case GL_MODULATE:
				alphacombine = W3D_COMBINE_MODULATE; break;
			case GL_ADD:
				alphacombine = W3D_COMBINE_ADD; break;
			case GL_ADD_SIGNED:
				alphacombine = W3D_COMBINE_ADDSIGNED; break;
			case GL_INTERPOLATE:
				alphacombine = W3D_COMBINE_INTERPOLATE; break;
			case GL_SUBTRACT:
				alphacombine = W3D_COMBINE_SUBTRACT; break;
		}

		for (j=0; j<3; j++) {
			switch(context->texture.colorSource[j][i]) {
				case GL_TEXTURE:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE0:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE0_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE0_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE0_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE0_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE1:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE1_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE1_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE1_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE1_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE2:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE2_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE2_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE2_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE2_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE3:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE3_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_TEXTURE3_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE3_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_TEXTURE3_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_CONSTANT:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_FACTOR_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_FACTOR_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_FACTOR_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_FACTOR_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_PRIMARY_COLOR:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_DIFFUSE_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_DIFFUSE_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_DIFFUSE_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_DIFFUSE_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_PREVIOUS:
					switch(context->texture.colorOperand[j][i]) {
						case GL_SRC_COLOR:
							colorarg[j] = W3D_ARG_PREVIOUS_COLOR;
							break;
						case GL_ONE_MINUS_SRC_COLOR:
							colorarg[j] = W3D_ARG_PREVIOUS_COLOR | W3D_ARG_COMPLEMENT;
							break;
						case GL_SRC_ALPHA:
							colorarg[j] = W3D_ARG_PREVIOUS_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							colorarg[j] = W3D_ARG_PREVIOUS_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
			} // switch color arg

			switch(context->texture.alphaSource[j][i]) {
				case GL_TEXTURE:
					switch(context->texture.alphaOperand[j][i]) {
					case GL_SRC_ALPHA:
						alphaarg[j] = W3D_ARG_TEXTURE_ALPHA;
						break;
					case GL_ONE_MINUS_SRC_ALPHA:
						alphaarg[j] = W3D_ARG_TEXTURE_ALPHA | W3D_ARG_COMPLEMENT;
						break;
					}
					break;
				case GL_TEXTURE0:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE0_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE0_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE1:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE1_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE1_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE2:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE2_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE2_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_TEXTURE3:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE3_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_TEXTURE3_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_CONSTANT:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_FACTOR_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_FACTOR_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_PRIMARY_COLOR:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_DIFFUSE_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_DIFFUSE_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
				case GL_PREVIOUS:
					switch(context->texture.alphaOperand[j][i]) {
						case GL_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_PREVIOUS_ALPHA;
							break;
						case GL_ONE_MINUS_SRC_ALPHA:
							alphaarg[j] = W3D_ARG_PREVIOUS_ALPHA | W3D_ARG_COMPLEMENT;
							break;
					}
					break;
			} // Switch alpha arg
		} // for j
		IWarp3D->W3D_SetTextureBlendTags(context->w3dContext,
			W3D_BLEND_STAGE,			i,
				W3D_COLOR_COMBINE,		colorcombine,
					W3D_COLOR_ARG_A,	colorarg[0],
					W3D_COLOR_ARG_B,	colorarg[1],
					W3D_COLOR_ARG_C,	colorarg[2],

				W3D_ALPHA_COMBINE,		alphacombine,
					W3D_ALPHA_ARG_A,	alphaarg[0],
					W3D_ALPHA_ARG_B,	alphaarg[1],
					W3D_ALPHA_ARG_C,	alphaarg[2],

				W3D_COLOR_SCALE,		context->texture.colorScale[i],
				W3D_ALPHA_SCALE,		context->texture.alphaScale[i],

				W3D_BLEND_FACTOR,		context->texture.envColor[i],
		TAG_DONE);


	} // for texture unit

	IWarp3D->W3D_SetTextureBlend(context->w3dContext, NULL);

}



void cgl_GLTexGenfv(struct GLContextIFace *Self, GLenum coord, GLenum pname, GLfloat *params)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexGenfv(Self, coord, pname, params))

	uint32 current = context->texture.ActiveTexture;
	GLtexgen *texgen = 0;

	if (coord == GL_S) {
		texgen = & context->texture.TexGenS[current];
	}
	else if (coord == GL_T) {
		texgen = & context->texture.TexGenT[current];
	}
	else if (coord == GL_Q) {
		texgen = & context->texture.TexGenQ[current];
	}
	else if (coord == GL_R) {
		return;
	}
	GLFlagError(context, texgen == 0, GL_INVALID_ENUM);

	switch (pname) {
		case GL_EYE_PLANE:
			if (context->InverseModelViewValid != GL_TRUE) {
				m_BuildInvertedFull(context);
			}
			#define a(x) (context->InverseModelView.v[OF_##x])
			texgen->eyePlane[0] = a(11)*params[0] + a(21)*params[1] + a(31)*params[2] + a(41)*params[3];
			texgen->eyePlane[1] = a(12)*params[0] + a(22)*params[1] + a(32)*params[2] + a(42)*params[3];
			texgen->eyePlane[2] = a(13)*params[0] + a(23)*params[1] + a(33)*params[2] + a(43)*params[3];
			texgen->eyePlane[3] = a(14)*params[0] + a(24)*params[1] + a(34)*params[2] + a(44)*params[3];
			#undef a
			break;
		case GL_OBJECT_PLANE:
			texgen->objectPlane[0] = params[0];
			texgen->objectPlane[1] = params[1];
			texgen->objectPlane[2] = params[2];
			texgen->objectPlane[3] = params[3];
			break;
		default:
			Self->GLTexGeni(coord, pname, (GLint)params[0]);
			break;
	}
}

void cgl_GLTexGeni(struct GLContextIFace *Self, GLenum coord, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);

	DL_CHECK(TexGeni(Self, coord, pname, param))

	uint32 current = context->texture.ActiveTexture;
	GLtexgen *texgen = 0;

	if (coord == GL_S) {
		texgen = & context->texture.TexGenS[current];
	}
	else if (coord == GL_T) {
		texgen = & context->texture.TexGenT[current];
	}
	else if (coord == GL_Q) {
		texgen = & context->texture.TexGenQ[current];
	}
	else if (coord == GL_R) {
		return;
	}

	GLFlagError(context, texgen == 0, GL_INVALID_ENUM);

	switch (pname) {
		case GL_TEXTURE_GEN_MODE:
			GLFlagError(context,
				param != GL_OBJECT_LINEAR
			&& param != GL_EYE_LINEAR
			&& param != GL_SPHERE_MAP
			&& param != GL_REFLECTION_MAP
			&& param != GL_NORMAL_MAP,
				GL_INVALID_ENUM);

			GLFlagError(context, (param == GL_SPHERE_MAP || param == GL_REFLECTION_MAP || param == GL_NORMAL_MAP) && coord == GL_Q, GL_INVALID_ENUM);

			texgen->mode = param;

			if (param == GL_SPHERE_MAP) {
				texgen->needR = GL_TRUE;
				texgen->needM = GL_TRUE;
			}
			else if (param == GL_REFLECTION_MAP) {
				texgen->needR = GL_TRUE;
				texgen->needM = GL_FALSE;
			}
			else {
				texgen->needR = GL_FALSE;
				texgen->needM = GL_FALSE;
			}

			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
			break;
	}
}

GLboolean cgl_GLIsTexture(struct GLContextIFace *Self, GLuint texture){
	GLcontext context = GET_INSTANCE(Self);

	if (texture > context->TexBufferSize) {
		return GL_FALSE;
	}
	if (context->GeneratedTextures[texture] != 0) {
		return GL_TRUE;
	}
	else {
		return GL_FALSE;
	}
}

void cgl_GLGetTexLevelParameterfv(struct GLContextIFace *Self, GLenum gltarget, GLint level,
		GLenum pname, GLfloat *param)
{
	GLcontext context = GET_INSTANCE(Self);
	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_PROXY_TEXTURE_2D &&
						gltarget != GL_TEXTURE_1D && gltarget != GL_PROXY_TEXTURE_1D, GL_INVALID_ENUM);
	int current;
	MGLTexture *curTex;

	if (gltarget == GL_TEXTURE_2D || gltarget == GL_TEXTURE_1D) {
		current = context->texture.CurrentBinding[context->texture.ActiveTexture];
		curTex  = &context->w3dTextures[current];

		switch (pname) {
			case GL_TEXTURE_WIDTH:
				if (!curTex->texObj) {
					param[0] = 0;
				}
				else {
					uint32 width = curTex->texObj->texwidth >> level;
					param[0] = width > 0 ? width : 1;
				}
				break;
			case GL_TEXTURE_HEIGHT:
				if (!curTex->texObj) {
					param[0] = 0;
				}
				else {
					uint32 height = curTex->texObj->texheight >> level;
					param[0] = height > 0 ? height : 1;
				}
				break;
			case GL_TEXTURE_DEPTH:
				param[0] = 0;
				break;
			case GL_TEXTURE_INTERNAL_FORMAT:
				param[0] = curTex->internalFormat;
				break;
			case GL_TEXTURE_BORDER:
				param[0] = 0;
				break;
			case GL_TEXTURE_RED_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].redsize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_GREEN_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].greensize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_BLUE_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].bluesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_ALPHA_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].alphasize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_LUMINANCE_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].luminancesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_INTENSITY_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].intensitysize;
				}
				else {
					param[0] = 0;
				}
				break;

#ifdef MGL_TEXTURE_COMPRESSION
			case GL_TEXTURE_COMPRESSED_ARB:
				if (i2w3d[curTex->internalFormat].w3dFormat >= W3D_COMPRESSED_R5G6B5 &&
					i2w3d[curTex->internalFormat].w3dFormat <= W3D_COMPRESSED_A8R5G6B5) {
					param[0] = GL_TRUE;
				}
				else {
					param[0] = GL_FALSE;
				}
				break;

/*
			case GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB:
				if (i2w3d[curTex->internalFormat].w3dFormat >= W3D_COMPRESSED_R5G6B5 &&
					i2w3d[curTex->internalFormat].w3dFormat <= W3D_COMPRESSED_A8R5G6B5) {
					param[0] = CompressedTextureSize(i2w3d[curTex->internalFormat].internalformat,
						curTex->texObj->texwidth >> level, 
						curTex->texObj->texwidth >> level);
				}
				else {
					param[0] = 0;
				}
				break;
*/
#endif

			default:
				context->CurrentError = GL_INVALID_ENUM;
				break;
		}
	}

	if (gltarget == GL_PROXY_TEXTURE_2D || gltarget == GL_PROXY_TEXTURE_1D) {
		switch (pname) {
			case GL_TEXTURE_WIDTH:
				param[0] = (GLfloat)context->proxy_width;
				break;
			case GL_TEXTURE_HEIGHT:
				param[0] = (GLfloat)context->proxy_height;
				break;
			case GL_TEXTURE_DEPTH:
				param[0] = 0.0;
				break;
			case GL_TEXTURE_INTERNAL_FORMAT:
				param[0] = (GLfloat)context->proxy_internalFormat;
				break;
			case GL_TEXTURE_BORDER:
				param[0] = 0.0;
				break;
			case GL_TEXTURE_RED_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].redsize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			case GL_TEXTURE_GREEN_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].greensize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			case GL_TEXTURE_BLUE_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].bluesize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			case GL_TEXTURE_ALPHA_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].alphasize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			case GL_TEXTURE_LUMINANCE_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].luminancesize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			case GL_TEXTURE_INTENSITY_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = (GLfloat)i2w3d[context->proxy_internalFormat].intensitysize;
				}
				else {
					param[0] = 0.0;
				}
				break;
			default:
				context->CurrentError = GL_INVALID_ENUM;
				break;
		}
	}
}

void cgl_GLGetTexLevelParameteriv(
	struct GLContextIFace *Self, GLenum gltarget, GLint level, GLenum pname, GLint *param
)
{
	GLcontext context = GET_INSTANCE(Self);
	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_PROXY_TEXTURE_2D &&
						gltarget != GL_TEXTURE_2D && gltarget != GL_PROXY_TEXTURE_2D, GL_INVALID_ENUM);
	int current;
	MGLTexture *curTex;

	if (gltarget == GL_TEXTURE_2D || gltarget == GL_TEXTURE_1D) {
		current = context->texture.CurrentBinding[context->texture.ActiveTexture];
		curTex = &context->w3dTextures[current];

		switch (pname) {
			case GL_TEXTURE_WIDTH:
				if (!curTex->texObj) {
					param[0] = 0;
				}
				else {
					uint32 width = curTex->texObj->texwidth >> level;
					param[0] = width > 0 ? width : 1;
				}
				break;
			case GL_TEXTURE_HEIGHT:
				if (!curTex->texObj) {
					param[0] = 0;
				}
				else {
					uint32 height = curTex->texObj->texheight >> level;
					param[0] = height > 0 ? height : 1;
				}
				break;
			case GL_TEXTURE_DEPTH:
				param[0] = 0;
				break;
			case GL_TEXTURE_INTERNAL_FORMAT:
				param[0] = curTex->internalFormat;
				break;
			case GL_TEXTURE_BORDER:
				param[0] = 0;
				break;
			case GL_TEXTURE_RED_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].redsize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_GREEN_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].greensize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_BLUE_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].bluesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_ALPHA_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].alphasize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_LUMINANCE_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].luminancesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_INTENSITY_SIZE:
				if (curTex->internalFormat >= 0) {
					param[0] = i2w3d[curTex->internalFormat].intensitysize;
				}
				else {
					param[0] = 0;
				}
				break;
			default:
				context->CurrentError = GL_INVALID_ENUM;
				break;
		}
	}

	if (gltarget == GL_PROXY_TEXTURE_2D || gltarget == GL_PROXY_TEXTURE_1D) {
		switch (pname) {
			case GL_TEXTURE_WIDTH:
				param[0] = context->proxy_width;
				break;
			case GL_TEXTURE_HEIGHT:
				param[0] = context->proxy_height;
				break;
			case GL_TEXTURE_DEPTH:
				param[0] = 0;
				break;
			case GL_TEXTURE_INTERNAL_FORMAT:
				param[0] = context->proxy_internalFormat;
				break;
			case GL_TEXTURE_BORDER:
				param[0] = 0;
				break;
			case GL_TEXTURE_RED_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].redsize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_GREEN_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].greensize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_BLUE_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].bluesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_ALPHA_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].alphasize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_LUMINANCE_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].luminancesize;
				}
				else {
					param[0] = 0;
				}
				break;
			case GL_TEXTURE_INTENSITY_SIZE:
				if (context->proxy_internalFormat >= 0) {
					param[0] = i2w3d[context->proxy_internalFormat].intensitysize;
				}
				else {
					param[0] = 0;
				}
				break;
			default:
				context->CurrentError = GL_INVALID_ENUM;
				break;
		}
	}
}

void cgl_GLGetTexParameterfv(struct GLContextIFace *Self, GLenum gltarget, GLenum pname, GLfloat *param)
{
	GLcontext context = GET_INSTANCE(Self);
	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_TEXTURE_1D, GL_INVALID_ENUM);
	int current;
	MGLTexture *curTex;

#define FROM_BOOL(x)	(GLfloat)((x) ? 1.0f : 0.0f)
#define FROM_COLOR(x)	(GLfloat)(x)
#define FROM_FLOAT(x)	(x)
#define FROM_INT(x)		(GLfloat)(x)

	current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	curTex = &context->w3dTextures[current];

	switch (pname) {
		case GL_TEXTURE_RESIDENT:
			if (!curTex->texObj) {
				param[0] = 0.0;
			}
			else {
				param[0] = FROM_BOOL(curTex->texObj->resident);
			}
			break;
		case GL_TEXTURE_WRAP_S:
			param[0] = FROM_INT(curTex->WrapS);
			break;
		case GL_TEXTURE_WRAP_T:
			param[0] = FROM_INT(curTex->WrapT);
			break;
		case GL_TEXTURE_WRAP_R:
			param[0] = 0.0;
			break;
		case GL_TEXTURE_MIN_FILTER:
			param[0] = FROM_INT(curTex->MinFilter);
			break;
		case GL_TEXTURE_MAG_FILTER:
			param[0] = FROM_INT(curTex->MagFilter);
			break;
	}

#undef FROM_BOOL
#undef FROM_COLOR
#undef FROM_FLOAT
#undef FROM_INT

}


void cgl_GLGetTexParameteriv(struct GLContextIFace *Self, GLenum gltarget,
		GLenum pname, GLint *param)
{
	GLcontext context = GET_INSTANCE(Self);
	GLFlagError(context, gltarget != GL_TEXTURE_2D && gltarget != GL_TEXTURE_1D, GL_INVALID_ENUM);
	int current;
	MGLTexture *curTex;

#define FROM_BOOL(x)	(GLint)((x) ? 1 : 0)
#define FROM_COLOR(x)	(GLint)((0xffffffff * (x) - 1)/2)
#define FROM_FLOAT(x)	(GLint)(x)
#define FROM_INT(x)		(x)


	current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	curTex = &context->w3dTextures[current];

	switch (pname) {
		case GL_TEXTURE_RESIDENT:
			if (!curTex->texObj) {
				param[0] = 0.0;
			}
			else {
				param[0] = FROM_BOOL(curTex->texObj->resident);
			}
			break;
		case GL_TEXTURE_WRAP_S:
			param[0] = FROM_INT(curTex->WrapS);
			break;
		case GL_TEXTURE_WRAP_T:
			param[0] = FROM_INT(curTex->WrapT);
			break;
		case GL_TEXTURE_WRAP_R:
			param[0] = 0.0;
			break;
		case GL_TEXTURE_MIN_FILTER:
			param[0] = FROM_INT(curTex->MinFilter);
			break;
		case GL_TEXTURE_MAG_FILTER:
			param[0] = FROM_INT(curTex->MagFilter);
			break;
	}

#undef FROM_BOOL
#undef FROM_COLOR
#undef FROM_FLOAT
#undef FROM_INT

}

void cgl_GLGetTexImage(
	struct GLContextIFace *Self, GLenum target, GLint lod, GLenum format, GLenum type, GLvoid *pixels
)
{
	GLcontext context = GET_INSTANCE(Self);
	GLFlagError(context, target != GL_TEXTURE_2D && target != GL_TEXTURE_1D, GL_INVALID_ENUM);

	if (pixels == 0) {
		return;
	}
	if (context->NoMipMapping == GL_TRUE && lod != 0) {
		context->CurrentError = GL_INVALID_ENUM;
		return;
	}

	GLimage_info src;
	GLimage_info dst;
	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];

	/* Find and setup the source image */
	if (context->w3dTextures[current].texObj == NULL) {
		context->CurrentError = GL_INVALID_ENUM;
		return;
	}

		/* Peek the size. We didn't record it anywhere, so we peek it from the
			* texture object :S
			*/
	GLsizei width = (context->w3dTextures[current].texObj->texwidth);
	GLsizei height = (context->w3dTextures[current].texObj->texheight);

	if (width == 0) {
		width = 1;
	}
	if (height == 0) {
		height = 1;
	}

	struct W3DtoGL *formatInfo = MGLGetFormatInfo(context->w3dTextures[current].texObj->texfmtsrc);
	if (!formatInfo) {
		context->CurrentError = GL_INVALID_ENUM;
		return;
	}

	if (lod == 0) {
		src.data = context->w3dTextures[current].texData;
	}
	else {
		src.data = context->w3dTextures[current].mipmaps[lod-1];
	}

	src.stride = formatInfo->w3dBpp * width;
	src.format = formatInfo->glformat;
	src.type = formatInfo->gltype;
	src.pixel_stride = formatInfo->w3dBpp;

	/* Setup destination. Select a packer to get the pixel stride */
	dst.data = pixels;
	MGLSelectPacker(format, type, &dst.pixel_stride);
	dst.format = format;
	dst.type = type;
	dst.stride = width * dst.pixel_stride;

	MGLPackImage(context, width, height, &src, &dst, GL_FALSE);
}

