/*
** Haaf's Game Engine 1.8
** Copyright (C) 2003-2007, Relish Games
** hge.relishgames.com
**
** Core functions implementation: graphics
*/

// !!! FIXME: the texture data when locking/unlocking textures is in GL_BGRA format, not GL_RGBA.
// !!! FIXME:  ...but this mistake wasn't noticed for several games, since most didn't lock outside
// !!! FIXME:  of a piece of code that was #ifdef'd for Unix anyhow.
// !!! FIXME: But if you lock textures and the colors are wrong, that's what happened. We need to
// !!! FIXME:  sort out all the places where we're passing things around in RGBA to fix this.
// !!! FIXME:  In the mean time, it's usually easier to just change your application to expect
// !!! FIXME:  locked textures to be RGBA instead of BGRA.

// !!! FIXME: ...apparently we're locking textures upside down, too?

#include "hge_impl.h"

#define SUPPORT_CXIMAGE 1
#if SUPPORT_CXIMAGE
// conflict with Mac OS X 10.3.9 SDK...
#ifdef _T
#undef _T
#endif
#include "CxImage/ximage.h"
#else
/* Use DevIL instead of CXImage */
#include <IL/il.h>
#include <IL/ilu.h>
#endif

// avoiding glext.h here ...
#ifndef GL_TEXTURE_RECTANGLE_ARB
#define GL_TEXTURE_RECTANGLE_ARB 0x84F5
#endif
#ifndef GL_FRAMEBUFFER_EXT
#define GL_FRAMEBUFFER_EXT 0x8D40
#endif
#ifndef GL_RENDERBUFFER_EXT
#define GL_RENDERBUFFER_EXT 0x8D41
#endif
#ifndef GL_COLOR_ATTACHMENT0_EXT
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
#endif
#ifndef GL_DEPTH_ATTACHMENT_EXT
#define GL_DEPTH_ATTACHMENT_EXT 0x8D00
#endif
#ifndef GL_FRAMEBUFFER_COMPLETE_EXT
#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
#ifndef GL_YCBCR_422_APPLE
#define GL_YCBCR_422_APPLE 0x85B9
#endif
#ifndef GL_UNSIGNED_SHORT_8_8_APPLE
#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA
#endif
#ifndef GL_UNSIGNED_SHORT_8_8_REV_APPLE
#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB
#endif
static const char* GRAPHICS_SRC_FN="hge/graphics.cpp";
struct gltexture
{
	GLuint name;
	GLuint width;
	GLuint height;
	GLuint potw;  // Power-of-two width.
	GLuint poth;  // Power-of-two height.
	const char *filename;  // if backed by a file, not a managed buffer.
	DWORD *pixels;  // original rgba data.
	DWORD *lock_pixels;  // for locked texture
	bool is_render_target;
	bool lost;
	bool lock_readonly;
	GLint lock_x;
	GLint lock_y;
	GLint lock_width;
	GLint lock_height;
};

static DWORD *_DecodeImage(BYTE *data, const char *fname, DWORD size, int &width, int &height)
{
	width = height = 0;

	DWORD *pixels = NULL;
	const size_t fnamelen = fname ? strlen(fname) : 0;
	if ( (fnamelen > 5) && (strcasecmp((fname + fnamelen) - 5, ".rgba") == 0) )
	{
		DWORD *ptr = (DWORD *) data;
		DWORD w = ptr[0];
		DWORD h = ptr[1];
		BYTESWAP(w);
		BYTESWAP(h);
		if ( ((w * h * 4) + 8) == size )  // not truncated?
		{
			width = (int) w;
			height = (int) h;
			pixels = new DWORD[width * height];
			memcpy(pixels, ptr + 2, w * h * 4);  // !!! FIXME: ignores pitch.
		}
		return pixels;
	}

#if SUPPORT_CXIMAGE
	CxImage img;
	img.Decode(data, size, CXIMAGE_FORMAT_UNKNOWN);
	if (img.IsValid())
	{
		width = img.GetWidth();
		height = img.GetHeight();
		pixels = new DWORD[width * height];
		BYTE *wptr = (BYTE *) pixels;
		const bool hasalpha = img.AlphaIsValid();
		for (int y = 0; y < height; y++)
		{
			for (int x = 0; x < width; x++)
			{
				const RGBQUAD rgb = img.GetPixelColor(x, y, true);
				*(wptr++) = rgb.rgbRed;
				*(wptr++) = rgb.rgbGreen;
				*(wptr++) = rgb.rgbBlue;
				*(wptr++) = hasalpha ? rgb.rgbReserved : 0xFF;  // alpha.
			}
		}
	}
#else
	ilInit();
	iluInit();

	ILuint id;
	ilGenImages(1, &id);

	if(ilLoadImage(fname)) {
		printf("success: %s\n", fname);
		ILinfo info;
		iluGetImageInfo(&info);
		width = info.Width;
		height = info.Height;
		size = info.SizeOfData;
		pixels = new DWORD[width * height];
		ilCopyPixels(0, 0, 0, width, height, 0, IL_RGBA, IL_UNSIGNED_INT, pixels);
		ilShutDown();
	}
#endif

	return pixels;
}


void HGE_Impl::_BindTexture(gltexture *t)
{
	// The Direct3D renderer is using managed textures, so they aren't every
	//  actually "lost" ... we may have to rebuild them here, though.
	if ((t != NULL) && (t->lost))
		_ConfigureTexture(t, t->width, t->height, t->pixels);

	if ( ((HTEXTURE)t) != CurTexture )
	{
		pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, t ? t->name : 0);
		CurTexture = (HTEXTURE) t;
	}
}

void CALL HGE_Impl::Gfx_Clear(DWORD color)
{
	GLbitfield flags = GL_COLOR_BUFFER_BIT;
	if ( ((pCurTarget) && (pCurTarget->depth)) || bZBuffer )
		flags |= GL_DEPTH_BUFFER_BIT;

	const GLfloat a = ((GLfloat) ((color >> 24) & 0xFF)) / 255.0f;
	const GLfloat r = ((GLfloat) ((color >> 16) & 0xFF)) / 255.0f;
	const GLfloat g = ((GLfloat) ((color >>  8) & 0xFF)) / 255.0f;
	const GLfloat b = ((GLfloat) ((color >>  0) & 0xFF)) / 255.0f;
	pOpenGLDevice->glClearColor(r, g, b, a);
	pOpenGLDevice->glClear(flags);
}

