/*(LGPL)
------------------------------------------------------------
	glSDL 0.9 - SDL 2D API on top of OpenGL
------------------------------------------------------------
 * Copyright (C) 2001-2004, 2006-2007 David Olofson
 * This code is released under the terms of the GNU LGPL.
 */

#define	_GLSDL_NO_REDEFINES_
#include "glSDL.h"

#ifdef HAVE_OPENGL

#define LEAK_TRACKING

#define	DBG(x)		/*error messages, warnings*/
#define	DBG2(x)		/*texture allocation*/
#define	DBG3(x)		/*chopping/tiling*/
#define	DBG4(x)		/*texture uploading*/
#define	DBG5(x) 	/*two-way chopping/tiling*/
#define	DBG6(x) 	/*OpenGL lib loading*/

#undef	CKSTATS		/*colorkey statistics*/

/* Keep this on for now! Makes large surfaces faster. */
#define	FAKE_MAXTEXSIZE	256

#include <string.h>
#include <stdlib.h>
#include <math.h>

#if HAS_SDL_OPENGL_H
#include "SDL_opengl.h"
#else
#ifdef WIN32
#include <windows.h>
#endif
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif
#endif

#if defined(_WIN32) && !defined(APIENTRY) && \
		!defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
#include <windows.h>
#endif
#ifndef APIENTRY
#define APIENTRY
#endif


static inline void clip_rect(SDL_Rect *r, SDL_Rect *to)
{
	int dx1 = r->x;
	int dy1 = r->y;
	int dx2 = dx1 + r->w;
	int dy2 = dy1 + r->h;
	if(dx1 < to->x)
		dx1 = to->x;
	if(dy1 < to->y)
		dy1 = to->y;
	if(dx2 > to->x + to->w)
		dx2 = to->x + to->w;
	if(dy2 > to->y + to->h)
		dy2 = to->y + to->h;
	if(dx2 < dx1 || dy2 < dy1)
	{
		r->x = r->y = 0;
		r->w = r->h = 0;
	}
	else
	{
		r->x = dx1;
		r->y = dy1;
		r->w = dx2 - dx1;
		r->h = dy2 - dy1;
	}
}


/*----------------------------------------------------------
	OpenGL interface
----------------------------------------------------------*/

static struct
{
	void	(APIENTRY *Begin)(GLenum);
	void	(APIENTRY *BindTexture)(GLenum, GLuint);
	void	(APIENTRY *BlendFunc)(GLenum, GLenum);
	void	(APIENTRY *Color4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha);
	void	(APIENTRY *DeleteTextures)(GLsizei n, const GLuint *textures);
	void	(APIENTRY *Disable)(GLenum cap);
	void	(APIENTRY *Enable)(GLenum cap);
	void	(APIENTRY *End)(void);
	void	(APIENTRY *Flush)(void);
	void	(APIENTRY *GenTextures)(GLsizei n, GLuint *textures);
	GLenum	(APIENTRY *GetError)(void);
	void	(APIENTRY *GetIntegerv)(GLenum pname, GLint *params);
	void	(APIENTRY *LoadIdentity)(void);
	void	(APIENTRY *MatrixMode)(GLenum mode);
	void	(APIENTRY *Ortho)(GLdouble left, GLdouble right, GLdouble bottom,
			GLdouble top, GLdouble zNear, GLdouble zFar);
	void	(APIENTRY *PixelStorei)(GLenum pname, GLint param);
	void	(APIENTRY *ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height,
			GLenum format, GLenum type, GLvoid *pixels);
	void	(APIENTRY *TexCoord2f)(GLfloat s, GLfloat t);
	void	(APIENTRY *TexImage2D)(GLenum target, GLint level, GLint internalformat,
			GLsizei width, GLsizei height, GLint border,
			GLenum format, GLenum type, const GLvoid *pixels);
	void	(APIENTRY *TexParameteri)(GLenum target, GLenum pname, GLint param);
	void	(APIENTRY *TexSubImage2D)(GLenum target, GLint level,
			GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
			GLenum format, GLenum type, const GLvoid *pixels);
	void	(APIENTRY *Translatef)(GLfloat x, GLfloat y, GLfloat z);
	void	(APIENTRY *Vertex2i)(GLint x, GLint y);
	void	(APIENTRY *Viewport)(GLint x, GLint y, GLsizei width, GLsizei height);
	void	(APIENTRY *Rotated)(GLdouble, GLdouble, GLdouble, GLdouble);
	void	(APIENTRY *Scalef)(GLfloat, GLfloat, GLfloat);
	void	(APIENTRY *PushMatrix)(void);
	void	(APIENTRY *PopMatrix)(void);
} gl;


static int GetGL(void)
{
	int i;
	struct
	{
		const char	*name;
		void		**fn;
	} glfuncs[] = {
		{"glBegin", (void *)&gl.Begin },
		{"glBindTexture", (void *)&gl.BindTexture },
		{"glBlendFunc", (void *)&gl.BlendFunc },
		{"glColor4ub", (void *)&gl.Color4ub },
		{"glDeleteTextures", (void *)&gl.DeleteTextures },
		{"glDisable", (void *)&gl.Disable },
		{"glEnable", (void *)&gl.Enable },
		{"glEnd", (void *)&gl.End },
		{"glFlush", (void *)&gl.Flush },
		{"glGenTextures", (void *)&gl.GenTextures },
		{"glGetError", (void *)&gl.GetError },
		{"glGetIntegerv", (void *)&gl.GetIntegerv },
		{"glLoadIdentity", (void *)&gl.LoadIdentity },
		{"glMatrixMode", (void *)&gl.MatrixMode },
		{"glOrtho", (void *)&gl.Ortho },
		{"glPixelStorei", (void *)&gl.PixelStorei },
		{"glReadPixels", (void *)&gl.ReadPixels },
		{"glTexCoord2f", (void *)&gl.TexCoord2f },
		{"glTexImage2D", (void *)&gl.TexImage2D },
		{"glTexParameteri", (void *)&gl.TexParameteri },
		{"glTexSubImage2D", (void *)&gl.TexSubImage2D },
		{"glTranslatef", (void *)&gl.Translatef },
		{"glVertex2i", (void *)&gl.Vertex2i },
		{"glViewport", (void *)&gl.Viewport },
		{"glRotated", (void *)&gl.Rotated },
		{"glScalef", (void *)&gl.Scalef },
		{"glPushMatrix", (void *)&gl.PushMatrix },
		{"glPopMatrix", (void *)&gl.PopMatrix },
		{NULL, NULL }
	};
	for(i = 0; glfuncs[i].name; ++i)
	{
		*glfuncs[i].fn = SDL_GL_GetProcAddress(glfuncs[i].name);
		if(!*glfuncs[i].fn)
		{
			DBG6(fprintf(stderr,"glSDL/wrapper ERROR: Could not get "
					"OpenGL function '%s'!\n",
					glfuncs[i].name);)
			return -1;
		}
		DBG6(printf("Got OpenGL function '%s'.\n", glfuncs[i].name);)
	}
	return 0;
}


static int LoadGL(void)
{
	if(GetGL() < 0)
	{
		DBG6(printf("Couldn't get GL funcs! Trying to load lib...\n");)
		SDL_GL_LoadLibrary(NULL);
		if(GetGL() < 0)
		{
			DBG6(printf("Still couldn't get GL funcs!\n");)
			return -1;
		}
	}
	return 0;
}


static void UnloadGL(void)
{
}


static void print_glerror(int point)
{
#if (DBG(1)+0 == 1)
	const char *err = "<unknown>";
	switch(gl.GetError())
	{
	  case GL_NO_ERROR:
		return;
	  case GL_INVALID_ENUM:
		err = "GL_INVALID_ENUM";
		break;
	  case GL_INVALID_VALUE:
		err = "GL_INVALID_VALUE";
		break;
	  case GL_INVALID_OPERATION:
		err = "GL_INVALID_OPERATION";
		break;
	  case GL_STACK_OVERFLOW:
		err = "GL_STACK_OVERFLOW";
		break;
	  case GL_STACK_UNDERFLOW:
		err = "GL_STACK_UNDERFLOW";
		break;
	  case GL_OUT_OF_MEMORY:
		err = "GL_OUT_OF_MEMORY";
		break;
	  default:
		err = "<unknown>";
		break;
	}
	fprintf(stderr,"OpenGL error \"%s\" at point %d.\n", err, point);
#endif
}


