aboutsummaryrefslogblamecommitdiff
path: root/hge/graphics.cpp
blob: 6ee9578ffa08f4150855ff53a73bdca6a905ada0 (plain) (tree)
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346







































































































































































































































                                                                                                                 
                                                                                                                                
         




                                                          


                        


                                                  






                                                        

 















































































































































































































































































































































                                                                                                                                                       
                                                               












































































































































































































































































                                                                                                                                                                         
                                                                                                         




























                                                                                                 
                                          
                           
                                            




































































































































































































































































































































































































































































                                                                                                                                        
                                             
















































































































































































                                                                                                                                             
/*
** 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;
}