text: #ifdef BLAH_USE_OPENGL
  
  #include "../internal/graphics_backend.h"
  #include "../internal/platform_backend.h"
  #include <blah/core/log.h>
  #include <stdio.h>
  #include <string.h>
  #include <stddef.h>
  
  #ifdef _WIN32
  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  #else
  #define APIENTRY
  #endif
  
  // OpenGL Value Types
  typedef ptrdiff_t		GLintptr;
  typedef ptrdiff_t		GLsizeiptr;
  typedef unsigned int	GLenum;
  typedef unsigned char	GLboolean;
  typedef unsigned int	GLbitfield;
  typedef void			GLvoid;
  typedef signed char		GLbyte;		/* 1-byte signed */
  typedef short			GLshort;	/* 2-byte signed */
  typedef int				GLint;		/* 4-byte signed */
  typedef unsigned char	GLubyte;	/* 1-byte unsigned */
  typedef unsigned short	GLushort;	/* 2-byte unsigned */
  typedef unsigned int	GLuint;		/* 4-byte unsigned */
  typedef int				GLsizei;	/* 4-byte signed */
  typedef float			GLfloat;	/* single precision float */
  typedef float			GLclampf;	/* single precision float in [0,1] */
  typedef double			GLdouble;	/* double precision float */
  typedef double			GLclampd;	/* double precision float in [0,1] */
  typedef char			GLchar;
  
  // OpenGL Constants
  #define GL_DONT_CARE 0x1100
  #define GL_ZERO 0x0000
  #define GL_ONE 0x0001
  #define GL_BYTE 0x1400
  #define GL_UNSIGNED_BYTE 0x1401
  #define GL_SHORT 0x1402
  #define GL_UNSIGNED_SHORT 0x1403
  #define GL_INT 0x1404
  #define GL_UNSIGNED_INT 0x1405
  #define GL_FLOAT 0x1406
  #define GL_HALF_FLOAT 0x140B
  #define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365
  #define GL_UNSIGNED_SHORT_5_5_5_1_REV 0x8366
  #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
  #define GL_UNSIGNED_SHORT_5_6_5 0x8363
  #define GL_UNSIGNED_INT_24_8 0x84FA
  #define GL_VENDOR 0x1F00
  #define GL_RENDERER 0x1F01
  #define GL_VERSION 0x1F02
  #define GL_EXTENSIONS 0x1F03
  #define GL_COLOR_BUFFER_BIT 0x4000
  #define GL_DEPTH_BUFFER_BIT 0x0100
  #define GL_STENCIL_BUFFER_BIT 0x0400
  #define GL_SCISSOR_TEST 0x0C11
  #define GL_DEPTH_TEST 0x0B71
  #define GL_STENCIL_TEST 0x0B90
  #define GL_LINE 0x1B01
  #define GL_FILL 0x1B02
  #define GL_CW 0x0900
  #define GL_CCW 0x0901
  #define GL_FRONT 0x0404
  #define GL_BACK 0x0405
  #define GL_FRONT_AND_BACK 0x0408
  #define GL_CULL_FACE 0x0B44
  #define GL_POLYGON_OFFSET_FILL 0x8037
  #define GL_TEXTURE_2D 0x0DE1
  #define GL_TEXTURE_3D 0x806F
  #define GL_TEXTURE_CUBE_MAP 0x8513
  #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
  #define GL_BLEND 0x0BE2
  #define GL_SRC_COLOR 0x0300
  #define GL_ONE_MINUS_SRC_COLOR 0x0301
  #define GL_SRC_ALPHA 0x0302
  #define GL_ONE_MINUS_SRC_ALPHA 0x0303
  #define GL_DST_ALPHA 0x0304
  #define GL_ONE_MINUS_DST_ALPHA 0x0305
  #define GL_DST_COLOR 0x0306
  #define GL_ONE_MINUS_DST_COLOR 0x0307
  #define GL_SRC_ALPHA_SATURATE 0x0308
  #define GL_CONSTANT_COLOR 0x8001
  #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
  #define GL_CONSTANT_ALPHA 0x8003
  #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
  #define GL_SRC1_ALPHA 0x8589
  #define GL_SRC1_COLOR 0x88F9
  #define GL_ONE_MINUS_SRC1_COLOR 0x88FA
  #define GL_ONE_MINUS_SRC1_ALPHA 0x88FB
  #define GL_MIN 0x8007
  #define GL_MAX 0x8008
  #define GL_FUNC_ADD 0x8006
  #define GL_FUNC_SUBTRACT 0x800A
  #define GL_FUNC_REVERSE_SUBTRACT 0x800B
  #define GL_NEVER 0x0200
  #define GL_LESS 0x0201
  #define GL_EQUAL 0x0202
  #define GL_LEQUAL 0x0203
  #define GL_GREATER 0x0204
  #define GL_NOTEQUAL 0x0205
  #define GL_GEQUAL 0x0206
  #define GL_ALWAYS 0x0207
  #define GL_INVERT 0x150A
  #define GL_KEEP 0x1E00
  #define GL_REPLACE 0x1E01
  #define GL_INCR 0x1E02
  #define GL_DECR 0x1E03
  #define GL_INCR_WRAP 0x8507
  #define GL_DECR_WRAP 0x8508
  #define GL_REPEAT 0x2901
  #define GL_CLAMP_TO_EDGE 0x812F
  #define GL_MIRRORED_REPEAT 0x8370
  #define GL_NEAREST 0x2600
  #define GL_LINEAR 0x2601
  #define GL_NEAREST_MIPMAP_NEAREST 0x2700
  #define GL_NEAREST_MIPMAP_LINEAR 0x2702
  #define GL_LINEAR_MIPMAP_NEAREST 0x2701
  #define GL_LINEAR_MIPMAP_LINEAR 0x2703
  #define GL_COLOR_ATTACHMENT0 0x8CE0
  #define GL_DEPTH_ATTACHMENT 0x8D00
  #define GL_STENCIL_ATTACHMENT 0x8D20
  #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
  #define GL_RED 0x1903
  #define GL_RGB 0x1907
  #define GL_RGBA 0x1908
  #define GL_LUMINANCE 0x1909
  #define GL_RGB8 0x8051
  #define GL_RGBA8 0x8058
  #define GL_RGBA4 0x8056
  #define GL_RGB5_A1 0x8057
  #define GL_RGB10_A2_EXT 0x8059
  #define GL_RGBA16 0x805B
  #define GL_BGRA 0x80E1
  #define GL_DEPTH_COMPONENT16 0x81A5
  #define GL_DEPTH_COMPONENT24 0x81A6
  #define GL_RG 0x8227
  #define GL_RG8 0x822B
  #define GL_RG16 0x822C
  #define GL_R16F 0x822D
  #define GL_R32F 0x822E
  #define GL_RG16F 0x822F
  #define GL_RG32F 0x8230
  #define GL_RGBA32F 0x8814
  #define GL_RGBA16F 0x881A
  #define GL_DEPTH24_STENCIL8 0x88F0
  #define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3
  #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
  #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
  #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
  #define GL_DEPTH_COMPONENT 0x1902
  #define GL_DEPTH_STENCIL 0x84F9
  #define GL_TEXTURE_WRAP_S 0x2802
  #define GL_TEXTURE_WRAP_T 0x2803
  #define GL_TEXTURE_WRAP_R 0x8072
  #define GL_TEXTURE_MAG_FILTER 0x2800
  #define GL_TEXTURE_MIN_FILTER 0x2801
  #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
  #define GL_TEXTURE_BASE_LEVEL 0x813C
  #define GL_TEXTURE_MAX_LEVEL 0x813D
  #define GL_TEXTURE_LOD_BIAS 0x8501
  #define GL_PACK_ALIGNMENT 0x0D05
  #define GL_UNPACK_ALIGNMENT 0x0CF5
  #define GL_TEXTURE0 0x84C0
  #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
  #define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C
  #define GL_ARRAY_BUFFER 0x8892
  #define GL_ELEMENT_ARRAY_BUFFER 0x8893
  #define GL_STREAM_DRAW 0x88E0
  #define GL_STATIC_DRAW 0x88E4
  #define GL_DYNAMIC_DRAW 0x88E8
  #define GL_MAX_VERTEX_ATTRIBS 0x8869
  #define GL_FRAMEBUFFER 0x8D40
  #define GL_READ_FRAMEBUFFER 0x8CA8
  #define GL_DRAW_FRAMEBUFFER 0x8CA9
  #define GL_RENDERBUFFER 0x8D41
  #define GL_MAX_DRAW_BUFFERS 0x8824
  #define GL_POINTS 0x0000
  #define GL_LINES 0x0001
  #define GL_LINE_STRIP 0x0003
  #define GL_TRIANGLES 0x0004
  #define GL_TRIANGLE_STRIP 0x0005
  #define GL_QUERY_RESULT 0x8866
  #define GL_QUERY_RESULT_AVAILABLE 0x8867
  #define GL_SAMPLES_PASSED 0x8914
  #define GL_MULTISAMPLE 0x809D
  #define GL_MAX_SAMPLES 0x8D57
  #define GL_SAMPLE_MASK 0x8E51
  #define GL_FRAGMENT_SHADER 0x8B30
  #define GL_VERTEX_SHADER 0x8B31
  #define GL_ACTIVE_UNIFORMS 0x8B86
  #define GL_ACTIVE_ATTRIBUTES 0x8B89
  #define GL_FLOAT_VEC2 0x8B50
  #define GL_FLOAT_VEC3 0x8B51
  #define GL_FLOAT_VEC4 0x8B52
  #define GL_SAMPLER_2D 0x8B5E
  #define GL_FLOAT_MAT3x2 0x8B67
  #define GL_FLOAT_MAT4 0x8B5C
  #define GL_NUM_EXTENSIONS 0x821D
  #define GL_DEBUG_SOURCE_API 0x8246
  #define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247
  #define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248
  #define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249
  #define GL_DEBUG_SOURCE_APPLICATION 0x824A
  #define GL_DEBUG_SOURCE_OTHER 0x824B
  #define GL_DEBUG_TYPE_ERROR 0x824C
  #define GL_DEBUG_TYPE_PUSH_GROUP 0x8269
  #define GL_DEBUG_TYPE_POP_GROUP 0x826A
  #define GL_DEBUG_TYPE_MARKER 0x8268
  #define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D
  #define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E
  #define GL_DEBUG_TYPE_PORTABILITY 0x824F
  #define GL_DEBUG_TYPE_PERFORMANCE 0x8250
  #define GL_DEBUG_TYPE_OTHER 0x8251
  #define GL_DEBUG_SEVERITY_HIGH 0x9146
  #define GL_DEBUG_SEVERITY_MEDIUM 0x9147
  #define GL_DEBUG_SEVERITY_LOW 0x9148
  #define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
  #define GL_DEBUG_OUTPUT 0x92E0
  #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
  
  // OpenGL Functions
  #define GL_FUNCTIONS \
  	GL_FUNC(DebugMessageCallback, void, DEBUGPROC callback, const void* userParam) \
  	GL_FUNC(GetString, const GLubyte*, GLenum name) \
  	GL_FUNC(Flush, void, void) \
  	GL_FUNC(Enable, void, GLenum mode) \
  	GL_FUNC(Disable, void, GLenum mode) \
  	GL_FUNC(Clear, void, GLenum mask) \
  	GL_FUNC(ClearColor, void, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) \
  	GL_FUNC(ClearDepth, void, GLdouble depth) \
  	GL_FUNC(ClearStencil, void, GLint stencil) \
  	GL_FUNC(DepthMask, void, GLboolean enabled) \
  	GL_FUNC(DepthFunc, void, GLenum func) \
  	GL_FUNC(Viewport, void, GLint x, GLint y, GLint width, GLint height) \
  	GL_FUNC(Scissor, void, GLint x, GLint y, GLint width, GLint height) \
  	GL_FUNC(CullFace, void, GLenum mode) \
  	GL_FUNC(BlendEquation, void, GLenum eq) \
  	GL_FUNC(BlendEquationSeparate, void, GLenum modeRGB, GLenum modeAlpha) \
  	GL_FUNC(BlendFunc, void, GLenum sfactor, GLenum dfactor) \
  	GL_FUNC(BlendFuncSeparate, void, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) \
  	GL_FUNC(BlendColor, void, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) \
  	GL_FUNC(ColorMask, void, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) \
  	GL_FUNC(GetIntegerv, void, GLenum name, GLint* data) \
  	GL_FUNC(GenTextures, void, GLint n, void* textures) \
  	GL_FUNC(GenRenderbuffers, void, GLint n, void* textures) \
  	GL_FUNC(GenFramebuffers, void, GLint n, void* textures) \
  	GL_FUNC(ActiveTexture, void, GLuint id) \
  	GL_FUNC(BindTexture, void, GLenum target, GLuint id) \
  	GL_FUNC(BindRenderbuffer, void, GLenum target, GLuint id) \
  	GL_FUNC(BindFramebuffer, void, GLenum target, GLuint id) \
  	GL_FUNC(TexImage2D, void, GLenum target, GLint level, GLenum internalFormat, GLint width, GLint height, GLint border, GLenum format, GLenum type, void* data) \
  	GL_FUNC(FramebufferRenderbuffer, void, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \
  	GL_FUNC(FramebufferTexture2D, void, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) \
  	GL_FUNC(TexParameteri, void, GLenum target, GLenum name, GLint param) \
  	GL_FUNC(RenderbufferStorage, void, GLenum target, GLenum internalformat, GLint width, GLint height) \
  	GL_FUNC(GetTexImage, void, GLenum target, GLint level, GLenum format, GLenum type, void* data) \
  	GL_FUNC(DrawElements, void, GLenum mode, GLint count, GLenum type, void* indices) \
  	GL_FUNC(DrawElementsInstanced, void, GLenum mode, GLint count, GLenum type, void* indices, GLint amount) \
  	GL_FUNC(DeleteTextures, void, GLint n, GLuint* textures) \
  	GL_FUNC(DeleteRenderbuffers, void, GLint n, GLuint* renderbuffers) \
  	GL_FUNC(DeleteFramebuffers, void, GLint n, GLuint* textures) \
  	GL_FUNC(GenVertexArrays, void, GLint n, GLuint* arrays) \
  	GL_FUNC(BindVertexArray, void, GLuint id) \
  	GL_FUNC(GenBuffers, void, GLint n, GLuint* arrays) \
  	GL_FUNC(BindBuffer, void, GLenum target, GLuint buffer) \
  	GL_FUNC(BufferData, void, GLenum target, GLsizeiptr size, const void* data, GLenum usage) \
  	GL_FUNC(BufferSubData, void, GLenum target, GLintptr offset, GLsizeiptr size, const void* data) \
  	GL_FUNC(DeleteBuffers, void, GLint n, GLuint* buffers) \
  	GL_FUNC(DeleteVertexArrays, void, GLint n, GLuint* arrays) \
  	GL_FUNC(EnableVertexAttribArray, void, GLuint location) \
  	GL_FUNC(DisableVertexAttribArray, void, GLuint location) \
  	GL_FUNC(VertexAttribPointer, void, GLuint index, GLint size, GLenum type, GLboolean normalized, GLint stride, const void* pointer) \
  	GL_FUNC(VertexAttribDivisor, void, GLuint index, GLuint divisor) \
  	GL_FUNC(CreateShader, GLuint, GLenum type) \
  	GL_FUNC(AttachShader, void, GLuint program, GLuint shader) \
  	GL_FUNC(DetachShader, void, GLuint program, GLuint shader) \
  	GL_FUNC(DeleteShader, void, GLuint shader) \
  	GL_FUNC(ShaderSource, void, GLuint shader, GLsizei count, const GLchar** string, const GLint* length) \
  	GL_FUNC(CompileShader, void, GLuint shader) \
  	GL_FUNC(GetShaderiv, void, GLuint shader, GLenum pname, GLint* result) \
  	GL_FUNC(GetShaderInfoLog, void, GLuint shader, GLint maxLength, GLsizei* length, GLchar* infoLog) \
  	GL_FUNC(CreateProgram, GLuint, ) \
  	GL_FUNC(DeleteProgram, void, GLuint program) \
  	GL_FUNC(LinkProgram, void, GLuint program) \
  	GL_FUNC(GetProgramiv, void, GLuint program, GLenum pname, GLint* result) \
  	GL_FUNC(GetProgramInfoLog, void, GLuint program, GLint maxLength, GLsizei* length, GLchar* infoLog) \
  	GL_FUNC(GetActiveUniform, void, GLuint program, GLuint index, GLint bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name) \
  	GL_FUNC(GetActiveAttrib, void, GLuint program, GLuint index, GLint bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name) \
  	GL_FUNC(UseProgram, void, GLuint program) \
  	GL_FUNC(GetUniformLocation, GLint, GLuint program, const GLchar* name) \
  	GL_FUNC(GetAttribLocation, GLint, GLuint program, const GLchar* name) \
  	GL_FUNC(Uniform1f, void, GLint location, GLfloat v0) \
  	GL_FUNC(Uniform2f, void, GLint location, GLfloat v0, GLfloat v1) \
  	GL_FUNC(Uniform3f, void, GLint location, GLfloat v0, GLfloat v1, GLfloat v2) \
  	GL_FUNC(Uniform4f, void, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) \
  	GL_FUNC(Uniform1fv, void, GLint location, GLint count, const GLfloat* value) \
  	GL_FUNC(Uniform2fv, void, GLint location, GLint count, const GLfloat* value) \
  	GL_FUNC(Uniform3fv, void, GLint location, GLint count, const GLfloat* value) \
  	GL_FUNC(Uniform4fv, void, GLint location, GLint count, const GLfloat* value) \
  	GL_FUNC(Uniform1i, void, GLint location, GLint v0) \
  	GL_FUNC(Uniform2i, void, GLint location, GLint v0, GLint v1) \
  	GL_FUNC(Uniform3i, void, GLint location, GLint v0, GLint v1, GLint v2) \
  	GL_FUNC(Uniform4i, void, GLint location, GLint v0, GLint v1, GLint v2, GLint v3) \
  	GL_FUNC(Uniform1iv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform2iv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform3iv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform4iv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform1ui, void, GLint location, GLuint v0) \
  	GL_FUNC(Uniform2ui, void, GLint location, GLuint v0, GLuint v1) \
  	GL_FUNC(Uniform3ui, void, GLint location, GLuint v0, GLuint v1, GLuint v2) \
  	GL_FUNC(Uniform4ui, void, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3) \
  	GL_FUNC(Uniform1uiv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform2uiv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform3uiv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(Uniform4uiv, void, GLint location, GLint count, const GLint* value) \
  	GL_FUNC(UniformMatrix2fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix3fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix4fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix2x3fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix3x2fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix2x4fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix4x2fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix3x4fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(UniformMatrix4x3fv, void, GLint location, GLint count, GLboolean transpose, const GLfloat* value) \
  	GL_FUNC(PixelStorei, void, GLenum pname, GLint param)
  
  // Debug Function Delegate
  typedef void (APIENTRY* DEBUGPROC)(GLenum source,
  	GLenum type,
  	GLuint id,
  	GLenum severity,
  	GLsizei length,
  	const GLchar* message,
  	const void* userParam);
  
  namespace Blah
  {
  	struct State
  	{
  		// GL function pointers
  		#define GL_FUNC(name, ret, ...) typedef ret (*name ## Func) (__VA_ARGS__); name ## Func name;
  		GL_FUNCTIONS
  		#undef GL_FUNC
  
  		// state
  		void* context;
  
  		// info
  		int max_color_attachments;
  		int max_element_indices;
  		int max_element_vertices;
  		int max_renderbuffer_size;
  		int max_samples;
  		int max_texture_image_units;
  		int max_texture_size;
  		RendererFeatures features;
  	};
  
  	// static state
  	State gl;
  
  	// debug callback
  	void APIENTRY gl_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
  	{
  		// these are basically never useful
  		if (severity == GL_DEBUG_SEVERITY_NOTIFICATION &&
  			type == GL_DEBUG_TYPE_OTHER)
  			return;
  
  		const char* typeName = "";
  		const char* severityName = "";
  
  		switch (type)
  		{
  		case GL_DEBUG_TYPE_ERROR: typeName = "ERROR"; break;
  		case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeName = "DEPRECATED BEHAVIOR"; break;
  		case GL_DEBUG_TYPE_MARKER: typeName = "MARKER"; break;
  		case GL_DEBUG_TYPE_OTHER: typeName = "OTHER"; break;
  		case GL_DEBUG_TYPE_PERFORMANCE: typeName = "PEROFRMANCE"; break;
  		case GL_DEBUG_TYPE_POP_GROUP: typeName = "POP GROUP"; break;
  		case GL_DEBUG_TYPE_PORTABILITY: typeName = "PORTABILITY"; break;
  		case GL_DEBUG_TYPE_PUSH_GROUP: typeName = "PUSH GROUP"; break;
  		case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeName = "UNDEFINED BEHAVIOR"; break;
  		}
  
  		switch (severity)
  		{
  		case GL_DEBUG_SEVERITY_HIGH: severityName = "HIGH"; break;
  		case GL_DEBUG_SEVERITY_MEDIUM: severityName = "MEDIUM"; break;
  		case GL_DEBUG_SEVERITY_LOW: severityName = "LOW"; break;
  		case GL_DEBUG_SEVERITY_NOTIFICATION: severityName = "NOTIFICATION"; break;
  		}
  
  		if (type == GL_DEBUG_TYPE_ERROR)
  			Log::error("GL (%s:%s) %s", typeName, severityName, message);
  		else if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
  			Log::warn("GL (%s:%s) %s", typeName, severityName, message);
  		else
  			Log::print("GL (%s) %s", typeName, message);
  	}
  
  	// assign attributes
  	GLuint gl_mesh_assign_attributes(GLuint buffer, GLenum buffer_type, const VertexFormat& format, GLint divisor)
  	{
  		// bind
  		gl.BindBuffer(buffer_type, buffer);
  
  		// TODO: disable existing enabled attributes ..
  		// ...
  		// ...
  
  		// enable attributes
  		size_t ptr = 0;
  		for (int n = 0; n < format.attributes.size(); n++)
  		{
  			auto& attribute = format.attributes[n];
  			GLenum type = GL_UNSIGNED_BYTE;
  			size_t component_size = 0;
  			int components = 1;
  
  			if (attribute.type == VertexType::Float)
  			{
  				type = GL_FLOAT;
  				component_size = 4;
  				components = 1;
  			}
  			else if (attribute.type == VertexType::Float2)
  			{
  				type = GL_FLOAT;
  				component_size = 4;
  				components = 2;
  			}
  			else if (attribute.type == VertexType::Float3)
  			{
  				type = GL_FLOAT;
  				component_size = 4;
  				components = 3;
  			}
  			else if (attribute.type == VertexType::Float4)
  			{
  				type = GL_FLOAT;
  				component_size = 4;
  				components = 4;
  			}
  			else if (attribute.type == VertexType::Byte4)
  			{
  				type = GL_BYTE;
  				component_size = 1;
  				components = 4;
  			}
  			else if (attribute.type == VertexType::UByte4)
  			{
  				type = GL_UNSIGNED_BYTE;
  				component_size = 1;
  				components = 4;
  			}
  			else if (attribute.type == VertexType::Short2)
  			{
  				type = GL_SHORT;
  				component_size = 2;
  				components = 2;
  			}
  			else if (attribute.type == VertexType::UShort2)
  			{
  				type = GL_UNSIGNED_SHORT;
  				component_size = 2;
  				components = 2;
  			}
  			else if (attribute.type == VertexType::Short4)
  			{
  				type = GL_SHORT;
  				component_size = 2;
  				components = 4;
  			}
  			else if (attribute.type == VertexType::UShort4)
  			{
  				type = GL_UNSIGNED_SHORT;
  				component_size = 2;
  				components = 4;
  			}
  
  			uint32_t location = (uint32_t)(attribute.index);
  			gl.EnableVertexAttribArray(location);
  			gl.VertexAttribPointer(location, components, type, attribute.normalized, format.stride, (void*)ptr);
  			gl.VertexAttribDivisor(location, divisor);
  
  			ptr += components * component_size;
  		}
  
  		return format.stride;
  	}
  
  	// convert blend op enum
  	GLenum gl_get_blend_func(BlendOp operation)
  	{
  		switch (operation)
  		{
  		case BlendOp::Add:				return GL_FUNC_ADD;
  		case BlendOp::Subtract:			return GL_FUNC_SUBTRACT;
  		case BlendOp::ReverseSubtract:	return GL_FUNC_REVERSE_SUBTRACT;
  		case BlendOp::Min:				return GL_MIN;
  		case BlendOp::Max:				return GL_MAX;
  		};
  		return GL_FUNC_ADD;
  	}
  
  	// convert blend factor enum
  	GLenum gl_get_blend_factor(BlendFactor factor)
  	{
  		switch (factor)
  		{
  		case BlendFactor::Zero:						return GL_ZERO;
  		case BlendFactor::One:						return GL_ONE;
  		case BlendFactor::SrcColor:					return GL_SRC_COLOR;
  		case BlendFactor::OneMinusSrcColor:			return GL_ONE_MINUS_SRC_COLOR;
  		case BlendFactor::DstColor:					return GL_DST_COLOR;
  		case BlendFactor::OneMinusDstColor:			return GL_ONE_MINUS_DST_COLOR;
  		case BlendFactor::SrcAlpha:					return GL_SRC_ALPHA;
  		case BlendFactor::OneMinusSrcAlpha:			return GL_ONE_MINUS_SRC_ALPHA;
  		case BlendFactor::DstAlpha:					return GL_DST_ALPHA;
  		case BlendFactor::OneMinusDstAlpha:			return GL_ONE_MINUS_DST_ALPHA;
  		case BlendFactor::ConstantColor:			return GL_CONSTANT_COLOR;
  		case BlendFactor::OneMinusConstantColor:	return GL_ONE_MINUS_CONSTANT_COLOR;
  		case BlendFactor::ConstantAlpha:			return GL_CONSTANT_ALPHA;
  		case BlendFactor::OneMinusConstantAlpha:	return GL_ONE_MINUS_CONSTANT_ALPHA;
  		case BlendFactor::SrcAlphaSaturate:			return GL_SRC_ALPHA_SATURATE;
  		case BlendFactor::Src1Color:				return GL_SRC1_COLOR;
  		case BlendFactor::OneMinusSrc1Color:		return GL_ONE_MINUS_SRC1_COLOR;
  		case BlendFactor::Src1Alpha:				return GL_SRC1_ALPHA;
  		case BlendFactor::OneMinusSrc1Alpha:		return GL_ONE_MINUS_SRC1_ALPHA;
  		};
  
  		return GL_ZERO;
  	}
  
  	class OpenGL_Texture : public Texture
  	{
  	private:
  		GLuint m_id;
  		int m_width;
  		int m_height;
  		TextureSampler m_sampler;
  		TextureFormat m_format;
  		GLenum m_gl_internal_format;
  		GLenum m_gl_format;
  		GLenum m_gl_type;
  
  	public:
  		bool framebuffer_parent;
  
  		OpenGL_Texture(int width, int height, TextureFormat format)
  		{
  			m_id = 0;
  			m_width = width;
  			m_height = height;
  			m_sampler = TextureSampler(TextureFilter::None, TextureWrap::None, TextureWrap::None);
  			m_format = format;
  			framebuffer_parent = false;
  			m_gl_internal_format = GL_RED;
  			m_gl_format = GL_RED;
  			m_gl_type = GL_UNSIGNED_BYTE;
  
  			if (width > gl.max_texture_size || height > gl.max_texture_size)
  			{
  				Log::error("Exceeded Max Texture Size of %i", gl.max_texture_size);
  				return;
  			}
  
  			if (format == TextureFormat::R)
  			{
  				m_gl_internal_format = GL_RED;
  				m_gl_format = GL_RED;
  				m_gl_type = GL_UNSIGNED_BYTE;
  			}
  			else if (format == TextureFormat::RG)
  			{
  				m_gl_internal_format = GL_RG;
  				m_gl_format = GL_RG;
  				m_gl_type = GL_UNSIGNED_BYTE;
  			}
  			else if (format == TextureFormat::RGBA)
  			{
  				m_gl_internal_format = GL_RGBA;
  				m_gl_format = GL_RGBA;
  				m_gl_type = GL_UNSIGNED_BYTE;
  			}
  			else if (format == TextureFormat::DepthStencil)
  			{
  				m_gl_internal_format = GL_DEPTH24_STENCIL8;
  				m_gl_format = GL_DEPTH_STENCIL;
  				m_gl_type = GL_UNSIGNED_INT_24_8;
  			}
  			else
  			{
  				Log::error("Invalid Texture Format %i", format);
  				return;
  			}
  
  			gl.GenTextures(1, &m_id);
  			gl.ActiveTexture(GL_TEXTURE0);
  			gl.BindTexture(GL_TEXTURE_2D, m_id);
  			gl.TexImage2D(GL_TEXTURE_2D, 0, m_gl_internal_format, width, height, 0, m_gl_format, m_gl_type, nullptr);
  		}
  
  		~OpenGL_Texture()
  		{
  			if (m_id > 0)
  				gl.DeleteTextures(1, &m_id);
  		}
  
  		GLuint gl_id() const
  		{
  			return m_id;
  		}
  
  		virtual int width() const override
  		{
  			return m_width;
  		}
  
  		virtual int height() const override
  		{
  			return m_height;
  		}
  
  		virtual TextureFormat format() const override
  		{
  			return m_format;
  		}
  
  		void update_sampler(const TextureSampler& sampler)
  		{
  			if (m_sampler != sampler)
  			{
  				m_sampler = sampler;
  
  				gl.BindTexture(GL_TEXTURE_2D, m_id);
  				gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (m_sampler.filter == TextureFilter::Nearest ? GL_NEAREST : GL_LINEAR));
  				gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (m_sampler.filter == TextureFilter::Nearest ? GL_NEAREST : GL_LINEAR));
  				gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (m_sampler.wrap_x == TextureWrap::Clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT));
  				gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (m_sampler.wrap_y == TextureWrap::Clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT));
  			}
  		}
  
  		virtual void set_data(unsigned char* data) override
  		{
  			gl.ActiveTexture(GL_TEXTURE0);
  			gl.BindTexture(GL_TEXTURE_2D, m_id);
  			gl.TexImage2D(GL_TEXTURE_2D, 0, m_gl_internal_format, m_width, m_height, 0, m_gl_format, m_gl_type, data);
  		}
  
  		virtual void get_data(unsigned char* data) override
  		{
  			gl.ActiveTexture(GL_TEXTURE0);
  			gl.BindTexture(GL_TEXTURE_2D, m_id);
  			gl.GetTexImage(GL_TEXTURE_2D, 0, m_gl_internal_format, m_gl_type, data);
  		}
  
  		virtual bool is_framebuffer() const override
  		{
  			return framebuffer_parent;
  		}
  
  	};
  
  	class OpenGL_FrameBuffer : public FrameBuffer
  	{
  	private:
  		GLuint m_id;
  		int m_width;
  		int m_height;
  		StackVector<TextureRef, BLAH_ATTACHMENTS> m_attachments;
  
  	public:
  
  		OpenGL_FrameBuffer(int width, int height, const TextureFormat* attachments, int attachmentCount)
  		{
  			gl.GenFramebuffers(1, &m_id);
  			m_width = width;
  			m_height = height;
  
  			gl.BindFramebuffer(GL_FRAMEBUFFER, m_id);
  
  			for (int i = 0; i < attachmentCount; i++)
  			{
  				auto tex = Texture::create(width, height, attachments[i]);
  				auto gltex = ((OpenGL_Texture*)tex.get());
  
  				gltex->framebuffer_parent = true;
  				m_attachments.push_back(tex);
  
  				if (attachments[i] != TextureFormat::DepthStencil)
  				{
  					gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, gltex->gl_id(), 0);
  				}
  				else
  				{
  					gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, gltex->gl_id(), 0);
  				}
  			}
  		}
  
  		~OpenGL_FrameBuffer()
  		{
  			if (m_id > 0)
  			{
  				gl.DeleteFramebuffers(1, &m_id);
  				m_id = 0;
  			}
  		}
  
  		GLuint gl_id() const
  		{
  			return m_id;
  		}
  
  		virtual Attachments& attachments() override
  		{
  			return m_attachments;
  		}
  
  		virtual const Attachments& attachments() const override
  		{
  			return m_attachments;
  		}
  
  		virtual TextureRef& attachment(int index) override
  		{
  			return m_attachments[index];
  		}
  
  		virtual const TextureRef& attachment(int index) const override
  		{
  			return m_attachments[index];
  		}
  
  		virtual int width() const override
  		{
  			return m_width;
  		}
  
  		virtual int height() const override
  		{
  			return m_height;
  		}
  
  		virtual void clear(Color color) override
  		{
  			auto rgba = color.to_rgba();
  			unsigned char r = rgba >> 24;
  			unsigned char g = rgba >> 16;
  			unsigned char b = rgba >> 8;
  			unsigned char a = rgba;
  
  			gl.BindFramebuffer(GL_FRAMEBUFFER, m_id);
  			gl.Disable(GL_SCISSOR_TEST);
  			gl.ClearColor(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
  			gl.Clear(GL_COLOR_BUFFER_BIT);
  		}
  	};
  
  	class OpenGL_Shader : public Shader
  	{
  	private:
  		GLuint m_id;
  		Vector<UniformInfo> m_uniforms;
  
  	public:
  		Vector<GLint> uniform_locations;
  
  		OpenGL_Shader(const ShaderData* data)
  		{
  			m_id = 0;
  
  			if (data->vertex.length() <= 0)
  			{
  				Log::error("Vertex Shader is required");
  				return;
  			}
  
  			if (data->fragment.length() <= 0)
  			{
  				Log::error("Fragment Shader is required");
  				return;
  			}
  
  			GLchar log[1024];
  			GLsizei log_length = 0;
  
  			GLuint vertex_shader = gl.CreateShader(GL_VERTEX_SHADER);
  			{
  				const GLchar* source = (const GLchar*)data->vertex.cstr();
  				gl.ShaderSource(vertex_shader, 1, &source, nullptr);
  				gl.CompileShader(vertex_shader);
  				gl.GetShaderInfoLog(vertex_shader, 1024, &log_length, log);
  
  				if (log_length > 0)
  				{
  					gl.DeleteShader(vertex_shader);
  					Log::error(log);
  					return;
  				}
  			}
  
  			GLuint fragment_shader = gl.CreateShader(GL_FRAGMENT_SHADER);
  			{
  				const GLchar* source = (const GLchar*)data->fragment.cstr();
  				gl.ShaderSource(fragment_shader, 1, &source, nullptr);
  				gl.CompileShader(fragment_shader);
  				gl.GetShaderInfoLog(fragment_shader, 1024, &log_length, log);
  
  				if (log_length > 0)
  				{
  					gl.DeleteShader(vertex_shader);
  					gl.DeleteShader(fragment_shader);
  					Log::error(log);
  					return;
  				}
  			}
  
  			// create actual shader program
  			GLuint id = gl.CreateProgram();
  			gl.AttachShader(id, vertex_shader);
  			gl.AttachShader(id, fragment_shader);
  			gl.LinkProgram(id);
  			gl.GetProgramInfoLog(id, 1024, &log_length, log);
  			gl.DetachShader(id, vertex_shader);
  			gl.DetachShader(id, fragment_shader);
  			gl.DeleteShader(vertex_shader);
  			gl.DeleteShader(fragment_shader);
  
  			if (log_length > 0)
  			{
  				Log::error(log);
  				return;
  			}
  
  			// get uniforms
  			bool valid_uniforms = true;
  			{
  				const int max_name_length = 256;
  
  				GLint active_uniforms = 0;
  				gl.GetProgramiv(id, GL_ACTIVE_UNIFORMS, &active_uniforms);
  
  				for (int i = 0; i < active_uniforms; i++)
  				{
  					GLsizei length;
  					GLsizei size;
  					GLenum type;
  					GLchar name[max_name_length + 1];
  
  					gl.GetActiveUniform(id, i, max_name_length, &length, &size, &type, name);
  					name[length] = '\0';
  
  					// array names end with "[0]", and we don't want that
  					for (int n = 0; n < max_name_length; n++)
  						if (name[n] == '[')
  						{
  							if (name[n + 1] == '0' && name[n + 2] == ']')
  							{
  								name[n] = '\0';
  								break;
  							}
  						}
  
  					if (type == GL_SAMPLER_2D)
  					{
  						UniformInfo tex_uniform;
  						tex_uniform.name = name;
  						tex_uniform.buffer_index = 0;
  						tex_uniform.array_length = size;
  						tex_uniform.type = UniformType::Texture2D;
  						tex_uniform.shader = ShaderType::Fragment;
  						uniform_locations.push_back(gl.GetUniformLocation(id, name));
  						m_uniforms.push_back(tex_uniform);
  
  						UniformInfo sampler_uniform;
  						sampler_uniform.name = String(name).append("_sampler");
  						sampler_uniform.buffer_index = 0;
  						sampler_uniform.array_length = size;
  						sampler_uniform.type = UniformType::Sampler2D;
  						sampler_uniform.shader = ShaderType::Fragment;
  						uniform_locations.push_back(gl.GetUniformLocation(id, name));
  						m_uniforms.push_back(sampler_uniform);
  					}
  					else
  					{
  						UniformInfo uniform;
  						uniform.name = name;
  						uniform.type = UniformType::None;
  						uniform.buffer_index = 0;
  						uniform.array_length = size;
  						uniform_locations.push_back(gl.GetUniformLocation(id, name));
  						uniform.shader = (ShaderType)((int)ShaderType::Vertex | (int)ShaderType::Fragment);
  
  						if (type == GL_FLOAT)
  							uniform.type = UniformType::Float;
  						else if (type == GL_FLOAT_VEC2)
  							uniform.type = UniformType::Float2;
  						else if (type == GL_FLOAT_VEC3)
  							uniform.type = UniformType::Float3;
  						else if (type == GL_FLOAT_VEC4)
  							uniform.type = UniformType::Float4;
  						else if (type == GL_FLOAT_MAT3x2)
  							uniform.type = UniformType::Mat3x2;
  						else if (type == GL_FLOAT_MAT4)
  							uniform.type = UniformType::Mat4x4;
  						else
  						{
  							valid_uniforms = false;
  							Log::error("Unsupported Uniform Type");
  							break;
  						}
  
  						m_uniforms.push_back(uniform);
  					}
  
  				}
  			}
  
  			// assign ID if the uniforms were valid
  			if (!valid_uniforms)
  				gl.DeleteProgram(id);
  			else
  				m_id = id;
  		}
  
  		~OpenGL_Shader()
  		{
  			if (m_id > 0)
  				gl.DeleteProgram(m_id);
  			m_id = 0;
  		}
  
  		GLuint gl_id() const
  		{
  			return m_id;
  		}
  
  		virtual Vector<UniformInfo>& uniforms() override
  		{
  			return m_uniforms;
  		}
  
  		virtual const Vector<UniformInfo>& uniforms() const override
  		{
  			return m_uniforms;
  		}
  	};
  
  	class OpenGL_Mesh : public Mesh
  	{
  	private:
  		GLuint m_id;
  		GLuint m_index_buffer;
  		GLuint m_vertex_buffer;
  		GLuint m_instance_buffer;
  		int64_t m_index_count;
  		int64_t m_vertex_count;
  		int64_t m_instance_count;
  		uint16_t m_vertex_size;
  		uint16_t m_instance_size;
  		uint8_t m_vertex_attribs_enabled;
  		uint8_t m_instance_attribs_enabled;
  		Vector<GLuint> m_vertex_attribs;
  		Vector<GLuint> m_instance_attribs;
  		GLenum m_index_format;
  		int m_index_size;
  
  	public:
  
  		OpenGL_Mesh()
  		{
  			m_id = 0;
  			m_index_buffer = 0;
  			m_vertex_buffer = 0;
  			m_instance_buffer = 0;
  			m_index_count = 0;
  			m_vertex_count = 0;
  			m_instance_count = 0;
  			m_vertex_size = 0;
  			m_instance_size = 0;
  			m_vertex_attribs_enabled = 0;
  			m_instance_attribs_enabled = 0;
  
  			gl.GenVertexArrays(1, &m_id);
  		}
  
  		~OpenGL_Mesh()
  		{
  			if (m_vertex_buffer != 0)
  				gl.DeleteBuffers(1, &m_vertex_buffer);
  			if (m_index_buffer != 0)
  				gl.DeleteBuffers(1, &m_index_buffer);
  			if (m_instance_buffer != 0)
  				gl.DeleteBuffers(1, &m_instance_buffer);
  			if (m_id != 0)
  				gl.DeleteVertexArrays(1, &m_id);
  			m_id = 0;
  		}
  
  		GLuint gl_id() const
  		{
  			return m_id;
  		}
  
  		GLenum gl_index_format() const
  		{
  			return m_index_format;
  		}
  
  		int gl_index_size() const
  		{
  			return m_index_size;
  		}
  
  		virtual void index_data(IndexFormat format, const void* indices, int64_t count) override
  		{
  			m_index_count = count;
  
  			gl.BindVertexArray(m_id);
  			{
  				if (m_index_buffer == 0)
  					gl.GenBuffers(1, &(m_index_buffer));
  
  				switch (format)
  				{
  				case IndexFormat::UInt16:
  					m_index_format = GL_UNSIGNED_SHORT;
  					m_index_size = 2;
  					break;
  				case IndexFormat::UInt32:
  					m_index_format = GL_UNSIGNED_INT;
  					m_index_size = 4;
  					break;
  				}
  
  				gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
  				gl.BufferData(GL_ELEMENT_ARRAY_BUFFER, m_index_size * count, indices, GL_DYNAMIC_DRAW);
  			}
  			gl.BindVertexArray(0);
  		}
  
  		virtual void vertex_data(const VertexFormat& format, const void* vertices, int64_t count) override
  		{
  			m_vertex_count = count;
  
  			gl.BindVertexArray(m_id);
  			{
  				// Create Buffer if it doesn't exist yet
  				if (m_vertex_buffer == 0)
  					gl.GenBuffers(1, &(m_vertex_buffer));
  
  				// TODO:
  				// Cache this
  				m_vertex_size = gl_mesh_assign_attributes(m_vertex_buffer, GL_ARRAY_BUFFER, format, 0);
  
  				// Upload Buffer
  				gl.BindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
  				gl.BufferData(GL_ARRAY_BUFFER, m_vertex_size * count, vertices, GL_DYNAMIC_DRAW);
  			}
  			gl.BindVertexArray(0);
  		}
  
  		virtual void instance_data(const VertexFormat& format, const void* instances, int64_t count) override
  		{
  			m_instance_count = count;
  
  			gl.BindVertexArray(m_id);
  			{
  				// Create Buffer if it doesn't exist yet
  				if (m_instance_buffer == 0)
  					gl.GenBuffers(1, &(m_instance_buffer));
  
  				// TODO:
  				// Cache this
  				m_instance_size = gl_mesh_assign_attributes(m_instance_buffer, GL_ARRAY_BUFFER, format, 1);
  
  				// Upload Buffer
  				gl.BindBuffer(GL_ARRAY_BUFFER, m_instance_buffer);
  				gl.BufferData(GL_ARRAY_BUFFER, m_instance_size * count, instances, GL_DYNAMIC_DRAW);
  			}
  			gl.BindVertexArray(0);
  		}
  
  		virtual int64_t index_count() const override
  		{
  			return m_index_count;
  		}
  
  		virtual int64_t vertex_count() const override
  		{
  			return m_vertex_count;
  		}
  
  		virtual int64_t instance_count() const override
  		{
  			return m_instance_count;
  		}
  	};
  
  	bool GraphicsBackend::init()
  	{
  		gl = State();
  
  		// create gl context
  		gl.context = PlatformBackend::gl_context_create();
  		if (gl.context == nullptr)
  		{
  			Log::error("Failed to create OpenGL Context");
  			return false;
  		}
  		PlatformBackend::gl_context_make_current(gl.context);
  
  		// bind opengl functions
  		#define GL_FUNC(name, ...) gl.name = (State::name ## Func)(PlatformBackend::gl_get_func("gl" #name));
  		GL_FUNCTIONS
  		#undef GL_FUNC
  
  		// bind debug message callback
  		if (gl.DebugMessageCallback != nullptr)
  		{
  			gl.Enable(GL_DEBUG_OUTPUT);
  			gl.Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
  			gl.DebugMessageCallback(gl_message_callback, nullptr);
  		}
  
  		// get opengl info
  		gl.GetIntegerv(0x8CDF, &gl.max_color_attachments);
  		gl.GetIntegerv(0x80E9, &gl.max_element_indices);
  		gl.GetIntegerv(0x80E8, &gl.max_element_vertices);
  		gl.GetIntegerv(0x84E8, &gl.max_renderbuffer_size);
  		gl.GetIntegerv(0x8D57, &gl.max_samples);
  		gl.GetIntegerv(0x8872, &gl.max_texture_image_units);
  		gl.GetIntegerv(0x0D33, &gl.max_texture_size);
  
  		// log
  		Log::print("OpenGL %s, %s",
  			gl.GetString(GL_VERSION),
  			gl.GetString(GL_RENDERER));
  
  		// don't include row padding
  		gl.PixelStorei(GL_PACK_ALIGNMENT, 1);
  		gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
  
  		// assign info
  		gl.features.instancing = true;
  		gl.features.origin_bottom_left = true;
  		gl.features.max_texture_size = gl.max_texture_size;
  
  		return true;
  	}
  
  	Renderer GraphicsBackend::renderer()
  	{
  		return Renderer::OpenGL;
  	}
  
  	void GraphicsBackend::shutdown()
  	{
  		PlatformBackend::gl_context_destroy(gl.context);
  		gl.context = nullptr;
  	}
  
  	const RendererFeatures& GraphicsBackend::features()
  	{
  		return gl.features;
  	}
  
  	void GraphicsBackend::frame() {}
  	void GraphicsBackend::before_render() {}
  	void GraphicsBackend::after_render() {}
  
  	TextureRef GraphicsBackend::create_texture(int width, int height, TextureFormat format)
  	{
  		auto resource = new OpenGL_Texture(width, height, format);
  
  		if (resource->gl_id() <= 0)
  		{
  			delete resource;
  			return TextureRef();
  		}
  
  		return TextureRef(resource);
  	}
  
  	FrameBufferRef GraphicsBackend::create_framebuffer(int width, int height, const TextureFormat* attachments, int attachmentCount)
  	{
  		auto resource = new OpenGL_FrameBuffer(width, height, attachments, attachmentCount);
  
  		if (resource->gl_id() <= 0)
  		{
  			delete resource;
  			return FrameBufferRef();
  		}
  
  		return FrameBufferRef(resource);
  	}
  
  	ShaderRef GraphicsBackend::create_shader(const ShaderData* data)
  	{
  		auto resource = new OpenGL_Shader(data);
  
  		if (resource->gl_id() <= 0)
  		{
  			delete resource;
  			return ShaderRef();
  		}
  
  		return ShaderRef(resource);
  	}
  
  	MeshRef GraphicsBackend::create_mesh()
  	{
  		auto resource = new OpenGL_Mesh();
  
  		if (resource->gl_id() <= 0)
  		{
  			delete resource;
  			return MeshRef();
  		}
  
  		return MeshRef(resource);
  	}
  
  	void GraphicsBackend::render(const RenderPass& pass)
  	{
  		// Bind the Target
  		Point size;
  		if (pass.target == App::backbuffer)
  		{
  			gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
  			size.x = App::draw_width();
  			size.y = App::draw_height();
  		}
  		else if (pass.target)
  		{
  			auto framebuffer = (OpenGL_FrameBuffer*)pass.target.get();
  			gl.BindFramebuffer(GL_FRAMEBUFFER, framebuffer->gl_id());
  			size.x = pass.target->width();
  			size.y = pass.target->height();
  		}
  
  		auto shader_ref = pass.material->shader();
  		auto shader = (OpenGL_Shader*)shader_ref.get();
  		auto mesh = (OpenGL_Mesh*)pass.mesh.get();
  
  		// Use the Shader
  		// TODO: I don't love how material values are assigned or set here
  		// TODO: this should be cached?
  		{
  			gl.UseProgram(shader->gl_id());
  
  			int texture_slot = 0;
  			int gl_texture_slot = 0;
  			GLint texture_ids[64];
  			auto& uniforms = shader->uniforms();
  			auto data = pass.material->data();
  
  			for (int i = 0; i < uniforms.size(); i++)
  			{
  				auto location = shader->uniform_locations[i];
  				auto& uniform = uniforms[i];
  
  				// Sampler 2D's are assigned by updating texture values
  				if (uniform.type == UniformType::Sampler2D)
  					continue;
  
  				// Texture2D
  				if (uniform.type == UniformType::Texture2D)
  				{
  					for (int n = 0; n < uniform.array_length; n++)
  					{
  						auto tex = pass.material->get_texture(texture_slot, n);
  						auto sampler = pass.material->get_sampler(texture_slot, n);
  
  						gl.ActiveTexture(GL_TEXTURE0 + gl_texture_slot);
  
  						if (!tex)
  						{
  							gl.BindTexture(GL_TEXTURE_2D, 0);
  						}
  						else
  						{
  							auto gl_tex = ((OpenGL_Texture*)tex.get());
  							gl_tex->update_sampler(sampler);
  							gl.BindTexture(GL_TEXTURE_2D, gl_tex->gl_id());
  						}
  
  						texture_ids[n] = gl_texture_slot;
  						gl_texture_slot++;
  					}
  
  					gl.Uniform1iv(location, (GLint)uniform.array_length, &texture_ids[0]);
  					texture_slot++;
  					continue;
  				}
  
  				// Float
  				if (uniform.type == UniformType::Float)
  				{
  					gl.Uniform1fv(location, (GLint)uniform.array_length, data);
  					data += uniform.array_length;
  				}
  				// Float2
  				else if (uniform.type == UniformType::Float2)
  				{
  					gl.Uniform2fv(location, (GLint)uniform.array_length, data);
  					data += 2 * uniform.array_length;
  				}
  				// Float3
  				else if (uniform.type == UniformType::Float3)
  				{
  					gl.Uniform3fv(location, (GLint)uniform.array_length, data);
  					data += 3 * uniform.array_length;
  				}
  				// Float4
  				else if (uniform.type == UniformType::Float4)
  				{
  					gl.Uniform4fv(location, (GLint)uniform.array_length, data);
  					data += 4 * uniform.array_length;
  				}
  				// Matrix3x2
  				else if (uniform.type == UniformType::Mat3x2)
  				{
  					gl.UniformMatrix3x2fv(location, (GLint)uniform.array_length, 0, data);
  					data += 6 * uniform.array_length;
  				}
  				// Matrix4x4
  				else if (uniform.type == UniformType::Mat4x4)
  				{
  					gl.UniformMatrix4fv(location, (GLint)uniform.array_length, 0, data);
  					data += 16 * uniform.array_length;
  				}
  			}
  		}
  
  		// Blend Mode
  		{
  			GLenum colorOp = gl_get_blend_func(pass.blend.color_op);
  			GLenum alphaOp = gl_get_blend_func(pass.blend.alpha_op);
  			GLenum colorSrc = gl_get_blend_factor(pass.blend.color_src);
  			GLenum colorDst = gl_get_blend_factor(pass.blend.color_dst);
  			GLenum alphaSrc = gl_get_blend_factor(pass.blend.alpha_src);
  			GLenum alphaDst = gl_get_blend_factor(pass.blend.alpha_dst);
  
  			gl.Enable(GL_BLEND);
  			gl.BlendEquationSeparate(colorOp, alphaOp);
  			gl.BlendFuncSeparate(colorSrc, colorDst, alphaSrc, alphaDst);
  
  			gl.ColorMask(
  				((int)pass.blend.mask & (int)BlendMask::Red),
  				((int)pass.blend.mask & (int)BlendMask::Green),
  				((int)pass.blend.mask & (int)BlendMask::Blue),
  				((int)pass.blend.mask & (int)BlendMask::Alpha));
  
  			unsigned char r = pass.blend.rgba >> 24;
  			unsigned char g = pass.blend.rgba >> 16;
  			unsigned char b = pass.blend.rgba >> 8;
  			unsigned char a = pass.blend.rgba;
  
  			gl.BlendColor(
  				r / 255.0f,
  				g / 255.0f,
  				b / 255.0f,
  				a / 255.0f);
  		}
  
  		// Depth Function
  		{
  			if (pass.depth == Compare::None)
  			{
  				gl.Disable(GL_DEPTH_TEST);
  			}
  			else
  			{
  				gl.Enable(GL_DEPTH_TEST);
  
  				switch (pass.depth)
  				{
  				case Compare::None: break;
  				case Compare::Always:
  					gl.DepthFunc(GL_ALWAYS);
  					break;
  				case Compare::Equal:
  					gl.DepthFunc(GL_EQUAL);
  					break;
  				case Compare::Greater:
  					gl.DepthFunc(GL_GREATER);
  					break;
  				case Compare::GreatorOrEqual:
  					gl.DepthFunc(GL_GEQUAL);
  					break;
  				case Compare::Less:
  					gl.DepthFunc(GL_LESS);
  					break;
  				case Compare::LessOrEqual:
  					gl.DepthFunc(GL_LEQUAL);
  					break;
  				case Compare::Never:
  					gl.DepthFunc(GL_NEVER);
  					break;
  				case Compare::NotEqual:
  					gl.DepthFunc(GL_NOTEQUAL);
  					break;
  				}
  			}
  		}
  
  		// Cull Mode
  		{
  			if (pass.cull == Cull::None)
  			{
  				gl.Disable(GL_CULL_FACE);
  			}
  			else
  			{
  				gl.Enable(GL_CULL_FACE);
  
  				if (pass.cull == Cull::Back)
  					gl.CullFace(GL_BACK);
  				else if (pass.cull == Cull::Front)
  					gl.CullFace(GL_FRONT);
  				else
  					gl.CullFace(GL_FRONT_AND_BACK);
  			}
  		}
  
  		// Viewport
  		{
  			Rect viewport = pass.viewport;
  			viewport.y = size.y - viewport.y - viewport.h;
  
  			gl.Viewport((GLint)viewport.x, (GLint)viewport.y, (GLint)viewport.w, (GLint)viewport.h);
  		}
  
  		// Scissor
  		{
  			if (!pass.has_scissor)
  			{
  				gl.Disable(GL_SCISSOR_TEST);
  			}
  			else
  			{
  				Rect scissor = pass.scissor;
  				scissor.y = size.y - scissor.y - scissor.h;
  
  				if (scissor.w < 0)
  					scissor.w = 0;
  				if (scissor.h < 0)
  					scissor.h = 0;
  
  				gl.Enable(GL_SCISSOR_TEST);
  				gl.Scissor((GLint)scissor.x, (GLint)scissor.y, (GLint)scissor.w, (GLint)scissor.h);
  			}
  		}
  
  		// Draw the Mesh
  		{
  			gl.BindVertexArray(mesh->gl_id());
  
  			GLenum index_format = mesh->gl_index_format();
  			int index_size = mesh->gl_index_size();
  
  			if (pass.instance_count > 0)
  			{
  				gl.DrawElementsInstanced(
  					GL_TRIANGLES,
  					(GLint)(pass.index_count),
  					index_format,
  					(void*)(index_size* pass.index_start),
  					(GLint)pass.instance_count);
  			}
  			else
  			{
  				gl.DrawElements(
  					GL_TRIANGLES,
  					(GLint)(pass.index_count),
  					index_format,
  					(void*)(index_size * pass.index_start));
  			}
  
  			gl.BindVertexArray(0);
  		}
  	}
  
  	void GraphicsBackend::clear_backbuffer(Color color)
  	{
  		auto rgba = color.to_rgba();
  		unsigned char r = rgba >> 24;
  		unsigned char g = rgba >> 16;
  		unsigned char b = rgba >> 8;
  		unsigned char a = rgba;
  
  		gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
  		gl.Disable(GL_SCISSOR_TEST);
  		gl.ClearColor(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
  		gl.Clear(GL_COLOR_BUFFER_BIT);
  	}
  }
  
  #endif // BLAH_USE_OPENGL
  