/*----------------------------------------------------------
	OpenGL state wrapper
----------------------------------------------------------*/

static struct
{
	int	do_blend;
	int	do_texture;
	GLint	texture;
	GLenum	sfactor, dfactor;
} glstate;

static void gl_reset(void)
{
	glstate.do_blend = -1;
	glstate.do_blend = -1;
	glstate.texture = -1;
	glstate.sfactor = 0xffffffff;
	glstate.dfactor = 0xffffffff;
}

static __inline__ void gl_do_blend(int on)
{
	if(glstate.do_blend == on)
		return;

	if(on)
		gl.Enable(GL_BLEND);
	else
		gl.Disable(GL_BLEND);
	glstate.do_blend = on;
}

static __inline__ void gl_do_texture(int on)
{
	if(glstate.do_texture == on)
		return;

	if(on)
		gl.Enable(GL_TEXTURE_2D);
	else
		gl.Disable(GL_TEXTURE_2D);
	glstate.do_texture = on;
}

static __inline__ void gl_blendfunc(GLenum sfactor, GLenum dfactor)
{
	if((sfactor == glstate.sfactor) && (dfactor == glstate.dfactor))
		return;

	gl.BlendFunc(sfactor, dfactor);

	glstate.sfactor = sfactor;
	glstate.dfactor = dfactor;
}

static __inline__ void gl_texture(GLuint tx)
{
	if(tx == (unsigned)glstate.texture)
		return;

	gl.BindTexture(GL_TEXTURE_2D, tx);
	glstate.texture = tx;
}


/*----------------------------------------------------------
	Global stuff
----------------------------------------------------------*/

static int initialized = 0;

static int using_glsdl = 0;
#define	USING_GLSDL	(0 != using_glsdl)

#define	MAX_TEXINFOS	16384

static glSDL_TexInfo **texinfotab = NULL;
static GLint maxtexsize = 256;
static SDL_PixelFormat RGBfmt, RGBAfmt;

static void UnloadTexture(glSDL_TexInfo *txi);

static int scale = 1;
static int vsync = 1;

static SDL_Surface *fake_screen = NULL;


static int glSDL_BlitGL(SDL_Surface *src, SDL_Rect *srcrect,
			 SDL_Surface *dst, SDL_Rect *dstrect);


/*----------------------------------------------------------
	glSDL Global State
----------------------------------------------------------*/

static struct
{
	Uint8	alpha;		/* Alpha */
	Uint8	r, g, b;	/* Color modulation */
	float	cx, cy;		/* Transform center offset */
	float	sx, sy;		/* Scale */
	float	rot;		/* Rotation */
} state;


void glSDL_SetBlendAlpha(Uint8 alpha)
{
	state.alpha = alpha;
}


void glSDL_SetBlendColor(Uint8 r, Uint8 g, Uint8 b)
{
	state.r = r;
	state.g = g;
	state.b = b;
}


void glSDL_SetCenter(float x, float y)
{
	state.cx = x;
	state.cy = y;
}


void glSDL_SetRotation(float angle)
{
	state.rot = angle;
}


void glSDL_SetScale(float x, float y)
{
	state.sx = x;
	state.sy = y;
}


void glSDL_ResetState(void)
{
	glSDL_SetBlendAlpha(255);
	glSDL_SetBlendColor(255, 255, 255);
	glSDL_SetCenter(0.0f, 0.0f);
	glSDL_SetRotation(0.0f);
	glSDL_SetScale(1.0f, 1.0f);
}


/*----------------------------------------------------------
	Code
----------------------------------------------------------*/

/* Get texinfo for a surface. */
static __inline__ glSDL_TexInfo *glSDL_GetTexInfo(SDL_Surface *surface)
{
	if(texinfotab)
		return texinfotab[surface->unused1];
	else
		return NULL;
}


/* Allocate a "blank" texinfo for a suface. */
glSDL_TexInfo *glSDL_AllocTexInfo(SDL_Surface *surface)
{
	int handle, i = 0;
	glSDL_TexInfo *txi;
	if(!surface)
		return NULL;

	txi = glSDL_GetTexInfo(surface);
	if(txi)
		return txi;		/* There already is one! --> */

	/* Find a free handle... */
	handle = -1;
	for(i = 1; i < MAX_TEXINFOS + 1; ++i)
		if(NULL == texinfotab[i])
		{
			handle = i;
			break;
		}

	if(handle < 0)
	{
		DBG(fprintf(stderr, "glSDL/wrapper: Out of handles!\n"));
		return NULL;
	}

	/* ...and hook a new texinfo struct up to it. */
	texinfotab[handle] = (glSDL_TexInfo*)calloc(1, sizeof(glSDL_TexInfo));
	if(!texinfotab[handle])
		return NULL;

	/* Connect the surface to the new TexInfo. */
	surface->unused1 = (Uint32)handle;

	DBG2(fprintf(stderr, "glSDL/wrapper: Allocated TexInfo %d.\n", handle));

	return texinfotab[handle];
}


static void FreeTexInfo(Uint32 handle)
{
	if(handle >= MAX_TEXINFOS)
		return;
	if(!texinfotab[handle])
		return;

	UnloadTexture(texinfotab[handle]);
	texinfotab[handle]->textures = 0;
	free(texinfotab[handle]->texture);
	texinfotab[handle]->texture = NULL;
	free(texinfotab[handle]);
	texinfotab[handle] = NULL;
	DBG2(fprintf(stderr, "glSDL/wrapper: Freed TexInfo %d.\n", handle));
}


/* Detach and free the texinfo of a surface. */
void glSDL_FreeTexInfo(SDL_Surface *surface)
{
	if(!texinfotab)
		return;

	if(!surface)
		return;

	if(!glSDL_GetTexInfo(surface))
		return;

	FreeTexInfo(surface->unused1);
	GLSDL_FIX_SURFACE(surface);
}


/*
 * Calculate chopping/tiling of a surface to
 * fit it into the smallest possible OpenGL
 * texture.
 */
static int CalcChop(SDL_Surface *s, glSDL_TexInfo *txi)
{
	int rows, vw, vh;
	int vertical = 0;
	int texsize;
	int lastw, lasth, minsize;

	vw = s->w;
	vh = s->h;

	DBG3(fprintf(stderr, "w=%d, h=%d ", vw, vh));
	if(vh > vw)
	{
		int t = vw;
		vw = vh;
		vh = t;
		vertical = 1;
		DBG3(fprintf(stderr, "(vertical) \t"));
	}

	/*
	 * Check whether this is a "huge" surface - at least one dimension
	 * must be <= than the maximum texture size, or we'll have to chop
	 * in both directions.
	 */
	if(vh > maxtexsize)
	{
		/*
		 * Very simple hack for now; we just tile
		 * both ways with maximum size textures.
		 */
		texsize = maxtexsize;

		txi->tilemode = GLSDL_TM_HUGE;
		txi->texsize = texsize;
		txi->tilew = texsize;
		txi->tileh = texsize;
		txi->tilespertex = 1;

		/* Calculate number of textures needed */
		txi->textures = (vw + texsize - 1) / texsize;
		txi->textures *= (vh + texsize - 1) / texsize;
		txi->texture = (int*)malloc(txi->textures * sizeof(int));
		memset(txi->texture, -1, txi->textures * sizeof(int));
		DBG5(fprintf(stderr, "two-way tiling; textures=%d\n", txi->textures));
		if(!txi->texture)
		{
			fprintf(stderr, "glSDL/wrapper: INTERNAL ERROR: Failed to allocate"
					" texture name table!\n");
			return -3;
		}
		return 0;
	}

	/* Calculate minimum size */
	rows = 1;
	lastw = vw;
	lasth = vh;
	minsize = lastw > lasth ? lastw : lasth;
	while(1)
	{
		int w, h, size;
		++rows;
		w = vw / rows;
		h = rows * vh;
		size = w > h ? w : h;
		if(size >= minsize)
		{
			--rows;
			break;
		}
		lastw = w;
		lasth = h;
		minsize = size;
	}
	if(minsize > maxtexsize)
	{
		/* Handle multiple textures for very wide/tall surfaces. */
		minsize = maxtexsize;
		rows = (vw + minsize-1) / minsize;
	}
	DBG3(fprintf(stderr, "==> minsize=%d ", minsize));
	DBG3(fprintf(stderr, "(rows=%d) \t", rows));

	/* Recalculate with nearest higher power-of-2 width. */
	for(texsize = 1; texsize < minsize; texsize <<= 1)
		;
	txi->texsize = texsize;
	rows = (vw + texsize-1) / texsize;
	DBG3(fprintf(stderr, "==> texsize=%d (rows=%d) \t", texsize, rows));

	/* Calculate number of tiles per texture */
	txi->tilespertex = txi->texsize / vh;
	DBG3(fprintf(stderr, "tilespertex=%d \t", txi->tilespertex));

	/* Calculate number of textures needed */
	txi->textures = (rows + txi->tilespertex-1) / txi->tilespertex;
	txi->texture = (int*)malloc(txi->textures * sizeof(int));
	memset(txi->texture, -1, txi->textures * sizeof(int));
	DBG3(fprintf(stderr, "textures=%d, ", txi->textures));
	if(!txi->texture)
	{
		fprintf(stderr, "glSDL/wrapper: INTERNAL ERROR: Failed to allocate"
				" texture name table!\n");
		return -2;
	}

	/* Set up tile size. (Only one axis supported here!) */
	if(1 == rows)
	{
		txi->tilemode = GLSDL_TM_SINGLE;
		if(vertical)
		{
			txi->tilew = vh;
			txi->tileh = vw;
		}
		else
		{
			txi->tilew = vw;
			txi->tileh = vh;
		}
	}
	else if(vertical)
	{
		txi->tilemode = GLSDL_TM_VERTICAL;
		txi->tilew = vh;
		txi->tileh = texsize;
	}
	else
	{
		txi->tilemode = GLSDL_TM_HORIZONTAL;
		txi->tilew = texsize;
		txi->tileh = vh;
	}

	DBG3(fprintf(stderr, "tilew=%d, tileh=%d\n", txi->tilew, txi->tileh));
	return 0;
}


