/*
 * $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 "sysinc.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static char rcsid[] UNUSED = "$Id$";


void tex_FreeTextures(GLcontext context);
void tex_SetEnv(GLcontext context, GLenum env);
static ULONG tex_GLFilter2W3D(GLcontext, GLenum filter);
void tex_SetFilter(GLcontext context, GLenum min, GLenum mag);
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);
void MGLUpdate(GLcontext context, GLint xoffset, GLint yoffset, 
	GLsizei width, GLsizei height, GLubyte *pixels, GLubyte *dest, GLsizei dstWidth, GLint internal, GLint unpacker);
int32 SelectUnpacker(GLcontext context, GLenum format, GLenum type);
int32 SelectInternalFormat(GLcontext context, GLenum internalformat);

extern struct FormatTypeToUnpack ft2u[];
extern struct InternalToW3D i2w3d[];


extern float CLAMPF(float a);

static ULONG Allocated_Size = 0;
static ULONG Peak_Size      = 0;


void SetBlendStageState(GLcontext context, uint32 stage)
{
	if (context->enable.Texture2D[stage])
	{
		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);
		SetBlendStageState(context, i);
	}
}

void *tex_Alloc(ULONG size)
{
	ULONG *x;
	Allocated_Size += size+4;
	x=(ULONG *)IExec->AllocVec(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)
{
	ULONG *mem = (ULONG *)chunk;
	mem--;
	Allocated_Size -= *mem;
	Allocated_Size -= 4;
	IExec->FreeVec(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++;
		if (context->w3dTextures[j].texObj)
		{
			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 ULONG tex_GLFilter2W3D(GLcontext context, GLenum filter)
{
	switch(filter)
	{
		case GL_NEAREST:
			return W3D_NEAREST;
			
		case GL_LINEAR:
			return W3D_LINEAR;
			
		case GL_NEAREST_MIPMAP_NEAREST: 
			if (context->NoMipMapping)
				return W3D_NEAREST;
			return W3D_NEAREST_MIP_NEAREST;
			
		case GL_LINEAR_MIPMAP_NEAREST:
			if (context->NoMipMapping)
				return W3D_LINEAR;
			return W3D_LINEAR_MIP_NEAREST;
			
		case GL_NEAREST_MIPMAP_LINEAR:
			if (context->NoMipMapping)
				return W3D_NEAREST;
			return W3D_NEAREST_MIP_LINEAR;
			
		case GL_LINEAR_MIPMAP_LINEAR:
			if (context->NoMipMapping)
				return W3D_LINEAR;
			return W3D_LINEAR_MIP_LINEAR;
	}
	return 0;
}

void tex_SetFilter(GLcontext context, GLenum min, GLenum mag)
{
	ULONG minf, magf;

	W3D_Texture *tex = 
		context->w3dTextures[context->texture.CurrentBinding[
							context->texture.ActiveTexture]].texObj;

	if (!tex) 
		return;

	minf = tex_GLFilter2W3D(context, min);
	magf = tex_GLFilter2W3D(context, mag);
	//kprintf("Setting MinFilter %s, MagFilter %s\n", filters[minf-1], filters[magf-1]);
	IWarp3D->W3D_SetFilter(context->w3dContext, tex, minf, magf);
}

void tex_SetWrap(GLcontext context, GLenum wrap_s, GLenum wrap_t)
{
	ULONG 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);
}

void cgl_GLTexEnvi(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);

	switch(pname)
	{
	case GL_TEXTURE_ENV_MODE:
		context->texture.TextureEnv[context->texture.ActiveTexture] = (GLenum)param;
		tex_SetEnv(context, param);
		break;
	case GL_COMBINE_RGB:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		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:
		if (context->texture.alphaScale[context->texture.ActiveTexture] == param)
			break;
		context->combineDirty[context->texture.ActiveTexture] = GL_TRUE;
		context->texture.alphaScale[context->texture.ActiveTexture] = param;
		break;
	}
}


void cgl_GLTexEnvfv(struct GLContextIFace *Self, GLenum target, GLenum pname, GLfloat *param)
{
	GLcontext context = GET_INSTANCE(Self);

	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_GLTexParameteri(struct GLContextIFace *Self, GLenum target, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);
		
	GLenum min, mag;
	GLenum wraps, wrapt;
	
	switch(pname)
	{
		case GL_TEXTURE_MIN_FILTER:
			mag = context->texture.MagFilter;
			tex_SetFilter(context, (GLenum)param, mag);
			context->texture.MinFilter = (GLenum)param;
			break;
		case GL_TEXTURE_MAG_FILTER:
			min = context->texture.MinFilter;
			tex_SetFilter(context, min, (GLenum)param);
			context->texture.MagFilter = (GLenum)param;
			break;
		case GL_TEXTURE_WRAP_S:
			wrapt = context->texture.WrapT;
			tex_SetWrap(context, (GLenum)param, wrapt);
			context->texture.WrapS = (GLenum)param;
			break;
		case GL_TEXTURE_WRAP_T:
			wraps = context->texture.WrapS;
			tex_SetWrap(context, wraps, (GLenum)param);
			context->texture.WrapT = (GLenum)param;
			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
	}
}

void cgl_GLPixelStorei(struct GLContextIFace *Self, GLenum pname, GLint param)
{
	GLcontext context = GET_INSTANCE(Self);

	switch(pname)
	{
		case GL_PACK_ALIGNMENT:
			context->pixel_store.PackAlign = param;
			break;

		case GL_UNPACK_ALIGNMENT:
			context->pixel_store.UnpackAlign = param;
			break;

		case GL_UNPACK_ROW_LENGTH:
			context->pixel_store.UnpackRowLength = param ;
			break ;

		case GL_UNPACK_SKIP_PIXELS:
			context->pixel_store.UnpackSkipPixels = param ;
			break ;

		case GL_UNPACK_SKIP_ROWS:
			context->pixel_store.UnpackSkipRows = param ;
			break ;

		case GL_UNPACK_LSB_FIRST:
			context->pixel_store.UnpackLSBFirst = param;

		default:
			// Others here
			break;
	}
}

void cgl_GLBindTexture(struct GLContextIFace *Self, GLenum target, GLuint texture)
{	
	GLcontext context = GET_INSTANCE(Self);

	if (!context)
		return;
	
	GLFlagError(context, target != GL_TEXTURE_2D, 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)
	{   // Set to default for unbound objects
		context->texture.TextureEnv[context->texture.ActiveTexture]    = GL_MODULATE;
	}
	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, ULONG *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 uint32 MGLCalculateMipArray(uint32 width, uint32 height, uint32 bpp, GLubyte *mipmaps[])
{
	int level = 0;
	uint32 res = width * height * bpp;
	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;
		res += width * height * bpp;
		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);

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	ULONG w,h;
	ULONG targetsize;
	ULONG error;
	UBYTE *target;
	ULONG useFormat;
	int32 internal = SelectInternalFormat(context, internalformat);
	int32 unpacker = SelectUnpacker(context, format, type);

	if (pixels == 0)
		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, 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=(ULONG)width<<level;
		h=(ULONG)height<<level;
	}
	else
	{
		w = context->w3dTextures[current].texObj->texwidth;
		h = context->w3dTextures[current].texObj->texheight;
					
		if (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=(ULONG)width<<level;
		h=(ULONG)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);
		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
	*/
	useFormat = MGLConvert(context, internalformat, width, height, format, type,
		(GLubyte *)pixels, (GLubyte *)target); 
	/*
	** 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) 
			return;

//		dprintf("Allocated new texture object %p (%ldx%ld)\n", context->w3dTextures[current].texObj, w, h);

		/*
		** 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);
		tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]);

		RebindTextures(context);
	}
	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];
	ULONG w,h;
	ULONG targetsize;
	ULONG error;
	UBYTE *target;
	ULONG 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 = (ULONG)width;
	h = (ULONG)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
	    */

	    targetsize = (w * h * i2w3d[internal].w3dBpp);
	    
	    if (context->w3dTextures[current].texData)
			tex_Free(context->w3dTextures[current].texData);

	    context->w3dTextures[current].texData = (GLubyte *)tex_Alloc(targetsize);
	    if (!context->w3dTextures[current].texData)
			return;
			
		context->w3dTextures[current].internalFormat = internalformat;
	}

	target = context->w3dTextures[current].texData;

	/*
	** Convert the data to the target address
	*/

	useFormat = MGLConvert(context, internalformat, width, height, format, type,
		(GLubyte *)pixels, (GLubyte *)target); 

	/*
	** 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) 
	    	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);
	    tex_SetEnv(context, context->texture.TextureEnv[context->texture.ActiveTexture]);
	}
	else
	{		
		UpdateTexImage(context, current, target, level, NULL);
	}
}

void GLTexSubImage2DNoMIP(GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, 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);

	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;
	int internal = SelectInternalFormat(context, 
		context->w3dTextures[current].internalFormat);
	int unpacker = SelectUnpacker(context, format, type);


//	if (context->NoMipMapping == GL_TRUE)
	{
		GLTexSubImage2DNoMIP(context, target, level, xoffset, yoffset, width, height, format, type, (void *)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);
	GLFlagError(context, context->w3dTextures[context->texture.CurrentBinding[context->texture.ActiveTexture]].mipmaps[level] == 0, GL_INVALID_OPERATION);	
	GLFlagError(context, unpacker == -1, GL_INVALID_ENUM);

	dest = context->w3dTextures[current].mipmaps[level];
	MGLUpdate(context, xoffset, yoffset, width, height, (GLubyte *)pixels, dest, 
		max(1,context->w3dTextures[current].texObj->texwidth >> level),
		internal, unpacker);

	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, GLvoid *pixels)
{
	int current = context->texture.CurrentBinding[context->texture.ActiveTexture];
	GLubyte *dest;
	int internal = SelectInternalFormat(context, 
		context->w3dTextures[current].internalFormat);
	int unpacker = SelectUnpacker(context, format, type);

	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);	
	GLFlagError(context, unpacker == -1, GL_INVALID_ENUM);

	dest = context->w3dTextures[current].texData;
	MGLUpdate(context, xoffset, yoffset, width, height, pixels, dest, 
		context->w3dTextures[current].texObj->texwidth,
		internal, unpacker);

	UpdateTexImage(context, current, context->w3dTextures[current].texData, 0, NULL);
}


void cgl_GLColorTable(struct GLContextIFace *Self, GLenum target, GLenum internalformat, GLint width, GLenum format, GLenum type, GLvoid *data)
{
	GLcontext context = GET_INSTANCE(Self);

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

	unit -= GL_TEXTURE0;
	
	GLFlagError(context, unit >= context->NumTextureUnits, GL_INVALID_VALUE);
	
	context->texture.ActiveTexture = unit;
	
}

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) 
		{
			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_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;
		}	
			
		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);

	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_R)
		return;

	GLFlagError(context, texgen == 0, GL_INVALID_ENUM);
	
	switch (pname)
	{
		case GL_EYE_PLANE:
			/* FIXME: Transform to eye space */
			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);

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