/*
 * $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>

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


#ifndef __PPC__
extern struct ExecBase *SysBase;
#endif

void tex_FreeTextures(GLcontext context);
void tex_SetEnv(GLcontext context, GLenum env);
ULONG tex_GLFilter2W3D(GLenum filter);
void tex_SetFilter(GLcontext context, GLenum min, GLenum mag);
void tex_SetWrap(GLcontext context, GLenum wrap_s, GLenum wrap_t);
void RGBA_RGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height);
void RGBA_ARGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height);
void RGB_RGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height);
void RGB_ARGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height);
ULONG MGLConvert(GLcontext context, const GLvoid *inputp, UWORD *output, int width, int height, GLenum internalformat, GLenum format);

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

void *tex_Alloc(ULONG size)
{
	ULONG *x;
	Allocated_Size += size+4;
	x=(ULONG *)malloc(size+4);

	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;
	free(mem);
}

void tex_Statistic(void)
{
	printf("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 GLDeleteTextures(GLcontext context, GLsizei n, const GLuint *textures)
{
	int i;
	for (i=0; i<n; i++)
	{
		int j = *textures++;
		if (context->w3dTexBuffer[j])
		{
			W3D_FreeTexObj(context->w3dContext, context->w3dTexBuffer[j]);
			context->w3dTexBuffer[j] = NULL;
		}
		if (context->w3dTexMemory[j])
		{
			void *x = context->w3dTexMemory[j];
			tex_Free(x);
			context->w3dTexMemory[j] = NULL;
		}
	}
}

void GLGenTextures(GLcontext context, GLsizei n, GLuint *textures)
{
	int i,j;

	j = 1;
	for (i=0; i<n; i++)
	{
		while (j < context->TexBufferSize)
		{
			if (context->w3dTexBuffer[j] == NULL) break;
			j++;
		}
		GLFlagError(context, j == context->TexBufferSize, GL_INVALID_OPERATION);
		*textures = j;
		textures++;
		j++;
	}
}


void tex_FreeTextures(GLcontext context)
{
	int i;

	for (i=0; i<context->TexBufferSize; i++)
	{
		if (context->w3dTexBuffer[i])
		{
			W3D_FreeTexObj(context->w3dContext, context->w3dTexBuffer[i]);
		}

		if (context->w3dTexMemory[i])
		{
			tex_Free(context->w3dTexMemory[i]);
		}

		context->w3dTexBuffer[i] = 0;
		context->w3dTexMemory[i] = 0;
	}
	W3D_FreeAllTexObj(context->w3dContext);
}

void tex_SetEnv(GLcontext context, GLenum env)
{
	W3D_Texture *tex = context->w3dTexBuffer[context->CurrentBinding];
	if (!tex) return;

	switch(env)
	{
		case GL_MODULATE:   W3D_SetTexEnv(context->w3dContext, tex, W3D_MODULATE, NULL);
							break;
		case GL_DECAL:      W3D_SetTexEnv(context->w3dContext, tex, W3D_DECAL, NULL);
							break;
		case GL_REPLACE:    W3D_SetTexEnv(context->w3dContext, tex, W3D_REPLACE, NULL);
							break;
		default:            break;
	}
}

ULONG tex_GLFilter2W3D(GLenum filter)
{
	switch(filter)
	{
		case GL_NEAREST:                return W3D_NEAREST;
		case GL_LINEAR:                 return W3D_LINEAR;
		case GL_NEAREST_MIPMAP_NEAREST: return W3D_NEAREST_MIP_NEAREST;
		case GL_LINEAR_MIPMAP_NEAREST:  return W3D_LINEAR_MIP_NEAREST;
		case GL_NEAREST_MIPMAP_LINEAR:  return W3D_NEAREST_MIP_LINEAR;
		case GL_LINEAR_MIPMAP_LINEAR:   return W3D_LINEAR_MIP_LINEAR;
	}
	return 0;
}

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

	W3D_Texture *tex = context->w3dTexBuffer[context->CurrentBinding];
	if (!tex) return;

	minf = tex_GLFilter2W3D(min);
	magf = tex_GLFilter2W3D(mag);

	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->w3dTexBuffer[context->CurrentBinding];
	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;

	W3D_SetWrapMode(context->w3dContext, tex, Ws, Wt, NULL);
}

void GLTexEnvi(GLcontext context, GLenum target, GLenum pname, GLint param)
{
	//LOG(2, glTexEnvi, "%d %d", pname, param);
	tex_SetEnv(context, param);
	context->TexEnv = (GLenum)param;
}

void GLTexParameteri(GLcontext context, GLenum target, GLenum pname, GLint param)
{
	GLenum min, mag;
	GLenum wraps, wrapt;
	//LOG(2, glTexParameteri, "%d %d %d", target, pname, param);
	switch(pname)
	{
		case GL_TEXTURE_MIN_FILTER:
			mag = context->MagFilter;
			tex_SetFilter(context, mag, (GLenum)param);
			context->MinFilter = (GLenum)param;
			break;
		case GL_TEXTURE_MAG_FILTER:
			min = context->MinFilter;
			tex_SetFilter(context, (GLenum)param, min);
			context->MagFilter = (GLenum)param;
			break;
		case GL_TEXTURE_WRAP_S:
			wrapt = context->WrapT;
			tex_SetWrap(context, (GLenum)param, wrapt);
			context->WrapS = (GLenum)param;
			break;
		case GL_TEXTURE_WRAP_T:
			wraps = context->WrapS;
			tex_SetWrap(context, wraps, (GLenum)param);
			context->WrapT = (GLenum)param;
			break;
		default:
			GLFlagError(context, 1, GL_INVALID_ENUM);
	}
}

void GLPixelStorei(GLcontext context, GLenum pname, GLint param)
{
	switch(pname)
	{
		case GL_PACK_ALIGNMENT:
			context->PackAlign = param;
			break;

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

      case GL_UNPACK_ROW_LENGTH:
	 context->CurUnpackRowLength = param ;
	 break ;

      case GL_UNPACK_SKIP_PIXELS:
	 context->CurUnpackSkipPixels = param ;
	 break ;

      case GL_UNPACK_SKIP_ROWS:
	 context->CurUnpackSkipRows = param ;
	 break ;

		default:
			// Others here
			break;
	}
}

void GLBindTexture(GLcontext context, GLenum target, GLuint texture)
{
	//LOG(2, glBindTexture, "%d %d", target, texture);
	GLFlagError(context, target != GL_TEXTURE_2D, GL_INVALID_ENUM);
	context->CurrentBinding = texture;
	if (context->w3dTexBuffer[context->CurrentBinding] == NULL)
	{   // Set to default for unbound objects
		context->TexEnv    = GL_MODULATE;
		context->MinFilter = GL_NEAREST;
		context->MagFilter = GL_NEAREST;
		context->WrapS     = GL_REPEAT;
		context->WrapT     = GL_REPEAT;
	}
}

/*
** There are two possible output formats:
** - RGB
** - RGBA
**
** There are two possible input formats:
** - RGB
** - RGBA
**
** Thus there must be four conversion routines, since the
** routine must be able to add or remove the alpha component
**
** The following set of routines assumes that input is always given as
** a stream of GL_UNSIGNED_BYTE with either three or four components.
**
** ROOM FOR IMPROVMENT
** These routines assume way too much to be considered anything else but
** special cases. The whole texture stuff should be reworked to include
** possible convertion routines for all kinds of textures. This could, of
** course, be left to Warp3D, but this would mean there has to be two sets
** of textures in memory, which is clearly too much. Perhaps we should
** change the behaviour of Warp3D in this point - room for discussion.
*/