void CALL HGE_Impl::Gfx_SetClipping(int x, int y, int w, int h)
{
	int scr_width, scr_height;
	struct { int X; int Y; int Width; int Height; float MinZ; float MaxZ; } vp;

	if(!pCurTarget) {
		scr_width=pHGE->System_GetStateInt(HGE_SCREENWIDTH);
		scr_height=pHGE->System_GetStateInt(HGE_SCREENHEIGHT);
	}
	else {
		scr_width=Texture_GetWidth(pCurTarget->tex);
		scr_height=Texture_GetHeight(pCurTarget->tex);
	}

	if(!w) {
		vp.X=0;
		vp.Y=0;
		vp.Width=scr_width;
		vp.Height=scr_height;
	}
	else
	{
		if(x<0) { w+=x; x=0; }
		if(y<0) { h+=y; y=0; }

		if(x+w > scr_width) w=scr_width-x;
		if(y+h > scr_height) h=scr_height-y;

		vp.X=x;
		vp.Y=y;
		vp.Width=w;
		vp.Height=h;
	}

	if ((clipX == vp.X) && (clipY == vp.Y) && (clipW == vp.Width) && (clipH == vp.Height))
		return;   // nothing to do here, don't call into the GL.

	vp.MinZ=0.0f;
	vp.MaxZ=1.0f;

	_render_batch();

	clipX = vp.X;
	clipY = vp.Y;
	clipW = vp.Width;
	clipH = vp.Height;
	pOpenGLDevice->glScissor(vp.X, (scr_height-vp.Y)-vp.Height, vp.Width, vp.Height);
}

void CALL HGE_Impl::Gfx_SetTransform(float x, float y, float dx, float dy, float rot, float hscale, float vscale)
{
	if ((x == 0.0f) && (y == 0.0f) && (dx == 0.0f) && (dy == 0.0f) && (rot == 0.0f) && (hscale == 0.0f) && (vscale == 0.0f))
	{
		//reset everything
		pOpenGLDevice->glMatrixMode(GL_MODELVIEW);
		pOpenGLDevice->glLoadIdentity();
		bTransforming=false;
		return;
	}

	_render_batch();
	bTransforming = true;

	pOpenGLDevice->glMatrixMode(GL_MODELVIEW);
	//we have to reset the matrix in all cases.
	//or this would cause insane transforming...
	pOpenGLDevice->glLoadIdentity();
	pOpenGLDevice->glTranslatef(-x, -y, 0.0f);
	pOpenGLDevice->glScalef(hscale, vscale, 1.0f);
	pOpenGLDevice->glRotatef(rot, 0.0f, 0.0f, 1.0f);
	pOpenGLDevice->glTranslatef(x+dx, y+dy, 0.0f);
}

bool CALL HGE_Impl::Gfx_BeginScene(HTARGET targ)
{
	CRenderTargetList *target=(CRenderTargetList *)targ;

	if(VertArray)
	{
		_PostError("Gfx_BeginScene: Scene is already being rendered");
		return false;
	}

	if(target != pCurTarget)
	{
		if (pOpenGLDevice->have_GL_EXT_framebuffer_object)
			pOpenGLDevice->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, (target) ? target->frame : 0);

		if ( ((target) && (target->depth)) || (bZBuffer) )
			pOpenGLDevice->glEnable(GL_DEPTH_TEST);
		else
			pOpenGLDevice->glDisable(GL_DEPTH_TEST);

		// d3d's SetRenderTarget() forces the viewport to surface size...
		if (target)
		{
			pOpenGLDevice->glScissor(0, 0, target->width, target->height);
			pOpenGLDevice->glViewport(0, 0, target->width, target->height);
			_SetProjectionMatrix(target->width, target->height);
		}
		else
		{
			pOpenGLDevice->glScissor(0, 0, nScreenWidth, nScreenHeight);
			pOpenGLDevice->glViewport(0, 0, nScreenWidth, nScreenHeight);
			_SetProjectionMatrix(nScreenWidth, nScreenHeight);
		}

		pOpenGLDevice->glMatrixMode(GL_MODELVIEW);
		pOpenGLDevice->glLoadIdentity();

		pCurTarget=target;
	}

	VertArray = pVB;
	return true;
}

void CALL HGE_Impl::Gfx_EndScene()
{
	_render_batch(true);

	// no "real" render targets? Push the framebuffer to a texture.
	// This is not going to work in lots of legitimate scenarios, but it will
	//  most of the time, so it's better than nothing when you lack FBOs.
	if ((pCurTarget) && (!pOpenGLDevice->have_GL_EXT_framebuffer_object))
	{
		gltexture *pTex = (gltexture *) pCurTarget->tex;
		if ((pTex != NULL) && (pTex->lost))
			_ConfigureTexture(pTex, pTex->width, pTex->height, pTex->pixels);

		const int width = pCurTarget->width;
		const int height = pCurTarget->height;
		pOpenGLDevice->glFinish();
		DWORD *pixels = new DWORD[width * height];
		pOpenGLDevice->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
		pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, pTex->name);
		pOpenGLDevice->glTexSubImage2D(pOpenGLDevice->TextureTarget, 0, 0, 0, width, height,
		                               GL_RGBA, GL_UNSIGNED_BYTE, pixels);
		pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, CurTexture ? (((gltexture *) CurTexture)->name) : 0);
		delete[] pixels;
	}

	if(!pCurTarget) SDL_GL_SwapBuffers();
	//const GLenum err = pOpenGLDevice->glGetError();
	//if (err != GL_NO_ERROR) printf("GL error! 0x%X\n", (int) err);
	//Gfx_Clear(0xFF | (0xFF<<24) | (random() & 0xFF << 16) | (random() & 0xFF << 8));
	//Gfx_Clear(0xFF000000);
}

void HGE_Impl::_SetTextureFilter()
{
	const GLenum filter = (bTextureFilter) ? GL_LINEAR : GL_NEAREST;
	pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_MIN_FILTER, filter);
	pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_MAG_FILTER, filter);
}


bool HGE_Impl::_PrimsOutsideClipping(const hgeVertex *v, const int verts)
{
	if (bTransforming)
		return false;  // screw it, let the GL do the clipping.

	const int maxX = clipX + clipW;
	const int maxY = clipY + clipH;
	for (int i = 0; i < verts; i++, v++)
	{
		const int x = v->x;
		const int y = v->y;
		if ((x > clipX) && (x < maxX) && (y > clipY) && (y < maxY))
			return false;
	}
	return true;
}


void CALL HGE_Impl::Gfx_RenderLine(float x1, float y1, float x2, float y2, DWORD color, float z)
{
	if (VertArray)
	{
		if(CurPrimType!=HGEPRIM_LINES || nPrim>=VERTEX_BUFFER_SIZE/HGEPRIM_LINES || CurTexture || CurBlendMode!=BLEND_DEFAULT)
		{
			_render_batch();

			CurPrimType=HGEPRIM_LINES;
			if(CurBlendMode != BLEND_DEFAULT) _SetBlendMode(BLEND_DEFAULT);
			_BindTexture(NULL);
		}

		int i=nPrim*HGEPRIM_LINES;
		VertArray[i].x = x1; VertArray[i+1].x = x2;
		VertArray[i].y = y1; VertArray[i+1].y = y2;
		VertArray[i].z     = VertArray[i+1].z = z;
		VertArray[i].col   = VertArray[i+1].col = color;
		VertArray[i].tx    = VertArray[i+1].tx =
		VertArray[i].ty    = VertArray[i+1].ty = 0.0f;

		if (!_PrimsOutsideClipping(&VertArray[i], HGEPRIM_LINES))
			nPrim++;
	}
}