/* Add a glSDL_TexInfo struct to an SDL_Surface */
static int glSDL_AddTexInfo(SDL_Surface *surface)
{
	glSDL_TexInfo *txi;

	if(!surface)
		return -1;
	if(IS_GLSDL_SURFACE(surface))
		return 0;	/* Do nothing */

	glSDL_AllocTexInfo(surface);
	txi = glSDL_GetTexInfo(surface);
	if(!txi)
		return -2;	/* Oops! Didn't get a texinfo... --> */

	if(CalcChop(surface, txi) < 0)
		return -3;

	SDL_SetClipRect(surface, NULL);

	return 0;
}


/* Create a surface of the prefered OpenGL RGB texture format */
static SDL_Surface *CreateRGBSurface(int w, int h)
{
	SDL_Surface *s;
	Uint32 rmask, gmask, bmask;
	int bits = 24;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	rmask = 0x00ff0000;
	gmask = 0x0000ff00;
	bmask = 0x000000ff;
#else
	rmask = 0x000000ff;
	gmask = 0x0000ff00;
	bmask = 0x00ff0000;
#endif
	s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
			bits, rmask, gmask, bmask, 0);
	if(s)
		GLSDL_FIX_SURFACE(s);

	glSDL_AddTexInfo(s);
	return s;
}


/* Create a surface of the prefered OpenGL RGBA texture format */
static SDL_Surface *CreateRGBASurface(int w, int h)
{
	SDL_Surface *s;
	Uint32 rmask, gmask, bmask, amask;
	int bits = 32;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	rmask = 0xff000000;
	gmask = 0x00ff0000;
	bmask = 0x0000ff00;
	amask = 0x000000ff;
#else
	rmask = 0x000000ff;
	gmask = 0x0000ff00;
	bmask = 0x00ff0000;
	amask = 0xff000000;
#endif
	s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
			bits, rmask, gmask, bmask, amask);
	if(s)
		GLSDL_FIX_SURFACE(s);

	glSDL_AddTexInfo(s);
	return s;
}


static void init_formats(void)
{
	SDL_Surface *s = CreateRGBSurface(1, 1);
	if(!s)
		return;
	RGBfmt = *(s->format);
	glSDL_FreeSurface(s);

	s = CreateRGBASurface(1, 1);
	if(!s)
		return;
	RGBAfmt = *(s->format);
	glSDL_FreeSurface(s);
}


static int FormatIsOk(SDL_Surface *surface)
{
	SDL_PixelFormat *pf;
	if(!surface)
		return 1;	/* Well, there ain't much we can do anyway... */

	pf = surface->format;

	/* Colorkeying always requires conversion! */
	if(surface->flags & SDL_SRCCOLORKEY)
		return 0;

	/* We need pitch == (width * BytesPerPixel) for glTex[Sub]Image2D() */
	if(surface->pitch != (surface->w * pf->BytesPerPixel))
		return 0;

	if(pf->Amask)
	{
		if(pf->BytesPerPixel != RGBAfmt.BytesPerPixel)
			return 0;
		if(pf->Rmask != RGBAfmt.Rmask)
			return 0;
		if(pf->Gmask != RGBAfmt.Gmask)
			return 0;
		if(pf->Bmask != RGBAfmt.Bmask)
			return 0;
		if(pf->Amask != RGBAfmt.Amask)
			return 0;
	}
	else
	{
		if(pf->BytesPerPixel != RGBfmt.BytesPerPixel)
			return 0;
		if(pf->Rmask != RGBfmt.Rmask)
			return 0;
		if(pf->Gmask != RGBfmt.Gmask)
			return 0;
		if(pf->Bmask != RGBfmt.Bmask)
			return 0;
	}
	return 1;
}



static void key2alpha(SDL_Surface *surface)
{
	int x, y;
#ifdef CKSTATS
	int transp = 0;
#endif
	Uint32 rgbmask = surface->format->Rmask |
			surface->format->Gmask |
			surface->format->Bmask;
	Uint32 ckey = surface->format->colorkey & rgbmask;
	if(SDL_LockSurface(surface) < 0)
		return;

	for(y = 0; y < surface->h; ++y)
	{
		Uint32 *px = (Uint32 *)((char *)surface->pixels + y*surface->pitch);
		for(x = 0; x < surface->w; ++x)
			if((px[x] & rgbmask) == ckey)
			{
				px[x] = 0;
#ifdef CKSTATS
				++transp;
#endif
			}
	}
#ifdef CKSTATS
	printf("glSDL/wrapper: key2alpha(); %dx%d surface, %d opaque pixels.\n",
			surface->w, surface->h,
			surface->w * surface->h - transp);
#endif
	SDL_UnlockSurface(surface);
}



/*----------------------------------------------------------
	SDL style API
----------------------------------------------------------*/

static void KillAllTextures(void)
{
	if(texinfotab)
	{
		unsigned i;
#ifdef LEAK_TRACKING
		int leaked = 0;
		for(i = 3; i < MAX_TEXINFOS + 1; ++i)
			if(texinfotab[i])
			{
				++leaked;
				fprintf(stderr, "glSDL/wrapper: Leaked TexInfo"
						" %d! (%d %dx%d textures)\n",
						i,
						texinfotab[i]->textures,
						texinfotab[i]->texsize,
						texinfotab[i]->texsize
						);
			}
		if(leaked)
			fprintf(stderr, "glSDL/wrapper: Leaked %d TexInfos!\n", leaked);
#endif
		for(i = 1; i < MAX_TEXINFOS + 1; ++i)
			FreeTexInfo(i);
		free(texinfotab);
		texinfotab = NULL;
	}
}

void glSDL_Quit(void)
{
	if(SDL_WasInit(SDL_INIT_VIDEO))
	{
		glSDL_FreeTexInfo(SDL_GetVideoSurface());
		if(fake_screen)
		{
			glSDL_FreeTexInfo(fake_screen);
			SDL_FreeSurface(fake_screen);
			fake_screen = NULL;
		}
		UnloadGL();
		SDL_QuitSubSystem(SDL_INIT_VIDEO);
	}
#ifndef LEAK_TRACKING
	KillAllTextures();
#endif
}


void glSDL_FullQuit(void)
{
#ifdef LEAK_TRACKING
	KillAllTextures();
#endif
	glSDL_Quit();
	SDL_Quit();
}


void glSDL_QuitSubSystem(Uint32 flags)
{
	if(flags & SDL_INIT_VIDEO)
		glSDL_Quit();
	SDL_QuitSubSystem(flags);
}