#define CORRECT_ALIGN \
		if ((int)input % context->PackAlign) \
		{ \
			input += context->PackAlign - ((int)input % context->PackAlign);\
		} \

#define SETUP_LOOP(x) \
	GLubyte *oldInput;                          \
	int realWidth = width*x;                    \
	if (context->CurUnpackRowLength > 0)        \
	    realWidth = context->CurUnpackRowLength*x;

#define START_LOOP \
	oldInput = input;

#define NEXT_LOOP       \
	input = oldInput + realWidth;


#define ARGBFORM(a,r,g,b) \
	(UWORD)(((( a & 0xf0 ) << 8)  \
	|(( r & 0xf0 ) << 4)  \
	|(( g & 0xf0 )     )  \
	|(( b & 0xf0 ) >> 4)))

#define RGBFORM(r,g,b) \
	(UWORD)(0x8000 | ((r & 0xF8) << 7) \
			   | ((g & 0xF8) << 2) \
			   | ((b & 0xF8) >> 3))


#define REDBYTE(rgb)    (((UWORD)rgb & 0x7C00) >> 7)
#define GREENBYTE(rgb)  (((UWORD)rgb & 0x03E0) >> 2)
#define BLUEBYTE(rgb)   (((UWORD)rgb & 0x001F) << 3)
#define RGB_GET(i) ((UBYTE *)(context->PaletteData)+3*i)
#define ARGB_GET(i) ((UBYTE *)(context->PaletteData)+4*i)

