/* Silence deprecation warnings on modern macOS/iOS */
#define GL_SILENCE_DEPRECATION
#define GLES_SILENCE_DEPRECATION

#include "Core.h"
#if CC_GFX_BACKEND == CC_GFX_BACKEND_GL1
#include "_GraphicsBase.h"
#include "Errors.h"
#include "Window.h"

#if defined CC_BUILD_WIN
	#define CC_BUILD_GL11_FALLBACK
#endif

/* The OpenGL backend is a bit of a mess, since it's really 2 backends in one:
 * - OpenGL 1.5 or OpenGL 1.2 + GL_ARB_vertex_buffer_object (default desktop backend)
 * - OpenGL 1.1 (completely lacking GPU, fallbacks to say Windows built-in software rasteriser)

  NOTE: Make sure when changing this or Graphics_GL11.c, to keep things in sync
*/
#include "../misc/opengl/GLCommon.h"

/* e.g. GLAPI void APIENTRY glFunction(int value); */
#define GL_FUNC(retType, name, args) GLAPI retType APIENTRY name args;
#include "../misc/opengl/GL1Funcs.h"
#if defined CC_BUILD_SYMBIAN
#include "../misc/opengl/GL2Funcs.h"

static cc_bool mbx;
#endif

#if CC_BUILD_MAXSTACK <= (64 * 1024)
static cc_uint16 gl_indices[GFX_MAX_INDICES];
#define GL_INDICES
#endif

/* OpenGL functions use stdcall instead of cdecl on Windows */
static void (APIENTRY *_glBindBuffer)(GLenum target, GfxResourceID buffer); /* NOTE: buffer is actually a GLuint in OpenGL */
static void (APIENTRY *_glDeleteBuffers)(GLsizei n, const GLuint *buffers);
static void (APIENTRY *_glGenBuffers)(GLsizei n, GLuint *buffers);
static void (APIENTRY *_glBufferData)(GLenum target, cc_uintptr size, const GLvoid* data, GLenum usage);
static void (APIENTRY *_glBufferSubData)(GLenum target, cc_uintptr offset, cc_uintptr size, const GLvoid* data);


#if defined CC_BUILD_GL11_FALLBACK
/* Note the following about calling OpenGL functions on Windows */
/*  1) wglGetProcAddress returns a context specific address */
/*  2) dllimport functions are implemented using indirect function pointers */
/*     https://web.archive.org/web/20080321171626/http://blogs.msdn.com/oldnewthing/archive/2006/07/20/672695.aspx */
/*     https://web.archive.org/web/20071016185327/http://blogs.msdn.com/oldnewthing/archive/2006/07/27/680250.aspx */
/* Therefore one layer of indirection can be avoided by calling wglGetProcAddress functions instead */
/*  e.g. if _glDrawElements = wglGetProcAddress("glDrawElements") */
/*    call [glDrawElements]  --> opengl32.dll thunk--> GL driver thunk --> GL driver implementation */
/*    call [_glDrawElements] --> GL driver thunk --> GL driver implementation */

/* e.g. typedef void (APIENTRY *FP_glFunction)(int value); */
#undef  GL_FUNC
#define GL_FUNC(retType, name, args) typedef retType (APIENTRY *FP_ ## name)args;
#include "../misc/opengl/GL1Funcs.h"

/* e.g. static void (APIENTRY *_glFunction)(int value); */
#undef  GL_FUNC
#define GL_FUNC(retType, name, args) static retType (APIENTRY *_ ## name)args;
#include "../misc/opengl/GL1Funcs.h"

static const struct DynamicLibSym coreFuncs[] = {
	/* e.g. { DYNAMICLIB_QUOTE(name), (void**)&_ ## name }, */
	#undef  GL_FUNC
	#define GL_FUNC(retType, name, args) { DYNAMICLIB_QUOTE(name), (void**)&_ ## name },
	#include "../misc/opengl/GL1Funcs.h"
};
#else
	#include "../misc/opengl/GL1Macros.h"
#endif

typedef void (*GL_SetupVBFunc)(void);
typedef void (*GL_SetupVBRangeFunc)(int startVertex);
static GL_SetupVBFunc gfx_setupVBFunc;
static GL_SetupVBRangeFunc gfx_setupVBRangeFunc;