template <class T> static inline const T Min(const T a, const T b) { return a < b ? a : b; }
template <class T> static inline const T Max(const T a, const T b) { return a > b ? a : b; }

void CALL HGE_Impl::Gfx_RenderTriple(const hgeTriple *triple)
{
	if (VertArray)
	{
		const hgeVertex *v = triple->v;
		if (_PrimsOutsideClipping(v, HGEPRIM_TRIPLES))
		{
			// check for overlap, despite triangle points being outside clipping...
			const int maxX = clipX + clipW;
			const int maxY = clipY + clipH;
			const int leftmost = Min(Min(v[0].x, v[1].x), v[2].x);
			const int rightmost = Max(Max(v[0].x, v[1].x), v[2].x);
			const int topmost = Min(Min(v[0].y, v[1].y), v[2].y);
			const int bottommost = Max(Max(v[0].y, v[1].y), v[2].y);
			if ( ((clipX < leftmost) || (clipX > rightmost)) &&
			     ((maxX < leftmost) || (maxX > rightmost)) &&
			     ((clipY < topmost) || (clipY > bottommost)) &&
			     ((maxY < topmost) || (maxY > bottommost)) )
				return;  // no, this is really totally clipped.
		}

		if(CurPrimType!=HGEPRIM_TRIPLES || nPrim>=VERTEX_BUFFER_SIZE/HGEPRIM_TRIPLES || CurTexture!=triple->tex || CurBlendMode!=triple->blend)
		{
			_render_batch();

			CurPrimType=HGEPRIM_TRIPLES;
			if(CurBlendMode != triple->blend) _SetBlendMode(triple->blend);
			_BindTexture((gltexture *) triple->tex);
		}

		memcpy(&VertArray[nPrim*HGEPRIM_TRIPLES], triple->v, sizeof(hgeVertex)*HGEPRIM_TRIPLES);
		nPrim++;
	}
}

void CALL HGE_Impl::Gfx_RenderQuad(const hgeQuad *quad)
{
	if (VertArray)
	{
		const hgeVertex *v = quad->v;
		if (_PrimsOutsideClipping(v, HGEPRIM_QUADS))
		{
			// check for overlap, despite quad points being outside clipping...
			const int maxX = clipX + clipW;
			const int maxY = clipY + clipH;
			const int leftmost = Min(Min(Min(v[0].x, v[1].x), v[2].x), v[3].x);
			const int rightmost = Max(Max(Max(v[0].x, v[1].x), v[2].x), v[3].x);
			const int topmost = Min(Min(Min(v[0].y, v[1].y), v[2].y), v[3].y);
			const int bottommost = Max(Max(Max(v[0].y, v[1].y), v[2].y), v[3].y);
			if ( ((clipX < leftmost) || (clipX > rightmost)) &&
			     ((maxX < leftmost) || (maxX > rightmost)) &&
			     ((clipY < topmost) || (clipY > bottommost)) &&
			     ((maxY < topmost) || (maxY > bottommost)) )
				return;  // no, this is really totally clipped.
		}

		if(CurPrimType!=HGEPRIM_QUADS || nPrim>=VERTEX_BUFFER_SIZE/HGEPRIM_QUADS || CurTexture!=quad->tex || CurBlendMode!=quad->blend)
		{
			_render_batch();

			CurPrimType=HGEPRIM_QUADS;
			if(CurBlendMode != quad->blend) _SetBlendMode(quad->blend);
			_BindTexture((gltexture *) quad->tex);
		}

		memcpy(&VertArray[nPrim*HGEPRIM_QUADS], quad->v, sizeof(hgeVertex)*HGEPRIM_QUADS);
		nPrim++;
	}
}

hgeVertex* CALL HGE_Impl::Gfx_StartBatch(int prim_type, HTEXTURE tex, int blend, int *max_prim)
{
	if(VertArray)
	{
		_render_batch();

		CurPrimType=prim_type;
		if(CurBlendMode != blend) _SetBlendMode(blend);
		_BindTexture((gltexture *) tex);
		*max_prim=VERTEX_BUFFER_SIZE / prim_type;
		return VertArray;
	}
	else return 0;
}

void CALL HGE_Impl::Gfx_FinishBatch(int nprim)
{
	nPrim = nprim;
}

bool HGE_Impl::_BuildTarget(CRenderTargetList *pTarget, GLuint texname, int width, int height, bool zbuffer)
{
	bool okay = true;  // no FBOs? Fake success by default.
	if (pOpenGLDevice->have_GL_EXT_framebuffer_object)
	{
		pOpenGLDevice->glGenFramebuffersEXT(1, &pTarget->frame);
		if (zbuffer)
			pOpenGLDevice->glGenRenderbuffersEXT(1, &pTarget->depth);
		pOpenGLDevice->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, pTarget->frame);
		pOpenGLDevice->glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, pOpenGLDevice->TextureTarget, texname, 0);
		if (zbuffer)
		{
			pOpenGLDevice->glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, pTarget->depth);
			pOpenGLDevice->glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height);
			pOpenGLDevice->glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, pTarget->depth);
		}

		GLenum rc = pOpenGLDevice->glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if ((rc == GL_FRAMEBUFFER_COMPLETE_EXT) && (pOpenGLDevice->glGetError() == GL_NO_ERROR))
		{
			pOpenGLDevice->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			okay = true;
		}
		else
		{
			pOpenGLDevice->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
			pOpenGLDevice->glDeleteRenderbuffersEXT(1, &pTarget->depth);
			pOpenGLDevice->glDeleteFramebuffersEXT(1, &pTarget->frame);
		}
		pOpenGLDevice->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, pCurTarget ? pCurTarget->frame : 0);
	}

	return okay;
}

HTARGET CALL HGE_Impl::Target_Create(int width, int height, bool zbuffer)
{
	bool okay = false;
	CRenderTargetList *pTarget = new CRenderTargetList;
	memset(pTarget, '\0', sizeof (CRenderTargetList));

	pTarget->tex = _BuildTexture(width, height, NULL);
	gltexture *gltex = (gltexture *) pTarget->tex;
	gltex->is_render_target = true;
	gltex->lost = false;
	_ConfigureTexture(gltex, width, height, NULL);

	pTarget->width = width;
	pTarget->height = height;

	okay = _BuildTarget(pTarget, gltex->name, width, height, zbuffer);
	if (!okay)
	{
		System_Log("%s: OpenGL: Failed to create render target!",GRAPHICS_SRC_FN);
		Texture_Free(pTarget->tex);
		delete pTarget;
		return 0;
	}

	pTarget->next=pTargets;
	pTargets=pTarget;

	return (HTARGET)pTarget;
}