/*
** This function converts a non-alpha texture buffer into a
** alpha'ed texture buffer. This is used in case additive blending
** was selected but the texture does not have alpha, and additive blending
** is not
*/
void tex_AddAlpha(UWORD *output, int width, int height)
{
	int size = width*height;
	int r,g,b,a;
	UWORD x;

	while (size)
	{
	x=*output;
	
	r = REDBYTE(x);
	g = GREENBYTE(x);
	b = BLUEBYTE(x);

	/*a=(r+g+b)/3;*/
	a=(r>g)?r:g;
	a=(a>b)?a:b;

	// These should emulate additive blending, so ensure it's in some appropriate range
/*/        if (a > 240)
	{
		a = 240;
	}*/

	a *= 0.8;

	*output = ARGBFORM(a,r,g,b);

	output++; size--;
	}
}


/*
** Convert the currently bound texture to a format that has an alpha channel.
** Also sets the texture parameters according to the current settings.
*/
void tex_ConvertTexture(GLcontext context)
{
	W3D_Texture *newtex;
	W3D_Texture *oldtex = context->w3dTexBuffer[context->CurrentBinding];
	UWORD *output = (UWORD *)context->w3dTexMemory[context->CurrentBinding];
	struct TagItem AllocTags[20];

	if (!oldtex) return;
	if (oldtex->texfmtsrc == context->w3dAlphaFormat) return;

	tex_AddAlpha(output, oldtex->texwidth, oldtex->texheight);
	AllocTags[0].ti_Tag  = W3D_ATO_IMAGE;
	AllocTags[0].ti_Data = (ULONG)context->w3dTexMemory[context->CurrentBinding];

	AllocTags[1].ti_Tag  = W3D_ATO_FORMAT;
	AllocTags[1].ti_Data = context->w3dAlphaFormat;

	AllocTags[2].ti_Tag  = W3D_ATO_WIDTH;
	AllocTags[2].ti_Data = oldtex->texwidth;

	AllocTags[3].ti_Tag  = W3D_ATO_HEIGHT;
	AllocTags[3].ti_Data = oldtex->texheight;

	AllocTags[4].ti_Tag  = TAG_DONE;
	AllocTags[4].ti_Data = 0;

	newtex = W3D_AllocTexObj(context->w3dContext, NULL, AllocTags);
	if (!newtex)    return;
	
	W3D_FreeTexObj(context->w3dContext, oldtex);
	context->w3dTexBuffer[context->CurrentBinding] = newtex;

	tex_SetEnv(context, context->TexEnv);
	tex_SetFilter(context, context->MinFilter, context->MagFilter);
	tex_SetWrap(context, context->WrapS, context->WrapT);
}

void RGBA_RGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE r,g,b,a;

	SETUP_LOOP(4)

	for (i=0; i<height;i++)
	{
		START_LOOP
		for (j=0; j<width; j++)
		{
			r=*input++;
			g=*input++;
			b=*input++;
			a=*input++;
			*output++ = RGBFORM(r,g,b);
		}
		NEXT_LOOP
		CORRECT_ALIGN

	}
}