#include "_GLShared.h"
static void GLBackend_Init(void);

void Gfx_Create(void) {
	GLContext_Create();
#ifdef CC_BUILD_GL11_FALLBACK
	GLContext_GetAll(coreFuncs, Array_Elems(coreFuncs));
#endif
#ifndef CC_BUILD_GLES
	customMipmapsLevels = true;
#endif
	Gfx.BackendType     = CC_GFX_BACKEND_GL1;

	GL_InitCommon();
	GLBackend_Init();
	Gfx_RestoreState();
	GLContext_SetVSync(gfx_vsync);
}


/*########################################################################################################################*
*------------------------------------------------Buffer generation/deletion-----------------------------------------------*
*#########################################################################################################################*/
/* Necessary to implement this way, so works on both little endian and big endian systems */
typedef GfxResourceID (*GenGLBuffer)(void);
typedef void (*DelGLBuffer)(GfxResourceID id);

static GfxResourceID defaultGenBuffer(void) {
	GLuint buf = 0;
	_glGenBuffers(1, &buf);
	return uint_to_ptr(buf);
}

static void defaultDelBuffer(GfxResourceID id) {
	GLuint buf = ptr_to_uint(id);
	_glDeleteBuffers(1, &buf);
}

static GenGLBuffer genBuffer = defaultGenBuffer;
static DelGLBuffer delBuffer = defaultDelBuffer;


/*########################################################################################################################*
*-------------------------------------------------------Index buffers-----------------------------------------------------*
*#########################################################################################################################*/
GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
#ifndef GL_INDICES
	cc_uint16 gl_indices[GFX_MAX_INDICES];
#endif
	GfxResourceID id = genBuffer();
	cc_uint32 size   = count * sizeof(cc_uint16);

	fillFunc(gl_indices, count, obj);
	_glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id);
	_glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, gl_indices, GL_STATIC_DRAW);
	return id;
}

void Gfx_BindIb(GfxResourceID ib) { _glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib); }

void Gfx_DeleteIb(GfxResourceID* ib) {
	GfxResourceID id = *ib;
	if (!id) return;

	delBuffer(id);
	*ib = 0;
}


/*########################################################################################################################*
*------------------------------------------------------Vertex buffers-----------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
	GfxResourceID id = genBuffer();
	_glBindBuffer(GL_ARRAY_BUFFER, id);
	return id;
}

void Gfx_BindVb(GfxResourceID vb) { 
	_glBindBuffer(GL_ARRAY_BUFFER, vb); 
}

void Gfx_DeleteVb(GfxResourceID* vb) {
	GfxResourceID id = *vb;
	if (id) delBuffer(id);
	*vb = 0;
}

void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
	return FastAllocTempMem(count * strideSizes[fmt]);
}

static cc_bool UnlockVb(GfxResourceID vb) {
	_glBufferData(GL_ARRAY_BUFFER, tmpSize, tmpData, GL_STATIC_DRAW);
#if defined CC_BUILD_SYMBIAN
	return _glGetError() != GL_OUT_OF_MEMORY;
#else
	return true;
#endif
}


/*########################################################################################################################*
*--------------------------------------------------Dynamic vertex buffers-------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
	GfxResourceID id = genBuffer();
	cc_uint32 size   = maxVertices * strideSizes[fmt];

	_glBindBuffer(GL_ARRAY_BUFFER, id);
	_glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
	return id;
}

void Gfx_BindDynamicVb(GfxResourceID vb) {
	_glBindBuffer(GL_ARRAY_BUFFER, vb); 
}

void Gfx_DeleteDynamicVb(GfxResourceID* vb) {
	GfxResourceID id = *vb;
	if (id) delBuffer(id);
	*vb = 0;
}

void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
	return FastAllocTempMem(count * strideSizes[fmt]);
}

void Gfx_UnlockDynamicVb(GfxResourceID vb) {
	_glBindBuffer(GL_ARRAY_BUFFER, vb);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, tmpSize, tmpData);
}

void Gfx_SetDynamicVbData(GfxResourceID vb, void* vertices, int vCount) {
	cc_uint32 size = vCount * gfx_stride;
	_glBindBuffer(GL_ARRAY_BUFFER, vb);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, size, vertices);
}


/*########################################################################################################################*
*----------------------------------------------------------Drawing--------------------------------------------------------*
*#########################################################################################################################*/
/* no client side array, use vertex buffer object */
#define VB_PTR 0
#define IB_PTR NULL

