summary refs log tree commit diff
path: root/src/Graphics_GL2.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Graphics_GL2.c
initial commit
Diffstat (limited to 'src/Graphics_GL2.c')
-rw-r--r--src/Graphics_GL2.c661
1 files changed, 661 insertions, 0 deletions
diff --git a/src/Graphics_GL2.c b/src/Graphics_GL2.c
new file mode 100644
index 0000000..d9d6a8d
--- /dev/null
+++ b/src/Graphics_GL2.c
@@ -0,0 +1,661 @@
+/* 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_GL2
+#include "_GraphicsBase.h"
+#include "Errors.h"
+#include "Window.h"
+
+/* OpenGL 2.0 backend (alternative modern-ish backend) */
+#include "../misc/opengl/GLCommon.h"
+
+/* e.g. GLAPI void APIENTRY glFunction(int args); */
+#define GL_FUNC(_retType, name) GLAPI _retType APIENTRY name
+#include "../misc/opengl/GL1Funcs.h"
+
+/* Functions must be dynamically linked on Windows */
+#ifdef CC_BUILD_WIN
+/* e.g. static void (APIENTRY *_glFunction)(int args); */
+#undef  GL_FUNC
+#define GL_FUNC(_retType, name) static _retType (APIENTRY *name)
+#include "../misc/opengl/GL2Funcs.h"
+
+#define GLSym(sym) { DYNAMICLIB_QUOTE(sym), (void**)& ## sym }
+static const struct DynamicLibSym core_funcs[] = {
+	GLSym(glBindBuffer), GLSym(glDeleteBuffers), GLSym(glGenBuffers), GLSym(glBufferData), GLSym(glBufferSubData),
+
+	GLSym(glCreateShader),  GLSym(glDeleteShader),  GLSym(glGetShaderiv), GLSym(glGetShaderInfoLog), GLSym(glShaderSource),
+	GLSym(glAttachShader),  GLSym(glBindAttribLocation), GLSym(glCompileShader), GLSym(glDetachShader), GLSym(glLinkProgram),
+
+	GLSym(glCreateProgram), GLSym(glDeleteProgram), GLSym(glGetProgramiv), GLSym(glGetProgramInfoLog), GLSym(glUseProgram),
+
+	GLSym(glDisableVertexAttribArray), GLSym(glEnableVertexAttribArray), GLSym(glVertexAttribPointer),
+
+	GLSym(glGetUniformLocation), GLSym(glUniform1f), GLSym(glUniform2f), GLSym(glUniform3f), GLSym(glUniformMatrix4fv),
+};
+#else
+#include "../misc/opengl/GL2Funcs.h"
+#endif
+
+#define _glBindTexture    glBindTexture
+#define _glDeleteTextures glDeleteTextures
+#define _glGenTextures    glGenTextures
+#define _glTexImage2D     glTexImage2D
+#define _glTexSubImage2D  glTexSubImage2D
+
+#include "_GLShared.h"
+static GfxResourceID white_square;
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Index buffers-----------------------------------------------------*
+*#########################################################################################################################*/
+static GLuint GL_GenAndBind(GLenum target) {
+	GLuint id;
+	glGenBuffers(1, &id);
+	glBindBuffer(target, id);
+	return id;
+}
+
+GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
+	cc_uint16 indices[GFX_MAX_INDICES];
+	GLuint id      = GL_GenAndBind(GL_ELEMENT_ARRAY_BUFFER);
+	cc_uint32 size = count * sizeof(cc_uint16);
+
+	fillFunc(indices, count, obj);
+	glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, indices, GL_STATIC_DRAW);
+	return uint_to_ptr(id);
+}
+
+void Gfx_BindIb(GfxResourceID ib) { 
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ptr_to_uint(ib)); 
+}
+
+void Gfx_DeleteIb(GfxResourceID* ib) {
+	GLuint id = ptr_to_uint(*ib);
+	if (!id) return;
+	glDeleteBuffers(1, &id);
+	*ib = 0;
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Vertex buffers-----------------------------------------------------*
+*#########################################################################################################################*/
+static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
+	GLuint id = GL_GenAndBind(GL_ARRAY_BUFFER);
+	return uint_to_ptr(id);
+}
+
+void Gfx_BindVb(GfxResourceID vb) { 
+	glBindBuffer(GL_ARRAY_BUFFER, ptr_to_uint(vb)); 
+}
+
+void Gfx_DeleteVb(GfxResourceID* vb) {
+	GLuint id = ptr_to_uint(*vb);
+	if (id) glDeleteBuffers(1, &id);
+	*vb = 0;
+}
+
+void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
+	return FastAllocTempMem(count * strideSizes[fmt]);
+}
+
+void Gfx_UnlockVb(GfxResourceID vb) {
+	glBufferData(GL_ARRAY_BUFFER, tmpSize, tmpData, GL_STATIC_DRAW);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------Dynamic vertex buffers-------------------------------------------------*
+*#########################################################################################################################*/
+static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
+	GLuint id      = GL_GenAndBind(GL_ARRAY_BUFFER);
+	cc_uint32 size = maxVertices * strideSizes[fmt];
+
+	glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
+	return uint_to_ptr(id);
+}
+
+void Gfx_BindDynamicVb(GfxResourceID vb) {
+	glBindBuffer(GL_ARRAY_BUFFER, ptr_to_uint(vb)); 
+}
+
+void Gfx_DeleteDynamicVb(GfxResourceID* vb) {
+	GLuint id = ptr_to_uint(*vb);
+	if (id) glDeleteBuffers(1, &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, ptr_to_uint(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, ptr_to_uint(vb));
+	glBufferSubData(GL_ARRAY_BUFFER, 0, size, vertices);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------OpenGL modern------------------------------------------------------*
+*#########################################################################################################################*/
+#define FTR_TEXTURE_UV (1 << 0)
+#define FTR_ALPHA_TEST (1 << 1)
+#define FTR_TEX_OFFSET (1 << 2)
+#define FTR_LINEAR_FOG (1 << 3)
+#define FTR_DENSIT_FOG (1 << 4)
+#define FTR_HASANY_FOG (FTR_LINEAR_FOG | FTR_DENSIT_FOG)
+#define FTR_FS_MEDIUMP (1 << 7)
+
+#define UNI_MVP_MATRIX (1 << 0)
+#define UNI_TEX_OFFSET (1 << 1)
+#define UNI_FOG_COL    (1 << 2)
+#define UNI_FOG_END    (1 << 3)
+#define UNI_FOG_DENS   (1 << 4)
+#define UNI_MASK_ALL   0x1F
+
+/* cached uniforms (cached for multiple programs */
+static struct Matrix _view, _proj, _mvp;
+static cc_bool gfx_texTransform;
+static float _texX, _texY;
+static PackedCol gfx_fogColor;
+static float gfx_fogEnd = -1.0f, gfx_fogDensity = -1.0f;
+static int gfx_fogMode = -1;
+
+/* shader programs (emulate fixed function) */
+static struct GLShader {
+	int features;     /* what features are enabled for this shader */
+	int uniforms;     /* which associated uniforms need to be resent to GPU */
+	GLuint program;   /* OpenGL program ID (0 if not yet compiled) */
+	int locations[5]; /* location of uniforms (not constant) */
+} shaders[6 * 3] = {
+	/* no fog */
+	{ 0              },
+	{ 0              | FTR_ALPHA_TEST },
+	{ FTR_TEXTURE_UV },
+	{ FTR_TEXTURE_UV | FTR_ALPHA_TEST },
+	{ FTR_TEXTURE_UV | FTR_TEX_OFFSET },
+	{ FTR_TEXTURE_UV | FTR_TEX_OFFSET | FTR_ALPHA_TEST },
+	/* linear fog */
+	{ FTR_LINEAR_FOG | 0              },
+	{ FTR_LINEAR_FOG | 0              | FTR_ALPHA_TEST },
+	{ FTR_LINEAR_FOG | FTR_TEXTURE_UV },
+	{ FTR_LINEAR_FOG | FTR_TEXTURE_UV | FTR_ALPHA_TEST },
+	{ FTR_LINEAR_FOG | FTR_TEXTURE_UV | FTR_TEX_OFFSET },
+	{ FTR_LINEAR_FOG | FTR_TEXTURE_UV | FTR_TEX_OFFSET | FTR_ALPHA_TEST },
+	/* density fog */
+	{ FTR_DENSIT_FOG | 0              },
+	{ FTR_DENSIT_FOG | 0              | FTR_ALPHA_TEST },
+	{ FTR_DENSIT_FOG | FTR_TEXTURE_UV },
+	{ FTR_DENSIT_FOG | FTR_TEXTURE_UV | FTR_ALPHA_TEST },
+	{ FTR_DENSIT_FOG | FTR_TEXTURE_UV | FTR_TEX_OFFSET },
+	{ FTR_DENSIT_FOG | FTR_TEXTURE_UV | FTR_TEX_OFFSET | FTR_ALPHA_TEST },
+};
+static struct GLShader* gfx_activeShader;
+
+/* Generates source code for a GLSL vertex shader, based on shader's flags */
+static void GenVertexShader(const struct GLShader* shader, cc_string* dst) {
+	int uv = shader->features & FTR_TEXTURE_UV;
+	int tm = shader->features & FTR_TEX_OFFSET;
+
+	String_AppendConst(dst,         "attribute vec3 in_pos;\n");
+	String_AppendConst(dst,         "attribute vec4 in_col;\n");
+	if (uv) String_AppendConst(dst, "attribute vec2 in_uv;\n");
+	String_AppendConst(dst,         "varying vec4 out_col;\n");
+	if (uv) String_AppendConst(dst, "varying vec2 out_uv;\n");
+	String_AppendConst(dst,         "uniform mat4 mvp;\n");
+	if (tm) String_AppendConst(dst, "uniform vec2 texOffset;\n");
+
+	String_AppendConst(dst,         "void main() {\n");
+	String_AppendConst(dst,         "  gl_Position = mvp * vec4(in_pos, 1.0);\n");
+	String_AppendConst(dst,         "  out_col = in_col;\n");
+	if (uv) String_AppendConst(dst, "  out_uv  = in_uv;\n");
+	if (tm) String_AppendConst(dst, "  out_uv  = out_uv + texOffset;\n");
+	String_AppendConst(dst,         "}");
+}
+
+/* Generates source code for a GLSL fragment shader, based on shader's flags */
+static void GenFragmentShader(const struct GLShader* shader, cc_string* dst) {
+	int uv = shader->features & FTR_TEXTURE_UV;
+	int al = shader->features & FTR_ALPHA_TEST;
+	int fl = shader->features & FTR_LINEAR_FOG;
+	int fd = shader->features & FTR_DENSIT_FOG;
+	int fm = shader->features & FTR_HASANY_FOG;
+
+#ifdef CC_BUILD_GLES
+	int mp = shader->features & FTR_FS_MEDIUMP;
+	if (mp) String_AppendConst(dst, "precision mediump float;\n");
+	else    String_AppendConst(dst, "precision highp float;\n");
+#endif
+
+	String_AppendConst(dst,         "varying vec4 out_col;\n");
+	if (uv) String_AppendConst(dst, "varying vec2 out_uv;\n");
+	if (uv) String_AppendConst(dst, "uniform sampler2D texImage;\n");
+	if (fm) String_AppendConst(dst, "uniform vec3 fogCol;\n");
+	if (fl) String_AppendConst(dst, "uniform float fogEnd;\n");
+	if (fd) String_AppendConst(dst, "uniform float fogDensity;\n");
+
+	String_AppendConst(dst,         "void main() {\n");
+	if (uv) String_AppendConst(dst, "  vec4 col = texture2D(texImage, out_uv) * out_col;\n");
+	else    String_AppendConst(dst, "  vec4 col = out_col;\n");
+	if (al) String_AppendConst(dst, "  if (col.a < 0.5) discard;\n");
+	if (fm) String_AppendConst(dst, "  float depth = 1.0 / gl_FragCoord.w;\n");
+	if (fl) String_AppendConst(dst, "  float f = clamp((fogEnd - depth) / fogEnd, 0.0, 1.0);\n");
+	if (fd) String_AppendConst(dst, "  float f = clamp(exp(fogDensity * depth), 0.0, 1.0);\n");
+	if (fm) String_AppendConst(dst, "  col.rgb = mix(fogCol, col.rgb, f);\n");
+	String_AppendConst(dst,         "  gl_FragColor = col;\n");
+	String_AppendConst(dst,         "}");
+}
+
+/* Tries to compile GLSL shader code */
+static GLint CompileShader(GLint shader, const cc_string* src) {
+	const char* str = src->buffer;
+	int len = src->length;
+	GLint temp;
+
+	glShaderSource(shader, 1, &str, &len);
+	glCompileShader(shader);
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &temp);
+	return temp;
+}
+
+/* Logs information then aborts program */
+static void ShaderFailed(GLint shader) {
+	char logInfo[2048];
+	GLint temp;
+	if (!shader) Logger_Abort("Failed to create shader");
+
+	temp = 0;
+	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &temp);
+
+	if (temp > 1) {
+		glGetShaderInfoLog(shader, 2047, NULL, logInfo);
+		logInfo[2047] = '\0';
+		Window_ShowDialog("Failed to compile shader", logInfo);
+	}
+	Logger_Abort("Failed to compile shader");
+}
+
+/* Tries to compile vertex and fragment shaders, then link into an OpenGL program */
+static void CompileProgram(struct GLShader* shader) {
+	char tmpBuffer[2048]; cc_string tmp;
+	GLuint vs, fs, program;
+	GLint temp;
+
+	vs = glCreateShader(GL_VERTEX_SHADER);
+	if (!vs) { Platform_LogConst("Failed to create vertex shader"); return; }
+	
+	String_InitArray(tmp, tmpBuffer);
+	GenVertexShader(shader, &tmp);
+	if (!CompileShader(vs, &tmp)) ShaderFailed(vs);
+
+	fs = glCreateShader(GL_FRAGMENT_SHADER);
+	if (!fs) { Platform_LogConst("Failed to create fragment shader"); glDeleteShader(vs); return; }
+
+	tmp.length = 0;
+	GenFragmentShader(shader, &tmp);
+	if (!CompileShader(fs, &tmp)) {
+		/* Sometimes fails 'highp precision is not supported in fragment shader' */
+		/* So try compiling shader again without highp precision */
+		shader->features |= FTR_FS_MEDIUMP;
+
+		tmp.length = 0;
+		GenFragmentShader(shader, &tmp);
+		if (!CompileShader(fs, &tmp)) ShaderFailed(fs);
+	}
+
+
+	program  = glCreateProgram();
+	if (!program) Logger_Abort("Failed to create program");
+	shader->program = program;
+
+	glAttachShader(program, vs);
+	glAttachShader(program, fs);
+
+	/* Force in_pos/in_col/in_uv attributes to be bound to 0,1,2 locations */
+	/* Although most browsers assign the attributes in this order anyways, */
+	/* the specification does not require this. (e.g. Safari doesn't) */
+	glBindAttribLocation(program, 0, "in_pos");
+	glBindAttribLocation(program, 1, "in_col");
+	glBindAttribLocation(program, 2, "in_uv");
+
+	glLinkProgram(program);
+	glGetProgramiv(program, GL_LINK_STATUS, &temp);
+
+	if (temp) {
+		glDetachShader(program, vs);
+		glDetachShader(program, fs);
+
+		glDeleteShader(vs);
+		glDeleteShader(fs);
+
+		shader->locations[0] = glGetUniformLocation(program, "mvp");
+		shader->locations[1] = glGetUniformLocation(program, "texOffset");
+		shader->locations[2] = glGetUniformLocation(program, "fogCol");
+		shader->locations[3] = glGetUniformLocation(program, "fogEnd");
+		shader->locations[4] = glGetUniformLocation(program, "fogDensity");
+		return;
+	}
+	temp = 0;
+	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &temp);
+
+	if (temp > 0) {
+		glGetProgramInfoLog(program, 2047, NULL, tmpBuffer);
+		tmpBuffer[2047] = '\0';
+		Window_ShowDialog("Failed to compile program", tmpBuffer);
+	}
+	Logger_Abort("Failed to compile program");
+}
+
+/* Marks a uniform as changed on all programs */
+static void DirtyUniform(int uniform) {
+	int i;
+	for (i = 0; i < Array_Elems(shaders); i++) {
+		shaders[i].uniforms |= uniform;
+	}
+}
+
+/* Sends changed uniforms to the GPU for current program */
+static void ReloadUniforms(void) {
+	struct GLShader* s = gfx_activeShader;
+	if (!s) return; /* NULL if context is lost */
+
+	if (s->uniforms & UNI_MVP_MATRIX) {
+		glUniformMatrix4fv(s->locations[0], 1, false, (float*)&_mvp);
+		s->uniforms &= ~UNI_MVP_MATRIX;
+	}
+	if ((s->uniforms & UNI_TEX_OFFSET) && (s->features & FTR_TEX_OFFSET)) {
+		glUniform2f(s->locations[1], _texX, _texY);
+		s->uniforms &= ~UNI_TEX_OFFSET;
+	}
+	if ((s->uniforms & UNI_FOG_COL) && (s->features & FTR_HASANY_FOG)) {
+		glUniform3f(s->locations[2], PackedCol_R(gfx_fogColor) / 255.0f, PackedCol_G(gfx_fogColor) / 255.0f,
+									 PackedCol_B(gfx_fogColor) / 255.0f);
+		s->uniforms &= ~UNI_FOG_COL;
+	}
+	if ((s->uniforms & UNI_FOG_END) && (s->features & FTR_LINEAR_FOG)) {
+		glUniform1f(s->locations[3], gfx_fogEnd);
+		s->uniforms &= ~UNI_FOG_END;
+	}
+	if ((s->uniforms & UNI_FOG_DENS) && (s->features & FTR_DENSIT_FOG)) {
+		/* See https://docs.microsoft.com/en-us/previous-versions/ms537113(v%3Dvs.85) */
+		/* The equation for EXP mode is exp(-density * z), so just negate density here */
+		glUniform1f(s->locations[4], -gfx_fogDensity);
+		s->uniforms &= ~UNI_FOG_DENS;
+	}
+}
+
+/* Switches program to one that duplicates current fixed function state */
+/* Compiles program and reloads uniforms if needed */
+static void SwitchProgram(void) {
+	struct GLShader* shader;
+	int index = 0;
+
+	if (gfx_fogEnabled) {
+		index += 6;                       /* linear fog */
+		if (gfx_fogMode >= 1) index += 6; /* exp fog */
+	}
+
+	if (gfx_format == VERTEX_FORMAT_TEXTURED) index += 2;
+	if (gfx_texTransform) index += 2;
+	if (gfx_alphaTest)    index += 1;
+
+	shader = &shaders[index];
+	if (shader == gfx_activeShader) { ReloadUniforms(); return; }
+	if (!shader->program) CompileProgram(shader);
+
+	gfx_activeShader = shader;
+	glUseProgram(shader->program);
+	ReloadUniforms();
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Textures--------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_BindTexture(GfxResourceID texId) {
+	/* Texture 0 has different behaviour depending on backend */
+	/*   Desktop OpenGL  - pure white 1x1 texture */
+	/*   WebGL/OpenGL ES - pure black 1x1 texture */
+	/* So for consistency, always use a 1x1 pure white texture */
+	if (!texId) texId = white_square;
+	glBindTexture(GL_TEXTURE_2D, ptr_to_uint(texId));
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------State management----------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_SetFog(cc_bool enabled) { gfx_fogEnabled = enabled; SwitchProgram(); }
+void Gfx_SetFogCol(PackedCol color) {
+	if (color == gfx_fogColor) return;
+	gfx_fogColor = color;
+	DirtyUniform(UNI_FOG_COL);
+	ReloadUniforms();
+}
+
+void Gfx_SetFogDensity(float value) {
+	if (gfx_fogDensity == value) return;
+	gfx_fogDensity = value;
+	DirtyUniform(UNI_FOG_DENS);
+	ReloadUniforms();
+}
+
+void Gfx_SetFogEnd(float value) {
+	if (gfx_fogEnd == value) return;
+	gfx_fogEnd = value;
+	DirtyUniform(UNI_FOG_END);
+	ReloadUniforms();
+}
+
+void Gfx_SetFogMode(FogFunc func) {
+	if (gfx_fogMode == func) return;
+	gfx_fogMode = func;
+	SwitchProgram();
+}
+
+static void SetAlphaTest(cc_bool enabled) { SwitchProgram(); }
+
+void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
+	cc_bool enabled = !depthOnly;
+	SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], 
+				  enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Matrices--------------------------------------------------------*
+*#########################################################################################################################*/
+void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
+	if (type == MATRIX_VIEW)       _view = *matrix;
+	if (type == MATRIX_PROJECTION) _proj = *matrix;
+
+	Matrix_Mul(&_mvp, &_view, &_proj);
+	DirtyUniform(UNI_MVP_MATRIX);
+	ReloadUniforms();
+}
+void Gfx_LoadIdentityMatrix(MatrixType type) {
+	Gfx_LoadMatrix(type, &Matrix_Identity);
+}
+
+void Gfx_EnableTextureOffset(float x, float y) {
+	_texX = x; _texY = y;
+	gfx_texTransform = true;
+	DirtyUniform(UNI_TEX_OFFSET);
+	SwitchProgram();
+}
+
+void Gfx_DisableTextureOffset(void) {
+	gfx_texTransform = false;
+	SwitchProgram();
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------State setup-------------------------------------------------------*
+*#########################################################################################################################*/
+static void GLContext_GetAll(const struct DynamicLibSym* syms, int count) {
+	int i;
+	for (i = 0; i < count; i++) 
+	{
+		*syms[i].symAddr = GLContext_GetAddress(syms[i].name);
+	}
+}
+
+static void GLBackend_Init(void) {
+#ifdef CC_BUILD_WIN
+	GLContext_GetAll(core_funcs, Array_Elems(core_funcs));
+#endif
+
+#ifdef CC_BUILD_GLES
+	// OpenGL ES 2.0 doesn't support custom mipmaps levels, but 3.2 does
+	// Note that GL_MAJOR_VERSION and GL_MINOR_VERSION were not actually
+	//  implemented until 3.0.. but hopefully older GPU drivers out there
+	//  don't try and set a value even when it's unsupported
+	#define _GL_MAJOR_VERSION 33307
+	#define _GL_MINOR_VERSION 33308
+	
+	GLint major = 0, minor = 0;
+	glGetIntegerv(_GL_MAJOR_VERSION, &major);
+	glGetIntegerv(_GL_MINOR_VERSION, &minor);
+	customMipmapsLevels = major >= 3 && minor >= 2;
+#else
+    customMipmapsLevels = true;
+    const GLubyte* ver  = glGetString(GL_VERSION);
+    int major = ver[0] - '0', minor = ver[2] - '0';
+    if (major >= 2) return;
+
+    // OpenGL 1.x.. will likely either not work or perform poorly
+    cc_string str; char strBuffer[1024];
+    String_InitArray_NT(str, strBuffer);
+    String_Format2(&str,"Modern OpenGL build requires at least OpenGL 2.0\n" \
+                        "Your system only supports OpenGL %i.%i however\n\n" \
+                        "As such ClassiCube will likely perform poorly or not work\n" \
+                        "It is recommended you use the Normal OpenGL build instead\n",
+                        &major, &minor);
+    strBuffer[str.length] = '\0';
+    Window_ShowDialog("Compatibility warning", strBuffer);
+#endif
+}
+
+static void Gfx_FreeState(void) {
+	int i;
+	FreeDefaultResources();
+	gfx_activeShader = NULL;
+
+	for (i = 0; i < Array_Elems(shaders); i++) {
+		glDeleteProgram(shaders[i].program);
+		shaders[i].program = 0;
+	}
+	Gfx_DeleteTexture(&white_square);
+}
+
+static void Gfx_RestoreState(void) {
+	InitDefaultResources();
+	glEnableVertexAttribArray(0);
+	glEnableVertexAttribArray(1);
+	gfx_format = -1;
+
+	DirtyUniform(UNI_MASK_ALL);
+	GL_ClearColor(gfx_clearColor);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	glDepthFunc(GL_LEQUAL);
+
+	/* 1x1 dummy white texture */
+	struct Bitmap bmp;
+	BitmapCol pixels[1] = { BITMAPCOLOR_WHITE };
+	Bitmap_Init(bmp, 1, 1, pixels);
+	Gfx_RecreateTexture(&white_square, &bmp, 0, false);
+}
+cc_bool Gfx_WarnIfNecessary(void) { return false; }
+
+
+/*########################################################################################################################*
+*----------------------------------------------------------Drawing--------------------------------------------------------*
+*#########################################################################################################################*/
+typedef void (*GL_SetupVBFunc)(void);
+typedef void (*GL_SetupVBRangeFunc)(int startVertex);
+static GL_SetupVBFunc gfx_setupVBFunc;
+static GL_SetupVBRangeFunc gfx_setupVBRangeFunc;
+
+static void GL_SetupVbColoured(void) {
+	glVertexAttribPointer(0, 3, GL_FLOAT,         false, SIZEOF_VERTEX_COLOURED, uint_to_ptr( 0));
+	glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true,  SIZEOF_VERTEX_COLOURED, uint_to_ptr(12));
+}
+
+static void GL_SetupVbTextured(void) {
+	glVertexAttribPointer(0, 3, GL_FLOAT,         false, SIZEOF_VERTEX_TEXTURED, uint_to_ptr( 0));
+	glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true,  SIZEOF_VERTEX_TEXTURED, uint_to_ptr(12));
+	glVertexAttribPointer(2, 2, GL_FLOAT,         false, SIZEOF_VERTEX_TEXTURED, uint_to_ptr(16));
+}
+
+static void GL_SetupVbColoured_Range(int startVertex) {
+	cc_uint32 offset = startVertex * SIZEOF_VERTEX_COLOURED;
+	glVertexAttribPointer(0, 3, GL_FLOAT,         false, SIZEOF_VERTEX_COLOURED, uint_to_ptr(offset     ));
+	glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true,  SIZEOF_VERTEX_COLOURED, uint_to_ptr(offset + 12));
+}
+
+static void GL_SetupVbTextured_Range(int startVertex) {
+	cc_uint32 offset = startVertex * SIZEOF_VERTEX_TEXTURED;
+	glVertexAttribPointer(0, 3, GL_FLOAT,         false, SIZEOF_VERTEX_TEXTURED, uint_to_ptr(offset     ));
+	glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true,  SIZEOF_VERTEX_TEXTURED, uint_to_ptr(offset + 12));
+	glVertexAttribPointer(2, 2, GL_FLOAT,         false, SIZEOF_VERTEX_TEXTURED, uint_to_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) {
+		glEnableVertexAttribArray(2);
+		gfx_setupVBFunc      = GL_SetupVbTextured;
+		gfx_setupVBRangeFunc = GL_SetupVbTextured_Range;
+	} else {
+		glDisableVertexAttribArray(2);
+		gfx_setupVBFunc      = GL_SetupVbColoured;
+		gfx_setupVBRangeFunc = GL_SetupVbColoured_Range;
+	}
+	SwitchProgram();
+}
+
+void Gfx_DrawVb_Lines(int verticesCount) {
+	gfx_setupVBFunc();
+	glDrawArrays(GL_LINES, 0, verticesCount);
+}
+
+void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) {
+	gfx_setupVBRangeFunc(startVertex);
+	glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, NULL);
+}
+
+void Gfx_DrawVb_IndexedTris(int verticesCount) {
+	gfx_setupVBFunc();
+	glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, NULL);
+}
+
+void Gfx_BindVb_Textured(GfxResourceID vb) {
+	Gfx_BindVb(vb);
+	GL_SetupVbTextured();
+}
+
+void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
+	if (startVertex + verticesCount > GFX_MAX_VERTICES) {
+		GL_SetupVbTextured_Range(startVertex);
+		glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, NULL);
+		GL_SetupVbTextured();
+	} else {
+		/* ICOUNT(startVertex) * 2 = startVertex * 3  */
+		glDrawElements(GL_TRIANGLES, ICOUNT(verticesCount), GL_UNSIGNED_SHORT, (void*)(startVertex * 3));
+	}
+}
+#endif