void RGBA_ARGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE r,g,b,a;
	SETUP_LOOP(4)

	for (i=0; i<height;i++)
	{
		START_LOOP
		for (j=0; j<width; j++)
		{
			r=*input++;
			g=*input++;
			b=*input++;
			a=*input++;
			*output++ = ARGBFORM(a,r,g,b);
		}
		NEXT_LOOP
		CORRECT_ALIGN
	}
}

void RGB_RGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE r,g,b;
	SETUP_LOOP(3)

	for (i=0; i<height;i++)
	{
		START_LOOP
		for (j=0; j<width; j++)
		{
			r=*input++;
			g=*input++;
			b=*input++;
			*output++ = RGBFORM(r,g,b);
		}
		NEXT_LOOP
		CORRECT_ALIGN
	}
}

void RGB_ARGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE r,g,b;
	SETUP_LOOP(3)

	for (i=0; i<height;i++)
	{
		START_LOOP
		for (j=0; j<width; j++)
		{
			r=*input++;
			g=*input++;
			b=*input++;
			*output++ = ARGBFORM(255,r,g,b);
		}
		NEXT_LOOP
		CORRECT_ALIGN
	}
}

void INDEX_RGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE ind;
	UBYTE r,g,b;

	SETUP_LOOP(3)

	if (context->PaletteFormat == GL_RGB)
	{
		for (i=0; i<height; i++)
		{
			START_LOOP
			for (j=0; j<width; j++)
			{
				ind = *input++;
				r = *RGB_GET(ind);
				g = *(RGB_GET(ind)+1);
				b = *(RGB_GET(ind)+2);
				*output++ = RGBFORM(r,g,b);
			}
			NEXT_LOOP
			CORRECT_ALIGN
		}
	}
	else
	{
		for (i=0; i<height; i++)
		{
			START_LOOP
			for (j=0; j<width; j++)
			{
				ind = *input++;
				r = *RGB_GET(ind);
				g = *(ARGB_GET(ind)+1);
				b = *(ARGB_GET(ind)+2);
				*output++ = RGBFORM(r,g,b);
			}
			NEXT_LOOP
			CORRECT_ALIGN
		}

	}
}

void INDEX_ARGB(GLcontext context, GLubyte *input, UWORD *output, int width, int height)
{
	int i,j;
	UBYTE ind;
	UBYTE r,g,b,a;
	SETUP_LOOP(4)

	if (context->PaletteFormat == GL_RGB)
	{
		for (i=0; i<height; i++)
		{
			START_LOOP
			for (j=0; j<width; j++)
			{
				ind = *input++;
				r = *RGB_GET(ind);
				g = *(RGB_GET(ind)+1);
				b = *(RGB_GET(ind)+2);
				*output++ = ARGBFORM(255,r,g,b);
			}
			NEXT_LOOP
			CORRECT_ALIGN
		}
	}
	else
	{
		for (i=0; i<height; i++)
		{
			START_LOOP
			for (j=0; j<width; j++)
			{
				ind = *input++;
				r = *RGB_GET(ind);
				g = *(ARGB_GET(ind)+1);
				b = *(ARGB_GET(ind)+2);
				a = *(ARGB_GET(ind)+3);
				*output++ = ARGBFORM(a,r,g,b);
			}
			NEXT_LOOP
			CORRECT_ALIGN
		}

	}
}

void L8_L8(GLcontext context, GLubyte *input, GLubyte *output, int width, int height)
{
	int i,j;
	SETUP_LOOP(1)

	for (i=0; i<height;i++)
	{
		START_LOOP
		for (j=0; j<width; j++)
		{
			*output++ = *input++;
		}
		NEXT_LOOP
		CORRECT_ALIGN
	}
}