static void GL_SetupVbColoured(void) {
	_glVertexPointer(3, GL_FLOAT,        SIZEOF_VERTEX_COLOURED, (GLpointer)(VB_PTR +  0));
	_glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_COLOURED, (GLpointer)(VB_PTR + 12));
}

static void GL_SetupVbTextured(void) {
	_glVertexPointer(3, GL_FLOAT,        SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR +  0));
	_glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + 12));
	_glTexCoordPointer(2, GL_FLOAT,      SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + 16));
}

static void GL_SetupVbColoured_Range(int startVertex) {
	cc_uint32 offset = startVertex * SIZEOF_VERTEX_COLOURED;
	_glVertexPointer(3, GL_FLOAT,          SIZEOF_VERTEX_COLOURED, (GLpointer)(VB_PTR + offset +  0));
	_glColorPointer(4, GL_UNSIGNED_BYTE,   SIZEOF_VERTEX_COLOURED, (GLpointer)(VB_PTR + offset + 12));
}

static void GL_SetupVbTextured_Range(int startVertex) {
	cc_uint32 offset = startVertex * SIZEOF_VERTEX_TEXTURED;
	_glVertexPointer(3,  GL_FLOAT,         SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset +  0));
	_glColorPointer(4, GL_UNSIGNED_BYTE,   SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset + 12));
	_glTexCoordPointer(2, GL_FLOAT,        SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset + 16));
}

void Gfx_SetVertexFormat(VertexFormat fmt) {
	if (fmt == gfx_format) return;
	gfx_format = fmt;
	gfx_stride = strideSizes[fmt];

	if (fmt == VERTEX_FORMAT_TEXTURED) {
		_glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		_glEnable(GL_TEXTURE_2D);

		gfx_setupVBFunc      = GL_SetupVbTextured;
		gfx_setupVBRangeFunc = GL_SetupVbTextured_Range;
	} else {
		_glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		_glDisable(GL_TEXTURE_2D);

		gfx_setupVBFunc      = GL_SetupVbColoured;
		gfx_setupVBRangeFunc = GL_SetupVbColoured_Range;
	}
}

void Gfx_DrawVb_Lines(int verticesCount) {
	gfx_setupVBFunc();
	_glDrawArrays(GL_LINES, 0, verticesCount);
}

void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex, DrawHints hints) {
	gfx_setupVBRangeFunc(startVertex);
	_glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, IB_PTR);
}

void Gfx_DrawVb_IndexedTris(int verticesCount) {
	gfx_setupVBFunc();
	_glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, IB_PTR);
}

void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
	cc_uint32 offset = startVertex * SIZEOF_VERTEX_TEXTURED;
	_glVertexPointer(3, GL_FLOAT,        SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset +  0));
	_glColorPointer(4, GL_UNSIGNED_BYTE, SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset + 12));
	_glTexCoordPointer(2, GL_FLOAT,      SIZEOF_VERTEX_TEXTURED, (GLpointer)(VB_PTR + offset + 16));
	_glDrawElements(GL_TRIANGLES,        ICOUNT(verticesCount),  GL_UNSIGNED_SHORT, IB_PTR);
}


/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_BindTexture(GfxResourceID texID) { setTexture(texID); }


/*########################################################################################################################*
*-----------------------------------------------------State management----------------------------------------------------*
*#########################################################################################################################*/
static PackedCol gfx_fogColor;
static float gfx_fogEnd, gfx_fogDensity;
static int gfx_fogMode;

void Gfx_SetFog(cc_bool enabled) {
	gfx_fogEnabled = enabled;
	if (enabled) { _glEnable(GL_FOG); } else { _glDisable(GL_FOG); }
}