SDL_Surface *glSDL_SetVideoMode(int width, int height, int bpp, Uint32 flags)
{
	SDL_Surface *screen;
	GLint gl_doublebuf;

	if(!initialized)
	{
		glSDL_ResetState();
		initialized = 1;
	}

	if(USING_GLSDL)
	{
		glSDL_FreeTexInfo(SDL_GetVideoSurface());
		if(fake_screen)
		{
			glSDL_FreeTexInfo(fake_screen);
			SDL_FreeSurface(fake_screen);
			fake_screen = NULL;
		}
		UnloadGL();
		using_glsdl = 0;
	}

	if(!(flags & SDL_GLSDL))
	{
		screen = SDL_SetVideoMode(width, height, bpp, flags);
		if(screen)
			GLSDL_FIX_SURFACE(screen);
		return screen;
	}

	if((SDL_Linked_Version()->major <= 1) &&
			(SDL_Linked_Version()->minor <= 2) &&
			(SDL_Linked_Version()->patch < 5))
		fprintf(stderr, "glSDL/wrapper WARNING: Using SDL version"
				" 1.2.5 or later is strongly"
				" recommended!\n");

	if(LoadGL() < 0)
	{
		fprintf(stderr, "glSDL/wrapper ERROR: Could not load OpenGL library!\n");
		return NULL;
	}

/*
 * FIXME: Here's the place to insert proper handling of this call being
 *        used for resizing the window... For now, just make sure we
 *        don't end up with invalid texinfos and stuff no matter what.
 */
	KillAllTextures();

	texinfotab = (glSDL_TexInfo**)calloc(MAX_TEXINFOS + 1, sizeof(glSDL_TexInfo *));
	if(!texinfotab)
		return NULL;

	/* Remove flag to avoid confusion inside SDL - just in case! */
	flags &= ~SDL_GLSDL;

	flags |= SDL_OPENGL;
	if(bpp == 15)
	{
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	}
	else if(bpp == 16)
	{
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	}
	else if(bpp >= 24)
	{
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	}
	gl_doublebuf = (flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF;
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, gl_doublebuf);
	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, vsync);
	scale = 1;

	screen = SDL_SetVideoMode(width*scale, height*scale, bpp, flags);
	if(!screen)
	{
		KillAllTextures();
		return NULL;
	}

	GLSDL_FIX_SURFACE(screen);

#ifdef	FAKE_MAXTEXSIZE
	maxtexsize = FAKE_MAXTEXSIZE;
#else
	gl.GetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize);
#endif
	DBG(fprintf(stderr, "glSDL/wrapper: Max texture size: %d\n", maxtexsize));

	init_formats();
	gl_reset();

	if(glSDL_AddTexInfo(screen) < 0)
	{
		DBG(fprintf(stderr, "glSDL/wrapper: Failed to add info to screen surface!\n"));
		SDL_QuitSubSystem(SDL_INIT_VIDEO);
		return NULL;
	}

	glSDL_SetClipRect(screen, &screen->clip_rect);

	gl.Viewport(0, 0, screen->w * scale, screen->h * scale);
	/*
	 * Note that this projection is upside down in
	 * relation to the OpenGL coordinate system.
	 */
	gl.MatrixMode(GL_PROJECTION);
	gl.LoadIdentity();
	gl.Ortho(0, scale * (float)screen->w, scale * (float)screen->h, 0,
			-1.0, 1.0);

	gl.MatrixMode(GL_MODELVIEW);
	gl.LoadIdentity();
	gl.Translatef(0.0f, 0.0f, 0.0f);

	gl.Disable(GL_DEPTH_TEST);
	gl.Disable(GL_CULL_FACE);

	/*
	 * Create a software shadow buffer of the requested size.
	 * This is used for blit-from-screen and simulation of
	 * direct software rendering. (Dog slow crap. It's only
	 * legitimate use is probably screen shots.)
	 */
	fake_screen = CreateRGBSurface(screen->w / scale,
			screen->h / scale);
	using_glsdl = 1;
	return fake_screen;
}


SDL_Surface *glSDL_GetVideoSurface(void)
{
	if(fake_screen)
		return fake_screen;
	else
		return SDL_GetVideoSurface();
}


void glSDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects)
{
	if(IS_GLSDL_SURFACE(screen))
		glSDL_Flip(screen);
	else
		SDL_UpdateRects(screen, numrects, rects);
}


void glSDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h)
{
	SDL_Rect r;
	r.x = x;
	r.y = y;
	r.w = w;
	r.h = h;
	glSDL_UpdateRects(screen, 1, &r);
}


int glSDL_Flip(SDL_Surface *screen)
{
	if(!IS_GLSDL_SURFACE(screen))
		return SDL_Flip(screen);

	SDL_GL_SwapBuffers();
	return 0;
}


void glSDL_FreeSurface(SDL_Surface *surface)
{
	if(!surface)
		return;
	glSDL_FreeTexInfo(surface);
	SDL_FreeSurface(surface);
}


int glSDL_LockSurface(SDL_Surface *surface)
{
	if(!surface)
		return 0;

	if(IS_GLSDL_SURFACE(surface))
	{
		if((surface == fake_screen) ||
				(SDL_GetVideoSurface() == surface))
		{
			if(scale > 1)
				return -1;

			glSDL_Invalidate(fake_screen, NULL);

			gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
					fake_screen->pitch /
					fake_screen->format->BytesPerPixel);

			gl.ReadPixels(0, 0, fake_screen->w, fake_screen->h,
					GL_RGB, GL_UNSIGNED_BYTE,
					fake_screen->pixels);
			return 0;
		}
		else
		{
			glSDL_Invalidate(surface, NULL);
			return SDL_LockSurface(surface);
		}
	}
	else
		return SDL_LockSurface(surface);
}


void glSDL_UnlockSurface(SDL_Surface *surface)
{
	if(!surface)
		return;

	if(IS_GLSDL_SURFACE(surface))
	{
		glSDL_UploadSurface(surface);
		if((surface == fake_screen) ||
				(SDL_GetVideoSurface() == surface))
			glSDL_BlitGL(fake_screen, NULL,
					SDL_GetVideoSurface(), NULL);
	}
	else
		SDL_UnlockSurface(surface);
}


int glSDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key)
{
	int res = SDL_SetColorKey(surface, flag, key);
	if(res < 0)
		return res;
	/*
	 * If an application does this *after* SDL_DisplayFormat,
	 * we're basically screwed, unless we want to do an
	 * in-place surface conversion hack here.
	 *
	 * What we do is just kill the glSDL texinfo... No big
	 * deal in most cases, as glSDL only converts once anyway,
	 * *unless* you keep modifying the surface.
	 */
	if(IS_GLSDL_SURFACE(surface))
		glSDL_FreeTexInfo(surface);
	return res;
}


int glSDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha)
{
	/*
	 * This is just parameters to OpenGL, so the actual
	 * "work" is done in glSDL_BlitSurface().
	 */
	return SDL_SetAlpha(surface, flag, alpha);
}


SDL_bool glSDL_SetClipRect(SDL_Surface *surface, SDL_Rect *rect)
{
	SDL_bool res;
	SDL_Surface *screen;
	SDL_Rect fsr;
	if(!surface)
		return SDL_FALSE;

	screen = SDL_GetVideoSurface();

	res = SDL_SetClipRect(surface, rect);
	if(!res)
		return SDL_FALSE;

	if(!rect)
	{
		fsr.x = 0;
		fsr.y = 0;
		fsr.w = screen->w;
		fsr.h = screen->h;
		rect = &fsr;
	}
	if(surface == fake_screen)
	{
		SDL_Rect r;
		r.x = rect->x;
		r.y = rect->y;
		r.w = rect->w;
		r.h = rect->h;
		surface = screen;
		SDL_SetClipRect(surface, rect);
		return SDL_TRUE;
	}
	return res;
}