ULONG MGLConvert(GLcontext context, const GLvoid *inputp, UWORD *output, int width, int height, GLenum internalformat, GLenum format)
{
	GLvoid *input = (GLvoid *)inputp;

	switch(internalformat) // The format the texture should have
	{
		case GL_LUMINANCE:
			switch(format) // the format of the pixel (input) data
			{
				case GL_LUMINANCE:
					L8_L8(context, (GLubyte *)input, (GLubyte *)output, width, height);
					return W3D_L8;
			}
			break;

		case 3:
		case GL_RGB:
			switch(format) // the format of the pixel (input) data
			{
				case GL_RGB:
					RGB_RGB(context, (GLubyte *)input, output, width, height);
					return context->w3dFormat;
				case GL_RGBA:
					RGBA_RGB(context, (GLubyte *)input, output, width, height);
					return context->w3dFormat;
				case GL_COLOR_INDEX:
					INDEX_RGB(context, (GLubyte *)input, output, width, height);
					return context->w3dFormat;
			}
			break;

		case 4:
		case GL_RGBA:
			switch(format) // format of the pixel (input) data
			{
				case GL_RGB:
					RGB_ARGB(context, (GLubyte *)input, output, width, height);
					return context->w3dAlphaFormat;
				case GL_RGBA:
					RGBA_ARGB(context, (GLubyte *)input, output, width, height);
					return context->w3dAlphaFormat;
				case GL_COLOR_INDEX:
					INDEX_ARGB(context, (GLubyte *)input, output, width, height);
					return context->w3dAlphaFormat;
			}
			break;
	}

	return 0;
}

void GLTexImage2DNoMIP(GLcontext context, GLenum gltarget, GLint level,
	GLint internalformat, GLsizei width, GLsizei height, GLint border,
	GLenum format, GLenum type, const GLvoid *pixels);