void Gfx_SetFogCol(PackedCol color) {
	float rgba[4];
	if (color == gfx_fogColor) return;

	rgba[0] = PackedCol_R(color) / 255.0f; 
	rgba[1] = PackedCol_G(color) / 255.0f;
	rgba[2] = PackedCol_B(color) / 255.0f; 
	rgba[3] = PackedCol_A(color) / 255.0f;

	_glFogfv(GL_FOG_COLOR, rgba);
	gfx_fogColor = color;
}

void Gfx_SetFogDensity(float value) {
	if (value == gfx_fogDensity) return;
	_glFogf(GL_FOG_DENSITY, value);
	gfx_fogDensity = value;
}

void Gfx_SetFogEnd(float value) {
	if (value == gfx_fogEnd) return;
	_glFogf(GL_FOG_END, value);
	gfx_fogEnd = value;
}

void Gfx_SetFogMode(FogFunc func) {
	static GLint modes[3] = { GL_LINEAR, GL_EXP, GL_EXP2 };
	if (func == gfx_fogMode) return;

#ifdef CC_BUILD_GLES
	/* OpenGL ES doesn't support glFogi, so use glFogf instead */
	/*  https://www.khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/ */
	_glFogf(GL_FOG_MODE, modes[func]);
#else
	_glFogi(GL_FOG_MODE, modes[func]);
#endif
	gfx_fogMode = func;
}

static void SetAlphaTest(cc_bool enabled) {
	if (enabled) { _glEnable(GL_ALPHA_TEST); } else { _glDisable(GL_ALPHA_TEST); }
}

void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
	cc_bool enabled = !depthOnly;
#ifdef CC_BUILD_SYMBIAN
	if (mbx) {
		/* On PowerVR MBX cards, glColorMask appears to be unimplemented in hardware */
		/*  and thus any usage of it significantly hurts performance. */
		/* So instead, draw with blending of 'new_RGB = current_RGB' - that way */
		/*  depth buffer is updated while the colour buffer remain unchanged. */
		if (depthOnly) {
			_glBlendFunc(GL_ZERO, GL_ONE);
			Gfx_SetAlphaBlending(true);
		} else {
			_glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}
		return;
	}
#endif
	SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], 
				  enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]);
	
	if (enabled) { _glEnable(GL_TEXTURE_2D); } else { _glDisable(GL_TEXTURE_2D); }
}


/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
static GLenum matrix_modes[] = { GL_PROJECTION, GL_MODELVIEW, GL_TEXTURE };
static int lastMatrix;

void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
	if (type != lastMatrix) { lastMatrix = type; _glMatrixMode(matrix_modes[type]); }

	if (matrix == &Matrix_Identity) {
		_glLoadIdentity();
	} else {
		_glLoadMatrixf((const float*)matrix);
	}
}

void Gfx_LoadMVP(const struct Matrix* view, const struct Matrix* proj, struct Matrix* mvp) {
	Gfx_LoadMatrix(MATRIX_VIEW, view);
	Gfx_LoadMatrix(MATRIX_PROJ, proj);
	Matrix_Mul(mvp, view, proj);
}

static struct Matrix texMatrix = Matrix_IdentityValue;
void Gfx_EnableTextureOffset(float x, float y) {
	texMatrix.row4.x = x; texMatrix.row4.y = y;
	Gfx_LoadMatrix(2, &texMatrix);
}

void Gfx_DisableTextureOffset(void) { Gfx_LoadMatrix(2, &Matrix_Identity); }


/*########################################################################################################################*
*-------------------------------------------------------State setup-------------------------------------------------------*
*#########################################################################################################################*/
static void Gfx_FreeState(void) { FreeDefaultResources(); }
static void Gfx_RestoreState(void) {
	InitDefaultResources();
	_glEnableClientState(GL_VERTEX_ARRAY);
	_glEnableClientState(GL_COLOR_ARRAY);

	gfx_format = -1;
	lastMatrix = -1;

	gfx_clearColor = 0;
	gfx_fogColor   = 0;
	gfx_fogEnd     = -1.0f;
	gfx_fogDensity = -1.0f;
	gfx_fogMode    = -1;

	_glHint(GL_FOG_HINT, GL_NICEST);
#if defined CC_BUILD_SYMBIAN
	_glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
#endif

	_glAlphaFunc(GL_GREATER, 0.5f);
	_glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	_glDepthFunc(GL_LEQUAL);
}