void CALL HGE_Impl::Target_Free(HTARGET target)
{
	CRenderTargetList *pTarget=pTargets, *pPrevTarget=NULL;

	while(pTarget)
	{
		if((CRenderTargetList *)target == pTarget)
		{
			if(pPrevTarget)
				pPrevTarget->next = pTarget->next;
			else
				pTargets = pTarget->next;

			if (pOpenGLDevice->have_GL_EXT_framebuffer_object)
			{
				if (pCurTarget == (CRenderTargetList *)target)
					pOpenGLDevice->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
				if (pTarget->depth)
					pOpenGLDevice->glDeleteRenderbuffersEXT(1, &pTarget->depth);
				pOpenGLDevice->glDeleteFramebuffersEXT(1, &pTarget->frame);
			}

			if (pCurTarget == (CRenderTargetList *)target)
				pCurTarget = 0;

			Texture_Free(pTarget->tex);
			delete pTarget;
			return;
		}

		pPrevTarget = pTarget;
		pTarget = pTarget->next;
	}
}

HTEXTURE CALL HGE_Impl::Target_GetTexture(HTARGET target)
{
	CRenderTargetList *targ=(CRenderTargetList *)target;
	if(target) return targ->tex;
	else return 0;
}

static inline bool _IsPowerOfTwo(const GLuint x)
{
	return ((x & (x - 1)) == 0);
}

static inline GLuint _NextPowerOfTwo(GLuint x)
{
	x--;
	for (unsigned i = 1; i < (sizeof(GLuint) * 8); i <<= 1)
		x |= x >> i;
	return x + 1;
}

void HGE_Impl::_ConfigureTexture(gltexture *t, int width, int height, DWORD *pixels)
{
	GLuint tex = 0;
	pOpenGLDevice->glGenTextures(1, &tex);

	t->lost = false;
	t->name = tex;
	t->width = width;
	t->height = height;
	t->pixels = pixels;
	t->potw = 0;
	t->poth = 0;

	// see if we're backed by a file and not RAM.
	const bool loadFromFile = ((pixels == NULL) && (t->filename != NULL));
	if (loadFromFile)
	{
		DWORD size = 0;
		BYTE *data = (BYTE *) pHGE->Resource_Load(t->filename, &size);
		if (data != NULL)
		{
			int w, h;
			pixels = _DecodeImage(data, t->filename, size, w, h);
			if ((w != width) || (h != height))  // yikes, file changed?
			{
				delete[] pixels;
				pixels = NULL;
			}
			Resource_Free(data);
		}
	}

	pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, tex);
	if (pOpenGLDevice->TextureTarget != GL_TEXTURE_RECTANGLE_ARB)
	{
		pOpenGLDevice->glTexParameterf(pOpenGLDevice->TextureTarget, GL_TEXTURE_MIN_LOD, 0.0f);
		pOpenGLDevice->glTexParameterf(pOpenGLDevice->TextureTarget, GL_TEXTURE_MAX_LOD, 0.0f);
		pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_BASE_LEVEL, 0);
		pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_MAX_LEVEL, 0);
	}
	const GLenum intfmt = pOpenGLDevice->have_GL_EXT_texture_compression_s3tc ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_RGBA;
	if ((pOpenGLDevice->have_GL_ARB_texture_rectangle) || (pOpenGLDevice->have_GL_ARB_texture_non_power_of_two) || (_IsPowerOfTwo(width) && _IsPowerOfTwo(height))) {
		pOpenGLDevice->glTexImage2D(pOpenGLDevice->TextureTarget, 0, intfmt, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
	}
	else
	{
		t->potw = _NextPowerOfTwo(width);
		t->poth = _NextPowerOfTwo(height);
		pOpenGLDevice->glTexImage2D(pOpenGLDevice->TextureTarget, 0, intfmt, t->potw, t->poth, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
		pOpenGLDevice->glTexSubImage2D(pOpenGLDevice->TextureTarget, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
	}

	pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, CurTexture ? (((gltexture *) CurTexture)->name) : 0);

	if (loadFromFile)
		delete[] pixels;
}

HTEXTURE HGE_Impl::_BuildTexture(int width, int height, DWORD *pixels)
{
	gltexture *retval = new gltexture;
	memset(retval, '\0', sizeof (gltexture));
	retval->lost = true;  // we'll actually generate a texture and upload when forced.
	retval->width = width;
	retval->height = height;
	retval->pixels = pixels;
	return (HTEXTURE)retval;
}

HTEXTURE CALL HGE_Impl::Texture_Create(int width, int height)
{
	DWORD *pixels = new DWORD[width * height];
	memset(pixels, '\0', sizeof (DWORD) * width * height);
	HTEXTURE retval = _BuildTexture(width, height, pixels);

	// the Direct3D renderer doesn't add these to the (textures) list, but we need them for when we "lose" the GL context.
	if (retval != 0)
	{
		CTextureList *texItem=new CTextureList;
		texItem->tex=retval;
		texItem->width=width;
		texItem->height=height;
		texItem->next=textures;
		textures=texItem;
	}

	return retval;
}

HTEXTURE CALL HGE_Impl::Texture_Load(const char *filename, DWORD size, bool bMipmap)
{
	HTEXTURE retval = 0;
	int width = 0;
	int height = 0;

	void *data;
	DWORD _size;
	CTextureList *texItem;
	const char *fname = NULL;
	if(size) { data=(void *)filename; _size=size; }
	else
	{
		fname = filename;
		data=pHGE->Resource_Load(filename, &_size);
		if(!data) return 0;
	}

	DWORD *pixels = _DecodeImage((BYTE *) data, fname, _size, width, height);
	if (pixels != NULL)
		retval = _BuildTexture(width, height, pixels);

	if(!size) Resource_Free(data);

	if (retval == 0)
	{
		STUBBED("texture load fail!");
		_PostError("Can't create texture");
	}
	else
	{
		texItem=new CTextureList;
		texItem->tex=retval;
		texItem->width=width;
		texItem->height=height;
		texItem->next=textures;
		textures=texItem;

		// force an upload to the GL and lose our copy if it's backed by
		//  a file. We won't keep it here to conserve system RAM.
		if (!size)
		{
			gltexture *t = (gltexture *) retval;
			_ConfigureTexture(t, t->width, t->height, t->pixels);
			delete[] t->pixels;
			t->pixels = NULL;
			t->filename = strcpy(new char[strlen(filename) + 1], filename);
		}
	}

	return retval;
}

void CALL HGE_Impl::Texture_Free(HTEXTURE tex)
{
	if (pOpenGLDevice == NULL)
		return;  // in case we already shut down.

	CTextureList *texItem=textures, *texPrev=0;

	while(texItem)
	{
		if(texItem->tex==tex)
		{
			if(texPrev) texPrev->next=texItem->next;
			else textures=texItem->next;
			delete texItem;
			break;
		}
		texPrev=texItem;
		texItem=texItem->next;
	}
	if(tex)
	{
		gltexture *pTex = (gltexture *) tex;
		delete[] pTex->filename;
		delete[] pTex->lock_pixels;
		delete[] pTex->pixels;
		pOpenGLDevice->glDeleteTextures(1, &pTex->name);
		delete pTex;
	}
}

int CALL HGE_Impl::Texture_GetWidth(HTEXTURE tex, bool bOriginal)
{
	CTextureList *texItem=textures;

	if(bOriginal)
	{
		while(texItem)
		{
			if(texItem->tex==tex) return texItem->width;
			texItem=texItem->next;
		}
	}
	else
	{
		return ((gltexture*)tex)->width;
	}
	return 0;
}


int CALL HGE_Impl::Texture_GetHeight(HTEXTURE tex, bool bOriginal)
{
	CTextureList *texItem=textures;

	if(bOriginal)
	{
		while(texItem)
		{
			if(texItem->tex==tex) return texItem->height;
			texItem=texItem->next;
		}
	}
	else
	{
		return ((gltexture*)tex)->height;
	}
	return 0;
}

// HGE extension!
// fast path for pushing YUV video to a texture instead of having to
//  lock/convert-to-rgba/unlock...current HGE semantics involve a
//  lot of unnecessary overhead on this, not to mention the conversion
//  on the CPU is painful on PowerPC chips.
// This lets us use OpenGL extensions to move data to the hardware
//  without conversion.
// Don't taunt this function. Side effects are probably rampant.
bool CALL HGE_Impl::HGEEXT_Texture_PushYUV422(HTEXTURE tex, const BYTE *yuv)
{
	if (!pOpenGLDevice->have_GL_APPLE_ycbcr_422)
		return false;

	gltexture *pTex=(gltexture*)tex;
	assert(!pTex->lock_pixels);

	if (pTex->lost)  // just reupload the whole thing.
		_ConfigureTexture(pTex, pTex->width, pTex->height, pTex->pixels);

	// Any existing pixels aren't valid anymore.
	if (pTex->pixels)
	{
		delete[] pTex->pixels;
		pTex->pixels = NULL;
	}

	pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, pTex->name);
	pOpenGLDevice->glTexSubImage2D(pOpenGLDevice->TextureTarget, 0, 0, 0,
	                               pTex->width, pTex->height, GL_YCBCR_422_APPLE,
                                   GL_UNSIGNED_SHORT_8_8_APPLE, yuv);
	pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, CurTexture ? (((gltexture *) CurTexture)->name) : 0);
	return true;
}