static int glSDL_BlitFromGL(SDL_Rect *srcrect,
		SDL_Surface *dst, SDL_Rect *dstrect)
{
	int i, sy0, dy0;
	SDL_Rect sr, dr;

	if(scale > 1)
		return -1;

	/* In case the destination has an OpenGL texture... */
	glSDL_Invalidate(dst, dstrect);

	/* Abuse the fake screen buffer a little. */
	gl.PixelStorei(GL_UNPACK_ROW_LENGTH, fake_screen->pitch /
			fake_screen->format->BytesPerPixel);
	if(srcrect)
	{
		sr = *srcrect;
		dr.x = dr.y = 0;
		dr.w = fake_screen->w;
		dr.h = fake_screen->h;
		clip_rect(&sr, &dr);
		gl.ReadPixels(sr.x, sr.y, sr.w, sr.h, GL_RGB, GL_UNSIGNED_BYTE,
				fake_screen->pixels);
	}
	else
	{
		sr.x = sr.y = 0;
		sr.w = dst->w;
		srcrect = &sr;
		gl.ReadPixels(0, 0, fake_screen->w, fake_screen->h,
				GL_RGB, GL_UNSIGNED_BYTE, fake_screen->pixels);
	}

	/* Blit to the actual target! (Vert. flip... Uuurgh!) */
	if(dstrect)
		dr = *dstrect;
	else
	{
		dr.x = dr.y = 0;
		dstrect = &dr;
	}

	i = srcrect->h;
	sy0 = srcrect->y;
	dy0 = dstrect->y + dstrect->h - 1;
	while(i--)
	{
		sr.y = sy0 + i;
		dr.y = dy0 - i;
		sr.h = 1;
		if(SDL_BlitSurface(fake_screen, &sr, dst, &dr) < 0)
			return -1;
	}
	return 0;
}


static __inline__ void BlitGL_single(glSDL_TexInfo *txi,
		float sx1, float sy1, SDL_Rect *dst, unsigned char alpha)
{
	float sx2, sy2, texscale;
	if(!txi->textures)
		return;
	if(-1 == txi->texture[0])
		return;
	gl_texture(txi->texture[0]);

	texscale = 1.0 / (float)txi->texsize;
	sx2 = (sx1 + (float)dst->w) * texscale;
	sy2 = (sy1 + (float)dst->h) * texscale;
	sx1 *= texscale;
	sy1 *= texscale;

	gl.Begin(GL_QUADS);
	gl.Color4ub(state.r, state.g, state.b, alpha);
	gl.TexCoord2f(sx1, sy1);
	gl.Vertex2i(dst->x, dst->y);
	gl.TexCoord2f(sx2, sy1);
	gl.Vertex2i(dst->x + dst->w, dst->y);
	gl.TexCoord2f(sx2, sy2);
	gl.Vertex2i(dst->x + dst->w, dst->y + dst->h);
	gl.TexCoord2f(sx1, sy2);
	gl.Vertex2i(dst->x, dst->y + dst->h);
	gl.End();
}


static void BlitGL_htile(glSDL_TexInfo *txi,
		float sx1, float sy1, SDL_Rect *dst, unsigned char alpha)
{
	int tex;
	float tile, sx2, sy2, yo;
	float texscale = 1.0 / (float)txi->texsize;
	float tileh = (float)txi->tileh * texscale;
	sx2 = (sx1 + (float)dst->w) * texscale;
	sy2 = (sy1 + (float)dst->h) * texscale;
	sx1 *= texscale;
	sy1 *= texscale;
	tile = floor(sx1);
	tex = (int)tile / txi->tilespertex;
	yo = ((int)tile % txi->tilespertex) * tileh;

	if(tex >= txi->textures)
		return;
	if(-1 == txi->texture[tex])
		return;
	gl_texture(txi->texture[tex]);

	gl.Begin(GL_QUADS);
	while(tile < sx2)
	{
		int tdx1 = dst->x;
		int tdx2 = dst->x + dst->w;
		float tsx1 = sx1 - tile;
		float tsx2 = sx2 - tile;

		/* Clip to current tile */
		if(tsx1 < 0.0)
		{
			tdx1 -= (int)(tsx1 * txi->texsize);
			tsx1 = 0.0;
		}
		if(tsx2 > 1.0)
		{
			tdx2 -= (int)((tsx2 - 1.0) * txi->texsize);
			tsx2 = 1.0;
		}

		/* Maybe select next texture? */
		if(yo + tileh > 1.0)
		{
			++tex;
			gl.End();
			if(tex >= txi->textures)
				return;
			if(-1 == txi->texture[tex])
				return;
			gl_texture(txi->texture[tex]);
			yo = 0.0;
			gl.Begin(GL_QUADS);
		}

		gl.Color4ub(state.r, state.g, state.b, alpha);
		gl.TexCoord2f(tsx1, yo + sy1);
		gl.Vertex2i(tdx1, dst->y);
		gl.TexCoord2f(tsx2, yo + sy1);
		gl.Vertex2i(tdx2, dst->y);
		gl.TexCoord2f(tsx2, yo + sy2);
		gl.Vertex2i(tdx2, dst->y + dst->h);
		gl.TexCoord2f(tsx1, yo + sy2);
		gl.Vertex2i(tdx1, dst->y + dst->h);

		tile += 1.0;
		yo += tileh;
	}
	gl.End();
}


static void BlitGL_vtile(glSDL_TexInfo *txi,
		float sx1, float sy1, SDL_Rect *dst, unsigned char alpha)
{
	int tex;
	float tile, sx2, sy2, xo;
	float texscale = 1.0 / (float)txi->texsize;
	float tilew = (float)txi->tilew * texscale;
	sx2 = (sx1 + (float)dst->w) * texscale;
	sy2 = (sy1 + (float)dst->h) * texscale;
	sx1 *= texscale;
	sy1 *= texscale;
	tile = floor(sy1);
	tex = (int)tile / txi->tilespertex;
	xo = ((int)tile % txi->tilespertex) * tilew;

	if(tex >= txi->textures)
		return;
	if(-1 == txi->texture[tex])
		return;
	gl_texture(txi->texture[tex]);

	gl.Begin(GL_QUADS);
	while(tile < sy2)
	{
		int tdy1 = dst->y;
		int tdy2 = dst->y + dst->h;
		float tsy1 = sy1 - tile;
		float tsy2 = sy2 - tile;

		/* Clip to current tile */
		if(tsy1 < 0.0)
		{
			tdy1 -= (int)(tsy1 * txi->texsize);
			tsy1 = 0.0;
		}
		if(tsy2 > 1.0)
		{
			tdy2 -= (int)((tsy2 - 1.0) * txi->texsize);
			tsy2 = 1.0;
		}

		/* Maybe select next texture? */
		if(xo + tilew > 1.0)
		{
			++tex;
			gl.End();
			if(tex >= txi->textures)
				return;
			if(-1 == txi->texture[tex])
				return;
			gl_texture(txi->texture[tex]);
			xo = 0.0;
			gl.Begin(GL_QUADS);
		}

		gl.Color4ub(state.r, state.g, state.b, alpha);
		gl.TexCoord2f(xo + sx1, tsy1);
		gl.Vertex2i(dst->x, tdy1);
		gl.TexCoord2f(xo + sx2, tsy1);
		gl.Vertex2i(dst->x + dst->w, tdy1);
		gl.TexCoord2f(xo + sx2, tsy2);
		gl.Vertex2i(dst->x + dst->w, tdy2);
		gl.TexCoord2f(xo + sx1, tsy2);
		gl.Vertex2i(dst->x, tdy2);

		tile += 1.0;
		xo += tilew;
	}
	gl.End();
}