cc_bool Gfx_WarnIfNecessary(void) {
	cc_string renderer = String_FromReadonly((const char*)_glGetString(GL_RENDERER));

	if (String_ContainsConst(&renderer, "llvmpipe")) {
		Chat_AddRaw("&cSoftware rendering is being used, performance will greatly suffer.");
		Chat_AddRaw("&cVSync may not work, and you may see disappearing clouds and map edges.");
		Chat_AddRaw("&cYou may need to install video card drivers.");

		Gfx.Limitations |= GFX_LIMIT_VERTEX_ONLY_FOG;
		return true;
	}
	if (String_ContainsConst(&renderer, "Intel")) {
		Chat_AddRaw("&cIntel graphics cards are known to have issues with the OpenGL build.");
		Chat_AddRaw("&cVSync may not work, and you may see disappearing clouds and map edges.");
		#ifdef CC_BUILD_WIN
		Chat_AddRaw("&cTry downloading the Direct3D 9 build instead.");
		#endif

		Gfx.Limitations |= GFX_LIMIT_VERTEX_ONLY_FOG;
		return true;
	}
#if defined CC_BUILD_SYMBIAN
	if (String_ContainsConst(&renderer, "SGX")) {
	} else if (String_ContainsConst(&renderer, "MBX")) {
		Gfx.Limitations |= GFX_LIMIT_MINIMAL;
		mbx = true;
	} else if (!String_ContainsConst(&renderer, "HW")) {
		Chat_AddRaw("&cSoftware rendering is being used, performance will greatly suffer.");

		Gfx.Limitations |= GFX_LIMIT_MINIMAL;
		return true;
	}
#endif
	return false;
}

cc_bool Gfx_GetUIOptions(struct MenuOptionsScreen* s) { return false; }

void Gfx_GetApiInfo(cc_string* info) {
	int pointerSize = sizeof(void*) * 8;

	String_Format1(info, "-- Using OpenGL (%i bit) --\n", &pointerSize);
	GetGLApiInfo(info);
}


/*########################################################################################################################*
*-------------------------------------------------------Compatibility-----------------------------------------------------*
*#########################################################################################################################*/
#ifdef CC_BUILD_GL11_FALLBACK
static FP_glDrawElements    _realDrawElements;
static FP_glColorPointer    _realColorPointer;
static FP_glTexCoordPointer _realTexCoordPointer;
static FP_glVertexPointer   _realVertexPointer;

/* On Windows, can replace the GL function drawing with these 1.1 fallbacks */
/* fake vertex buffer objects by using client side pointers instead */
typedef struct legacy_buffer { cc_uint8* data; } legacy_buffer;
static legacy_buffer* cur_ib;
static legacy_buffer* cur_vb;
#define legacy_GetBuffer(target) (target == GL_ELEMENT_ARRAY_BUFFER ? &cur_ib : &cur_vb);

static GfxResourceID legacy_genBuffer(void) {
	return Mem_TryAllocCleared(1, sizeof(legacy_buffer));
}

static void legacy_deleteBuffer(GfxResourceID id) {
	Mem_Free(id);
}

static void APIENTRY legacy_bindBuffer(GLenum target, GfxResourceID src) {
	legacy_buffer** buffer = legacy_GetBuffer(target);
	*buffer = (legacy_buffer*)src;
}

static void APIENTRY legacy_bufferData(GLenum target, cc_uintptr size, const GLvoid* data, GLenum usage) {
	legacy_buffer* buffer = *legacy_GetBuffer(target);
	Mem_Free(buffer->data);

	buffer->data = Mem_TryAlloc(size, 1);
	if (data) Mem_Copy(buffer->data, data, size);
}

static void APIENTRY legacy_bufferSubData(GLenum target, cc_uintptr offset, cc_uintptr size, const GLvoid* data) {
	legacy_buffer* buffer = *legacy_GetBuffer(target);
	Mem_Copy(buffer->data, data, size);
}


struct GL10Texture {
	int width, height;
	unsigned char* pixels;
};
static struct GL10Texture* gl10_tex;