DWORD * CALL HGE_Impl::Texture_Lock(HTEXTURE tex, bool bReadOnly, int left, int top, int width, int height)
{
	gltexture *pTex=(gltexture*)tex;

	if (pTex->lock_pixels)
	{
		assert(false && "multiple lock of texture...");
		return 0;
	}

	// see if we're backed by a file and not RAM.
	const bool loadFromFile = ((pTex->pixels == NULL) && (pTex->filename != NULL));
	if (loadFromFile)
	{
		DWORD size = 0;
		BYTE *data = (BYTE *) pHGE->Resource_Load(pTex->filename, &size);
		if (data != NULL)
		{
			int w, h;
			pTex->pixels = _DecodeImage(data, pTex->filename, size, w, h);
			if ((w != (int)pTex->width) || (h != (int)pTex->height))  // yikes, file changed?
			{
				delete[] pTex->pixels;
				pTex->pixels = NULL;
			}
			Resource_Free(data);
		}
		if (pTex->pixels != NULL)
		{
			// can't go back to file after we lock, since app might change data.
			if (!bReadOnly)
			{
				delete[] pTex->filename;
				pTex->filename = NULL;
			}
		}
	}

	if ((pTex->pixels == NULL) && (!pTex->is_render_target))  // can't lock this texture...?!
		return 0;

	// !!! FIXME: is this right?
	if((width == 0) && (height == 0))
	{
		width = pTex->width;
		height = pTex->height;
	}

	// !!! FIXME: do something with this?
	assert(width > 0);
	assert(width <= (int)pTex->width);
	assert(height > 0);
	assert(height <= (int)pTex->height);
	assert(left >= 0);
	assert(left <= width);
	assert(top >= 0);
	assert(top <= height);

	pTex->lock_readonly = bReadOnly;
	pTex->lock_x = left;
	pTex->lock_y = top;
	pTex->lock_width = width;
	pTex->lock_height = height;
	pTex->lock_pixels = new DWORD[width * height];

	DWORD *dst = pTex->lock_pixels;

	if (pTex->is_render_target)
	{
		assert(false && "need to bind fbo before glReadPixels...");
		DWORD *upsideDown = new DWORD[width * height];
		DWORD *src = upsideDown + ((height-1) * width);
		pOpenGLDevice->glReadPixels(left, (pTex->height-top)-height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, upsideDown);
		for (int i = 0; i < height; i++)
		{
			memcpy(dst, src, width * sizeof (DWORD));
			dst += width;
			src -= width;
		}
		delete[] upsideDown;
	}
	else
	{
		DWORD *src = pTex->pixels + ((top*pTex->width) + left);
		for (int i = 0; i < height; i++)
		{
			memcpy(dst, src, width * sizeof (DWORD));
			dst += width;
			src += pTex->width;
		}
	}

	return pTex->lock_pixels;
}


void CALL HGE_Impl::Texture_Unlock(HTEXTURE tex)
{
	gltexture *pTex=(gltexture*)tex;

	if (pTex->lock_pixels == NULL) return;  // not locked.

	if (!pTex->lock_readonly)  // have to reupload to the hardware.
	{
		// need to update pTex->pixels ...
		const DWORD *src = pTex->lock_pixels;
		DWORD *dst = pTex->pixels + ((pTex->lock_y*pTex->width) + pTex->lock_x);
		for (int i = 0; i < pTex->lock_height; i++)
		{
			memcpy(dst, src, pTex->lock_width * sizeof (DWORD));
			dst += pTex->width;
			src += pTex->lock_width;
		}

		if (pTex->lost)  // just reupload the whole thing.
			_ConfigureTexture(pTex, pTex->width, pTex->height, pTex->pixels);
		else
		{
			pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, pTex->name);
			pOpenGLDevice->glTexSubImage2D(pOpenGLDevice->TextureTarget, 0, pTex->lock_x,
			                               (pTex->height-pTex->lock_y)-pTex->lock_height,
			                               pTex->lock_width, pTex->lock_height, GL_RGBA,
			                               GL_UNSIGNED_BYTE, pTex->lock_pixels);
			pOpenGLDevice->glBindTexture(pOpenGLDevice->TextureTarget, CurTexture ? (((gltexture *) CurTexture)->name) : 0);
		}
	}

	// if we were read-only and we're backed by a file, ditch the uncompressed copy in system RAM.
	if ((pTex->filename != NULL) && (pTex->lock_readonly))
	{
		delete[] pTex->pixels;
		pTex->pixels = NULL;
	}

	delete[] pTex->lock_pixels;
	pTex->lock_pixels = NULL;
	pTex->lock_readonly = false;
	pTex->lock_x = -1;
	pTex->lock_y = -1;
	pTex->lock_width = -1;
	pTex->lock_height = -1;
}

//////// Implementation ////////

#define DEBUG_VERTICES 0
#if DEBUG_VERTICES
static inline void print_vertex(const hgeVertex *v)
{
    printf("  (%f, %f, %f), 0x%X, (%f, %f)\n", v->x, v->y, v->z, v->col, v->tx, v->ty);
}
#endif