static void BlitGL_hvtile(SDL_Surface *src, glSDL_TexInfo *txi,
		float sx1, float sy1, SDL_Rect *dst, unsigned char alpha)
{
	int x, y, last_tex, tex;
	float sx2, sy2;
	float texscale = 1.0 / (float)txi->texsize;
	int tilesperrow = (src->w + txi->tilew - 1) / txi->tilew;
	sx2 = (sx1 + (float)dst->w) * texscale;
	sy2 = (sy1 + (float)dst->h) * texscale;
	sx1 *= texscale;
	sy1 *= texscale;

	last_tex = tex = (int)(floor(sy1) * tilesperrow + floor(sx1));
	if(tex >= txi->textures)
		return;
	if(-1 == txi->texture[tex])
		return;
	gl_texture(txi->texture[tex]);

	gl.Begin(GL_QUADS);
	for(y = (int)floor(sy1); y < sy2; ++y)
	{
		int tdy1 = dst->y;
		int tdy2 = dst->y + dst->h;
		float tsy1 = sy1 - y;
		float tsy2 = sy2 - y;

		/* Clip to current tile */
		if(tsy1 < 0.0)
		{
			tdy1 -= (int)(tsy1 * txi->texsize);
			tsy1 = 0.0;
		}
		if(tsy2 > 1.0)
		{
			tdy2 -= (int)((tsy2 - 1.0) * txi->texsize);
			tsy2 = 1.0;
		}
		for(x = (int)floor(sx1); x < sx2; ++x)
		{
			int tdx1 = dst->x;
			int tdx2 = dst->x + dst->w;
			float tsx1 = sx1 - x;
			float tsx2 = sx2 - x;

			/* Clip to current tile */
			if(tsx1 < 0.0)
			{
				tdx1 -= (int)(tsx1 * txi->texsize);
				tsx1 = 0.0;
			}
			if(tsx2 > 1.0)
			{
				tdx2 -= (int)((tsx2 - 1.0) * txi->texsize);
				tsx2 = 1.0;
			}

			/* Select texture */
			tex = y * tilesperrow + x;
			if(tex != last_tex)
			{
				gl.End();
				if(tex >= txi->textures)
					return;
				if(-1 == txi->texture[tex])
					return;
				gl_texture(txi->texture[tex]);
				last_tex = tex;
				gl.Begin(GL_QUADS);
			}

			gl.Color4ub(state.r, state.g, state.b, alpha);
			gl.TexCoord2f(tsx1, tsy1);
			gl.Vertex2i(tdx1, tdy1);
			gl.TexCoord2f(tsx2, tsy1);
			gl.Vertex2i(tdx2, tdy1);
			gl.TexCoord2f(tsx2, tsy2);
			gl.Vertex2i(tdx2, tdy2);
			gl.TexCoord2f(tsx1, tsy2);
			gl.Vertex2i(tdx1, tdy2);
		}
	}
	gl.End();
}


/*
 * Calculate the actual blit rectangle and source offset
 * for a blit from a rectangle in a surface with specified
 * size to a surface with a cliprect.
 *
 * In:	rect	source rectangle
 *	w, h	source surface size
 *	(x, y)	destination coordinate
 *	clip	destination clip rectangle
 *
 * Out:	(x, y)	source top-left offset
 *	rect	destination rectangle
 *
 * Returns 1 if the result is visible, otherwise 0.
 */
static __inline__ int blitclip(SDL_Rect *rect, int w, int h,
		int *x, int *y, SDL_Rect *clip)
{
	int sx1, sy1, sx2, sy2;
	int dx1, dy1, dx2, dy2;

	/* Get source and destination coordinates */
	sx1 = rect->x;
	sy1 = rect->y;
	sx2 = sx1 + rect->w;
	sy2 = sy1 + rect->h;
	dx1 = *x;
	dy1 = *y;

	/* Keep source rect inside source surface */
	if(sx1 < 0)
	{
		dx1 -= sx1;
		sx1 = 0;
	}
	if(sy1 < 0)
	{
		dy1 -= sy1;
		sy1 = 0;
	}
	if(sx2 > w)
		sx2 = w;
	if(sy2 > h)
		sy2 = h;

	/* Cull blits from void space */
	if(sx1 >= sx2 || sy1 >= sy2)
		return 0;

	/* Calculate destination lower-right */
	dx2 = dx1 + (sx2 - sx1);
	dy2 = dy1 + (sy2 - sy1);

	if(clip)
	{
		/* Clip to destination cliprect */
		if(dx1 < clip->x)
		{
			sx1 += clip->x - dx1;
			dx1 = clip->x;
		}
		if(dy1 < clip->y)
		{
			sy1 += clip->y - dy1;
			dy1 = clip->y;
		}
		if(dx2 > clip->x + clip->w)
			dx2 = clip->x + clip->w;
		if(dy2 > clip->y + clip->h)
			dy2 = clip->y + clip->h;
	}

	/* Cull nop/off-screen blits */
	if(dx1 >= dx2 || dy1 >= dy2)
		return 0;

	*x = sx1;
	*y = sy1;
	rect->x = dx1;
	rect->y = dy1;
	rect->w = dx2 - dx1;
	rect->h = dy2 - dy1;
	return 1;
}


static int glSDL_BlitGL(SDL_Surface *src, SDL_Rect *srcrect,
			 SDL_Surface *dst, SDL_Rect *dstrect)
{
	glSDL_TexInfo *txi;
	SDL_Rect r;
	int x, y;
	unsigned char alpha;
	int plain = (state.rot == 0.0f) &&
			(state.sx == 1.0f) &&
			(state.sy == 1.0f);
	if(!src || !dst)
		return -1;

	/* Get source and destination coordinates */
	if(srcrect)
		r = *srcrect;
	else
	{
		r.x = r.y = 0;
		r.w = src->w;
		r.h = src->h;
	}
	if(dstrect)
	{
		x = dstrect->x;
		y = dstrect->y;
	}
	else
		x = y = 0;

	/* Clip! */
	if(!blitclip(&r, src->w, src->h, &x, &y,
			plain ? &dst->clip_rect : NULL))
	{
		if(dstrect)
			dstrect->w = dstrect->h = 0;
		return 0;
	}

	/* Write back the resulting cliprect */
	if(dstrect)
		*dstrect = r;

	/* Make sure we have a source with a valid texture */
	glSDL_UploadSurface(src);
	txi = glSDL_GetTexInfo(src);
	if(!txi)
		return -1;

	/* Set up blending */
	if(src->flags & (SDL_SRCALPHA | SDL_SRCCOLORKEY) ||
			(state.alpha != 255))
	{
		gl_blendfunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		gl_do_blend(1);
	}
	else
		gl_do_blend(0);

	/* Enable texturing */
	gl_do_texture(1);

	/*
	 * Note that we actually *prevent* the use of "full surface alpha"
	 * and alpha channel in combination - to stay SDL 2D compatible.
	 */
	if((src->flags & SDL_SRCALPHA) &&
			(!src->format->Amask || (src->flags & SDL_SRCCOLORKEY)))
		alpha = src->format->alpha;
	else
		alpha = 255;

	/* ...however, the GL only global state alpha is always applied! */
	alpha = (Uint32)alpha * state.alpha * 258 >> 16;

	/* Render! */
	if(!plain)
	{
		int rcx = (r.w >> 1) + state.cx;
		int rcy = (r.h >> 1) + state.cy;
		gl.PushMatrix();
		gl.Translatef(r.x + rcx, r.y + rcy, 0.0f);
		if(state.rot)
			gl.Rotated(state.rot, 0.0f, 0.0f, 1.0f);
		if(state.sx || state.sy)
			gl.Scalef(state.sx, state.sy, 1.0f);
		r.x = -rcx;
		r.y = -rcy;
	}
	switch(txi->tilemode)
	{
	  case GLSDL_TM_SINGLE:
		BlitGL_single(txi, x, y, &r, alpha);
		break;
	  case GLSDL_TM_HORIZONTAL:
		BlitGL_htile(txi, x, y, &r, alpha);
		break;
	  case GLSDL_TM_VERTICAL:
		BlitGL_vtile(txi, x, y, &r, alpha);
		break;
	  case GLSDL_TM_HUGE:
		BlitGL_hvtile(src, txi, x, y, &r, alpha);
		break;
	}
	if(!plain)
		gl.PopMatrix();
	return 0;
}


int glSDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect,
		SDL_Surface *dst, SDL_Rect *dstrect)
{
	SDL_Surface *vs;
	if(!src || !dst)
		return -1;

	/*
	 * Figure out what to do:
	 *      Not using glSDL:        SDL_BlitSurface()
	 *      screen->screen:         _glSDL_BlitFromGL() + _glSDL_BlitGL()
	 *      surface->screen:        _glSDL_BlitGL()
	 *      screen->surface:        _glSDL_BlitFromGL()
	 *      surface->surface:       SDL_BlitSurface()
	 */
	if(!USING_GLSDL)
		return SDL_BlitSurface(src, srcrect, dst, dstrect);

	vs = SDL_GetVideoSurface();
	if(src == fake_screen)
		src = vs;
	if(dst == fake_screen)
		dst = vs;
	if(src == vs)
	{
		if(dst == vs)
		{
			glSDL_BlitFromGL(srcrect, fake_screen, dstrect);
			return glSDL_BlitGL(fake_screen, srcrect,
					dst, dstrect);
		}
		else
		{
			return glSDL_BlitFromGL(srcrect, dst, dstrect);
		}
	}
	else
	{
		if(dst == vs)
		{
			return glSDL_BlitGL(src, srcrect,
					dst, dstrect);
		}
		else
		{
			glSDL_Invalidate(dst, dstrect);
			return SDL_BlitSurface(src, srcrect, dst, dstrect);
		}
	}
}


int glSDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color)
{
	SDL_Surface *vs = SDL_GetVideoSurface();
	SDL_PixelFormat *pf = dst->format;
	int dx1, dy1, dx2, dy2;
	Uint32 r, g, b;

	if(dst == fake_screen)
		dst = vs;
	if(vs != dst)
		glSDL_Invalidate(dst, dstrect);
	if((vs != dst) || !USING_GLSDL)
		return SDL_FillRect(dst, dstrect, color);

	if(dstrect)
	{
		dx1 = dstrect->x;
		dy1 = dstrect->y;
		dx2 = dx1 + dstrect->w;
		dy2 = dy1 + dstrect->h;
		if(dx1 < dst->clip_rect.x)
			dx1 = dst->clip_rect.x;
		if(dy1 < dst->clip_rect.y)
			dy1 = dst->clip_rect.y;
		if(dx2 > dst->clip_rect.x + dst->clip_rect.w)
			dx2 = dst->clip_rect.x + dst->clip_rect.w;
		if(dy2 > dst->clip_rect.y + dst->clip_rect.h)
			dy2 = dst->clip_rect.y + dst->clip_rect.h;
		dstrect->x = dx1;
		dstrect->y = dy1;
		dstrect->w = dx2 - dx1;
		dstrect->h = dy2 - dy1;
		if(!dstrect->w || !dstrect->h)
			return 0;
	}
	else
	{
		dx1 = dst->clip_rect.x;
		dy1 = dst->clip_rect.y;
		dx2 = dx1 + dst->clip_rect.w;
		dy2 = dy1 + dst->clip_rect.h;
	}

	r = color & pf->Rmask;
	r = r >> pf->Rshift;
	r = r << pf->Rloss;
	g = color & pf->Gmask;
	g = g >> pf->Gshift;
	g = g << pf->Gloss;
	b = color & pf->Bmask;
	b = b >> pf->Bshift;
	b = b << pf->Bloss;

	r = r * state.r * 258 >> 16;
	g = g * state.g * 258 >> 16;
	b = b * state.b * 258 >> 16;

	gl_do_texture(0);
	if(state.alpha != 255)
	{
		gl_blendfunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		gl_do_blend(1);
	}
	else
		gl_do_blend(0);

	gl.Begin(GL_QUADS);
	gl.Color4ub(r, g, b, state.alpha);
	gl.Vertex2i(dx1, dy1);
	gl.Vertex2i(dx2, dy1);
	gl.Vertex2i(dx2, dy2);
	gl.Vertex2i(dx1, dy2);
	gl.End();
	return 0;
}


SDL_Surface *glSDL_DisplayFormat(SDL_Surface *surface)
{
	SDL_Surface *s, *tmp;
	if(USING_GLSDL)
	{
		int use_rgba = (surface->flags & SDL_SRCCOLORKEY) ||
				((surface->flags & SDL_SRCALPHA) &&
				surface->format->Amask);
		if(use_rgba)
			tmp = SDL_ConvertSurface(surface, &RGBAfmt, SDL_SWSURFACE);
		else
			tmp = SDL_ConvertSurface(surface, &RGBfmt, SDL_SWSURFACE);
		if(!tmp)
			return NULL;
		GLSDL_FIX_SURFACE(tmp);
		SDL_SetAlpha(tmp, 0, 0);

		if(surface->flags & SDL_SRCCOLORKEY)
		{
			/*
			 * We drop colorkey data here, but we have to,
			 * or we'll run into trouble when converting,
			 * in particular from indexed color formats.
			 */
			SDL_SetColorKey(tmp, SDL_SRCCOLORKEY,
					surface->format->colorkey);
			key2alpha(tmp);
		}
		SDL_SetColorKey(tmp, 0, 0);

		if(use_rgba)
			s = CreateRGBASurface(surface->w, surface->h);
		else
			s = CreateRGBSurface(surface->w, surface->h);
		if(!s)
		{
			glSDL_FreeSurface(tmp);
			return NULL;
		}
		SDL_BlitSurface(tmp, NULL, s, NULL);
		glSDL_FreeSurface(tmp);

		if(surface->flags & SDL_SRCALPHA)
			SDL_SetAlpha(s, SDL_SRCALPHA,
					surface->format->alpha);
		return s;
	}
	else
	{
		s = SDL_DisplayFormat(surface);
		if(s)
			GLSDL_FIX_SURFACE(s);
		return s;
	}
}


SDL_Surface *glSDL_DisplayFormatAlpha(SDL_Surface *surface)
{
	SDL_Surface *s, *tmp;
	if(USING_GLSDL)
	{
		tmp = SDL_ConvertSurface(surface, &RGBAfmt, SDL_SWSURFACE);
		if(!tmp)
			return NULL;
		GLSDL_FIX_SURFACE(tmp);

		SDL_SetAlpha(tmp, 0, 0);
		SDL_SetColorKey(tmp, 0, 0);
		s = CreateRGBASurface(surface->w, surface->h);
		if(!s)
		{
			glSDL_FreeSurface(tmp);
			return NULL;
		}
		SDL_BlitSurface(tmp, NULL, s, NULL);
		glSDL_FreeSurface(tmp);

		if(surface->flags & SDL_SRCCOLORKEY)
		{
			SDL_SetColorKey(s, SDL_SRCCOLORKEY,
					surface->format->colorkey);
			key2alpha(s);
		}
		if(surface->flags & SDL_SRCALPHA)
			SDL_SetAlpha(s, SDL_SRCALPHA,
					surface->format->alpha);
		return s;
	}
	else
	{
		s = SDL_DisplayFormatAlpha(surface);
		if(s)
			GLSDL_FIX_SURFACE(s);
		return s;
	}
}


SDL_Surface *glSDL_ConvertSurface
			(SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags)
{
	SDL_Surface *s = SDL_ConvertSurface(src, fmt, flags);
	if(s)
		GLSDL_FIX_SURFACE(s);
	return s;
}