static void gl10_bindTexture(GfxResourceID texID) {
	gl10_tex = (struct GL10Texture*)texID;
	if (gl10_tex && gl10_tex->pixels) {
		_glTexImage2D(GL_TEXTURE_2D, 0, 4, gl10_tex->width, gl10_tex->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, gl10_tex->pixels);
	} else {
		BitmapCol pixel = BITMAPCOLOR_WHITE;
		_glTexImage2D(GL_TEXTURE_2D, 0, 4, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
	}
}

static void gl10_deleteTexture(GfxResourceID id) {
	struct GL10Texture* tex = (struct GL10Texture*)id;
	if (tex->pixels) Mem_Free(tex->pixels);
	if (tex) Mem_Free(tex);
}

static GfxResourceID gl10_genTexture(void) {
	return Mem_AllocCleared(1, sizeof(struct GL10Texture), "GL 1.0 texture");
}

static void APIENTRY gl10_texImage(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels) {
	int i;
	gl10_tex->width  = width;
	gl10_tex->height = height;
	gl10_tex->pixels = Mem_Alloc(width * height, 4, "GL 1.0 pixels");

	ConvertRGBA(gl10_tex->pixels, pixels, width * height);
}

static void APIENTRY gl10_texSubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels) {
	/* TODO */
}

static void APIENTRY gl10_disableClientState(GLenum target) { }

static void APIENTRY gl10_enableClientState(GLenum target) { }

static cc_uint8* gl10_vb;
static void APIENTRY gl10_drawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) {
	/* TODO */
	int i;
	_glBegin(GL_QUADS);
	count = (count * 4) / 6;

	if (gfx_format == VERTEX_FORMAT_TEXTURED) {
		struct VertexTextured* src = (struct VertexTextured*)gl10_vb;
		for (i = 0; i < count; i++, src++) 
		{
			_glColor4ub(PackedCol_R(src->Col), PackedCol_G(src->Col), PackedCol_B(src->Col), PackedCol_A(src->Col));
			_glTexCoord2f(src->U, src->V);
			_glVertex3f(src->x, src->y, src->z);
		}
	} else {
		struct VertexColoured* src = (struct VertexColoured*)gl10_vb;
		for (i = 0; i < count; i++, src++) 
		{
			_glColor4ub(PackedCol_R(src->Col), PackedCol_G(src->Col), PackedCol_B(src->Col), PackedCol_A(src->Col));
			_glVertex3f(src->x, src->y, src->z);
		}
	}

	_glEnd();
}
static void APIENTRY gl10_colorPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
}
static void APIENTRY gl10_texCoordPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
}
static void APIENTRY gl10_vertexPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
	gl10_vb = cur_vb->data + (cc_uintptr)offset;
}


static void APIENTRY gl11_drawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) {
	_realDrawElements(mode, count, type, (cc_uintptr)indices + cur_ib->data);
}
static void APIENTRY gl11_colorPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
	_realColorPointer(size,    type, stride, (cc_uintptr)cur_vb->data + offset);
}
static void APIENTRY gl11_texCoordPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
	_realTexCoordPointer(size, type, stride, (cc_uintptr)cur_vb->data + offset);
}
static void APIENTRY gl11_vertexPointer(GLint size, GLenum type, GLsizei stride, GLpointer offset) {
	_realVertexPointer(size,   type, stride, (cc_uintptr)cur_vb->data + offset);
}