void HGE_Impl::_render_batch(bool bEndScene)
{
	if(VertArray)
	{
		if(nPrim)
		{
			const float h = (float) ((pCurTarget) ? pCurTarget->height : nScreenHeight);

			// texture rectangles range from 0 to size, not 0 to 1.  :/
			float texwmult = 1.0f;
			float texhmult = 1.0f;

			if (CurTexture)
			{
				_SetTextureFilter();
				const gltexture *pTex = ((gltexture *)CurTexture);
				if (pOpenGLDevice->TextureTarget == GL_TEXTURE_RECTANGLE_ARB)
				{
					texwmult = pTex->width;
					texhmult = pTex->height;
				}
				else if ((pTex->potw != 0) && (pTex->poth != 0))
				{
					texwmult = ( ((float)pTex->width) / ((float)pTex->potw) );
					texhmult = ( ((float)pTex->height) / ((float)pTex->poth) );
				}
			}

			for (int i = 0; i < nPrim*CurPrimType; i++)
			{
				// (0, 0) is the lower left in OpenGL, upper left in D3D.
				VertArray[i].y = h - VertArray[i].y;

				// Z axis is inverted in OpenGL from D3D.
				VertArray[i].z = -VertArray[i].z;

				// (0, 0) is lower left texcoord in OpenGL, upper left in D3D.
				// Also, scale for texture rectangles vs. 2D textures.
				VertArray[i].tx *= texwmult;
				VertArray[i].ty = (1.0f - VertArray[i].ty) * texhmult;

				// Colors are RGBA in OpenGL, ARGB in Direct3D.
				const DWORD color = VertArray[i].col;
				BYTE *col = (BYTE *) &VertArray[i].col;
				const BYTE a = ((color >> 24) & 0xFF);
				const BYTE r = ((color >> 16) & 0xFF);
				const BYTE g = ((color >>  8) & 0xFF);
				const BYTE b = ((color >>  0) & 0xFF);
				col[0] = r;
				col[1] = g;
				col[2] = b;
				col[3] = a;
			}

			switch(CurPrimType)
			{
				case HGEPRIM_QUADS:
					pOpenGLDevice->glDrawElements(GL_TRIANGLES, nPrim * 6, GL_UNSIGNED_SHORT, pIB);
					#if DEBUG_VERTICES
					for (int i = 0; i < nPrim*6; i+=3)
					{
						printf("QUAD'S TRIANGLE:\n");
						print_vertex(&pVB[pIB[i+0]]);
						print_vertex(&pVB[pIB[i+1]]);
						print_vertex(&pVB[pIB[i+2]]);
					}
					printf("DONE.\n");
					#endif
					break;

				case HGEPRIM_TRIPLES:
					pOpenGLDevice->glDrawArrays(GL_TRIANGLES, 0, nPrim * 3);
					break;

				case HGEPRIM_LINES:
					pOpenGLDevice->glDrawArrays(GL_LINES, 0, nPrim * 2);
					break;
			}

			nPrim=0;
		}
		if(bEndScene) VertArray = 0;
		else VertArray = pVB;
	}
}

void HGE_Impl::_SetBlendMode(int blend)
{
	if((blend & BLEND_ALPHABLEND) != (CurBlendMode & BLEND_ALPHABLEND))
	{
		if(blend & BLEND_ALPHABLEND) pOpenGLDevice->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else pOpenGLDevice->glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	}

	if((blend & BLEND_ZWRITE) != (CurBlendMode & BLEND_ZWRITE))
	{
		if(blend & BLEND_ZWRITE) pOpenGLDevice->glDepthMask(GL_TRUE);
		else pOpenGLDevice->glDepthMask(GL_FALSE);
	}

	if((blend & BLEND_COLORADD) != (CurBlendMode & BLEND_COLORADD))
	{
		if(blend & BLEND_COLORADD) pOpenGLDevice->glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
		else pOpenGLDevice->glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	}

	CurBlendMode = blend;
}

void HGE_Impl::_SetProjectionMatrix(int width, int height)
{
	pOpenGLDevice->glMatrixMode(GL_PROJECTION);
	pOpenGLDevice->glLoadIdentity();
	pOpenGLDevice->glOrtho(0, (float)width, 0, (float)height, 0.0f, 1.0f);
	bTransforming = false;
	clipX = 0;
	clipY = 0;
	clipW = width;
	clipH = height;
}

void HGE_Impl::_UnloadOpenGLEntryPoints()
{
	#define GL_PROC(ext,fn,call,ret,params) pOpenGLDevice->fn = NULL;
	#include "hge_glfuncs.h"
	#undef GL_PROC
}

bool HGE_Impl::_HaveOpenGLExtension(const char *extlist, const char *ext)
{
	const char *ptr = strstr(extlist, ext);
	if (ptr == NULL)
		return false;

	const char endchar = ptr[strlen(ext)];
	if ((endchar == '\0') || (endchar == ' '))
		return true;  // extension is in the list.

	return false;  // just not supported, fail.
}