void GLTexImage2D(GLcontext context, GLenum gltarget, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
{
	int current = context->CurrentBinding;
	ULONG w,h;
	ULONG targetsize;
	ULONG error;
	UBYTE *target;
	int i,iw,ih;
	void *miparray[16];
	ULONG useFormat;

	struct TagItem AllocTags[20];

	if (context->NoMipMapping == GL_TRUE)
	{
		GLTexImage2DNoMIP(context, gltarget, level, internalformat, width,
			height, border, format, type, pixels);
		return;
	}

	//LOG(3, glTexImage2D, "target = %d level=%d, size=%d×%d", gltarget, level, width, height);
	GLFlagError(context, type != GL_UNSIGNED_BYTE, 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
	*/

	w=(ULONG)width;
	h=(ULONG)height;
	if (level)
	{
		int i=level;
		while (i)
		{
			w*=2; h*=2; i--;
		}
	}

	if (context->w3dTexBuffer[current] == NULL)
	{
		/*
		** Create a new texture object
		** Get the memory
		*/

		targetsize = (w * h * context->w3dBytesPerTexel * 4) / 3;
		if (context->w3dTexMemory[current])
			tex_Free(context->w3dTexMemory[current]);
		context->w3dTexMemory[current] = (GLubyte *)tex_Alloc(targetsize);
		if (!context->w3dTexMemory[current])
			return;
	}

	/*
	** Find the starting address for the given mipmap level in the
	** texture memory area
	*/

	target = context->w3dTexMemory[current];
	i = level;
	iw = w; ih=h;
	while (i)
	{
		target += iw * ih * context->w3dBytesPerTexel;
		i      -- ;
		if (iw>1) iw/=2;
		if (ih>1) ih/=2;
	}

	/*
	** Convert the data to the target address
	*/
	useFormat = MGLConvert(context, pixels, (UWORD *)target, width, height, internalformat, format);

	/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTexBuffer[current] == NULL)
	{
		i=0;
		iw=w;
		ih=h;
		target = context->w3dTexMemory[current];
		while (1)
		{
			miparray[i++] = target;
			if (iw == 1 && ih == 1) break;

			target += iw * ih * context->w3dBytesPerTexel;
			if (iw > 1)     iw     /= 2;
			if (ih > 1)     ih     /= 2;
		}

		AllocTags[0].ti_Tag  = W3D_ATO_IMAGE;
		AllocTags[0].ti_Data = (ULONG)context->w3dTexMemory[current];

		AllocTags[1].ti_Tag  = W3D_ATO_FORMAT;
		AllocTags[1].ti_Data = useFormat;

		AllocTags[2].ti_Tag  = W3D_ATO_WIDTH;
		AllocTags[2].ti_Data = w;

		AllocTags[3].ti_Tag  = W3D_ATO_HEIGHT;
		AllocTags[3].ti_Data = h;

		AllocTags[4].ti_Tag  = W3D_ATO_MIPMAP;
		AllocTags[4].ti_Data = 0;

		AllocTags[5].ti_Tag  = W3D_ATO_MIPMAPPTRS;
		AllocTags[5].ti_Data = (ULONG)miparray;

		AllocTags[6].ti_Tag  = TAG_DONE;
		AllocTags[6].ti_Data = 0;

		context->w3dTexBuffer[current] = W3D_AllocTexObj(context->w3dContext, &error, AllocTags);

		if (context->w3dTexBuffer[current] == NULL || error != W3D_SUCCESS) return;

		/*
		** Set the appropriate wrap modes, texture env, and filters
		*/
		tex_SetWrap(context, context->WrapS, context->WrapT);
		tex_SetFilter(context, context->MinFilter, context->MagFilter);
		tex_SetEnv(context, context->TexEnv);

	}
	else
	{
		W3D_UpdateTexImage(context->w3dContext, context->w3dTexBuffer[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->CurrentBinding;
	ULONG w,h;
	ULONG targetsize;
	ULONG error;
	UBYTE *target;
	ULONG useFormat;
	static struct TagItem AllocTags[20];

	//LOG(3, glTexImage2DNoMIP, "target = %d level=%d, size=%d×%d", gltarget, level, width, height);
	GLFlagError(context, type != GL_UNSIGNED_BYTE, GL_INVALID_OPERATION);
	GLFlagError(context, gltarget != GL_TEXTURE_2D, GL_INVALID_ENUM);

	if (level != 0) return;

	w=(ULONG)width;
	h=(ULONG)height;

	if (context->w3dTexBuffer[current] == NULL)
	{
	/*
	** Create a new texture object
	** Get the memory
	*/

	targetsize = (w * h * context->w3dBytesPerTexel);
	if (context->w3dTexMemory[current])
		tex_Free(context->w3dTexMemory[current]);
	context->w3dTexMemory[current] = (GLubyte *)tex_Alloc(targetsize);
	if (!context->w3dTexMemory[current])
		return;
	}

	target = context->w3dTexMemory[current];

	/*
	** Convert the data to the target address
	*/
	useFormat = MGLConvert(context, pixels, (UWORD *)target, width, height, internalformat, format);

	/*
	** Create a new W3D_Texture if none was present, using the converted
	** data.
	** Otherwise, call W3D_UpdateTexImage
	*/

	if (context->w3dTexBuffer[current] == NULL)
	{
	    W3D_Texture *tex;

	    AllocTags[0].ti_Tag  = W3D_ATO_IMAGE;
	    AllocTags[0].ti_Data = (ULONG)context->w3dTexMemory[current];

	    AllocTags[1].ti_Tag  = W3D_ATO_FORMAT;
	    AllocTags[1].ti_Data = useFormat;

	    AllocTags[2].ti_Tag  = W3D_ATO_WIDTH;
	    AllocTags[2].ti_Data = w;

	    AllocTags[3].ti_Tag  = W3D_ATO_HEIGHT;
	    AllocTags[3].ti_Data = h;

	    AllocTags[4].ti_Tag  = TAG_DONE;
	    AllocTags[4].ti_Data = 0;

	    tex = W3D_AllocTexObj(context->w3dContext, &error, AllocTags);

	    if (tex == NULL || error != W3D_SUCCESS) return;

	    context->w3dTexBuffer[current] = tex;

	    /*
	    ** Set the appropriate wrap modes, texture env, and filters
	    */
	    tex_SetWrap(context, context->WrapS, context->WrapT);
	    tex_SetFilter(context, context->MinFilter, context->MagFilter);
	    tex_SetEnv(context, context->TexEnv);

	}
	else
	{
	    W3D_UpdateTexImage(context->w3dContext, context->w3dTexBuffer[current],
		    context->w3dTexMemory[current], 0, NULL);
	}
}

inline void tex_UpdateScanlineAlpha(UWORD *start, UBYTE *pixels, int numpixels)
{
	int i;
	UBYTE r,g,b,a;
	for (i=0; i<numpixels; i++)
	{
		r=*pixels++;
		g=*pixels++;
		b=*pixels++;
		a=*pixels++;

		*start = ARGBFORM(a,r,g,b);
		start++;
	}
}

inline void tex_UpdateScanlineNoAlpha(UWORD *start, UBYTE *pixels, int numpixels)
{
	int i;
	UBYTE r,g,b,a;
	for (i=0; i<numpixels; i++)
	{
		r=*pixels++;
		g=*pixels++;
		b=*pixels++;
		a=*pixels++;

		*start = RGBFORM(r,g,b);
		start++;
	}
}

inline void tex_UpdateScanlineVerbatim(UBYTE *start, UBYTE *pixels, int numpixels)
{
	memcpy(pixels, start, numpixels);
}

void GLTexSubImage2DNoMIP(GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);

void GLTexSubImage2D(GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)
{
	int current = context->CurrentBinding;

	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->w3dTexBuffer[current] == NULL, GL_INVALID_OPERATION);

//    GLFlagError(context, 1, GL_INVALID_OPERATION);
}

void GLTexSubImage2DNoMIP(GLcontext context, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)
{
	UBYTE *where;
	UBYTE *from = pixels;
	int current = context->CurrentBinding;
	int linelength;
	int sourcelength, sourceunit = 0;
	int i;


	GLFlagError(context, target!=GL_TEXTURE_2D, GL_INVALID_ENUM);
	GLFlagError(context, context->w3dTexBuffer[context->CurrentBinding] == NULL, GL_INVALID_OPERATION);
	GLFlagError(context, type != GL_UNSIGNED_BYTE, GL_INVALID_ENUM);

	switch(format)
	{
		case GL_LUMINANCE:
			sourceunit = 1;
			break;

		case GL_RGBA:
		case 4:
			sourceunit = 4;
			break;

		case GL_RGB:
		case 3:
			sourceunit = 3;
			break;
	}

	linelength = context->w3dTexBuffer[current]->texwidth * context->w3dBytesPerTexel;

	sourcelength = width * sourceunit;

	where = (UBYTE *)context->w3dTexMemory[current]
	  +          linelength * yoffset
	  +          context->w3dBytesPerTexel * xoffset;

	if (context->w3dTexBuffer[current]->texfmtsrc == context->w3dFormat)
	{
		for (i=0; i<height; i++)
		{
			tex_UpdateScanlineNoAlpha((UWORD *)where, from, width);
			where += linelength;
			from  += sourcelength;
		}
	}
	else if (context->w3dTexBuffer[current]->texfmtsrc == context->w3dAlphaFormat)
	{
		for (i=0; i<height; i++)
		{
			tex_UpdateScanlineAlpha((UWORD *)where, from, width);
			where += linelength;
			from  += sourcelength;
		}
	}
	else
	{
		for (i=0; i<height; i++)
		{
			tex_UpdateScanlineVerbatim((UBYTE *)where, from, width*sourceunit);
			where += linelength;
			from  += sourcelength;
		}
	}

	W3D_UpdateTexImage(context->w3dContext, context->w3dTexBuffer[current],
	context->w3dTexMemory[current], 0, NULL);
}

void GLTexGeni(GLcontext context, GLenum coord, GLenum mode, GLenum map)
{
//    GLFlagError(context, 1, GL_INVALID_OPERATION);
}

void GLColorTable(GLcontext context, GLenum target, GLenum internalformat, GLint width, GLenum format, GLenum type, GLvoid *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 GLActiveTextureARB(GLcontext context, GLenum unit)
{
    // Not yet implemented
}