static void FallbackOpenGL(void) {
	Window_ShowDialog("Performance warning",
		"Your system only supports only OpenGL 1.1\n" \
		"This is usually caused by graphics drivers not being installed\n\n" \
		"As such you will likely experience very poor performance");
	customMipmapsLevels = false;
		
	genBuffer        = legacy_genBuffer;
	delBuffer        = legacy_deleteBuffer;
	_glBindBuffer    = legacy_bindBuffer;
	_glBufferData    = legacy_bufferData;
	_glBufferSubData = legacy_bufferSubData;

	_realDrawElements    = _glDrawElements;    _realColorPointer  = _glColorPointer;
	_realTexCoordPointer = _glTexCoordPointer; _realVertexPointer = _glVertexPointer;

	_glDrawElements    = gl11_drawElements;    _glColorPointer  = gl11_colorPointer;
	_glTexCoordPointer = gl11_texCoordPointer; _glVertexPointer = gl11_vertexPointer;

	/* OpenGL 1.0 fallback support */
	if (_realDrawElements) return;
	Window_ShowDialog("Performance warning", "OpenGL 1.0 only support, expect awful performance");

	_glDrawElements    = gl10_drawElements;    _glColorPointer  = gl10_colorPointer;
	_glTexCoordPointer = gl10_texCoordPointer; _glVertexPointer = gl10_vertexPointer;

	setTexture        = gl10_bindTexture;
	genTexture        = gl10_genTexture;
	delTexture        = gl10_deleteTexture;
	_glTexImage2D     = gl10_texImage;
	_glTexSubImage2D  = gl10_texSubImage;

	_glDisableClientState = gl10_disableClientState;
	_glEnableClientState  = gl10_enableClientState;
}
#else
/* No point in even trying for other systems */
static void FallbackOpenGL(void) {
	Logger_FailToStart("Only OpenGL 1.1 supported.\n\n" \
		"Compile the game with CC_BUILD_GL11, or ask on the ClassiCube forums for it");
}
#endif

static void GLBackend_Init(void) {
#if defined CC_BUILD_SYMBIAN
	static const cc_string bgra_ext = String_FromConst("EXT_texture_format_BGRA8888");
	static const cc_string bgra_sym = String_FromConst("GL_IMG_texture_format_BGRA8888");
	cc_string extensions = String_FromReadonly((const char*)_glGetString(GL_EXTENSIONS));
	
	cc_bool has_ext_bgra = String_CaselessContains(&extensions, &bgra_ext);
	cc_bool has_sym_bgra = String_CaselessContains(&extensions, &bgra_sym);
	
	_glGenBuffers    = glGenBuffers;
	_glDeleteBuffers = glDeleteBuffers;
	_glBindBuffer    = glBindBuffer;
	_glBufferData    = glBufferData;
	_glBufferSubData = glBufferSubData;
	
	convert_rgba = PIXEL_FORMAT != GL_RGBA && !has_ext_bgra && !has_sym_bgra;
#else
	static const struct DynamicLibSym coreVboFuncs[] = {
		DynamicLib_ReqSym2("glBindBuffer",    glBindBuffer), DynamicLib_ReqSym2("glDeleteBuffers", glDeleteBuffers),
		DynamicLib_ReqSym2("glGenBuffers",    glGenBuffers), DynamicLib_ReqSym2("glBufferData",    glBufferData),
		DynamicLib_ReqSym2("glBufferSubData", glBufferSubData)
	};
	static const struct DynamicLibSym arbVboFuncs[] = {
		DynamicLib_ReqSym2("glBindBufferARB",    glBindBuffer), DynamicLib_ReqSym2("glDeleteBuffersARB", glDeleteBuffers),
		DynamicLib_ReqSym2("glGenBuffersARB",    glGenBuffers), DynamicLib_ReqSym2("glBufferDataARB",    glBufferData),
		DynamicLib_ReqSym2("glBufferSubDataARB", glBufferSubData)
	};

	static const cc_string vboExt  = String_FromConst("GL_ARB_vertex_buffer_object");
	static const cc_string bgraExt = String_FromConst("GL_EXT_bgra");
	cc_string extensions = String_FromReadonly((const char*)_glGetString(GL_EXTENSIONS));
	const GLubyte* ver   = _glGetString(GL_VERSION);

	/* Version string is always: x.y. (and whatever afterwards) */
	int major = ver[0] - '0', minor = ver[2] - '0';

	/* Supported in core since 1.5 */
	if (major > 1 || (major == 1 && minor >= 5)) {
		GLContext_GetAll(coreVboFuncs, Array_Elems(coreVboFuncs));
	} else if (String_CaselessContains(&extensions, &vboExt)) {
		GLContext_GetAll(arbVboFuncs,  Array_Elems(arbVboFuncs));
	} else {
		/* Some old IRIX cards don't support BGRA */
		convert_rgba = major == 1 && minor <= 1 && !String_CaselessContains(&extensions, &bgraExt);
		FallbackOpenGL();
	}
#endif
}
#endif