SDL_Surface *glSDL_CreateRGBSurface
			(Uint32 flags, int width, int height, int depth, 
			Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
{
	SDL_Surface *s = SDL_CreateRGBSurface(flags, width, height, depth, 
			Rmask, Gmask, Bmask, Amask);
	if(s)
		GLSDL_FIX_SURFACE(s);
	return s;
}


SDL_Surface *glSDL_CreateRGBSurfaceFrom(void *pixels,
			int width, int height, int depth, int pitch,
			Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
{
	SDL_Surface *s = SDL_CreateRGBSurfaceFrom(pixels,
			width, height, depth, pitch,
			Rmask, Gmask, Bmask, Amask);
	if(s)
		GLSDL_FIX_SURFACE(s);
	return s;
}


SDL_Surface *glSDL_LoadBMP(const char *file)
{
	SDL_Surface *s = SDL_LoadBMP(file);
	if(s)
		GLSDL_FIX_SURFACE(s);
	return s;
}


int glSDL_SaveBMP(SDL_Surface *surface, const char *file)
{
	SDL_Rect r;
	SDL_Surface *buf;
	SDL_Surface *screen = SDL_GetVideoSurface();

	if(!USING_GLSDL)
		return SDL_SaveBMP(surface, file);

	if((surface != screen) && (surface != fake_screen))
		return SDL_SaveBMP(surface, file);

	buf = CreateRGBSurface(fake_screen->w, fake_screen->h);

	r.x = 0;
	r.y = 0;
	r.w = fake_screen->w;
	r.h = fake_screen->h;
	if(glSDL_BlitFromGL(&r, buf, &r) < 0)
		return -1;
	
	return SDL_SaveBMP(buf, file);

	glSDL_FreeSurface(buf);
}




static int InitTexture(SDL_Surface *datasurf, glSDL_TexInfo *txi, int tex)
{
	gl.GenTextures(1, (unsigned int *)&txi->texture[tex]);
	gl.BindTexture(GL_TEXTURE_2D, txi->texture[tex]);
	gl.PixelStorei(GL_UNPACK_ROW_LENGTH, datasurf->pitch /
			datasurf->format->BytesPerPixel);
	gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.TexImage2D(GL_TEXTURE_2D, 0,
			datasurf->format->Amask ? GL_RGBA8 : GL_RGB8,
			txi->texsize, txi->texsize, 0,
			datasurf->format->Amask ? GL_RGBA : GL_RGB,
			GL_UNSIGNED_BYTE, NULL);
	print_glerror(1);
	return 0;
}


/* Image tiled horizontally (wide surface), or not at all */
static int UploadHoriz(SDL_Surface *datasurf, glSDL_TexInfo *txi)
{
	int bpp = datasurf->format->BytesPerPixel;
	int res;
	int tex = 0;
	int fromx = 0;
	int toy = txi->texsize;	/* To init first texture */
	while(1)
	{
		int thistw = datasurf->w - fromx;
		if(thistw > txi->tilew)
			thistw = txi->tilew;
		else if(thistw <= 0)
			break;
		if(toy + txi->tileh > txi->texsize)
		{
			toy = 0;
			res = InitTexture(datasurf, txi, tex);
			if(res < 0)
				return res;
			++tex;
		}
		gl.TexSubImage2D(GL_TEXTURE_2D, 0, 0, toy,
				thistw, txi->tileh,
				datasurf->format->Amask ? GL_RGBA : GL_RGB,
				GL_UNSIGNED_BYTE,
				(char *)datasurf->pixels + bpp * fromx);
		print_glerror(2);
		fromx += txi->tilew;
		toy += txi->tileh;
		gl.Flush();
	}
	return 0;
}


/* Image tiled vertically (tall surface) */
static int UploadVert(SDL_Surface *datasurf, glSDL_TexInfo *txi)
{
	int res;
	int tex = 0;
	int fromy = 0;
	int tox = txi->texsize;	/* To init first texture */
	while(1)
	{
		int thisth = datasurf->h - fromy;
		if(thisth > txi->tileh)
			thisth = txi->tileh;
		else if(thisth <= 0)
			break;
		if(tox + txi->tilew > txi->texsize)
		{
			tox = 0;
			res = InitTexture(datasurf, txi, tex);
			if(res < 0)
				return res;
			++tex;
		}
		gl.TexSubImage2D(GL_TEXTURE_2D, 0, tox, 0,
				txi->tilew, thisth,
				datasurf->format->Amask ? GL_RGBA : GL_RGB,
				GL_UNSIGNED_BYTE,
				(char *)datasurf->pixels + datasurf->pitch * fromy);
		print_glerror(3);
		fromy += txi->tileh;
		tox += txi->tilew;
		gl.Flush();
	}
	return 0;
}


/* Image tiled two-way (huge surface) */
static int UploadHuge(SDL_Surface *datasurf, glSDL_TexInfo *txi)
{
	int bpp = datasurf->format->BytesPerPixel;
	int res;
	int tex = 0;
	int y = 0;
	while(y < datasurf->h)
	{
		int x;
		int thisth = datasurf->h - y;
		if(thisth > txi->tileh)
			thisth = txi->tileh;
		x = 0;
		while(x < datasurf->w)
		{
			int thistw = datasurf->w - x;
			if(thistw > txi->tilew)
				thistw = txi->tilew;
			res = InitTexture(datasurf, txi, tex++);
			if(res < 0)
				return res;
			gl.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
					thistw, thisth,
					datasurf->format->Amask ? GL_RGBA : GL_RGB,
					GL_UNSIGNED_BYTE,
					(char *)datasurf->pixels +
					datasurf->pitch * y + bpp * x);
			print_glerror(4);
			x += txi->tilew;
			gl.Flush();
		}
		y += txi->tileh;
	}
	return 0;
}


/* Upload all textures for a surface. */
static int UploadTextures(SDL_Surface *datasurf, glSDL_TexInfo *txi)
{
	switch(txi->tilemode)
	{
	  case GLSDL_TM_SINGLE:
	  case GLSDL_TM_HORIZONTAL:
		UploadHoriz(datasurf, txi);
		break;
	  case GLSDL_TM_VERTICAL:
		UploadVert(datasurf, txi);
		break;
	  case GLSDL_TM_HUGE:
		UploadHuge(datasurf, txi);
		break;
	}
	return 0;
}


SDL_Surface *glSDL_IMG_Load(const char *file)
{
	SDL_Surface *s;
	s = IMG_Load(file);
	if(s)
		GLSDL_FIX_SURFACE(s);
	return s;
}


/*----------------------------------------------------------
	glSDL specific API extensions
----------------------------------------------------------*/

int glSDL_VSync(int use)
{
	int old = vsync;
	if(use >= 0)
		vsync = use;
	return old;
}


void glSDL_Invalidate(SDL_Surface *surface, SDL_Rect *area)
{
	glSDL_TexInfo *txi;
	if(!surface)
		return;
	txi = glSDL_GetTexInfo(surface);
	if(!txi)
		return;
	if(!area)
	{
		txi->invalid_area.x = 0;
		txi->invalid_area.y = 0;
		txi->invalid_area.w = surface->w;
		txi->invalid_area.h = surface->h;
		return;
	}
	txi->invalid_area = *area;
}


int glSDL_UploadSurface(SDL_Surface *surface)
{
	SDL_Surface *datasurf = surface;
	glSDL_TexInfo *txi;
	int i;
	/* 
	 * For now, we just assume that *every* texture needs
	 * conversion before uploading.
	 */

	/* If there's no TexInfo, add one. */
	if(!IS_GLSDL_SURFACE(surface))
		glSDL_AddTexInfo(surface);

	txi = glSDL_GetTexInfo(surface);
	if(!txi)
		return -1;

	/* No partial updates implemented yet... */
	if(txi->invalid_area.w)
		glSDL_UnloadSurface(surface);
	else
	{
		int missing = 0;
		if(txi->textures)
		{
			for(i = 0; i < txi->textures; ++i)
				if(-1 == txi->texture[i])
				{
					missing = 1;
					break;
				}
			if(!missing)
				return 0;	/* They're already there! */
		}
	}

	if(txi->texsize > maxtexsize)
	{
		fprintf(stderr, "glSDL/wrapper: INTERNAL ERROR: Too large texture!\n");
		return -1;	/* This surface wasn't tiled properly... */
	}

	/*
	 * Kludge: Convert if not of preferred RGB or RGBA format.
	 *
	 *	Conversion should only be done when *really* needed.
	 *	That is, it should rarely have to be done with OpenGL
	 *	1.2+.
	 *
	 *	Besides, any surface that's been SDL_DisplayFormat()ed
	 *	should already be in the best known OpenGL format -
	 *	preferably one that makes DMA w/o conversion possible.
	 */
	if(FormatIsOk(surface))
		datasurf = surface;
	else
	{
		DBG(fprintf(stderr, "glSDL/wrapper: WARNING: On-the-fly conversion performed!\n"));
		if(surface->format->Amask)
			datasurf = glSDL_DisplayFormatAlpha(surface);
		else
			datasurf = glSDL_DisplayFormat(surface);
		if(!datasurf)
			return -2;
	}

	if(UploadTextures(datasurf, txi) < 0)
		return -3;

	if(datasurf != surface)
		glSDL_FreeSurface(datasurf);
	return 0;
}


static void UnloadTexture(glSDL_TexInfo *txi)
{
	int i;
	if(SDL_WasInit(SDL_INIT_VIDEO))
		for(i = 0; i < txi->textures; ++i)
			gl.DeleteTextures(1, (unsigned int *)&txi->texture[i]);
	memset(&txi->invalid_area, 0, sizeof(txi->invalid_area));
}


void glSDL_UnloadSurface(SDL_Surface *surface)
{
	glSDL_TexInfo *txi;
	if(!IS_GLSDL_SURFACE(surface))
		return;

	txi = glSDL_GetTexInfo(surface);
	if(txi)
		UnloadTexture(txi);
}


#else	/* HAVE_OPENGL */

/*----------------------------------------------------------
	Dummies for glSDL extensions that require OpenGL
----------------------------------------------------------*/

void glSDL_Invalidate(SDL_Surface *surface, SDL_Rect *area)
{
}

int glSDL_UploadSurface(SDL_Surface *surface)
{
	return 0;
}

void glSDL_UnloadSurface(SDL_Surface *surface)
{
}

int glSDL_VSync(int use)
{
	return 0;
}

void glSDL_SetBlendAlpha(Uint8 alpha)
{
}

void glSDL_SetBlendColor(Uint8 r, Uint8 g, Uint8 b)
{
}

void glSDL_SetCenter(float x, float y)
{
}

void glSDL_SetRotation(float angle)
{
}

void glSDL_SetScale(float x, float y)
{
}

void glSDL_ResetState(void)
{
}

#endif	/* HAVE_OPENGL */