bool HGE_Impl::_LoadOpenGLEntryPoints()
{
	System_Log("%s: OpenGL: loading entry points and examining extensions...",GRAPHICS_SRC_FN);

	// these can be reset to false below...
	pOpenGLDevice->have_base_opengl = true;
	pOpenGLDevice->have_GL_ARB_texture_rectangle = true;
	pOpenGLDevice->have_GL_ARB_texture_non_power_of_two = true;
	pOpenGLDevice->have_GL_EXT_framebuffer_object = true;
	pOpenGLDevice->have_GL_EXT_texture_compression_s3tc = true;
	pOpenGLDevice->have_GL_ARB_vertex_buffer_object = true;
	pOpenGLDevice->have_GL_APPLE_ycbcr_422 = true;

	#define GL_PROC(ext,fn,call,ret,params) \
		if (pOpenGLDevice->have_##ext) { \
			if ((pOpenGLDevice->fn = (_HGE_PFN_##fn) SDL_GL_GetProcAddress(#fn)) == NULL) { \
				System_Log("Failed to load OpenGL entry point '" #fn "'"); \
				pOpenGLDevice->have_##ext = false; \
			} \
		} else {}
	#include "hge_glfuncs.h"
	#undef GL_PROC

	if (!pOpenGLDevice->have_base_opengl)
	{
		_UnloadOpenGLEntryPoints();
		return false;
	}

	System_Log("%s: GL_RENDERER: %s",GRAPHICS_SRC_FN, (const char *) pOpenGLDevice->glGetString(GL_RENDERER));
	System_Log("%s: GL_VENDOR: %s",GRAPHICS_SRC_FN, (const char *) pOpenGLDevice->glGetString(GL_VENDOR));
	System_Log("%s: GL_VERSION: %s",GRAPHICS_SRC_FN, (const char *) pOpenGLDevice->glGetString(GL_VERSION));

	const char *verstr = (const char *) pOpenGLDevice->glGetString(GL_VERSION);
	int maj = 0;
	int min = 0;
	sscanf(verstr, "%d.%d", &maj, &min);

	if ( (maj < 1) || ((maj == 1) && (min < 2)) )
	{
		_PostError("OpenGL implementation must be at least version 1.2");
		_UnloadOpenGLEntryPoints();
		return false;
	}

	const char *exts = (const char *) pOpenGLDevice->glGetString(GL_EXTENSIONS);

	// NPOT texture support ...

	if (_HaveOpenGLExtension(exts, "GL_ARB_texture_rectangle"))
		pOpenGLDevice->have_GL_ARB_texture_rectangle = true;
	else if (_HaveOpenGLExtension(exts, "GL_EXT_texture_rectangle"))
		pOpenGLDevice->have_GL_ARB_texture_rectangle = true;
	else if (_HaveOpenGLExtension(exts, "GL_NV_texture_rectangle"))
		pOpenGLDevice->have_GL_ARB_texture_rectangle = true;
	else
		pOpenGLDevice->have_GL_ARB_texture_rectangle = false;

	if (maj >= 2)
		pOpenGLDevice->have_GL_ARB_texture_non_power_of_two = true;
	else if (_HaveOpenGLExtension(exts, "GL_ARB_texture_non_power_of_two"))
		pOpenGLDevice->have_GL_ARB_texture_non_power_of_two = true;
	else
		pOpenGLDevice->have_GL_ARB_texture_non_power_of_two = false;

	if (pOpenGLDevice->have_GL_ARB_texture_rectangle)
	{
		System_Log("%s: OpenGL: Using GL_ARB_texture_rectangle",GRAPHICS_SRC_FN);
		pOpenGLDevice->TextureTarget = GL_TEXTURE_RECTANGLE_ARB;
	}
	else if (pOpenGLDevice->have_GL_ARB_texture_non_power_of_two)
	{
		System_Log("%s: OpenGL: Using GL_ARB_texture_non_power_of_two",GRAPHICS_SRC_FN);
		pOpenGLDevice->TextureTarget = GL_TEXTURE_2D;
	}
	else
	{
		// We can fake this with POT textures. Get a real OpenGL!
		System_Log("%s: OpenGL: Using power-of-two textures. This costs more memory!",GRAPHICS_SRC_FN);
		pOpenGLDevice->TextureTarget = GL_TEXTURE_2D;
	}

	// render-to-texture support ...

	// is false if an entry point is missing, but we still need to check for the extension string...
	if (pOpenGLDevice->have_GL_EXT_framebuffer_object)
	{
		// Disable this on Mac OS X Tiger, since some drivers appear to be buggy.
		if ((pHGE->MacOSXVersion) && (pHGE->MacOSXVersion < 0x1050))
			pOpenGLDevice->have_GL_EXT_framebuffer_object = false;
		else if (_HaveOpenGLExtension(exts, "GL_EXT_framebuffer_object"))
			pOpenGLDevice->have_GL_EXT_framebuffer_object = true;
		else
			pOpenGLDevice->have_GL_EXT_framebuffer_object = false;
	}

	if (pOpenGLDevice->have_GL_EXT_framebuffer_object)
		System_Log("%s: OpenGL: Using GL_EXT_framebuffer_object",GRAPHICS_SRC_FN);
	else
		System_Log("%s: OpenGL: WARNING! No render-to-texture support. Things may render badly.",GRAPHICS_SRC_FN);


	// Texture compression ...

	if (bForceTextureCompression &&
	    _HaveOpenGLExtension(exts, "GL_ARB_texture_compression") &&
	    _HaveOpenGLExtension(exts, "GL_EXT_texture_compression_s3tc"))
		pOpenGLDevice->have_GL_EXT_texture_compression_s3tc = true;
	else
		pOpenGLDevice->have_GL_EXT_texture_compression_s3tc = false;

	if (pOpenGLDevice->have_GL_EXT_texture_compression_s3tc)
		System_Log("%s: OpenGL: Using GL_EXT_texture_compression_s3tc",GRAPHICS_SRC_FN);
	else if (bForceTextureCompression)
	{
		bForceTextureCompression = false;  // oh well.
		System_Log("%s: OpenGL: WARNING: no texture compression support, in a low-memory system.",GRAPHICS_SRC_FN);
		System_Log("%s: OpenGL:  Performance may be very bad!",GRAPHICS_SRC_FN);
	}

	// YUV textures...

	if (_HaveOpenGLExtension(exts, "GL_APPLE_ycbcr_422"))
		pOpenGLDevice->have_GL_APPLE_ycbcr_422 = true;
	else
		pOpenGLDevice->have_GL_APPLE_ycbcr_422 = false;

	if (pOpenGLDevice->have_GL_APPLE_ycbcr_422)
		System_Log("%s: OpenGL: Using GL_APPLE_ycbcr_422 to render YUV frames.",GRAPHICS_SRC_FN);
	else
		System_Log("%s: OpenGL: WARNING: no YUV texture support; videos may render slowly.",GRAPHICS_SRC_FN);

	// VBOs...

	// is false if an entry point is missing, but we still need to check for the extension string...
	if (pOpenGLDevice->have_GL_ARB_vertex_buffer_object)
	{
		if (_HaveOpenGLExtension(exts, "GL_ARB_vertex_buffer_object"))
			pOpenGLDevice->have_GL_ARB_vertex_buffer_object = true;
		else
			pOpenGLDevice->have_GL_ARB_vertex_buffer_object = false;
	}

	if (pOpenGLDevice->have_GL_ARB_vertex_buffer_object)
		System_Log("%s: OpenGL: Using GL_ARB_vertex_buffer_object",GRAPHICS_SRC_FN);
	else
		System_Log("%s: OpenGL: WARNING! No VBO support; performance may suffer.",GRAPHICS_SRC_FN);

	return true;
}

bool HGE_Impl::_GfxInit()
{
	CurTexture = 0;

// Init OpenGL ... SDL should have created a context at this point.
	assert(pOpenGLDevice == NULL);
	pOpenGLDevice = new COpenGLDevice;
	if (!_LoadOpenGLEntryPoints())
		return false;   // already called _PostError().

	nScreenBPP=SDL_GetVideoSurface()->format->BitsPerPixel;

	_AdjustWindow();

	System_Log("%s: Mode: %d x %d\n",GRAPHICS_SRC_FN,nScreenWidth,nScreenHeight);
// Create vertex batch buffer

	VertArray=0;
	textures=0;
	IndexBufferObject=0;

// Init all stuff that can be lost

	if(!_init_lost()) return false;

	// make sure the framebuffers are cleared and force to screen
	pOpenGLDevice->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	SDL_GL_SwapBuffers();
	pOpenGLDevice->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	SDL_GL_SwapBuffers();

	return true;
}

void HGE_Impl::_AdjustWindow()
{
	// no-op.
}

void HGE_Impl::_Resize(int width, int height)
{
	if(hwndParent)
	{
		//if(procFocusLostFunc) procFocusLostFunc();
		STUBBED("resize");
		#if 0
		d3dppW.BackBufferWidth=width;
		d3dppW.BackBufferHeight=height;
		nScreenWidth=width;
		nScreenHeight=height;

		_SetProjectionMatrix(nScreenWidth, nScreenHeight);
		_GfxRestore();
		#endif

		//if(procFocusGainFunc) procFocusGainFunc();
	}
}

void HGE_Impl::_GfxDone()
{
	//CRenderTargetList *target=pTargets;

	while(textures)	Texture_Free(textures->tex);
	while(pTargets)	Target_Free((HTARGET) pTargets);
	textures=0;
	pTargets=0;

	VertArray = 0;
	delete[] pVB;
	pVB=0;
	delete[] pIB;
	pIB=0;

	if(pOpenGLDevice)
	{
		if (pOpenGLDevice->have_GL_ARB_vertex_buffer_object)
		{
			if (IndexBufferObject != 0)
			{
				pOpenGLDevice->glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
				pOpenGLDevice->glDeleteBuffersARB(1, &IndexBufferObject);
				IndexBufferObject = 0;
			}
		}

		delete pOpenGLDevice;
		pOpenGLDevice=0;
	}
}


bool HGE_Impl::_GfxRestore()
{
	if(!pOpenGLDevice) return false;

	delete[] pVB;
	pVB=0;

	delete[] pIB;
	pIB=0;

	_UnloadOpenGLEntryPoints();
	if (!_LoadOpenGLEntryPoints())
		return false;

	if(!_init_lost()) return false;

	if(procGfxRestoreFunc) return procGfxRestoreFunc();

	return true;
}


bool HGE_Impl::_init_lost()
{
	_BindTexture(NULL);  // make sure nothing is bound, so everything that we do bind regenerates.

	for (CTextureList *item = textures; item != NULL; item = item->next)
	{
		gltexture *t = (gltexture *) item->tex;
		if (t == NULL) continue;
		t->lost = true;
		t->name = 0;
	}

	CRenderTargetList *target=pTargets;
	while(target)
	{
		gltexture *tex = (gltexture *) target->tex;
		_BindTexture(tex);  // force texture recreation.
		_BindTexture(NULL);
		_BuildTarget(target, tex ? tex->name : 0, target->width, target->height, target->depth != 0);
		target=target->next;
	}

// Create Vertex buffer
	// We just use a client-side array, since you can reasonably count on support
	//  existing in any GL, and it basically offers the same functionality that
	//  HGE uses in Direct3D: it locks the vertex buffer, unlocks in time to
	//  draw something, then relocks again immediately...more or less, that method
	//  offers the same performance metrics as a client-side array.
	// We _will_ stuff the indices in a buffer object, though, if possible,
	//  since they never change...this matches the D3D behaviour better, since
	//  they lock, store, and forget about it, but without a buffer object,
	//  we'd have to pass the array over the bus every glDrawElements() call.
	//  It's not worth the tapdance for vertex buffer objects though, due to
	//  HGE's usage patterns.
	pVB = new hgeVertex[VERTEX_BUFFER_SIZE];

// Create and setup Index buffer
	pIB = new GLushort[VERTEX_BUFFER_SIZE*6/4];
	GLushort *pIndices = pIB;
	int n = 0;
	for(int i=0; i<VERTEX_BUFFER_SIZE/4; i++) {
		*pIndices++=n;
		*pIndices++=n+1;
		*pIndices++=n+2;
		*pIndices++=n+2;
		*pIndices++=n+3;
		*pIndices++=n;
		n+=4;
	}

	#if !DEBUG_VERTICES  // need pIB for DEBUG_VERTICES.
	if (pOpenGLDevice->have_GL_ARB_vertex_buffer_object)
	{
		// stay bound forever. The Index Buffer Object never changes.
		pOpenGLDevice->glGenBuffersARB(1, &IndexBufferObject);
		pOpenGLDevice->glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, IndexBufferObject);
		pOpenGLDevice->glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof (GLushort) * ((VERTEX_BUFFER_SIZE*6)/4), pIB, GL_STATIC_DRAW);
		delete[] pIB;
		pIB=0;
	}
	#endif

	// always use client-side arrays; set it up once at startup.
	pOpenGLDevice->glVertexPointer(3, GL_FLOAT, sizeof (hgeVertex), &pVB[0].x);
	pOpenGLDevice->glColorPointer(4, GL_UNSIGNED_BYTE, sizeof (hgeVertex), &pVB[0].col);
	pOpenGLDevice->glTexCoordPointer(2, GL_FLOAT, sizeof (hgeVertex), &pVB[0].tx);
	pOpenGLDevice->glEnableClientState(GL_VERTEX_ARRAY);
	pOpenGLDevice->glEnableClientState(GL_COLOR_ARRAY);
	pOpenGLDevice->glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// Set common render states

	pOpenGLDevice->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	pOpenGLDevice->glPixelStorei(GL_PACK_ALIGNMENT, 1);

	//pD3DDevice->SetRenderState( D3DRS_LASTPIXEL, FALSE );
	pOpenGLDevice->glDisable(GL_TEXTURE_2D);
	if (pOpenGLDevice->have_GL_ARB_texture_rectangle)
		pOpenGLDevice->glDisable(GL_TEXTURE_RECTANGLE_ARB);
	pOpenGLDevice->glEnable(pOpenGLDevice->TextureTarget);
	pOpenGLDevice->glEnable(GL_SCISSOR_TEST);
	pOpenGLDevice->glDisable(GL_CULL_FACE);
	pOpenGLDevice->glDisable(GL_LIGHTING);
	pOpenGLDevice->glDepthFunc(GL_GEQUAL);

	if (bZBuffer)
		pOpenGLDevice->glEnable(GL_DEPTH_TEST);
	else
		pOpenGLDevice->glDisable(GL_DEPTH_TEST);

	pOpenGLDevice->glEnable(GL_BLEND);
	pOpenGLDevice->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	pOpenGLDevice->glEnable(GL_ALPHA_TEST);
	pOpenGLDevice->glAlphaFunc(GL_GEQUAL, 1.0f / 255.0f);

	pOpenGLDevice->glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	_SetTextureFilter();

	// !!! FIXME: this isn't what HGE's Direct3D code does, but the game I'm working with
	// !!! FIXME:  forces clamping outside of HGE, so I just wedged it in here.
	// Apple says texture rectangle on ATI X1000 chips only supports CLAMP_TO_EDGE.
	// Texture rectangle only supports CLAMP* wrap modes anyhow.
	pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	pOpenGLDevice->glTexParameteri(pOpenGLDevice->TextureTarget, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	nPrim=0;
	CurPrimType=HGEPRIM_QUADS;
	CurBlendMode = BLEND_DEFAULT;
	CurTexture = 0;

	pOpenGLDevice->glScissor(0, 0, nScreenWidth, nScreenHeight);
	pOpenGLDevice->glViewport(0, 0, nScreenWidth, nScreenHeight);

	// make sure the framebuffer is cleared and force to screen
	pOpenGLDevice->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	_SetProjectionMatrix(nScreenWidth, nScreenHeight);
	pOpenGLDevice->glMatrixMode(GL_MODELVIEW);
	pOpenGLDevice->glLoadIdentity();

	return true;
}
